Skip to content

Conversation

@tomt1664
Copy link
Member

@tomt1664 tomt1664 commented Sep 2, 2025

This ELIP describes proposed features for Elements that enable the use of issued assets for transaction fees in addition to the policy asset (pegged asset).

This will enable an Elements blockchain/sidechain to use any issued asset for the transaction fees, with the assets used and rates applied being agreed and set by the block creators. Changes are proposed to relay/mempool policy and block creation.

Several mechanisms are proposed to set, synchronise and publish the assets and rates that are accepted by the block producers.

@tomt1664 tomt1664 marked this pull request as ready for review September 8, 2025 13:50
@psgreco psgreco requested review from apoelstra and delta1 September 8, 2025 14:38
@apoelstra
Copy link
Member

Can you squash all the commits that affect the same file, add commit descriptions, and a PR description saying what this is and where it is used?

@delta1
Copy link
Member

delta1 commented Sep 11, 2025

Some spelling/typos:

elip-0202.mediawiki:18: modication ==> modification
elip-0202.mediawiki:18: implimented ==> implemented
elip-0202.mediawiki:18: curent ==> current
elip-0202.mediawiki:26: implimentaion ==> implementation
elip-0202.mediawiki:26: ouput ==> output
elip-0202.mediawiki:28: recieved ==> received
elip-0202.mediawiki:28: transparrent ==> transparent
elip-0202.mediawiki:32: appllied ==> applied
elip-0202.mediawiki:32: requring ==> requiring
elip-0202.mediawiki:38: addtion ==> addition
elip-0202.mediawiki:38: conensus ==> consensus
elip-0202.mediawiki:38: requring ==> requiring
elip-0202.mediawiki:40: aplly ==> apply
elip-0202.mediawiki:40: addtion ==> addition
elip-0202.mediawiki:40: curretly ==> currently
elip-0202.mediawiki:48: minumum ==> minimum
elip-0202.mediawiki:50: trasaction ==> transaction
elip-0202.mediawiki:50: retriving ==> retrieving
elip-0202.mediawiki:52: requring ==> requiring
elip-0202.mediawiki:67: confirgured ==> configured
elip-0202.mediawiki:188: Ouput ==> Output

Copy link
Member

@delta1 delta1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass, a few minor things

Add controller descritpion

Add publication descritpion

Add test vectors

fix typos
@apoelstra
Copy link
Member

apoelstra commented Sep 15, 2025

In 5f0dbbd:

trailing whitespace on most lines

typo conseus -> consensus

There are three instances of , however. These are each run-on sentences and should be changed to . However,

typo tokensied -> tokenized (prefer the American spelling, and also move the i) (I'd also be fine keeping tokenised which appears elsewhere)

You say "The total transaction fee calculated..." but then give a formula that appears to be about the minimum relay feerate. You don't specify how to calculate the total transaction fee. You also don't specify whether multiplication should use a floor or ceiling if rates are non-integral.

In "Issued asset fees" you say that the maximum fee rate (I guess you mean exchange rate, since the units for this are sats/asset?) is 92233720368. But in practice this is then multiplied by the actual fee rate (in sats/weight). What happens in case of overflow? You mention MAX_MONEY and say that "currently in BlockAssembler the total value of fee outputs 'will not' exceed MAX_MONEY" but what does this actually mean?

Is ASSET_RATE_SCALE_FACTOR the same for all assets?

@tomt1664
Copy link
Member Author

trailing whitespace on most lines

typo conseus -> consensus

There are three instances of , however. These are each run-on sentences and should be changed to . However,

typo tokensied -> tokenized (prefer the American spelling, and also move the i) (I'd also be fine keeping tokenised which appears elsewhere)

Fixed.

You say "The total transaction fee calculated..." but then give a formula that appears to be about the minimum relay feerate. You don't specify how to calculate the total transaction fee.

I've changed the explanation of this to correct some things and make it clearer. I've renamed the asset 'fee rate' to asset fee rate multiplier to emphasise that this value just scales the existing policy asset minimum rates as applied to mempool and relay policy.

You also don't specify whether multiplication should use a floor or ceiling if rates are non-integral.

I initially used a double for the issued asset fee rate multiplier, but then changed for a fixed precision integer (I believe this to be a more robust approach, in the same way rates currently specified in sat/vbyte use a fixed precision of 1000), so all arithmetic is in unit_64. ASSET_RATE_SCALE_FACTOR sets the precision of the issued asset fee rate multiplier.

Config and RPC input/output uses floating points.

In "Issued asset fees" you say that the maximum fee rate (I guess you mean exchange rate, since the units for this are sats/asset?) is 92233720368. But in practice this is then multiplied by the actual fee rate (in sats/weight). What happens in case of overflow?

The minRelay rate or min mempool rate would have be bigger than ASSET_RATE_SCALE_FACTOR. But a check for this should be added.

You mention MAX_MONEY and say that "currently in BlockAssembler the total value of fee outputs 'will not' exceed MAX_MONEY" but what does this actually mean?

This was in relation to this PR ElementsProject/elements#1476
Currently any individual fee output or total fees in a block cannot exceed MAX_MONEY. It's possible that with assets with very large issuances, it possible to make a block with very large fees that exceed MAX_MONEY that is not consensus valid. This is just a statement that BlockAssembler must always create a valid block by making sure total fees < MAX_MONEY until that restriction is removed.

Is ASSET_RATE_SCALE_FACTOR the same for all assets?

Yes, it just defines the precision for the issued asset fee rate multiplier.

@apoelstra
Copy link
Member

In 9641693:

This introduces the terms "input" and "output" without defining them, and still says nothing about how overflow in feerates is handled.

The minRelay rate or min mempool rate would have be bigger than ASSET_RATE_SCALE_FACTOR. But a check for this should be added.

It would just need to be greater than one. But my question was not about min relay, but about how these transactions' feerates are computed so that transactions can be ordered in the mempool (AFAICT this is never mentioned in this document).

@tomt1664
Copy link
Member Author

Sorry for the delay in replying. I've gone through and changed the logic of how the rate multiplier is applied, changing it to computing an 'effective rate' for a transaction with an issued asset fee which can then be used in place of the policyAsset rate for both meeting relay/mempool minimums and mempool ordering.

This introduces the terms "input" and "output" without defining them, and still says nothing about how overflow in feerates is handled.

I've removed that, and specified clearly the format of values used in configuration and RPC arguments and how it is then converted to the fixed precision integer.

and still says nothing about how overflow in feerates is handled.

It would just need to be greater than one.

Yes, you are right. I've added a maximum value parameter for the rate multiplier.
In the calculation for the new effective fee rate, the rate multiplier is multiplied by the fee rate in the issued asset. This could potentially overflow int64, but only if the fee rate in the issued asset multiplied by the multiplier gives an effective fee rate of > 922 * COIN (a value of 922 LBTC/vbyte). It's implausible a rate multiplier and fee would be set to this level on purpose, but to protect against that possibility, if the result of the multiplication does exceed that value, then the effective fee rate is set to this maximum.

But my question was not about min relay, but about how these transactions' feerates are computed so that transactions can be ordered in the mempool (AFAICT this is never mentioned in this document).

The 'effective rate' for an issued asset fee transaction is now used for both comparison with minimums and also for mempool ordering. Statements explaining this have been added to the text.

@tomt1664 tomt1664 requested a review from delta1 September 22, 2025 11:38

For fees outputs in an issued asset, the fee rate (in units/vbyte) will be scaled by a fee rate multiplier to determine an 'effective rate'. This effective rate is then used in place of the policyAsset fee rate for the purpose of meeting relay and mempool minimums and for mempool ordering.

E.g. An issued asset is USDT. The current conversion rate between USDT and LBTC (<code>policyAsset</code>) is 100:1 (i.e. 100 USDT to one LBTC) the issued asset fee rate multiplier would then be set to 0.01 (i.e. the fee paid in USDT will be multiplied by 0.01 for comparison with LBTC fees). If the USDT asset is used for the fee in any transaction, it should pay 100 times in USDT base units the fee rate that would have applied in LBTC (the policyAsset). If the current minimum fee rate for mempool and relay is 1 sat/vbyte in LBTC (i.e. the policy asset fee rate), then the effective minimum fee rate for this asset would be 100 units/byte. The minimum fee output value required for a transaction of size e.g. <code>vsize = 257</code> transaction to be relayed would be: <code>257 x 1 x 100 = 25700</code> base units of the USDT asset.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 4b5eeb2:

I don't undersztand this example. For one thing, it seems cherry-picked to have an integer ratio; what if the actual exchange rate is 105:100 say? For another thing, it seems to normalize the fee to USDT rather than L-BTC. If there are multiple fee outputs with different assets then which one is chosen to normalise to?

Copy link
Member Author

@tomt1664 tomt1664 Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple integer ratio was chosen to make the arithmetic of this introductory example easier, but maybe it's too abstract. A more realistic exchange rate can be used, which illustrates precision/rounding issues.

The fee is paid in USDT, and the monetary value of that fee must equal (or exceed) the monetary value of the fee currently required in LBTC. The multiplier converts the monetary value of an amount of USDT to an amount of LBTC.

Another attempt:

"E.g. An issued asset is USDT. The current accepted exchange rate between USDT and LBTC (the policyAsset) is $114,171.23 to 1 LBTC. The issued asset fee rate multiplier for this ratio is then set to 1/114,171.23 = 0.00000876 (rounded to a precision of 1e-8). For a transaction that uses the USDT asset for the fee, the fee rate (in units/vbyte) multiplied by this number must be greater than or equal to the minimum fee rate (in sat/vbyte) applied in LBTC.

If the current minimum relay fee rate is 1 sat/vbyte in LBTC, then the minimum fee rate required in USDT would be 1/0.00000876 = 114156 units/vbyte (rounded up to the nearest integer). To relay this transaction, a client will determine the fee rate in the USDT asset, multiply it by 0.00000876 to calculate the effective rate and then verify it is greater than or equal to 1 sat/vbyte. "

In the case of multiple fee outputs, in principle, an effective fee (the fee amount scaled by the multiplier for each different asset) can be calculated for each fee output and then an effective fee rate for the transaction determined. However, it may be simpler to require wallets to use a single asset and output, and only consider the first one.


A new configuration option will be added to enable any node that is creating blocks to publish the assets, rates (and optionally destination) they are applying to their own policy. This information will be encoded into an <code>OP_RETURN</code> output for each accepted asset and included in the coinbase transaction for blocks that they create every <code>N</code> blocks (where <code>N</code> is specified in the configuration). Wallets and users will then be able to read and decode the latest assets and rates being applied by block creators and use this information for determining fees when creating transactions. For ease in retrieving this, all nodes will cache the latest rates which can be accessed via a new RPC. This publication method assumes that all block creators are applying the same assets and rates to their policy, which they would need to agree upon.

In order for the acceptance of issued asset and rates for fees to be reliable and predictable, all block creators and bridging nodes (and all relaying nodes) should be applying the same policy with the same list of accepted assets and rates. To coordinate this and apply changes to a large number of nodes is impractical, which may be required regularly as exchange rates change. To provide an alternative method for remotely modifying the accepted assets and rates, individual nodes can be configured to accept updates via an on-chain transaction - this way all block creator and bridge nodes can have their applied policy updated simultaneously without requiring direct access to the RPC interface.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 4b5eeb2:

This paragraph doesn't make sense after the previous paragraph. It says that the blocksigners will encode exchange rates in OP_RETURN outputs from coinbases, then describes an "alternate" method in which exchange rates are accepted "via an on-chain transaction" with no further details given. Is this the same thing as the OP_RETURN mechanism you just described? It is contrasted to "requiring direct access to the RPC interface" but you've only described using the RPC interface to access the current rates, not for setting them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this seems really overcomplicated. Ultimately the blocksigners are deciding what transactions are being put into blocks in what order. Why do we need a complex mechanism for people to set their mempool policy to something different? (If that is what's happening here -- I am really having trouble reading this. I think you could probably remove around 75% of the words in the Overview and Design sections without loss.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph doesn't make sense after the previous paragraph. It says that the blocksigners will encode exchange rates in OP_RETURN outputs from coinbases, then describes an "alternate" method in which exchange rates are accepted "via an on-chain transaction" with no further details given. Is this the same thing as the OP_RETURN mechanism you just described?

These are two separate features. The first paragraph introduces a method for publishing the assets and rates accepted by a node. The second is a separate method for remotely setting the assets and rates accepted by a node without direct access to it, as an alternative to configuring node directly.

It is contrasted to "requiring direct access to the RPC interface" but you've only described using the RPC interface to access the current rates, not for setting them.

It states above "This can be set as part of the node configuration, specifying the assetID, the relative rate multiplier (and optionally the coinbase destination for the fee collection by the block creator) before initialization, or set dynamically at runtime via a new RPC option."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this seems really overcomplicated. Ultimately the blocksigners are deciding what transactions are being put into blocks in what order. Why do we need a complex mechanism for people to set their mempool policy to something different? (If that is what's happening here -- I am really having trouble reading this. I think you could probably remove around 75% of the words in the Overview and Design sections without loss.)

The issue is that in practice 1) All block signers, and bridging nodes, and any other node that is relaying, must all be applying the same policy in order for the system to be usable. 2) This policy may be required to be updated and changed regularly.

Say for example there is a large change in the price of bitcoin and it is decided the fee rate multiplier for USDT needs to be changed, or the federation agrees to accept a new asset for fees. In Liquid, this would require updating the config of all functionary and bridging nodes, and redeploying everything. This is a proposed method to enable the remote updating of policy without requiring access to the nodes or any require external dependencies (like e.g. configuring a URL to read the asset policy from).

It was maybe a mistake to try and include all of this in a single ELIP (with a single overview), but I will separate it out and reduce it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants