solver is a Go implementation of a solver bot for the Arkade Intents. It ships the solverd daemon and the solver CLI.
A maker posts a swap offer as a VTXO on an Arkade. The solver bot watches the arkd transaction stream, finds offers that match its configured pairs and price ranges, and fulfills them atomically via an emulator-signed Arkade transaction.
arkd tx stream ─► Solver ─► Plugin.Match(tx) ─► intent ─► Plugin.Solve(intent)
│
└── runs enabled plugins in one runtime
A solver bot is a small runtime that subscribes to arkd's transaction stream and hands every PSBT it sees to one or more plugins. A plugin is just two methods:
type Plugin interface {
Match(ctx, tx) (intent any, ok bool) // is this tx relevant? extract what I need
Solve(ctx, intent) // react to it
}Match is the cheap filter+decode pass; Solve is the (usually slow) reaction.
The runtime (pkg/executor) calls Match sequentially for each plugin, then
spawns a goroutine for each matched Solve. Panics in either are recovered so
one buggy plugin can't take the bot down, and Run waits for in-flight solves
to drain on shutdown.
Most plugins read protocol data from the Arkade extension TLV in the OP_RETURN output of the funding tx. Today one plugin ships with the daemon:
pkg/banco— swap solver library. Decodes a swap offer, range-checks the amount and price, and fulfills via the emulator.
solverd composes enabled plugins into one Executor runtime. The runtime may
still use per-plugin arkd subscriptions internally so server-side filters can
drop unrelated txs before they reach the bot. Adding a new protocol means
writing a new Plugin and wiring it in cmd/solverd. See
pkg/executor/README.md for the plugin authoring guide.
Wire-protocol primitives for the swap.
Offer— typed swap offer, encoded as a TLV payload inside an Arkade extension packet (PacketType = 0x03). Methods:Serialize,ToPacket,FulfillScript, andVtxoScript(builds the swap taproot tree from the maker, emulator, and signer keys).DeserializeOffer/FindBancoOffer— decode an offer from raw bytes or pull one out of an Arkade extension.CreateOffer— maker-side helper: queries the emulator for its signer key, derives the maker address from the Arkade client, assembles anOffer, and returns the hex-encoded offer + extension packet + swap address to fund (CreateOfferParams/CreateOfferResult).GetOffers— queries the indexer for VTXOs sitting at a swap address, used by a maker to check whether its offer is still live ([]OfferStatus).FulfillOffer— taker-side atomic swap: builds the Arkade transaction that spends the swap VTXO to the maker's pkScript (payingWantAmount/WantAsset) and returns change to the taker, signs it with the emulator, and submits it (FulfillResult).
Generic plugin-based executor runtime. Consumes a stream of PSBT packets and dispatches each one to its registered plugins.
Plugininterface —Match(ctx, *psbt.Packet) (intent any, ok bool)decides whether a tx is interesting;Solve(ctx, intent)reacts to a match.Executor/New(plugins ...Plugin)— runtime wrapping one or more plugins.Run(ctx, source) error— subscribes plugins, fans matches out toSolvegoroutines, and returnsctx.Err()on cancel.
The solver plugin and its supporting types — the building block for a taker bot.
Plugin/NewPlugin(Config)— implementsexecutor.Pluginfor the swap protocol: decodes the offer from a tx, looks up a matching configured pair, range-checksWantAmount, validates price within 1% of the feed, and fulfills viacontract.FulfillOffer.Config— dependencies:arksdk.ArkClient, emulator client,PairRepository,PriceFeed, optionalFulfillmentListener, optionallogrus.FieldLogger, andPriceCacheTTL(default 5 minutes).Offer/NewOffer(*wire.MsgTx)— wrapscontract.OfferwithFundingTxid,DepositAsset, andDepositAmountextracted from the funding tx. Helpers:IsBTCDeposit,DepositAssetStr,WantAssetStr,ComputePrice.Pair/PairRepository— trading pair definition (base/quote, min/max amount, decimals, price-feed URL, invert flag) and the read-only repository interface used by the plugin.PriceFeed— pluggable price source; the plugin wraps it in an internal TTL cache.FulfillmentEvent/FulfillmentListener— emitted after every successful fulfillment; the daemon wires a listener that persists trades to SQLite.SubscribeArkd— helper that returns a<-chan *psbt.Packetfrom arkd's transaction stream, suitable for feeding intoExecutor.Run.
All pkg/ packages are intended to be importable by other projects and do not
depend on any internal/ code.
Daemon that boots a solver, a SQLite-backed wallet, the gRPC+REST API, and the web UI. Configured entirely through environment variables:
| Variable | Required | Default | Purpose |
|---|---|---|---|
SOLVER_ARK_URL |
✓ | — | arkd gRPC endpoint |
SOLVER_WALLET_SEED |
✓ | — | wallet seed (hex) |
SOLVER_EMULATOR_URL |
✓ | — | emulator endpoint |
SOLVER_WALLET_PASSWORD |
— | wallet unlock password | |
SOLVER_DATADIR |
$HOME/.solverd |
data directory (SQLite DB lives here) | |
SOLVER_GRPC_PORT |
7070 |
gRPC listener | |
SOLVER_HTTP_PORT |
7071 |
HTTP REST + web UI listener | |
SOLVER_LOG_LEVEL |
4 (Info) |
logrus level | |
SOLVER_BANCO_ENABLED |
true |
enable the swap plugin |
The banco plugin must be enabled. The daemon registers all enabled plugins in one solver runtime.
CLI client for the HTTP API. Points at http://localhost:7071 by default
(--server or SOLVER_SERVER to override). Commands:
solver pair add --pair BTC/<asset> --min … --max … --price-feed …
solver pair update …
solver pair remove --pair …
solver pair list
solver status
solver balance
solver address
make build # builds ./solverd and ./solver
make docker # builds the solverd image
make proto # regenerates api-spec/protobuf/gen
make sqlc # regenerates internal/infrastructure/db/sqlite/sqlc
make lint
make test # unit testsEnd-to-end tests run against a local nigiri + arkd stack:
make setup-test-env # boot nigiri + arkd + emulator, fund arkd wallet
make integrationtest # run ./test/e2e/...
make teardown-test-envIf nigiri is already running (e.g. in CI, where the vulpemventures/nigiri-github-action
sets it up), use make docker-run and make docker-stop instead — they bring up
the solverd-side stack and fund the arkd wallet without touching nigiri.