mempool-protection
v0.2.0
Published
TypeScript primitives for ordinal marketplace mempool sniping protection.
Maintainers
Readme
mempool-protection
TypeScript library for building mempool-sniping-protected ordinals listings with one function.
Install
npm install mempool-protectionAPI
The package intentionally exports a single workflow function:
createProtectedListingFlow(params)
It builds:
- lock transaction PSBT (seller inscription UTXO -> taproot output with 2-of-2 script path)
- protected sale PSBT (script-path spend of locked UTXO with required sighash model)
Minimal Example
import * as bitcoin from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
import { createProtectedListingFlow } from "mempool-protection";
bitcoin.initEccLib(ecc);
const network = bitcoin.networks.testnet;
const flow = createProtectedListingFlow({
network,
inscriptionInput: {
txid: "00".repeat(32),
vout: 0,
value: 343,
witnessUtxo: {
script: Buffer.from("0014...hex...", "hex"),
value: 343,
},
},
sellerPubKey: Buffer.from("02...hex...", "hex"),
marketplacePubKey: Buffer.from("03...hex...", "hex"),
committedOutput: {
address: "tb1q...",
value: 330,
},
marketFeeRateBps: 0,
});
// flow.lockingPsbt
// flow.salePsbtParams
Required:
networkinscriptionInputsellerPubKeymarketplacePubKeycommittedOutputmarketFeeRateBps
Optional (only when needed):
gasFundingInput: required only ifinscriptionInput.value < 343; when present, minimum value isMath.ceil(vbytes(tx1) * minRelayFeeRateSatPerVb)and tx1 inputs are limited to wrapped segwit, native segwit, or taproot- for taproot funding inputs, you can include
tapInternalKeyon the input object (32-byte x-only or 33-byte compressed key)
- for taproot funding inputs, you can include
minRelayFeeRateSatPerVb: optional relay floor used for gas-funding minimum calculation; defaults to0.1marketFeeBasisSats: if fee basis differs fromcommittedOutput.valuemultisigTaprootInternalPubKey: optional key-spend pubkey for the lock output taproot input (32-byte x-only or 33-byte compressed key)- default order when omitted:
inscriptionInput.tapInternalKey(if inscription input is taproot), thengasFundingInput.tapInternalKey(if gas input is taproot), thensellerPubKey
- default order when omitted:
signers: include only if the function should also sign (and optionally finalize) seller multisig input- signers must support
signSchnorrfor taproot script-path signing
- signers must support
Return value
createProtectedListingFlow returns:
lockingPsbtsalePsbtmultisigplannedLockedUtxolockedValueSatsmarketFeeSatsprefundingReclaimSats
Notes
- tx1 should meet relay floor fee rate:
fee(tx1) / vbytes(tx1) >= minRelayFeeRateSatPerVb(default0.1 sat/vB). salePsbtfrom this function is a listing template: exactly 1 input (the locked UTXO) and 1 output (committedOutput).- Sighash behavior for protected sale input is fixed:
- seller:
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY - marketplace:
SIGHASH_ALL
- seller:
- Seller unilateral cancellation remains intact in both stages:
- tx1 perspective: cancellation is trivial because seller controls the funding input(s) and can simply replace/cancel before confirmation.
- tx2 perspective: the locked output is taproot and includes a key-spend path on the seller key (or configured
multisigTaprootInternalPubKey), so seller can spend unilaterally without marketplace script-path cooperation.
- The library builds/signs PSBTs but does not broadcast transactions.
Example Transaction Shape (ASCII)
Example values:
inscriptionInput.value = 342gasFundingInput.value = 50minRelayFeeRateSatPerVb = 0.1(default)- tx1 estimated size is
189 vBfor2 inputs / 1 output tx1_fee_floor = Math.ceil(189 * 0.1) = 19 sats(choosetx1_fee = 19)lockedValueSats = 342 + 50 - 19 = 373committedOutput.value = 118000(seller payment UTXO)marketFeeRateBps = 100->marketFeeSats = 1180
TX1: Lock Transaction (RBF)
----------------------------------------------------------------
Inputs
[0] inscriptionInput (seller) 342
[1] gasFundingInput (seller, optional) 50
-----
392 in
Outputs
[0] taproot lock output (2-of-2 script path + key spend) 373
Fee
19 sats (>= ceil(vbytes(tx1) * minRelayFeeRateSatPerVb))TX2 Template: Produced By createProtectedListingFlow
----------------------------------------------------------------
Inputs
[0] listing input: TX1:0 locked taproot output 373
Outputs
[0] committedOutput (seller payment UTXO) 118000
Status
Underfunded by design. Marketplace buy flow adds buyer inputs/outputs.TX2 Final: Marketplace Buy Assembly (example ordering)
----------------------------------------------------------------
Inputs
[0] dummy A (p2tr) 330
[1] dummy B (p2tr) 330
[2] listing input (from template, p2tr) 373
[3] buyer payment input (p2wpkh) 120000
------
121033 in
Outputs
[0] combined dummy (p2tr) 660
[1] inscription receive output (user p2tr) 330
[2] corresponding payment UTXO (seller p2wpkh) 118000 (template output)
[3] marketplace fee (p2wpkh, optional) 1180
[4] buyer change (p2wpkh) 498
------
120668 out
Fee
365 satsImportant index rule for seller signature:
- listing input uses
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, so its corresponding output index must match. - in the example, listing input is index
2, so the seller payment output is also index2.
Computed guidance values returned by the library:
marketFeeSats = floor(marketFeeBasisSats * marketFeeRateBps / 10000)prefundingReclaimSats = max(0, lockedValueSats - inscriptionInput.value)
