How EarthBucks Swap Works: The Contracts
2026-10-25 · Ryan X. Charles
EarthBucks Swap is live. You can now trade EBX for Bitcoin Cash (BCH) with another user, directly from your wallet to their wallet. Nobody holds your coins during the trade, and EarthBucks never has your private keys.
This is the first post of three about Swap:
- The contracts (this post): how a swap works on the two blockchains.
- The app: how to use Swap, with its limits and its fee.
- The history: how Bitcoin Cash came into EarthBucks, and what comes next.
This post is technical. It explains the scripts on both chains, the order of the steps, the deadlines, and what the wallets do when a step goes wrong.
The problem
Alice has EBX and wants BCH. Bob has BCH and wants EBX. They agree on a price. Who sends first?
If Alice sends her EBX first, Bob can keep it and send nothing. If Bob sends his BCH first, Alice can do the same. An exchange solves this problem: both users give their coins to the exchange, and the exchange makes the trade. But then the exchange holds the coins of both users, and it can lose them or steal them.
An atomic swap solves the problem with no third party. Either both users get the agreed coins, or both users keep their own coins. No step can leave one user with both sides.
In Swap, the user who gives EBX is the seller (Alice, in this post), and the user who gives BCH is the buyer (Bob).
Hash time lock contracts
A swap uses one contract on each chain: a hash time lock contract (HTLC). Each HTLC locks coins with two ways to spend them:
- Claim: the other user can spend the coins with a secret and their signature.
- Refund: the user who locked the coins can take them back with their signature, after a refund height (a block number).
Alice makes a random 32-byte secret in her browser. She gives Bob only the SHA-256 hash of the secret. Both contracts use the same hash. Thus the secret that claims one lock also claims the other lock.
The key idea is this: when Alice claims Bob’s BCH, she must show the secret on the BCH blockchain. Then Bob reads the secret from her claim, and he uses it to claim her EBX. Bob does not have to trust a message from Alice. He gets the secret from the transaction itself.
The steps
A trade starts when a user takes a card in the Swap room. Then the wallets of both users do these steps. You do not have to do them by hand.
- The hash. Alice’s wallet makes the secret and gives its hash. If Alice’s wallet does not give the hash within one hour of the take, the trade is cancelled. No coins are locked yet.
- The terms. Both wallets sign the full terms of the swap. The terms include the hash, the four public keys (EBX and BCH, for each user), the amounts, the refund heights, the number of BCH confirmations, the fees and the time margins. A wallet signs only terms that it checked itself.
- Alice locks EBX. Bob’s wallet waits until the EBX lock is final.
- Bob locks BCH. Alice’s wallet checks the BCH lock. By default, it checks for a double-spend, which takes a few seconds. If the maker of the card chose to wait, it waits until the lock has enough confirmations.
- Alice claims the BCH. Her claim shows the secret.
- Bob claims the EBX with the secret from Alice’s claim.
Why Alice locks first
Alice knows the secret, and Bob does not. If Bob locked first, Alice could wait, look at the market, and then decide to claim or not. When Alice locks first, she commits before Bob does. Bob locks only after he sees Alice’s lock on the EBX blockchain, with the correct amount and the correct hash.
Why the BCH lock needs a check, and the EBX lock does not
The two chains are different here.
EarthBucks finalizes a transaction before it goes into a block, and a final EBX transaction never reverts. Thus Bob’s wallet can lock BCH as soon as Alice’s EBX lock is final. It does not wait for an EBX block.
A BCH transaction can revert: a reorganization can replace its block, or a double spend can replace it before it gets into a block. Thus Alice’s wallet checks Bob’s BCH lock before it shows the secret. If Alice claimed and Bob’s lock then reverted, Bob would have the secret and could claim the EBX, but Alice would have no BCH.
By default, a trade does not wait for a BCH block. Alice’s wallet waits until the lock has been in the mempool of our BCH node for 5 seconds. Then it asks the node for a double-spend proof of the lock and of its unconfirmed parents. BCH nodes send these proofs to each other when they see two transactions that spend the same coin. With no proof, Alice’s wallet claims. With a proof, it claims only after a block includes the lock. This check stops most double-spends, but not a double-spend that a miner helps with. The seller has this risk, and the trade limits keep it small.
The maker of a card can choose to wait for confirmations, for example for a large trade. Then the number of confirmations depends on the amount: 1 confirmation for up to 1 BCH, and 1 more for each further 1 BCH, up to 6.
The deadlines
The refund heights are the most important safety values of a swap. Each
refund height is a block height on its own chain, counted from the heights
when the terms were made (the origin). With c BCH confirmations, where
c is 1 for a trade that does not wait:
- The BCH refund is
2c + 9BCH blocks after the origin. - The EBX refund is
3c + 28EBX blocks after the origin.
For a trade with one confirmation, the BCH refund comes about 11 blocks (about 1 hour and 50 minutes) after the origin, and the EBX refund comes about 31 blocks (about 5 hours and 10 minutes) after the origin. Both chains aim at one block each 10 minutes.
Why Bob’s refund comes first
Alice can claim Bob’s BCH until Bob’s refund is in a block. If Alice’s EBX refund opened before that, Alice could do both: take back her EBX and then claim Bob’s BCH. Thus Bob’s BCH refund must come well before Alice’s EBX refund.
Block times are random, so the two chains do not keep exact time with each
other. A slow run of BCH blocks could make the BCH refund come late. Thus
the EBX refund comes at least 19 + c BCH block times after the BCH
refund. With this gap, the probability that slow BCH blocks let Alice take
both coins is about 1 in 4,400 for each trade. The terms checker refuses
any terms with a smaller gap.
The same gap gives Bob his time: after Alice’s claim shows the secret, Bob has hours to claim the EBX before Alice’s EBX refund opens.
Room for each step
A wallet does not start a step unless the step can finish in time. Before a wallet locks coins, it checks that there is still room for the lock, the BCH confirmations, the claim, and a reaction margin of 30 minutes for each side. Before Alice claims, her wallet checks that her claim has its margins too. Thus no wallet locks coins that cannot be claimed in time.
The BCH contract
The BCH lock is a standard BCH script in a P2SH32 output. In short, the script is:
OP_DUP OP_1 OP_EQUAL
OP_IF
OP_DROP
OP_SIZE 32 OP_EQUALVERIFY
OP_SHA256 <hash> OP_EQUALVERIFY
[fee check]
<Alice's BCH public key>
OP_ELSE
OP_0 OP_EQUALVERIFY
<BCH refund height> OP_CHECKLOCKTIMEVERIFY OP_DROP
<Bob's BCH public key>
OP_ENDIF
OP_CHECKSIG
The claim gives <signature> <secret> 01. The refund gives <signature>
and an empty value. The secret must be exactly 32 bytes.
A pinned selector
The value at the top of the stack selects the branch. A normal script accepts many values as “true”. Then anybody who sees Alice’s claim in the mempool could change that value to a different “true” value, and thus change the transaction ID of her claim without her signature. BCH has no rule that stops this.
Thus the script pins the selector: the claim branch accepts only 01, and
the refund branch accepts only an empty value. Only the signer can change a
claim or a refund.
The EBX contract
The EBX lock is a new EBX script type, HTLCXR 90D 60D. Every EBX wallet output has a recovery path and an expiry. The rule for EarthBucks is that all EBX transactions must have expiry and recovery scripts, and a swap lock is no exception.
Thus the EBX lock is the HTLC inside the owner branch of the standard wallet output (PKHXR 90D 60D). In short:
OP_IF
OP_IF
OP_SIZE 32 OP_EQUALVERIFY
OP_SHA256 <hash> OP_EQUALVERIFY
[fee check]
OP_DUP OP_DOUBLEBLAKE3 <Bob's key hash> OP_EQUALVERIFY OP_CHECKSIG
OP_ELSE
<EBX refund height> OP_CHECKLOCKABSVERIFY OP_DROP
OP_DUP OP_DOUBLEBLAKE3 <Alice's key hash> OP_EQUALVERIFY OP_CHECKSIG
OP_ENDIF
OP_ELSE
OP_IF
<60 days> OP_CHECKLOCKRELVERIFY OP_DROP
OP_DUP OP_DOUBLEBLAKE3 <recovery key hash> OP_EQUALVERIFY OP_CHECKSIG
OP_ELSE
<90 days> OP_CHECKLOCKRELVERIFY OP_DROP OP_1
OP_ENDIF
OP_ENDIF
The first branch is the swap: claim with the secret, or refund after the refund height. The second branch is the same recovery (after 60 days) and expiry (after 90 days) as every other EBX output.
The terms checker makes sure that the EBX refund comes at most half of the recovery age after the origin. A refund comes after a few hours; recovery opens after 60 days. Thus recovery can never touch a live swap. It exists only for the reasons in You Own Your EarthBucks, such as lost keys.
To support this script, we added the OP_SHA256 opcode to the EBX script
language. EBX uses BLAKE3 for its transaction IDs and signatures, and that
does not change. But BCH has no BLAKE3, so both locks must use a hash
function that both chains have.
The payouts are normal wallet outputs too. Bob’s EBX claim pays a PKHXR 90D 60D output of his key, with his recovery key hash.
The fee is in the script
Swap charges the taker of a card a fee of 1% of the coin that the taker pays. The maker pays no fee. The fee is not a separate payment that a user could forget or refuse. It is in the contract.
The taker’s lock holds the amount plus the fee. Its claim branch has a fee check: the claim must pay at least the fee to the fee address, in a specific output. A claim that does not pay the fee is not valid. A refund needs no fee output, so a trade that fails costs no fee.
The fee check uses three introspection opcodes: OP_INPUTINDEX,
OP_OUTPUTVALUE and OP_OUTPUTBYTECODE. A script reads the transaction
that spends it with these opcodes. BCH has had them since 2022. We added
the same three opcodes to the EBX script language, with the same codes as
on BCH, so the two locks check the fee the same way.
The fee check reads the output at the position of the lock’s own input, plus one. Thus a claim of one lock in input 0 pays the fee in output 1. Two locks can never share one fee output, so one claim cannot pay one fee for two trades.
Keys and state
Each trade gets new keys. The keys of a trade come from your master key, with the same key derivation as the rest of your wallet: an EBX key and a BCH key for the trade, and an EBX recovery key hash. The server knows how each key derives, but it does not know the private keys.
Before it locks, each wallet pays its own swap key the exact amount of the lock, with the normal payment flow. Then it locks from that key. Thus each lock has one input and at most one change output, and the other wallet can check the lock exactly. A wallet sends this payment only when the lock still has room before the deadlines.
The browser does each step: it signs the terms, the payments, the locks and the claims. The server gets only signatures, signed transactions and the state of the trade. The state is encrypted by your wallet, and the server only stores it. Thus a reload, or a different tab of EarthBucks, continues the trade from where it stopped.
The edge cases
Most of the work on Swap was not the successful trade. It was what happens when a step goes wrong.
A user stops
- Alice gives no hash. The trade is cancelled after one hour. Nobody locked coins.
- Bob does not lock BCH. Alice’s wallet takes back her EBX after the EBX refund height.
- Alice does not claim the BCH. Bob’s wallet takes back his BCH after the BCH refund height, and Alice’s wallet takes back her EBX after the EBX refund height. Each user gets their own coins back.
- Bob does not claim the EBX after Alice claims. This is the one case where a user can lose. Bob has hours to claim, but if his wallet stays closed until Alice’s EBX refund opens, Alice can take back her EBX, and she also has his BCH. Thus Swap tells each user to keep an EarthBucks tab open until their trade is done.
A swap is atomic only while each wallet acts before its deadline. This is true of every atomic swap. The deadlines give each step a margin of hours, but they cannot protect a wallet that is closed for the full window.
A lock that comes too late
A trade with signed terms but no lock by the BCH refund height expires. The wallets still follow the trade. If a lock got into a block at the last moment, the wallet of its owner refunds it after its refund height.
The secret
Bob’s wallet finds the secret by its content: it takes the first 32-byte value of the input that spends the BCH lock, whose SHA-256 is the hash. It does not depend on a specific layout of Alice’s claim. Thus a claim with more inputs or more values still shows the secret to Bob.
A reorganization can remove Alice’s claim from the BCH blockchain. But the secret was public, and a public secret cannot become secret again. Bob’s wallet saves the secret as soon as it sees it, and it keeps it.
Lost messages to the network
A wallet can send a transaction and not get an answer. The transaction could be in the network, or not. The wallet does not take a missing answer as a failure. It saves each signed transaction before it sends it, and it sends the same transaction again, never a different one. The server also sends again any claim or refund that drops out of the mempool, also when no Swap room is open.
What the server can and cannot do
The server finds the other user, relays the messages, builds the transactions for the wallets to check, and watches both chains. It cannot move your coins: each lock pays only Bob with the secret, or Alice after the refund height. Your wallet checks each transaction before it signs.
The server is still needed to do a trade. Today, your wallet sends its transactions and gets its view of each chain through earthbucks.com. Your wallet trusts that view. For example, Bob’s wallet trusts the server when the server says that Alice’s EBX lock is final. If the server stops, the locks stay on the chains with their refund paths.
Real money, small amounts
Our first test trade on the real BCH and EBX networks settled on October 18. Since then, we have audited the swap code many times. Each trade is also limited to between $1 and $100 of BCH for now, and each account to $1,000 of trades in 24 hours. The next post explains these limits, and the rest of the app.
Try it now: open Swap, and select the Swap title at the top of the room to read a short summary of the steps.