Skip to content
LogoLogo

Instant swaps (dealer pools)

A dealer pool is a constant-product market between two coins, backed by a liquidity provider's own coins at one address. You ask the market board for a quote, build and sign the whole swap, and the pool's dealer checks its side and signs last, usually within seconds. One transaction: both coins move or nothing does. Nobody holds anyone else's coins. Design, rules and research: Dealer pools.

Pools run behind the gateway at /market/v1/pools, next to the market board. In the wallets they are the Instant tab of the Swap screen.

Find a pool and a price

An indicative price sets nothing aside: use it for live display while the user types.

import { createClient, listPools, pricePoolSwap } from 'arrekta-sdk'
 
const gw = createClient({ url: 'http://127.0.0.1:3000' })
const pools = await listPools(gw) // [{ pair: 'GOLD/ARRK', reserves, price, fee_bps, online, … }]
const p = await pricePoolSwap(gw, { give: arrkId, want: goldId, amount: 10_000_000n }) // 0.1 ARRK in
p.want.amount // GOLD out (base units) · p.price_impact_bps · p.pool.fee_bps
from arrekta_sdk import Client
 
gw = Client("http://127.0.0.1:3000")
pools = gw.list_pools()
p = gw.price_pool_swap(arrk_id, gold_id, 10_000_000)

exact: 'out' (exact="out") makes the amount what you receive. The curve is Uniswap v2's, with the pool's fee taken from what you pay; poolAmountOut / pool_amount_out compute it locally.

Take a firm quote and swap

A firm quote holds the pool coins your swap must spend for about 30 seconds. You name your own unspent coins that pay for it (each coin backs one open quote; asking again with the same coins replaces the old quote). Build the swap with exactly the quoted pool coins, spend the coins you named, sign your inputs and post it.

import { buildPoolSwap, decodeAddress, getPoolSwap, postPoolSwap, pstToBase64, quotePoolSwap, signPst } from 'arrekta-sdk'
 
const quote = await quotePoolSwap(gw, {
  give: arrkId,
  want: goldId,
  amount: 10_000_000n,
  takerCoins: [{ outpoint: `${txid}:${vout}`, address: myAddress }], // your coins that pay for it
})
const pst = buildPoolSwap({
  quote,
  poolLock: decodeAddress(quote.pool.address, 'regtest'),
  takerCoins: myCoins, // [{ outpoint, output }]: the coins you named, plus ARRK for the fee
  receive: myLock,
  change: myChangeLock,
  fee: 120_000n,
  network: 'regtest',
})
const signed = signPst(pst, [myKey])
const swap = await postPoolSwap(gw, { quoteId: quote.id, swap: pstToBase64(signed) })
// poll until the dealer has signed: posted → broadcast → confirmed (or rejected / expired)
const status = await getPoolSwap(gw, { txid: swap.txid })
from arrekta_sdk import PoolCoin, build_pool_swap, decode_address
 
quote = gw.quote_pool_swap(arrk_id, gold_id, 10_000_000, [{"outpoint": f"{txid}:{vout}", "address": my_address}])
pst = build_pool_swap(quote, decode_address(quote["pool"]["address"], "regtest"),
                      [PoolCoin(outpoint, output)], my_lock, my_change_lock, 120_000, "regtest")
signed = pst.sign([my_key])
swap = gw.post_pool_swap(quote["id"], signed.to_base64())
status = gw.get_pool_swap(swap["txid"])

When you receive ARRK, pass feeFromReceive: true (fee_from_receive=True): the fee comes out of what the pool pays you, so you need no ARRK of your own. The board refuses a swap that spends other pool coins, pays the pool less or takes more than quoted, or carries unsigned or spent inputs, or leaves out a coin you named (swap-terms, swap-unsigned, coin-spent, quote-expired).

In a wallet

const price = await wallet.poolPrice({ give: 'ARRK', want: 'GOLD', amount: '0.1' }) // live display
const quote = await wallet.poolQuote({ give: 'ARRK', want: 'GOLD', amount: '0.1' })
const prepared = await wallet.preparePoolSwap(quote) // exact preview, coin warnings included
const rec = await wallet.confirmPoolSwap(prepared.id) // signs and posts
await wallet.poolSwapStatus(rec.id) // completed once the dealer broadcast it

Run a pool

The arrekta-dealer service runs pools from its own seed: one address per pool, a signed heartbeat so the board quotes it, and its own check of every swap before it signs.

# .env
ARREKTA_DEALER_POOLS=GOLD/ARRK:30   # A/B:fee_bps (30 = 0.3 %), comma-separated
./all_svc/arrekta-compose.sh build dealer
./all_svc/arrekta-compose.sh up platform dealer
curl http://127.0.0.1:29009/healthz   # each pool's address and reserves: deposit both coins there

To build your own dealer, the SDKs carry the pieces: poolId, poolRegisterMessage, signPoolMessage, registerPool, pollPool (heartbeat and long poll), reportPoolSwap (pool_id, pool_register_message, sign_pool_message, register_pool, poll_pool, report_pool_swap in Python). Check every swap on your own reserves before signing: only your quoted coins, paid at least your curve price.

import { poolId, poolRegisterMessage, registerPool, signPoolMessage } from 'arrekta-sdk'
 
const id = poolId(address, base, quote)
const at = Math.floor(Date.now() / 1000)
const signature = signPoolMessage(poolKey, poolRegisterMessage(id, base, quote, address, 30, 'regtest', at))
await registerPool(gw, { address, publicKey, base, quote, feeBps: 30, at, signature })
import time
from arrekta_sdk import pool_id, pool_register_message, sign_pool_message
 
pid = pool_id(address, base, quote)
at = int(time.time())
sig = sign_pool_message(pool_key, pool_register_message(pid, base, quote, address, 30, "regtest", at))
gw.register_pool(address, public_key, base, quote, 30, at, sig)