Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fix-zcash-memo-fees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@xchainjs/zcash-js': patch
'@xchainjs/xchain-zcash': patch
---

Fix memo fee double-counting in buildTx/prepareMaxTx and bypass in buildMaxTx by standardizing all codepaths to use getFee as the single source of truth for memo output slot calculation
12 changes: 6 additions & 6 deletions packages/xchain-zcash/__tests__/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ describe('Zcash client', () => {
const fees = await client.getFees({ memo: 'test' })
const expected = {
type: FeeType.FlatFee,
average: baseAmount(25000, ZEC_DECIMAL),
fast: baseAmount(25000, ZEC_DECIMAL),
fastest: baseAmount(25000, ZEC_DECIMAL),
average: baseAmount(30000, ZEC_DECIMAL),
fast: baseAmount(30000, ZEC_DECIMAL),
fastest: baseAmount(30000, ZEC_DECIMAL),
}
expect(deepSerialize(fees)).toEqual(deepSerialize(expected))
})
Expand All @@ -161,9 +161,9 @@ describe('Zcash client', () => {
const fees = await client.getFees({ sender: 't1eiZYPXWurGMxFwoTu62531s8fAiExFh88' })
const expected = {
type: FeeType.FlatFee,
average: baseAmount(30000, ZEC_DECIMAL),
fast: baseAmount(30000, ZEC_DECIMAL),
fastest: baseAmount(30000, ZEC_DECIMAL),
average: baseAmount(40000, ZEC_DECIMAL),
fast: baseAmount(40000, ZEC_DECIMAL),
fastest: baseAmount(40000, ZEC_DECIMAL),
}
expect(deepSerialize(fees)).toEqual(deepSerialize(expected))
})
Expand Down
4 changes: 2 additions & 2 deletions packages/xchain-zcash/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ abstract class Client extends UTXOClient {
// Calculate total value of all UTXOs
const totalValue = utxos.reduce((sum, utxo) => sum + utxo.value, 0)

// Calculate flat fee for this transaction (2 outputs: recipient + potential memo)
const fee = getFee(utxos.length, memo ? 2 : 1, memo)
// Calculate flat fee for this transaction (1 = recipient only; getFee adds memo slots)
const fee = getFee(utxos.length, 1, memo)

// Calculate max sendable amount
const maxAmount = totalValue - fee
Expand Down
13 changes: 5 additions & 8 deletions packages/zcash-js/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ const TX_VERSION_GROUP_ID = 0x26a7270a
const TX_VERSION = 0x80000005

const PKH_OUTPUT_SIZE = 34
const MARGINAL_FEE = 5000
const GRACE_ACTIONS = 2
const MARGINAL_FEE = 10000

function calculateFee(inCount: number, outCount: number): number {
const logicalActions = inCount + outCount
return MARGINAL_FEE * Math.max(GRACE_ACTIONS, logicalActions)
const logicalActions = Math.max(inCount, outCount)
return MARGINAL_FEE * Math.max(1, logicalActions)
}

/**
Expand Down Expand Up @@ -104,8 +103,7 @@ export async function buildMaxTx(
if (utxos.length === 0) throw new Error('No UTXOs provided')

// For max tx: NO change output, just recipient + optional memo
const outputCount = memo ? 2 : 1
const fee = calculateFee(utxos.length, outputCount)
const fee = getFee(utxos.length, 1, memo) // 1 = recipient only; getFee adds memo slots

// Calculate total value and max sendable amount
const totalValue = sumBy(utxos, (u: UTXO) => u.satoshis)
Expand Down Expand Up @@ -152,8 +150,7 @@ export async function buildTx(
if (memo && memo.length > 80) throw new Error('Memo too long')

const inputs = selectUTXOS(utxos, amount, memo)
const outputCount = memo ? 3 : 2 // change + to + memo (if exists)
const fee = getFee(inputs.length, outputCount, memo)
const fee = getFee(inputs.length, 2, memo) // 2 = change + recipient; getFee adds memo slots
const change = sumBy(inputs, (u: UTXO) => u.satoshis) - amount - fee
if (change < 0) throw new Error('Not enough funds')

Expand Down
Loading