Back to projects

MEV Bot — automated NFT flipping (Ordinals)

Node.js automation for Ordinals marketplaces: scraping, Puppeteer, Unisat Wallet SDK, and a large configurable trading loop.

Year
2024

Overview

Ordinals Offer Bot is an automation system for Bitcoin Ordinals trading workflows on Magic Eden. It combines browser-driven interaction with the marketplace APIs, local signing via the UniSat wallet SDK, and a layered configuration model so multiple wallets and collections can be managed under one deployment.

The repository is organized as an npm monorepo: a shared core package holds the bot logic; a backend serves a small API and static assets; a frontend provides a web dashboard. The core can also run standalone for headless operation.


What the system does

At a high level, the platform supports three complementary behaviors:

  1. Automated listing — Periodically evaluates inventory held in configured wallets, compares it to live market data (floor prices by collection rank, optional trait-specific floors, and purchase history where available), and submits batch listings through Magic Eden’s listing flow when rules say a price update or new listing is appropriate.

  2. Token-level offers — On a schedule, discovers candidate listings from configured collections (with optional pagination and trait filters), applies business rules to decide offer amounts and expirations, obtains PSBTs from the marketplace, signs them locally, and posts offers in controlled batches across multiple wallets.

  3. Collection-level offers — For collections marked for this mode, cancels existing collection offers where needed, then creates new collection-wide offers subject to balance, fee, and configuration constraints, using concurrent browser workers for throughput.

Together, these flows aim to keep offers and listings aligned with market conditions and predefined risk parameters, while respecting rate limits and fee thresholds.


Architecture at a glance

LayerRole
Core (core/)Playwright- and/or Puppeteer-based automation, PSBT signing, session management, Telegram notifications (where enabled).
BackendHTTP API that imports core logic (e.g. start/stop token-offer loop) and serves the dashboard.
FrontendStatic UI that talks to the backend over REST.

Configuration is split so common runtime settings live in core/config/config.json and strategy-specific data (per-collection rules, trait filters, collection-offer flags) live under core/config/profiles/. The active profile is selected by profileMode in the common file; the loader merges common and profile JSON and can reload when files change.

Secrets such as private keys are not stored in JSON; they are expected from the environment (e.g. WIF material for signing), in line with a separation between policy (config) and credentials (env).


Core modules (conceptual)

Token offers (core/offers.js)

This module implements the item / token offer loop. It:

  • Refreshes merged configuration and, when used from the dashboard, supports start/stop with cleanup of timers and shared resources.
  • Maintains a session pool of browser contexts with per-session rate limiting and optional proxy use, to reduce throttling when calling Magic Eden endpoints repeatedly.
  • Selects collections that have a positive amountItems (how many listings to sample from the order book), evaluates whether collection-level preconditions are met (e.g. floor and wallet readiness), and fetches token lists—either by collection floor sort or by trait-specific attribute queries when traitFilters are set.
  • Filters tokens to those that are listed and where the computed offer is within configured min/max spreads relative to the relevant floor (collection or trait-specific).
  • Splits work across wallets in batches: fetch PSBTs, sign, post offers, wait between batches to avoid hammering APIs.

Gas/fees are checked against configurable USD ceilings before proceeding, so offer runs can abort when the network is uneconomical.

Collection offers (core/collectionOffers.js)

This path focuses on collection bids (offers on any item from a collection). It:

  • Launches a browser cluster for parallel requests where appropriate.
  • Cancels existing wallet collection offers when preparing a new cycle.
  • Builds and signs PSBTs for new collection offers and submits them, tracking success so remaining work can be distributed across wallets until limits are satisfied.

Timing between cycles is driven by configuration (sub-minute intervals are possible). Update flows for refreshing in-place offers exist in the codebase for scenarios where bids are adjusted rather than fully replaced.

Listing (core/list.js)

This module handles seller-side listing automation:

  • On an interval, loads each wallet’s Ordinals via Magic Eden’s wallet token API.
  • Enriches items with collection order-book snapshots (ranked listing slots used as reference prices) and on-chain activity to infer buy prices when possible.
  • Computes target list prices using collection config (positionToList, optional trait filters and trait floors, stop-loss style floors vs. purchase price, exclusions for certain inscriptions or bot addresses).
  • Uses Magic Eden’s batch listing PSBT flow: build unsigned PSBTs through the API, sign with local keys, submit batch listing.

Notifications (e.g. Telegram) can inform operators of re-lists, new listings, and sales detected via flags such as delist/sale indicators.


Configuration model

core/config/config.json holds shared controls, for example:

  • Profile selection (profileMode) and optional proxy settings.
  • Session pool sizing and request-rate limits for API-heavy paths.
  • Intervals for listing vs. token offers vs. collection offers.
  • Offer limits (per wallet, batch sizes, delays, expirations).
  • Wallet registry (receive/payment addresses and public keys for PSBT construction).
  • Exclusions (inscriptions never listed, addresses ignored for certain listing decisions).
  • Global knobs used by multiple modules (fee tier labels, notification-related assumptions, etc.).

core/config/profiles/<name>.json extends this with the collection catalog: each entry typically includes the collection symbol, spreads and multipliers that define acceptable offer bands, how many items to pull from the book and from which offset, which order-book rank informs listing decisions, optional trait filters (type/value pairs and per-trait item counts), and flags such as whether collection offers are enabled for that collection.

This split allows swapping a full strategy profile (e.g. conservative vs. aggressive) by changing one field and a file, without touching engine code.


Operational characteristics

  • Browser automation is used both to mimic client requests (headers, cookies where configured) and to work with JSON responses exposed like static API payloads.
  • Local signing keeps private material off remote servers; only signed PSBTs are sent onward.
  • Batching and staggered delays reduce the chance of API rejection under load.
  • Abort/stop paths in the token-offer runner clear intervals and close pooled sessions to avoid orphaned processes when driven from the API.

Technology stack (summary)

  • Node.js with Playwright (token offers, listing) and Puppeteer Cluster (collection offers) for automation.
  • UniSat wallet SDK for Bitcoin Taproot (P2TR) PSBT signing on mainnet.
  • Express (backend) and static HTML/JS (frontend), wired via npm workspaces.

Scope and audience

This document is intended for portfolio or technical readers who need a clear picture of responsibilities and data flow—not a line-by-line specification. Implementation details (exact endpoints, error branches, and internal helpers) live in the source files named above.

Disclaimer: Automated trading carries financial and operational risk. This software interacts with real markets and mainnet assets; use requires understanding of Bitcoin fees, marketplace rules, and local regulations. The authors are responsible for their own keys, configuration, and outcomes.