Joinmarket is based on maker-taker model and market makers offer liquidity for coinjoin. They get free coinjoin and earn fees from offers. Fidelity bonds are used for sybil resistance and makers lock some bitcoin while creating offers.
def get_fidelity_bond_template(self):
if not isinstance(self.wallet_service.wallet, FidelityBondMixin):
jlog.info("Not a fidelity bond wallet, not announcing fidelity bond")
return None
blocks = jm_single().bc_interface.get_current_block_height()
mediantime = jm_single().bc_interface.get_best_block_median_time()
BLOCK_COUNT_SAFETY = 2
RETARGET_INTERVAL = 2016
CERT_MAX_VALIDITY_TIME = 1
cert_expiry = ((blocks + BLOCK_COUNT_SAFETY) // RETARGET_INTERVAL) + CERT_MAX_VALIDITY_TIME
utxos = self.wallet_service.wallet.get_utxos_by_mixdepth(
include_disabled=True, includeheight=True
)[FidelityBondMixin.FIDELITY_BOND_MIXDEPTH]
timelocked_utxos = [
(outpoint, info) for outpoint, info in utxos.items()
if FidelityBondMixin.is_timelocked_path(info["path"])
]
if len(timelocked_utxos) == 0:
jlog.info("No timelocked coins in wallet, not announcing fidelity bond")
return
timelocked_utxos_with_confirmation_time = [
(
outpoint, info,
jm_single().bc_interface.get_block_time(
jm_single().bc_interface.get_block_hash(info["height"])
)
)
for (outpoint, info) in timelocked_utxos
]
interest_rate = get_interest_rate()
max_valued_bond = max(
timelocked_utxos_with_confirmation_time,
key=lambda x: FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
x[1]["value"], x[2], x[1]["path"][-1], mediantime, interest_rate
)
)
(utxo_priv, locktime), engine = self.wallet_service.wallet._get_key_from_path(
max_valued_bond[1]["path"]
)
utxo_pub = engine.privkey_to_pubkey(utxo_priv)
cert_priv = os.urandom(32) + b"\x01"
cert_pub = btc.privkey_to_pubkey(cert_priv)
cert_msg = b"fidelity-bond-cert|" + cert_pub + b"|" + str(cert_expiry).encode("ascii")
cert_sig = base64.b64decode(btc.ecdsa_sign(cert_msg, utxo_priv))
utxo = (max_valued_bond[0][0], max_valued_bond[0][1])
fidelity_bond = FidelityBond(utxo, utxo_pub, locktime, cert_expiry, cert_priv, cert_pub, cert_sig)
jlog.info("Announcing fidelity bond coin {}".format(fmt_utxo(utxo)))
return fidelity_bond
Can a joinmarket maker provide liquidity in joinstr using the same fidelity bond? Yes. They can even provide liquidity in joinstr without fidelity bonds. However, pool initiators with a proof of bond would be preferred over others by users.
Note: Joinstr has a different mechanism for sybil resistance using curve trees which does not require locking of bitcoin and only a proof of ownership is enough.
Maker in joinstr (pool initiator) without fidelity bonds
Create a pool
Use your own nostr relay (paid) for the pool
Earn fees from pool members
Joinmarket makers providing liquidity in joinstr
Create a pool with 'jm=true' in JSON
Use your own nostr relay (paid) for the pool
Share proof of bond with members without revealing UTXO
Earn fees from pool members
So, the only difference is that if the pool initiator is a joinmarket maker, users would prefer it over others.
Some clients or pools in joinstr could even allow joinmarket makers to join pools without proving ownership of a taproot UTXO (sybil resistance). So, fidelity bonds can be reused in joinstr. Paid nostr relays could offer discounts for users with proof of bond.
#!/usr/bin/env python
from autctapi import *
import asyncio
from dataclasses import asdict
async def run_all_requests_example():
config = AutctConfig()
config.privkey_file_str = "privkey-four"
proving_request = config.config_to_proof_req()
proving_result = await rpc_request(asdict(config), "RPCProver.prove", proving_request)
if proving_result["accepted"] == 0:
print("Proving request successful!")
else:
if proving_result["accepted"] not in RPC_PROOF_ERRORCODES:
print("Unrecognized error code from server!: {}".format(proving_result["accepted"]))
else:
print("Proving failed due to error: \n{}".format(RPC_PROOF_ERRORCODES[proving_result["accepted"]]))
print("\nNow we try verifying the proof that we just created:\n")
verifying_req = config.config_to_verify_req(proving_result["proof"])
verifying_result = await rpc_request(asdict(config), "RPCProofVerifier.verify", verifying_req)
if verifying_result["accepted"] not in RPC_VERIFY_ERRORCODES:
print("Unrecognized error code from server!: {}".format(verifying_result["accepted"]))
else:
print("Server response: \n{}".format(RPC_VERIFY_ERRORCODES[verifying_result["accepted"]]))
config.privkey_file_str = "new-privkey"
createkeys_request = config.config_to_createkeys_request()
createkeys_result = await rpc_request(asdict(config), "RPCCreateKeys.createkeys", createkeys_request)
print("Server response: \n{}".format(RPC_CREATEKEYS_ERRORCODES[createkeys_result["accepted"]]))
if createkeys_result["accepted"] == 0:
print("The server generated this address: {}, and stored the corresponding private key in {}".format(
createkeys_result["address"], createkeys_result["privkey_file_loc"]))
asyncio.run(run_all_requests_example())
https://github.com/AdamISZ/autct-api/blob/master/src/example.py
Fees comparison
Pool initiators aka makers can earn more sats in joinstr pools compared to joinmarket. Even if fees remain the same, it offers a different level of privacy with equal size outputs.
Conclusion
Sharing liquidity between decentralized coinjoin implementations benefits everyone. Although Adam Gibson has a different opinion on use of fidelity bonds for multiple things.