Client::send_on_chain produces a transaction that pays zero fee, so broadcasting it fails on any standard node:
sendrawtransaction RPC error: {"code":-26, "message": "min relay fee not met, 0 < 17"}
The let fee = Amount::from_sat(1_000) inside create_send_on_chain_transaction_inner is only used to overselect inputs during coin selection, it is never deducted from the transaction. The actual builder, create_unilateral_exit_transaction, sends every selected sat to the
destination + change, so sum(outputs) == sum(inputs) and the fee is 0. The sweep is effectively unusable on mainnet/signet today.
Environment
ark-client / ark-core v0.9.3 (also present in v0.9.0–v0.9.2).
- Any backend that enforces a min-relay-fee (i.e. all of them):
sendrawtransaction / esplora POST /tx.
Reproduction
- Have one or more spendable boarding outputs / unilaterally-exited VTXOs.
- Call
client.send_on_chain(addr, amount) with amount < total_selected(so a change output is produced — though it fails with or without change).
- The returned/broadcast transaction is rejected:
min relay fee not met, 0 < N.
Root cause
create_send_on_chain_transaction_inner (ark-client) selects inputs with a 1000-sat margin, then hands the builder only to_amount:
// TODO: Do not use an arbitrary fee.
let fee = Amount::from_sat(1_000);
let (onchain_inputs, vtxo_inputs) = coin_select_for_onchain(self, to_amount + fee).await?;
// ...
let tx = create_unilateral_exit_transaction(
to_address,
to_amount, // <-- the only amount the builder ever sees
change_address,
&onchain_inputs,
&vtxo_inputs,
sign,
)?;
create_unilateral_exit_transaction (ark-core, ark-core/src/unilateral_exit.rs) then computes change as everything that isn't to_amount:
let total_amount: Amount = onchain_inputs.iter().map(|o| o.amount)
.chain(vtxo_inputs.iter().map(|v| v.witness_utxo.value))
.sum();
// change = total - to_amount (no fee term)
let change_amount = total_amount.checked_sub(to_amount).ok_or_else(/* ... */)?;
let mut output = vec![TxOut { value: to_amount, script_pubkey: to_address.script_pubkey() }];
if change_amount > Amount::ZERO {
output.push(TxOut { value: change_amount, script_pubkey: change_address.script_pubkey() });
}
So outputs = to_amount + (total − to_amount) = total = inputs, and fee = inputs − outputs = 0. The 1000-sat fee used for selection is handed straight back to the user as change. There is no argument to create_unilateral_exit_transaction that reserves a fee, it is structurally a 0-fee builder.
Client::send_on_chainproduces a transaction that pays zero fee, so broadcasting it fails on any standard node:The
let fee = Amount::from_sat(1_000)insidecreate_send_on_chain_transaction_inneris only used to overselect inputs during coin selection, it is never deducted from the transaction. The actual builder,create_unilateral_exit_transaction, sends every selected sat to thedestination + change, so
sum(outputs) == sum(inputs)and the fee is0. The sweep is effectively unusable on mainnet/signet today.Environment
ark-client/ark-corev0.9.3(also present inv0.9.0–v0.9.2).sendrawtransaction/ esploraPOST /tx.Reproduction
client.send_on_chain(addr, amount)withamount < total_selected(so a change output is produced — though it fails with or without change).min relay fee not met, 0 < N.Root cause
create_send_on_chain_transaction_inner(ark-client) selects inputs with a 1000-sat margin, then hands the builder onlyto_amount:create_unilateral_exit_transaction(ark-core,ark-core/src/unilateral_exit.rs) then computes change as everything that isn'tto_amount:So
outputs = to_amount + (total − to_amount) = total = inputs, andfee = inputs − outputs = 0. The 1000-satfeeused for selection is handed straight back to the user as change. There is no argument tocreate_unilateral_exit_transactionthat reserves a fee, it is structurally a 0-fee builder.