Peer-to-peer swap offers
Two people swap coins in one transaction: either both legs happen or nothing moves. There is no exchange, no custody and no fee beyond the network's. The maker shares an offer (a link or QR code), the taker signs its side and sends back an acceptance, and the maker completes it. The maker signs last, so an offer never executes without its final approval. Design and rules: Swap offers and coin lists.
Read and check an offer
Both SDKs decode the same arrektaswap: envelope and reject anything inconsistent (swap-decode,
swap-version, swap-network, swap-terms, swap-expired). For an offer, the stated terms must
match the PST itself.
import { decodeSwapEnvelope, swapOfferTerms } from 'arrekta-sdk'
const offer = decodeSwapEnvelope(link, { network: 'regtest', now: Math.floor(Date.now() / 1000) })
offer.give // { asset, amount } what the maker gives (base units)
offer.want // { asset, amount } what the maker asks for
swapOfferTerms(offer.pst) // the same terms, recomputed from the transactionimport time
from arrekta_sdk import decode_swap_envelope, swap_offer_terms
offer = decode_swap_envelope(link, network="regtest", now=int(time.time()))
give, want = swap_offer_terms(offer.pst) # == (offer.give, offer.want)Make an offer
An offer is an unsigned PST with both construction flags set: the maker's inputs (what it gives),
an output paying what it wants to its own address, and its change. Set validUntil (transaction
version 2) so the swap cannot be mined after the offer expires.
import { PstFlags, createPst, encodeSwapEnvelope, swapOfferTerms } from 'arrekta-sdk'
const pst = createPst(tx /* version 2, validUntil = tip + N */, spentOutputs, 'regtest', {
flags: PstFlags.INPUTS_MODIFIABLE | PstFlags.OUTPUTS_MODIFIABLE,
})
const { give, want } = swapOfferTerms(pst)
const link = encodeSwapEnvelope({ v: 1, kind: 'offer', network: 'regtest', expires, give, want, pst, memo: '10 GOLD for 500 ARRK' })from arrekta_core.pst import FLAG_INPUTS_MODIFIABLE, FLAG_OUTPUTS_MODIFIABLE, Pst
from arrekta_sdk import SwapEnvelope, encode_swap_envelope, resolve_network, swap_offer_terms
pst = Pst.create(tx, spent_outputs, resolve_network("regtest").magic,
flags=FLAG_INPUTS_MODIFIABLE | FLAG_OUTPUTS_MODIFIABLE)
give, want = swap_offer_terms(pst)
link = encode_swap_envelope(SwapEnvelope("offer", "regtest", expires, give, want, pst, memo="10 GOLD for 500 ARRK"))Take it, then complete it
The taker appends its inputs (paying want and the fee) and an output taking give
(addPstInput / addPstOutput, or Pst.add_input / add_output), signs its inputs and returns a
kind: 'accept' envelope. Before signing, the maker checks that the taker only appended:
import { swapAcceptanceMatches } from 'arrekta-sdk'
if (!swapAcceptanceMatches(myOffer.pst, acceptance.pst)) throw new Error('the acceptance changed the offer')from arrekta_sdk import swap_acceptance_matches
assert swap_acceptance_matches(my_offer.pst, acceptance.pst)then signs, finalizes and broadcasts. It also checks its own net change is exactly −give, +want.
In a wallet
@arrekta/wallet-core does all of this, including coin selection, fees, reserving the offered coins
and previews with warnings for unverified or look-alike coins:
const offer = await maker.createSwapOffer({ give: { asset: 'GOLD', amount: '10' }, want: { asset: 'ARRK', amount: '5' } })
const take = await taker.prepareSwapTake(offer.offer) // preview first
const { acceptance } = await taker.confirmSwapTake(take.id) // signs the taker's side
const done = await maker.prepareSwapComplete(acceptance!) // checks it, previews
const { txid } = await maker.confirmSwapComplete(done.id) // signs and broadcastsThe desktop, mobile and extension wallets put this behind Swap: Market, New offer, Open a link, My swaps. To find a counterparty without sharing links by hand, list the offer on the market board.