From 5f0dbbd6ca01ae824d2e779383884768b62e3cf6 Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Tue, 2 Sep 2025 16:32:45 +0100 Subject: [PATCH 1/6] Create ELIP 202 for issued asset fees Add controller descritpion Add publication descritpion Add test vectors fix typos --- elip-0202.mediawiki | 226 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 elip-0202.mediawiki diff --git a/elip-0202.mediawiki b/elip-0202.mediawiki new file mode 100644 index 0000000..c236371 --- /dev/null +++ b/elip-0202.mediawiki @@ -0,0 +1,226 @@ +
+  ELIP: 202
+  Layer: Applications
+  Title: Issued Asset Fees
+  Author: Tom Trevethan 
+  Comments-Summary: No comments yet.
+  Comments-URI: https://github.com/ElementsProject/elips/wiki/Comments:ELIP-0202
+  Status: Draft
+  Type: Standards Track
+  Created: 2025-09-02
+  License: BSD-3-Clause
+
+ +==Introduction== + +===Abstract=== + +This document proposes a modification to Elements to enable transaction fees to be paid in issued assets in addition to the policyAsset. To enable this, the specific issued assets that will be accepted and the corresponding fee rate, relative to the applied policyAsset rate must be agreed and configured by block creators and communicated to users and implemented in wallets. To enable the use of issued asset fees requires changes to policy rules for transaction relay and block creator mempool acceptance, and changes to block creation to enable issued asset fees to be paid to specified destinations in coinbase outputs. Issued asset fee acceptance and payout in the coinbase transaction is valid within the current consensus rules and so none of the features described here require any consensus fork to deploy. In addition to the core changes to policy and block creation, this document also proposes features to enable both publication of accepted assets and fee rates on-chain for use by wallets, and a mechanism to update the assets and rates accepted by nodes via an authenticated on-chain transaction. + +===Copyright=== + +This document is licensed under the 3-clause BSD license. + +===Motivation=== + +In Elements, transaction fees are required in order to prevent denial of service attacks and to prioritise transactions for limited blockspace. Unlike in Bitcoin, where transaction fees are determined from the difference between the total input and output values, in Elements transaction fees are specified by an explicit unblinded output (signified by an empty scriptPubKey) and input and output totals for all assets must equal. In the current Elements implementaion, policy rules for relay and mempool acceptance require that the assetID of this fee output be equal to the policyAsset. The current consensus rules however allow for any assetID for the fee asset (so long as the transaction is otherwise valid) as well as no fee output (however, a specified fee output with zero fee is not consensus valid). The current conseus rules also allow for coinbase outputs for any asset with values equal to or less than the total fee outputs for that asset for all transactions in a block, however the current Elements code will only create a block with a single spendable coinbase output for the total of policyAsset fees. + +These current restrictions limit the usefulness of the Elements platform for transacting in issued assets, as policyAsset is always then required for the payment of transaction fees. For example, for a Bitcoin sidechain (e.q. Liquid) there are many issued assets for tokensied currencies (e.g. USDT) which many users will use for making payments, however they must obtain an amount of the policyAsset (e.g. LBTC) before they can transact, and the fees will be priced in BTC instead of USD. This leads to a poor user experience when transacting in tokenised stablecoins, as a new user will be required to purchase LBTC before they are able to spend USDT that they have received in their wallet. In addition, the value of transaction fee required will not be explicit in terms of USDT and depend on the current USD value of BTC. By enabling fees to be paid in specified issued assets and at specified rates, user wallets can transact in an issued asset like USDT seamlessly, with predictable and transparent fees. + +In addition to enabling the issued asset transaction fees to be accepted, and payment of these fees in the coinbase, there needs to be a method for wallets and users to know which assets are accepted by block creators and at what rates. The simplest and most robust way of achieving this is with a publication of the accepted assets and rates by block creators on chain, in the coinbase - this removes the requirement for separate servers to publish and relay this data. + +Finally, a method is required to set the assets and rates that will be applied and enforced on relay/bridge and block creation nodes. These can be set with direct access to the node, either via the node configuration or the RPC interface. However, these assets and rates may need to be updated frequently, and it may be impractical and have security implications to have direct access to functionary and bridge nodes to perform these updates via the RPC interface. Therefore, a method to perform this update remotely via an on-chain transaction is also required. Individual nodes can then be configured with a 'controller' script that authenticates changes to the asset fee policy without requiring direct access to any individual nodes. + +==Design== + +===Overview=== + +We propose a modification to Elements to enable to use of transaction fees paid in specified issued assets in addition to the policyAsset at specified rates relative to the base policyAsset rate applied. This requires no changes to the consensus rules, and these changes can be deployed without requiring a network fork. The specified assets and rates will be applied to relay and mempool policy, and the fees will be paid to specified destinations in the coinbase transaction of created blocks. + +Each node has complete control over which assets and rates to accept as policy, however in practice all block creators and bridge nodes *should* agree to apply the same asset fee policy in order to provide a reliable service for users. Each node will be able to directly specify what assets and at what rate they will apply to policy. This can be set as part of the node configuration, specifying the assetID, the relative rate (and optionally the coinbase destination for the fee collection by the block creator) before initialisation, or set dynamically at runtime via a new RPC option. Note that if the consesus parameter c_mandatory_coinbase_destination is set to a non-empty script, then this cannot be overridden and all asset fee coinbase outputs must be paid to the c_mandatory_coinbase_destination script. In addition, a new RPC will be added to query the current assets, rates (and destinations) currently being applied by any node. + +The asset fee rate will be applied relative to whatever the current policyAsset rate (in sat/vbyte) is applied for a given transaction. The total transaction fee calculated for mempool/relay policy and block creation is: + + +vsize * policy_asset_fee_rate * issued_asset_fee_rate +. + +E.g. An issued asset is USDT. The current conversion between USDT and LBTC (policyAsset) is 100:1 (i.e. 100 USDT to one LBTC) the rate applied (issued_asset_fee_rate) would then be 100. Therefore, if the USDT asset is used for the fee in a transaction, they should pay 100 times the fee amount in LBTC for the equivalent value. If the current minimum fee rate is 1 vsat/byte in LBTC (i.e. the policy_asset_fee_rate), then the minimum fee output value required for an e.g. vsize = 257 transaction would be: 257 x 1 x 100 = 25700 base units of the USDT asset. + +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 OP_RETURN output for each accepted asset and included in the coinbase transaction for blocks that they create every N blocks (where N 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. + +To enable this, each node can be configured with a specified scriptPubKey that authorises the policy update. This is called the 'controller' scriptPubKey (but it can be a multisig representing several entities that must agree to any update). The controller(s) create a transaction with the scriptPubKey as the first output (as well as spending from the same scriptPubKey), and this transaction contains an additional OP_RETURN output for each fee asset to be accepted, encoded with the assetID, rate (and optional destination). + +Each configured node detects the configured controller scriptPubKey in a confirmed transaction output. It then verifies that this transaction spends from an output with the same scriptPubKey (this authenticates the transaction by verifying input scriptPubKey witness) and decodes each of the OP_RETURN outputs in the transaction to update the node assetID and rate applied to policy (and destination if creating blocks and c_mandatory_coinbase_destination not used.) + +===Specification=== + +====Issued asset fees==== + +Accepted assets, rates (and optional destination) are stored in a vector assetFeeRates of CAssetFeeRate objects. +This object contains the CAsset asset ID, the int64_t fee rate and CScript fee destination. + +For each supported issued asset, there is specified fee rate (issued_asset_fee_rate). This is specified as a fixed precision number (the number of decimal places supported is specified by int64_t ASSET_RATE_SCALE_FACTOR = 100000000). The maximum possible rate is then std::numeric_limits::max() / ASSET_RATE_SCALE_FACTOR (9223372036854775807 / 100000000 = 92233720368). + +The fee asset and rate can be configured for an individual node with the option: + +-setfeeassetrate=: set the assetID:rate-script applied to relay and mempool policy for this node. Optional script to set destination for coinbase fees for each asset. If mandatory_coinbase_destination is set as a consensus rule, then this is not currently possible and will return an error. + +This can be repeated for additional fee assets and rates. The maximum number of fee assets allowed is set by MAX_ISSUED_ASSET_FEE_SIZE. + +A new accepted fee asset can be added on demand at runtime with the RPC: + +updatefeeassetrate add new assetID, rate, (optional) destination script to apply to node policy or update the rates of existing configured assets dynamically. + +getassetfeerate get all assetID, rate, script applied by this client. + +Note: In the current Elements, consensus enforces that any individual fee output, or the total sum of fee values in a block do not exceed MAX_MONEY. Issued asset fees could exceed this, as individual assets might have much larger issuances. Therefore, currently in BlockAssember the total value of fee outputs will not exceed MAX_MONEY when generating a new block. However, this may limit transaction capacity and fee revenue on the future. This limitation can be removed along with any hardfork enabling: https://github.com/ElementsProject/elements/pull/1476 + +====Fee rate publication==== + +Asset fee rates (and optional destination) encoded in coinbase OP_RETURN as follows: + +* 4 bytes: AFEE +* 32 bytes: asset ID +* 16 bytes: fee rate (uint_64 encoded) +* (up to) 35 bytes: fee destination script + +The maximum number of additional outputs is limited to MAX_ISSUED_ASSET_FEE_SIZE. The max size for transactions must be reduced by the additional space taken by the additional coinbase outputs. + +Config option: + +-writefeeassetrate=n Write issued asset fee rates accepted as policy to the coinbase of created blocks with interval n (default: 0). + +If n is 0 then fee assets and rates are not written. + +If n > 0 then any accepted assets and fee rates set via RPC or config are published every n blocks. +Any fee rates updated by setassetfeerate or controller script are not applied to policy until the end of an epoch. + +A new RPC getsignerfeerates retrieves the latest published accepted assets and rates (cached). +getassetfeerate has a single boolean argument to either retrieve the latest updated fee rates, or the rates applied in the current epoch. + +On node restart, the cached rates are updated from the latest publication. + +====Controller fee asset rate updates==== + +Feature for issued asset fees that enables an external controller to set issued asset fee policy via an onchain transaction, with asset fee rates encoded in OP_RETURN outputs. +Any node can be configured to apply issued asset fee policy that is set by a controller (this does not need to be a single entity, but can be defined by any multisig script). This is set with a config option: + +-confeeassetrate=scriptPubKey + +where scriptPubKey is the script that is used by the controller(s) to set the setting rate in a transaction. +-confeeassetrate cannot be set at the same time as -setfeeassetrate (will generate error). +A valid controller transaction must spend from an output where the scriptPubKey is also the same controller script (this is used as an authentication mechanism). + +The format for the OP_RETURN encoding is that same as that used for the fee rate publication. + +Python function to encode to OP_RETURN hex string: + + +def encode_asset_fee_rate(rate, asset_id, dest_script): + ASSET_RATE_SCALE_FACTOR = 10**8 # 8 decimal places + scaled_rate = round(rate * ASSET_RATE_SCALE_FACTOR) + # Convert to 8-byte hex string (16 characters) + encoded_rate = format(scaled_rate, '016x') + # Encode asset with rate and destination + return "afee" + asset_id + encoded_rate + dest_script + + +==Backwards Compatibility== + +No issues for backwards compatibility - all existing transactions with policy asset fees are unaffected by these features. No changes to consesus rules are required. + +==Test Vectors== + +* Configure issued asset with fee rate of issued_asset_fee_rate = 2 + +
-setfeeassetrate=e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0:2
+ +* Get configured fee asset rates + +
getassetfeerate
+ +Result: + +
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('2'), 'script': ''}]
+ +* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee at rate 2: + +** vsize: 257 + +** Fee: 514 + +
0200000001016f90831e415a738df732b62dd0b76e87bbe489096e43c892bf3dcbbfd173778d0000000000feffffff0301a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000002faf08000160014c5439a67eaa99a308719b544f327a942ae02e6a701a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000002f967de0016001467c8c91e8357e1e24d1721c4535a99608e47beb701a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee801000000000000020200006c0000000000024730440220031c1aa256e40a89cf622374e1ef281870a6abb6f588411800fe885f5195a49e02204f3c37cfe2318db87ac7ce4d7da70682e3477653f6222383ab72ea259a3b1fae0121029896a2fe9c710b8b71488b90038d3974144dc21c9c8750459162dc37d141459300000000000000
+ +* Update issued asset fee rate to 5 with RPC + +
updatefeeassetrate e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0 5
+ +* Get configured fee asset rates + +
getassetfeerate
+ +Result: + +
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('5'), 'script': ''}]
+ +* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee at rate 5: + +** vsize: 257 + +** Fee: 2570 + +
0200000001015ce5df8217dfa572d4539d0ae05178be26c27dbc43af35f6877f0160c8d329360100000000feffffff0301a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000001312d0000160014fc060cafa93c7f4d5e0963ba08c55540c28361bf01a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000001c830d4001600141f79c72bcdad5c20c17337c1f22c3b41f156c53d01a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000000000a0a00004100000000000247304402205bc1f97638e5b1932ba6df702e938bd1cfdc6da46a4db8511fe172f821f102c502206a6fbe66d81e89bd78b4157d58af15e8c940e0c0d797cbe91535fb31683e013601210291d9c10cf976e932db8ffe0f2bf1d9c1574ea3affe2e0f98ff15e1b328eb860300000000000000
+ +* Configuration for fee asset rate publication: + +
-writefeeassetrate=100
+ +
updatefeeassetrate b92b2e64772d8edc7703534fc5028906ab1468a7497af9e8620999132af96a22 1.86
+
updatefeeassetrate af6e22da7b20de2da56c6726c4a036c73f777c8fb5bfc356892c01a9a3dd58d3 3.781
+ +Coinbase transaction: + +
0200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401640101ffffffff0401230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000000000000002c6a2aafeeaf6e22da7b20de2da56c6726c4a036c73f777c8fb5bfc356892c01a9a3dd58d3000000008d1ba84001230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000000000000002c6a2aafeeb92b2e64772d8edc7703534fc5028906ab1468a7497af9e8620999132af96a22000000001689592001230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000000012a05f200001976a9149d8419680d03a0ac4133beb9e097d5c9ff7babbc88ac01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000000000000000000266a24aa21a9ed5aa9aac025ce73291c124e2ccfd44ae60837d1f20a8573318427075444287de800000000000001200000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ +Output 1: + +
afeeaf6e22da7b20de2da56c6726c4a036c73f777c8fb5bfc356892c01a9a3dd58d3000000008d1ba840
+ +Output 2: + +
afeeb92b2e64772d8edc7703534fc5028906ab1468a7497af9e8620999132af96a220000000016895920
+ +* Controller transaction + +
+controler_script = "00145956bc071791295bfd5161ddb770aa536289b3e3"
+controller_addr = "ert1qt9ttcpchjy54hl23v8wmwu922d3gnvlrz7sqx7"
+private_key = "cP3N3Z3rMTo4L2gNRDWmzo8nGNyVNnWwFX9DRVPqwcPnMWE8Rn33"
+
+ +Configuration + +
-confeeassetrate=00145956bc071791295bfd5161ddb770aa536289b3e3
+ +Fee Asset setting: + +
asset_id = b92b2e64772d8edc7703534fc5028906ab1468a7497af9e8620999132af96a22
+ +rate = 1.0 + +Encoded fee asset hex string: + +
afeeb92b2e64772d8edc7703534fc5028906ab1468a7497af9e8620999132af96a220000000005f5e100
+ +Controller transaction: + +
020000000001500d712c870c96c0bec05f41d765f4f5f203bd8d5b7ea6ef333b53b8c2c2d164000000006a47304402207f1b2c580ee052b8d0da06a139cd1e910ec93c8b11585d692646e6267b8354c4022045caab622e3adbae204263dfdc86e341fd9f3c013cbf2234fdea2c8ee2ba0d81012103ba4a2b1f401eb59e1e6b104f8043ce41b38b65bd24c10edb3df8863b0241e5afffffffff0301230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000000012a046b60001600145956bc071791295bfd5161ddb770aa536289b3e301230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000000000000002c6a2aafeeb92b2e64772d8edc7703534fc5028906ab1468a7497af9e8620999132af96a220000000005f5e10001230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000186a0000000000000
+ +
getassetfeerate
+ +
[{'asset': 'b92b2e64772d8edc7703534fc5028906ab1468a7497af9e8620999132af96a22', 'rate': 1, 'script': ''}]
+ + From 4936ec2312726d3f976df4ac8ee500f4c845ea48 Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Tue, 2 Sep 2025 16:34:32 +0100 Subject: [PATCH 2/6] Edit README for elip 0202 Fix path --- README.mediawiki | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.mediawiki b/README.mediawiki index ca4c53d..c0c2fd1 100644 --- a/README.mediawiki +++ b/README.mediawiki @@ -74,6 +74,13 @@ Having an ELIP here does not make it a formally accepted standard until its stat | Kilian Rausch | Standards Track | Active +|- style="background-color: #ffcfcf" +| [[elip-0202.mediawiki|202]] +| Applications +| Issued asset fees +| Tom Trevethan +| Standards Track +| Draft |} From 9641693b88a3bb0c36e8a602c6ba84a8b625594a Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Tue, 16 Sep 2025 12:33:34 +0100 Subject: [PATCH 3/6] fixed errors and renamed rate to multiplier --- elip-0202.mediawiki | 78 ++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/elip-0202.mediawiki b/elip-0202.mediawiki index c236371..9099917 100644 --- a/elip-0202.mediawiki +++ b/elip-0202.mediawiki @@ -15,7 +15,7 @@ ===Abstract=== -This document proposes a modification to Elements to enable transaction fees to be paid in issued assets in addition to the policyAsset. To enable this, the specific issued assets that will be accepted and the corresponding fee rate, relative to the applied policyAsset rate must be agreed and configured by block creators and communicated to users and implemented in wallets. To enable the use of issued asset fees requires changes to policy rules for transaction relay and block creator mempool acceptance, and changes to block creation to enable issued asset fees to be paid to specified destinations in coinbase outputs. Issued asset fee acceptance and payout in the coinbase transaction is valid within the current consensus rules and so none of the features described here require any consensus fork to deploy. In addition to the core changes to policy and block creation, this document also proposes features to enable both publication of accepted assets and fee rates on-chain for use by wallets, and a mechanism to update the assets and rates accepted by nodes via an authenticated on-chain transaction. +This document proposes a modification to Elements to enable transaction fees to be paid in issued assets in addition to the policyAsset. To enable this, the specific issued assets that will be accepted and the corresponding fee rate, relative to the applied policyAsset rate must be agreed and configured by block creators and communicated to users and implemented in wallets. To enable the use of issued asset fees requires changes to policy rules for transaction relay and block creator mempool acceptance, and changes to block creation to enable issued asset fees to be paid to specified destinations in coinbase outputs. Issued asset fee acceptance and payout in the coinbase transaction is valid within the current consensus rules and so none of the features described here require any consensus fork to deploy. In addition to the core changes to policy and block creation, this document also proposes features to enable both publication of accepted assets and fee rates on-chain for use by wallets, and a mechanism to update the assets and rates accepted by nodes via an authenticated on-chain transaction. ===Copyright=== @@ -23,35 +23,37 @@ This document is licensed under the 3-clause BSD license. ===Motivation=== -In Elements, transaction fees are required in order to prevent denial of service attacks and to prioritise transactions for limited blockspace. Unlike in Bitcoin, where transaction fees are determined from the difference between the total input and output values, in Elements transaction fees are specified by an explicit unblinded output (signified by an empty scriptPubKey) and input and output totals for all assets must equal. In the current Elements implementaion, policy rules for relay and mempool acceptance require that the assetID of this fee output be equal to the policyAsset. The current consensus rules however allow for any assetID for the fee asset (so long as the transaction is otherwise valid) as well as no fee output (however, a specified fee output with zero fee is not consensus valid). The current conseus rules also allow for coinbase outputs for any asset with values equal to or less than the total fee outputs for that asset for all transactions in a block, however the current Elements code will only create a block with a single spendable coinbase output for the total of policyAsset fees. +In Elements, transaction fees are required in order to prevent denial of service attacks and to prioritise transactions for limited blockspace. Unlike in Bitcoin, where transaction fees are determined from the difference between the total input and output values, in Elements transaction fees are specified by an explicit unblinded output (signified by an empty scriptPubKey) and input and output totals for all assets must equal. In the current Elements implementaion, policy rules for relay and mempool acceptance require that the assetID of this fee output be equal to the policyAsset. The current consensus rules however allow for any assetID for the fee asset (so long as the transaction is otherwise valid) as well as no fee output (however, a specified fee output with zero fee is not consensus valid). The current consensus rules also allow for coinbase outputs for any asset with values equal to or less than the total fee outputs for that asset for all transactions in a block. However the current Elements code will only create a block with a single spendable coinbase output for the total of policyAsset fees. -These current restrictions limit the usefulness of the Elements platform for transacting in issued assets, as policyAsset is always then required for the payment of transaction fees. For example, for a Bitcoin sidechain (e.q. Liquid) there are many issued assets for tokensied currencies (e.g. USDT) which many users will use for making payments, however they must obtain an amount of the policyAsset (e.g. LBTC) before they can transact, and the fees will be priced in BTC instead of USD. This leads to a poor user experience when transacting in tokenised stablecoins, as a new user will be required to purchase LBTC before they are able to spend USDT that they have received in their wallet. In addition, the value of transaction fee required will not be explicit in terms of USDT and depend on the current USD value of BTC. By enabling fees to be paid in specified issued assets and at specified rates, user wallets can transact in an issued asset like USDT seamlessly, with predictable and transparent fees. +These current restrictions limit the usefulness of the Elements platform for transacting in issued assets, as policyAsset is always then required for the payment of transaction fees. For example, for a Bitcoin sidechain (e.q. Liquid) there are many issued assets for tokenized currencies (e.g. USDT) which many users will use for making payments. However they must obtain an amount of the policyAsset (e.g. LBTC) before they can transact, and the fees will be priced in BTC instead of USD. This leads to a poor user experience when transacting in tokenized stablecoins, as a new user will be required to purchase LBTC before they are able to spend USDT that they have received in their wallet. In addition, the value of transaction fee required will not be explicit in terms of USDT and depend on the current USD value of BTC. By enabling fees to be paid in specified issued assets and at specified rates, user wallets can transact in an issued asset like USDT seamlessly, with predictable and transparent fees. -In addition to enabling the issued asset transaction fees to be accepted, and payment of these fees in the coinbase, there needs to be a method for wallets and users to know which assets are accepted by block creators and at what rates. The simplest and most robust way of achieving this is with a publication of the accepted assets and rates by block creators on chain, in the coinbase - this removes the requirement for separate servers to publish and relay this data. +In addition to enabling the issued asset transaction fees to be accepted, and payment of these fees in the coinbase, there needs to be a method for wallets and users to know which assets are accepted by block creators and at what rates. The simplest and most robust way of achieving this is with a publication of the accepted assets and rates by block creators on chain, in the coinbase - this removes the requirement for separate servers to publish and relay this data. -Finally, a method is required to set the assets and rates that will be applied and enforced on relay/bridge and block creation nodes. These can be set with direct access to the node, either via the node configuration or the RPC interface. However, these assets and rates may need to be updated frequently, and it may be impractical and have security implications to have direct access to functionary and bridge nodes to perform these updates via the RPC interface. Therefore, a method to perform this update remotely via an on-chain transaction is also required. Individual nodes can then be configured with a 'controller' script that authenticates changes to the asset fee policy without requiring direct access to any individual nodes. +Finally, a method is required to set the assets and rates that will be applied and enforced on relay/bridge and block creation nodes. These can be set with direct access to the node, either via the node configuration or the RPC interface. However, these assets and rates may need to be updated frequently, and it may be impractical and have security implications to have direct access to functionary and bridge nodes to perform these updates via the RPC interface. Therefore, a method to perform this update remotely via an on-chain transaction is also required. Individual nodes can then be configured with a 'controller' script that authenticates changes to the asset fee policy without requiring direct access to any individual nodes. ==Design== ===Overview=== -We propose a modification to Elements to enable to use of transaction fees paid in specified issued assets in addition to the policyAsset at specified rates relative to the base policyAsset rate applied. This requires no changes to the consensus rules, and these changes can be deployed without requiring a network fork. The specified assets and rates will be applied to relay and mempool policy, and the fees will be paid to specified destinations in the coinbase transaction of created blocks. +We propose a modification to Elements to enable to use of transaction fees paid in specified issued assets in addition to the policyAsset at specified rates relative to the current policyAsset rate applied. This requires no changes to the consensus rules, and these changes can be deployed without requiring a network fork. The specified assets and rates will be applied to relay and mempool policy, and the fees will be paid to specified destinations in the coinbase transaction of created blocks. -Each node has complete control over which assets and rates to accept as policy, however in practice all block creators and bridge nodes *should* agree to apply the same asset fee policy in order to provide a reliable service for users. Each node will be able to directly specify what assets and at what rate they will apply to policy. This can be set as part of the node configuration, specifying the assetID, the relative rate (and optionally the coinbase destination for the fee collection by the block creator) before initialisation, or set dynamically at runtime via a new RPC option. Note that if the consesus parameter c_mandatory_coinbase_destination is set to a non-empty script, then this cannot be overridden and all asset fee coinbase outputs must be paid to the c_mandatory_coinbase_destination script. In addition, a new RPC will be added to query the current assets, rates (and destinations) currently being applied by any node. +Each node has complete control over which assets and rates to accept as policy, however in practice all block creators and relaying nodes *should* agree to apply the same asset fee policy in order to provide a reliable service for users. Each node will be able to directly specify what assets and at what rate they will apply to policy. This can be set as part of the node configuration, specifying the assetID, the relative rate (and optionally the coinbase destination for the fee collection by the block creator) before initialisation, or set dynamically at runtime via a new RPC option. Note that if the consesus parameter c_mandatory_coinbase_destination is set to a non-empty script, then this cannot be overridden and all asset fee coinbase outputs must be paid to the c_mandatory_coinbase_destination script. In addition, a new RPC will be added to query the current assets, rates (and destinations) currently being applied by any node. -The asset fee rate will be applied relative to whatever the current policyAsset rate (in sat/vbyte) is applied for a given transaction. The total transaction fee calculated for mempool/relay policy and block creation is: +The asset fee rate will be applied relative the current policyAsset minimum rate (in sat/vbyte) that is applied for a given transaction (or package) for mempool acceptance and relay. The package fee rate calculated for minimum mempool and relay policy is: -vsize * policy_asset_fee_rate * issued_asset_fee_rate -. +policy_asset_fee_rate * issued_asset_fee_rate_multiplier / ASSET_RATE_SCALE_FACTOR +
+ +The fee rate multiplier is specified as a fixed precision number (the number of decimal places supported is specified by the int64_t parameter ASSET_RATE_SCALE_FACTOR (default 100000000) which is applied to all asset rate multipliers. The maximum possible rate is then std::numeric_limits::max() / ASSET_RATE_SCALE_FACTOR (9223372036854775807 / 100000000 = 92233720368). -E.g. An issued asset is USDT. The current conversion between USDT and LBTC (policyAsset) is 100:1 (i.e. 100 USDT to one LBTC) the rate applied (issued_asset_fee_rate) would then be 100. Therefore, if the USDT asset is used for the fee in a transaction, they should pay 100 times the fee amount in LBTC for the equivalent value. If the current minimum fee rate is 1 vsat/byte in LBTC (i.e. the policy_asset_fee_rate), then the minimum fee output value required for an e.g. vsize = 257 transaction would be: 257 x 1 x 100 = 25700 base units of the USDT asset. +E.g. An issued asset is USDT. The current conversion between USDT and LBTC (policyAsset) is 100:1 (i.e. 100 USDT to one LBTC) the rate multiplier applied (issued_asset_fee_rate_multiplier) would then be set to 100*ASSET_RATE_SCALE_FACTOR. If the USDT asset is used for the fee in any transaction, they should pay 100 times in USDT the fee rate that would have applied in LBTC. If the current minimum fee rate for mempool and relay is 1 vsat/byte in LBTC (i.e. the policy_asset_fee_rate), then the minimum fee output value required for a transaction of size e.g. vsize = 257 transaction would be: 257 x 1 x 100 = 25700 base units of the USDT asset. -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 OP_RETURN output for each accepted asset and included in the coinbase transaction for blocks that they create every N blocks (where N 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. +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 OP_RETURN output for each accepted asset and included in the coinbase transaction for blocks that they create every N blocks (where N 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. +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. -To enable this, each node can be configured with a specified scriptPubKey that authorises the policy update. This is called the 'controller' scriptPubKey (but it can be a multisig representing several entities that must agree to any update). The controller(s) create a transaction with the scriptPubKey as the first output (as well as spending from the same scriptPubKey), and this transaction contains an additional OP_RETURN output for each fee asset to be accepted, encoded with the assetID, rate (and optional destination). +To enable this, each node can be configured with a specified scriptPubKey that authorises the policy update. This is called the 'controller' scriptPubKey (but it can be a multisig representing several entities that must agree to any update). The controller(s) create a transaction with the scriptPubKey as the first output (as well as spending from the same scriptPubKey), and this transaction contains an additional OP_RETURN output for each fee asset to be accepted, encoded with the assetID, rate (and optional destination). Each configured node detects the configured controller scriptPubKey in a confirmed transaction output. It then verifies that this transaction spends from an output with the same scriptPubKey (this authenticates the transaction by verifying input scriptPubKey witness) and decodes each of the OP_RETURN outputs in the transaction to update the node assetID and rate applied to policy (and destination if creating blocks and c_mandatory_coinbase_destination not used.) @@ -59,16 +61,16 @@ Each configured node detects the configured controller scriptPubKey ====Issued asset fees==== -Accepted assets, rates (and optional destination) are stored in a vector assetFeeRates of CAssetFeeRate objects. -This object contains the CAsset asset ID, the int64_t fee rate and CScript fee destination. +Accepted assets, rate multipliers (and optional destination) are stored in a vector assetFeeRates of CAssetFeeRate objects. +This object contains the CAsset asset ID, the int64_t fee rate multiplier and CScript fee destination. -For each supported issued asset, there is specified fee rate (issued_asset_fee_rate). This is specified as a fixed precision number (the number of decimal places supported is specified by int64_t ASSET_RATE_SCALE_FACTOR = 100000000). The maximum possible rate is then std::numeric_limits::max() / ASSET_RATE_SCALE_FACTOR (9223372036854775807 / 100000000 = 92233720368). +For each supported issued asset, there is specified fee rate multiplier (issued_asset_fee_rate_multiplier). The fee asset and rate can be configured for an individual node with the option: --setfeeassetrate=: set the assetID:rate-script applied to relay and mempool policy for this node. Optional script to set destination for coinbase fees for each asset. If mandatory_coinbase_destination is set as a consensus rule, then this is not currently possible and will return an error. +-setfeeassetrate=: set the assetID:rate-script applied to relay and mempool policy for this node. Optional script to set destination for coinbase fees for each asset. If mandatory_coinbase_destination is set as a consensus rule, then this is not currently possible and will return an error. -This can be repeated for additional fee assets and rates. The maximum number of fee assets allowed is set by MAX_ISSUED_ASSET_FEE_SIZE. +This can be repeated for additional fee assets and rates. The maximum number of issued assets fee multipliers allowed is set by MAX_ISSUED_ASSET_FEE_SIZE. A new accepted fee asset can be added on demand at runtime with the RPC: @@ -76,6 +78,8 @@ A new accepted fee asset can be added on demand at runtime with the RPC: getassetfeerate get all assetID, rate, script applied by this client. +All fee rate multipliers are input and output as floating point numbers, scalled with ASSET_RATE_SCALE_FACTOR to/from the internal int64_t issued_asset_fee_rate_multiplier. + Note: In the current Elements, consensus enforces that any individual fee output, or the total sum of fee values in a block do not exceed MAX_MONEY. Issued asset fees could exceed this, as individual assets might have much larger issuances. Therefore, currently in BlockAssember the total value of fee outputs will not exceed MAX_MONEY when generating a new block. However, this may limit transaction capacity and fee revenue on the future. This limitation can be removed along with any hardfork enabling: https://github.com/ElementsProject/elements/pull/1476 ====Fee rate publication==== @@ -84,44 +88,44 @@ Asset fee rates (and optional destination) encoded in coinbase OP_RETURNAFEE * 32 bytes: asset ID -* 16 bytes: fee rate (uint_64 encoded) +* 16 bytes: fee rate multiplier (uint_64 encoded) * (up to) 35 bytes: fee destination script -The maximum number of additional outputs is limited to MAX_ISSUED_ASSET_FEE_SIZE. The max size for transactions must be reduced by the additional space taken by the additional coinbase outputs. +The maximum number of additional outputs is limited to MAX_ISSUED_ASSET_FEE_SIZE. The maximum size for transactions included in a generated block must be reduced by the additional space taken by the additional coinbase outputs. Config option: --writefeeassetrate=n Write issued asset fee rates accepted as policy to the coinbase of created blocks with interval n (default: 0). +-writefeeassetrate=n Write issued asset fee rate multipliers accepted as policy to the coinbase of created blocks with interval n (default: 0). -If n is 0 then fee assets and rates are not written. +If n is 0 then fee assets and rate multipliers are not written. If n > 0 then any accepted assets and fee rates set via RPC or config are published every n blocks. Any fee rates updated by setassetfeerate or controller script are not applied to policy until the end of an epoch. -A new RPC getsignerfeerates retrieves the latest published accepted assets and rates (cached). -getassetfeerate has a single boolean argument to either retrieve the latest updated fee rates, or the rates applied in the current epoch. +A new RPC getsignerfeerates retrieves the latest published accepted assets and rate multipliers (cached). +getassetfeerate has a single boolean argument to either retrieve the latest updated fee rate multipliers, or the rates applied in the current epoch. -On node restart, the cached rates are updated from the latest publication. +On node restart, the cached rates are updated from the latest publication. ====Controller fee asset rate updates==== -Feature for issued asset fees that enables an external controller to set issued asset fee policy via an onchain transaction, with asset fee rates encoded in OP_RETURN outputs. +Feature for issued asset fees that enables an external controller to set issued asset fee policy via an onchain transaction, with asset fee rate multipliers encoded in OP_RETURN outputs. Any node can be configured to apply issued asset fee policy that is set by a controller (this does not need to be a single entity, but can be defined by any multisig script). This is set with a config option: -confeeassetrate=scriptPubKey -where scriptPubKey is the script that is used by the controller(s) to set the setting rate in a transaction. --confeeassetrate cannot be set at the same time as -setfeeassetrate (will generate error). +where scriptPubKey is the script that is used by the controller(s) to set the asset and rate multipliers in a transaction. +-confeeassetrate cannot be set at the same time as -setfeeassetrate (this will generate an error). A valid controller transaction must spend from an output where the scriptPubKey is also the same controller script (this is used as an authentication mechanism). -The format for the OP_RETURN encoding is that same as that used for the fee rate publication. +The format for the OP_RETURN encoding is that same as that used for the fee rate publication. Python function to encode to OP_RETURN hex string: -def encode_asset_fee_rate(rate, asset_id, dest_script): +def encode_asset_fee_rate(rate_multiplier, asset_id, dest_script): ASSET_RATE_SCALE_FACTOR = 10**8 # 8 decimal places - scaled_rate = round(rate * ASSET_RATE_SCALE_FACTOR) + scaled_rate = round(rate_multiplier * ASSET_RATE_SCALE_FACTOR) # Convert to 8-byte hex string (16 characters) encoded_rate = format(scaled_rate, '016x') # Encode asset with rate and destination @@ -130,11 +134,11 @@ def encode_asset_fee_rate(rate, asset_id, dest_script): ==Backwards Compatibility== -No issues for backwards compatibility - all existing transactions with policy asset fees are unaffected by these features. No changes to consesus rules are required. +No issues for backwards compatibility - all existing transactions with policy asset fees are unaffected by these features. No changes to consesus rules are required. ==Test Vectors== -* Configure issued asset with fee rate of issued_asset_fee_rate = 2 +* Configure issued asset with fee rate of issued_asset_fee_rate_multiplier = 2
-setfeeassetrate=e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0:2
@@ -146,7 +150,7 @@ Result:
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('2'), 'script': ''}]
-* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee at rate 2: +* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee multiplier at rate 2: ** vsize: 257 @@ -154,7 +158,7 @@ Result:
0200000001016f90831e415a738df732b62dd0b76e87bbe489096e43c892bf3dcbbfd173778d0000000000feffffff0301a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000002faf08000160014c5439a67eaa99a308719b544f327a942ae02e6a701a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000002f967de0016001467c8c91e8357e1e24d1721c4535a99608e47beb701a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee801000000000000020200006c0000000000024730440220031c1aa256e40a89cf622374e1ef281870a6abb6f588411800fe885f5195a49e02204f3c37cfe2318db87ac7ce4d7da70682e3477653f6222383ab72ea259a3b1fae0121029896a2fe9c710b8b71488b90038d3974144dc21c9c8750459162dc37d141459300000000000000
-* Update issued asset fee rate to 5 with RPC +* Update issued asset fee rate multiplier to 5 with RPC
updatefeeassetrate e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0 5
@@ -166,7 +170,7 @@ Result:
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('5'), 'script': ''}]
-* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee at rate 5: +* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee at rate multiplier 5: ** vsize: 257 From 4b5eeb206931975787cb5d67114ecd4ed8d1557c Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Sat, 20 Sep 2025 17:25:15 +0100 Subject: [PATCH 4/6] updated fee rate multiplier logic and limits --- elip-0202.mediawiki | 92 +++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/elip-0202.mediawiki b/elip-0202.mediawiki index 9099917..5110350 100644 --- a/elip-0202.mediawiki +++ b/elip-0202.mediawiki @@ -15,7 +15,7 @@ ===Abstract=== -This document proposes a modification to Elements to enable transaction fees to be paid in issued assets in addition to the policyAsset. To enable this, the specific issued assets that will be accepted and the corresponding fee rate, relative to the applied policyAsset rate must be agreed and configured by block creators and communicated to users and implemented in wallets. To enable the use of issued asset fees requires changes to policy rules for transaction relay and block creator mempool acceptance, and changes to block creation to enable issued asset fees to be paid to specified destinations in coinbase outputs. Issued asset fee acceptance and payout in the coinbase transaction is valid within the current consensus rules and so none of the features described here require any consensus fork to deploy. In addition to the core changes to policy and block creation, this document also proposes features to enable both publication of accepted assets and fee rates on-chain for use by wallets, and a mechanism to update the assets and rates accepted by nodes via an authenticated on-chain transaction. +This document proposes a modification to Elements to enable transaction fees to be paid in issued assets in addition to the policyAsset. To enable this, the specific issued assets that will be accepted and the corresponding fee rate, relative to the applied policyAsset rate must be agreed and configured by block creators and communicated to users and implemented in wallets. To enable the use of issued asset fees requires changes to policy rules for transaction relay mempool acceptance and ordering, and changes to block creation to enable issued asset fees to be paid to specified destinations in coinbase outputs. Issued asset fee acceptance and payout in the coinbase transaction is valid within the current consensus rules and so none of the features described here require any consensus fork to deploy. In addition to the core changes to policy and block creation, this document also proposes features to enable both publication of accepted assets and fee rates on-chain for use by wallets, and a mechanism to update the assets and rates accepted by nodes via an authenticated on-chain transaction. ===Copyright=== @@ -23,7 +23,7 @@ This document is licensed under the 3-clause BSD license. ===Motivation=== -In Elements, transaction fees are required in order to prevent denial of service attacks and to prioritise transactions for limited blockspace. Unlike in Bitcoin, where transaction fees are determined from the difference between the total input and output values, in Elements transaction fees are specified by an explicit unblinded output (signified by an empty scriptPubKey) and input and output totals for all assets must equal. In the current Elements implementaion, policy rules for relay and mempool acceptance require that the assetID of this fee output be equal to the policyAsset. The current consensus rules however allow for any assetID for the fee asset (so long as the transaction is otherwise valid) as well as no fee output (however, a specified fee output with zero fee is not consensus valid). The current consensus rules also allow for coinbase outputs for any asset with values equal to or less than the total fee outputs for that asset for all transactions in a block. However the current Elements code will only create a block with a single spendable coinbase output for the total of policyAsset fees. +In Elements, transaction fees are required in order to prevent denial of service attacks and to prioritize transactions for limited block space. Unlike in Bitcoin, where transaction fees are determined from the difference between the total input and output values, in Elements transaction fees are specified by an explicit unblinded output (signified by an empty scriptPubKey) and input and output totals for all assets must equal. In the current Elements implementation, policy rules for relay and mempool acceptance require that the assetID of this fee output be equal to the policyAsset. The current consensus rules however allow for any assetID for the fee asset (so long as the transaction is otherwise valid) as well as no fee output (however, a specified fee output with zero fee is not consensus valid). The current consensus rules also allow for coinbase outputs for any asset with values equal to or less than the total fee outputs for that asset for all transactions in a block. However the current Elements code will only create a block with a single spendable coinbase output for the total of policyAsset fees. These current restrictions limit the usefulness of the Elements platform for transacting in issued assets, as policyAsset is always then required for the payment of transaction fees. For example, for a Bitcoin sidechain (e.q. Liquid) there are many issued assets for tokenized currencies (e.g. USDT) which many users will use for making payments. However they must obtain an amount of the policyAsset (e.g. LBTC) before they can transact, and the fees will be priced in BTC instead of USD. This leads to a poor user experience when transacting in tokenized stablecoins, as a new user will be required to purchase LBTC before they are able to spend USDT that they have received in their wallet. In addition, the value of transaction fee required will not be explicit in terms of USDT and depend on the current USD value of BTC. By enabling fees to be paid in specified issued assets and at specified rates, user wallets can transact in an issued asset like USDT seamlessly, with predictable and transparent fees. @@ -35,25 +35,19 @@ Finally, a method is required to set the assets and rates that will be applied a ===Overview=== -We propose a modification to Elements to enable to use of transaction fees paid in specified issued assets in addition to the policyAsset at specified rates relative to the current policyAsset rate applied. This requires no changes to the consensus rules, and these changes can be deployed without requiring a network fork. The specified assets and rates will be applied to relay and mempool policy, and the fees will be paid to specified destinations in the coinbase transaction of created blocks. +We propose a modification to Elements to enable to use of transaction fees paid in specified issued assets in addition to the policyAsset at specified rates relative to the current policyAsset rate applied. This requires no changes to the consensus rules, and these changes can be deployed without requiring a network fork. The specified assets and rates will be applied to relay and mempool policy, mempool ordering, and these fees will be paid to specified destinations in the coinbase transaction of created blocks. -Each node has complete control over which assets and rates to accept as policy, however in practice all block creators and relaying nodes *should* agree to apply the same asset fee policy in order to provide a reliable service for users. Each node will be able to directly specify what assets and at what rate they will apply to policy. This can be set as part of the node configuration, specifying the assetID, the relative rate (and optionally the coinbase destination for the fee collection by the block creator) before initialisation, or set dynamically at runtime via a new RPC option. Note that if the consesus parameter c_mandatory_coinbase_destination is set to a non-empty script, then this cannot be overridden and all asset fee coinbase outputs must be paid to the c_mandatory_coinbase_destination script. In addition, a new RPC will be added to query the current assets, rates (and destinations) currently being applied by any node. +Each node has complete control over which assets and at what rates to accept as policy, however in practice all block creators and relaying nodes *should* agree to apply the same asset fee policy in order to provide a reliable service for users. Each node will be able to directly specify what assets and at what rate they will apply to policy. 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. Note that if the consensus parameter c_mandatory_coinbase_destination is set to a non-empty script, then this cannot be overridden and all asset fee coinbase outputs must be paid to the c_mandatory_coinbase_destination script. In addition, a new RPC will be added to query the current assets, rate multiplier (and destinations) currently being applied by any node. -The asset fee rate will be applied relative the current policyAsset minimum rate (in sat/vbyte) that is applied for a given transaction (or package) for mempool acceptance and relay. The package fee rate calculated for minimum mempool and relay policy is: +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. - -policy_asset_fee_rate * issued_asset_fee_rate_multiplier / ASSET_RATE_SCALE_FACTOR - - -The fee rate multiplier is specified as a fixed precision number (the number of decimal places supported is specified by the int64_t parameter ASSET_RATE_SCALE_FACTOR (default 100000000) which is applied to all asset rate multipliers. The maximum possible rate is then std::numeric_limits::max() / ASSET_RATE_SCALE_FACTOR (9223372036854775807 / 100000000 = 92233720368). - -E.g. An issued asset is USDT. The current conversion between USDT and LBTC (policyAsset) is 100:1 (i.e. 100 USDT to one LBTC) the rate multiplier applied (issued_asset_fee_rate_multiplier) would then be set to 100*ASSET_RATE_SCALE_FACTOR. If the USDT asset is used for the fee in any transaction, they should pay 100 times in USDT the fee rate that would have applied in LBTC. If the current minimum fee rate for mempool and relay is 1 vsat/byte in LBTC (i.e. the policy_asset_fee_rate), then the minimum fee output value required for a transaction of size e.g. vsize = 257 transaction would be: 257 x 1 x 100 = 25700 base units of the USDT asset. +E.g. An issued asset is USDT. The current conversion rate between USDT and LBTC (policyAsset) 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. vsize = 257 transaction to be relayed would be: 257 x 1 x 100 = 25700 base units of the USDT asset. 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 OP_RETURN output for each accepted asset and included in the coinbase transaction for blocks that they create every N blocks (where N 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. -To enable this, each node can be configured with a specified scriptPubKey that authorises the policy update. This is called the 'controller' scriptPubKey (but it can be a multisig representing several entities that must agree to any update). The controller(s) create a transaction with the scriptPubKey as the first output (as well as spending from the same scriptPubKey), and this transaction contains an additional OP_RETURN output for each fee asset to be accepted, encoded with the assetID, rate (and optional destination). +To enable this, each node can be configured with a specified scriptPubKey that authorizes the policy update. This is called the 'controller' scriptPubKey (but it can be a multisig representing several entities that must agree to any update). The controller(s) create a transaction with the scriptPubKey as the first output (as well as spending from the same scriptPubKey), and this transaction contains an additional OP_RETURN output for each fee asset to be accepted, encoded with the assetID, rate (and optional destination). Each configured node detects the configured controller scriptPubKey in a confirmed transaction output. It then verifies that this transaction spends from an output with the same scriptPubKey (this authenticates the transaction by verifying input scriptPubKey witness) and decodes each of the OP_RETURN outputs in the transaction to update the node assetID and rate applied to policy (and destination if creating blocks and c_mandatory_coinbase_destination not used.) @@ -64,23 +58,43 @@ Each configured node detects the configured controller scriptPubKey Accepted assets, rate multipliers (and optional destination) are stored in a vector assetFeeRates of CAssetFeeRate objects. This object contains the CAsset asset ID, the int64_t fee rate multiplier and CScript fee destination. -For each supported issued asset, there is specified fee rate multiplier (issued_asset_fee_rate_multiplier). +For each supported issued asset, there is supplied fee rate multiplier: issued_asset_fee_rate_multiplier. This value scales the calculated the fee rate of a transaction that has an issued asset fee to give an effective_fee_rate which is then used to determine if this is equal to or greater than the relay and mempool minimums set with policyAsset fee rates. For mempool ordering, the effective fee rate is used for comparison with transactions with policyAsset fee rates. + +The fee rate multiplier is specified as a fixed precision int64_t number issued_asset_fee_rate_multiplier_int64 (the number of decimal places supported is specified by the int64_t parameter ASSET_MULTIPLIER_SCALE_FACTOR (default 100000000) which is applied to all asset multipliers. -The fee asset and rate can be configured for an individual node with the option: + +issued_asset_fee_rate_multiplier_int64 = static_cast(issued_asset_fee_rate_multiplier * ASSET_RATE_SCALE_FACTOR) + + +The effective_fee_rate is calculated as follows for any transaction where the fee output is paid in an amount of accepted issued asset: + + +effective_fee_rate = (asset_fee_rate * issued_asset_fee_rate_multiplier_int64) / ASSET_MULTIPLIER_SCALE_FACTOR + --setfeeassetrate=: set the assetID:rate-script applied to relay and mempool policy for this node. Optional script to set destination for coinbase fees for each asset. If mandatory_coinbase_destination is set as a consensus rule, then this is not currently possible and will return an error. +Where asset_fee_rate is the fee rate calculated using the transaction vsize the amount of the issued asset fee output. For every transaction in the mempool that has an issued asset fee, the effective_fee_rate is used in place of the policyAsset fee rate for the purposes of mempool ordering and minimum mempool fee rate calculation. -This can be repeated for additional fee assets and rates. The maximum number of issued assets fee multipliers allowed is set by MAX_ISSUED_ASSET_FEE_SIZE. +The value of issued_asset_fee_rate_multiplier_int64 must be between 1 and MAX_ASSET_MULTIPLIER -A new accepted fee asset can be added on demand at runtime with the RPC: +By default MAX_ASSET_MULTIPLIER = 10^15. Scaled as a fixed precision number, this enables values of issued_asset_fee_rate_multiplier between 0.00000001 and 10,000,000. -updatefeeassetrate add new assetID, rate, (optional) destination script to apply to node policy or update the rates of existing configured assets dynamically. +The value of (asset_fee_rate * issued_asset_fee_rate_multiplier_int64) cannot exceed the maximum allowed value of int64_t. If it does, then the effective_fee_rate is set to maximum allowable value of std::numeric_limits::max() / ASSET_RATE_SCALE_FACTOR = 92233720368 (which is 922.3372 LBTC/vbyte). -getassetfeerate get all assetID, rate, script applied by this client. +The fee asset and rate multiplier can be configured for an individual node with the option: -All fee rate multipliers are input and output as floating point numbers, scalled with ASSET_RATE_SCALE_FACTOR to/from the internal int64_t issued_asset_fee_rate_multiplier. +-setfeeassetrate=: set the assetID:multiplier-script applied to relay and mempool policy for this node. -Note: In the current Elements, consensus enforces that any individual fee output, or the total sum of fee values in a block do not exceed MAX_MONEY. Issued asset fees could exceed this, as individual assets might have much larger issuances. Therefore, currently in BlockAssember the total value of fee outputs will not exceed MAX_MONEY when generating a new block. However, this may limit transaction capacity and fee revenue on the future. This limitation can be removed along with any hardfork enabling: https://github.com/ElementsProject/elements/pull/1476 +The multiplier is supplied as a floating point number (issued_asset_fee_rate_multiplier) which is converted to fixed point integer by multiplication with ASSET_MULTIPLIER_SCALE_FACTOR. Optional script to set destination for coinbase fees for each asset. If mandatory_coinbase_destination is set as a consensus rule, then this is not currently possible and will return an error. + +This can be repeated for additional fee assets and rates. The maximum number of issued asset fee multipliers allowed is set by MAX_ISSUED_ASSET_FEE_SIZE (default 10). + +A new accepted fee asset can be added on demand at runtime with the RPC, or an existing asset updated with: + +updatefeeassetrate add new assetID, multiplier, (optional) destination script to apply to node policy or update the rates of existing configured assets dynamically. + +getassetfeerate get all assetID, multiplier, script applied by this client. + +Note: Consensus enforces that any individual fee output, and the total sum of fee values in a block do not exceed MAX_MONEY. Issued asset fees could in theory exceed this, as individual assets might have much larger issuance amounts than MAX_MONEY. Therefore, currently in BlockAssember the inclusion of issued asset fee paying transactions in a block will be limited so that the total value of fee outputs will not exceed MAX_MONEY when generating a new block. ====Fee rate publication==== @@ -115,32 +129,20 @@ Any node can be configured to apply issued asset fee policy that is set by a con -confeeassetrate=scriptPubKey where scriptPubKey is the script that is used by the controller(s) to set the asset and rate multipliers in a transaction. --confeeassetrate cannot be set at the same time as -setfeeassetrate (this will generate an error). +-conassetfeerate cannot be set at the same time as -setassetfeerate (this will generate an error). A valid controller transaction must spend from an output where the scriptPubKey is also the same controller script (this is used as an authentication mechanism). The format for the OP_RETURN encoding is that same as that used for the fee rate publication. -Python function to encode to OP_RETURN hex string: - - -def encode_asset_fee_rate(rate_multiplier, asset_id, dest_script): - ASSET_RATE_SCALE_FACTOR = 10**8 # 8 decimal places - scaled_rate = round(rate_multiplier * ASSET_RATE_SCALE_FACTOR) - # Convert to 8-byte hex string (16 characters) - encoded_rate = format(scaled_rate, '016x') - # Encode asset with rate and destination - return "afee" + asset_id + encoded_rate + dest_script - - ==Backwards Compatibility== -No issues for backwards compatibility - all existing transactions with policy asset fees are unaffected by these features. No changes to consesus rules are required. +No issues for backwards compatibility - all existing transactions with policy asset fees are unaffected by these features. No changes to consensus rules are required. ==Test Vectors== -* Configure issued asset with fee rate of issued_asset_fee_rate_multiplier = 2 +* Configure issued asset with fee rate of issued_asset_fee_rate_multiplier = 0.5 -
-setfeeassetrate=e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0:2
+
-setfeeassetrate=e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0:0.5
* Get configured fee asset rates @@ -148,9 +150,9 @@ No issues for backwards compatibility - all existing transactions with policy as Result: -
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('2'), 'script': ''}]
+
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('0.5'), 'script': ''}]
-* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee multiplier at rate 2: +* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee multiplier of 0.5: ** vsize: 257 @@ -158,19 +160,19 @@ Result:
0200000001016f90831e415a738df732b62dd0b76e87bbe489096e43c892bf3dcbbfd173778d0000000000feffffff0301a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000002faf08000160014c5439a67eaa99a308719b544f327a942ae02e6a701a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee8010000000002f967de0016001467c8c91e8357e1e24d1721c4535a99608e47beb701a0ab257f2cc546f09ac619d341fbbde0507f5ae97e45bfb6ebd8317743d26ee801000000000000020200006c0000000000024730440220031c1aa256e40a89cf622374e1ef281870a6abb6f588411800fe885f5195a49e02204f3c37cfe2318db87ac7ce4d7da70682e3477653f6222383ab72ea259a3b1fae0121029896a2fe9c710b8b71488b90038d3974144dc21c9c8750459162dc37d141459300000000000000
-* Update issued asset fee rate multiplier to 5 with RPC +* Update issued asset fee rate multiplier to 0.1 with RPC -
updatefeeassetrate e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0 5
+
updatefeeassetrate e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0 0.1
* Get configured fee asset rates -
getassetfeerate
+
getassetfeerates
Result: -
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('5'), 'script': ''}]
+
[{'asset': 'e86ed2437731d8ebb6bf457ee95a7f50e0bdfb41d319c69af046c52c7f25aba0', 'rate': Decimal('0.1'), 'script': ''}]
-* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee at rate multiplier 5: +* Transaction with 1 input and 2 outputs (+ 1 fee output) with asset fee at rate multiplier 0.1: ** vsize: 257 From 845e8ab74f3e8ed6fc1f2aec67819e3befa27c55 Mon Sep 17 00:00:00 2001 From: Tom Trevethan Date: Tue, 30 Sep 2025 16:30:26 +0100 Subject: [PATCH 5/6] updated example, removed repetitions, removed elip number --- README.mediawiki | 7 ------ elip-0202.mediawiki | 59 +++++++++++++++++++++------------------------ 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/README.mediawiki b/README.mediawiki index c0c2fd1..ca4c53d 100644 --- a/README.mediawiki +++ b/README.mediawiki @@ -74,13 +74,6 @@ Having an ELIP here does not make it a formally accepted standard until its stat | Kilian Rausch | Standards Track | Active -|- style="background-color: #ffcfcf" -| [[elip-0202.mediawiki|202]] -| Applications -| Issued asset fees -| Tom Trevethan -| Standards Track -| Draft |} diff --git a/elip-0202.mediawiki b/elip-0202.mediawiki index 5110350..141530d 100644 --- a/elip-0202.mediawiki +++ b/elip-0202.mediawiki @@ -1,10 +1,10 @@
-  ELIP: 202
+  ELIP: ???
   Layer: Applications
   Title: Issued Asset Fees
   Author: Tom Trevethan 
   Comments-Summary: No comments yet.
-  Comments-URI: https://github.com/ElementsProject/elips/wiki/Comments:ELIP-0202
+  Comments-URI: https://github.com/ElementsProject/elips/wiki/Comments:ELIP-0???
   Status: Draft
   Type: Standards Track
   Created: 2025-09-02
@@ -15,7 +15,7 @@
 
 ===Abstract===
 
-This document proposes a modification to Elements to enable transaction fees to be paid in issued assets in addition to the policyAsset. To enable this, the specific issued assets that will be accepted and the corresponding fee rate, relative to the applied policyAsset rate must be agreed and configured by block creators and communicated to users and implemented in wallets. To enable the use of issued asset fees requires changes to policy rules for transaction relay mempool acceptance and ordering, and changes to block creation to enable issued asset fees to be paid to specified destinations in coinbase outputs. Issued asset fee acceptance and payout in the coinbase transaction is valid within the current consensus rules and so none of the features described here require any consensus fork to deploy. In addition to the core changes to policy and block creation, this document also proposes features to enable both publication of accepted assets and fee rates on-chain for use by wallets, and a mechanism to update the assets and rates accepted by nodes via an authenticated on-chain transaction.
+This document proposes a modification to Elements to enable transaction fees to be paid in issued assets in addition to the policyAsset (or pegged asset). To enable this, the specific issued assets that will be accepted for fees and the corresponding fee rate (relative to the enforced policyAsset rate) must be agreed and configured by all block creators and also communicated to users and implemented in wallets. To enable the use of issued asset fees requires changes to policy rules for transaction relay mempool acceptance and ordering, and changes to block creation to enable issued asset fees to be paid to specified destinations in coinbase outputs. Issued asset fee acceptance and payout in the coinbase transaction is valid within the current consensus rules and so none of the features described here require any consensus fork to deploy. In addition to these changes to policy and block creation, this document also proposes features to enable both publication of accepted assets and fee rates on-chain for use by wallets, and a method to update the fee asset policy applied by nodes remotely via an authenticated on-chain transaction.
 
 ===Copyright===
 
@@ -23,56 +23,45 @@ This document is licensed under the 3-clause BSD license.
 
 ===Motivation===
 
-In Elements, transaction fees are required in order to prevent denial of service attacks and to prioritize transactions for limited block space. Unlike in Bitcoin, where transaction fees are determined from the difference between the total input and output values, in Elements transaction fees are specified by an explicit unblinded output (signified by an empty scriptPubKey) and input and output totals for all assets must equal. In the current Elements implementation, policy rules for relay and mempool acceptance require that the assetID of this fee output be equal to the policyAsset. The current consensus rules however allow for any assetID for the fee asset (so long as the transaction is otherwise valid) as well as no fee output (however, a specified fee output with zero fee is not consensus valid). The current consensus rules also allow for coinbase outputs for any asset with values equal to or less than the total fee outputs for that asset for all transactions in a block. However the current Elements code will only create a block with a single spendable coinbase output for the total of policyAsset fees.
+In Elements, transaction fees are required in order to prevent denial of service attacks and to prioritize transactions for limited block space and are specified by an explicit unblinded output (signified by an empty scriptPubKey). In the current Elements implementation, policy rules for transaction relay and mempool acceptance require that the assetID of this fee output be equal to the policyAsset. The current consensus rules however allow for any assetID for the fee asset (so long as the transaction is otherwise valid). The current consensus rules also allow for coinbase outputs for any asset with values equal to or less than the total fee outputs for that asset for all transactions in a block. However the current Elements code will only create a block with a single spendable coinbase output for the total of policyAsset fees. 
 
 These current restrictions limit the usefulness of the Elements platform for transacting in issued assets, as policyAsset is always then required for the payment of transaction fees. For example, for a Bitcoin sidechain (e.q. Liquid) there are many issued assets for tokenized currencies (e.g. USDT) which many users will use for making payments. However they must obtain an amount of the policyAsset (e.g. LBTC) before they can transact, and the fees will be priced in BTC instead of USD. This leads to a poor user experience when transacting in tokenized stablecoins, as a new user will be required to purchase LBTC before they are able to spend USDT that they have received in their wallet. In addition, the value of transaction fee required will not be explicit in terms of USDT and depend on the current USD value of BTC. By enabling fees to be paid in specified issued assets and at specified rates, user wallets can transact in an issued asset like USDT seamlessly, with predictable and transparent fees.
 
-In addition to enabling the issued asset transaction fees to be accepted, and payment of these fees in the coinbase, there needs to be a method for wallets and users to know which assets are accepted by block creators and at what rates. The simplest and most robust way of achieving this is with a publication of the accepted assets and rates by block creators on chain, in the coinbase - this removes the requirement for separate servers to publish and relay this data.
+In addition to enabling the issued asset transaction fees to be accepted, and payment of these fees in the coinbase of new blocks, there needs to be a method for wallets and users to know which assets are accepted by block creators and at what rates. The simplest and most robust way of achieving this is with a publication of the accepted assets and rates by block creators on chain, in the coinbase transaction - this removes the requirement for separate servers to publish and relay this data. 
 
-Finally, a method is required to set the assets and rates that will be applied and enforced on relay/bridge and block creation nodes. These can be set with direct access to the node, either via the node configuration or the RPC interface. However, these assets and rates may need to be updated frequently, and it may be impractical and have security implications to have direct access to functionary and bridge nodes to perform these updates via the RPC interface. Therefore, a method to perform this update remotely via an on-chain transaction is also required. Individual nodes can then be configured with a 'controller' script that authenticates changes to the asset fee policy without requiring direct access to any individual nodes.
+Finally, a method is required to configure which assets and corresponding fee rates will be applied to policy on relaying and block creation nodes. These can be set with direct access to the node, either via the node configuration or the RPC interface. However, the accepted assets and fee rates may need to be updated frequently, and it may be impractical and have security implications to have direct access to functionary and bridge nodes to perform these updates via the RPC interface. Therefore, a method to perform this update remotely via an on-chain transaction is also required. Individual nodes can then be configured with a 'controller' script that authenticates changes to the asset fee policy without requiring direct access to any individual nodes. 
 
 ==Design==
 
 ===Overview===
 
-We propose a modification to Elements to enable to use of transaction fees paid in specified issued assets in addition to the policyAsset at specified rates relative to the current policyAsset rate applied. This requires no changes to the consensus rules, and these changes can be deployed without requiring a network fork. The specified assets and rates will be applied to relay and mempool policy, mempool ordering, and these fees will be paid to specified destinations in the coinbase transaction of created blocks.
+Each node has complete control over which assets and at what fee rates to accept as policy. However, in practice all block creators and relaying nodes should agree to apply the same asset fee policy in order to provide a reliable service for users. The assets and fee rates accepted by any node 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 this can be set dynamically at runtime via a new RPC. In addition, a new RPC will be added to query the current assets, rate multiplier (and destinations) currently being applied to policy by any node.
 
-Each node has complete control over which assets and at what rates to accept as policy, however in practice all block creators and relaying nodes *should* agree to apply the same asset fee policy in order to provide a reliable service for users. Each node will be able to directly specify what assets and at what rate they will apply to policy. 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. Note that if the consensus parameter c_mandatory_coinbase_destination is set to a non-empty script, then this cannot be overridden and all asset fee coinbase outputs must be paid to the c_mandatory_coinbase_destination script. In addition, a new RPC will be added to query the current assets, rate multiplier (and destinations) currently being applied by any node.
+For fee outputs in an issued asset, the fee rate (in units/vbyte) will be scaled 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. 
 
-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 (policyAsset) 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. vsize = 257 transaction to be relayed would be: 257 x 1 x 100 = 25700 base units of the USDT asset. 
-
-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 OP_RETURN output for each accepted asset and included in the coinbase transaction for blocks that they create every N blocks (where N 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.
-
-To enable this, each node can be configured with a specified scriptPubKey that authorizes the policy update. This is called the 'controller' scriptPubKey (but it can be a multisig representing several entities that must agree to any update). The controller(s) create a transaction with the scriptPubKey as the first output (as well as spending from the same scriptPubKey), and this transaction contains an additional OP_RETURN output for each fee asset to be accepted, encoded with the assetID, rate (and optional destination).
-
-Each configured node detects the configured controller scriptPubKey in a confirmed transaction output. It then verifies that this transaction spends from an output with the same scriptPubKey (this authenticates the transaction by verifying input scriptPubKey witness) and decodes each of the OP_RETURN outputs in the transaction to update the node assetID and rate applied to policy (and destination if creating blocks and c_mandatory_coinbase_destination not used.)
+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) in LBTC applied to policy.
+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. 
 
 ===Specification===
 
 ====Issued asset fees====
 
 Accepted assets, rate multipliers (and optional destination) are stored in a vector assetFeeRates of CAssetFeeRate objects.
-This object contains the CAsset asset ID, the int64_t fee rate multiplier and CScript fee destination.
+This object contains the CAsset asset ID, the int64_t fee rate multiplier and optional CScript fee destination.
 
-For each supported issued asset, there is supplied fee rate multiplier: issued_asset_fee_rate_multiplier. This value scales the calculated the fee rate of a transaction that has an issued asset fee to give an effective_fee_rate which is then used to determine if this is equal to or greater than the relay and mempool minimums set with policyAsset fee rates. For mempool ordering, the effective fee rate is used for comparison with transactions with policyAsset fee rates. 
-
-The fee rate multiplier is specified as a fixed precision int64_t number issued_asset_fee_rate_multiplier_int64 (the number of decimal places supported is specified by the int64_t parameter ASSET_MULTIPLIER_SCALE_FACTOR (default 100000000) which is applied to all asset multipliers. 
+For each supported issued asset, there is a specified fee rate multiplier: issued_asset_fee_rate_multiplier (a float). This is represented internally as a fixed precision int64_t number issued_asset_fee_rate_multiplier_int64 (the number of decimal places supported is specified by the int64_t parameter ASSET_MULTIPLIER_SCALE_FACTOR (default 100000000) which is applied to all asset multipliers. 
 
 
-issued_asset_fee_rate_multiplier_int64 = static_cast(issued_asset_fee_rate_multiplier * ASSET_RATE_SCALE_FACTOR)
+issued_asset_fee_rate_multiplier_int64 = static_cast(std::ceil(issued_asset_fee_rate_multiplier * ASSET_RATE_SCALE_FACTOR))
 
 
-The effective_fee_rate is calculated as follows for any transaction where the fee output is paid in an amount of accepted issued asset:
+This value scales the calculated the fee rate of a transaction that has an issued asset fee to give an effective_fee_rate. 
 
 
 effective_fee_rate = (asset_fee_rate * issued_asset_fee_rate_multiplier_int64) / ASSET_MULTIPLIER_SCALE_FACTOR
 
 
-Where asset_fee_rate is the fee rate calculated using the transaction vsize the amount of the issued asset fee output. For every transaction in the mempool that has an issued asset fee, the effective_fee_rate is used in place of the policyAsset fee rate for the purposes of mempool ordering and minimum mempool fee rate calculation. 
+Where asset_fee_rate is the fee rate calculated using the transaction vsize and the amount of the issued asset fee output. For every transaction in the mempool that has an issued asset fee, the effective_fee_rate is used in place of the policyAsset fee rate for the purposes of mempool ordering and minimum mempool fee rate calculation. 
 
 The value of issued_asset_fee_rate_multiplier_int64 must be between 1 and MAX_ASSET_MULTIPLIER
 
@@ -80,11 +69,17 @@ By default MAX_ASSET_MULTIPLIER = 10^15. Scaled as a fixed precisio
 
 The value of (asset_fee_rate * issued_asset_fee_rate_multiplier_int64) cannot exceed the maximum allowed value of int64_t. If it does, then the effective_fee_rate is set to maximum allowable value of std::numeric_limits::max() / ASSET_RATE_SCALE_FACTOR = 92233720368 (which is 922.3372 LBTC/vbyte). 
 
+To calculate the value of an issued asset fee output required for a transaction of size vsize where the current minimum policyAsset fee rate is policy_asset_rate:
+
+
+fee_amount = vsize * policy_asset_rate * (1 + ((ASSET_RATE_SCALE_FACTOR - 1) / issued_asset_fee_rate_multiplier_int64))
+
+
 The fee asset and rate multiplier can be configured for an individual node with the option:
 
 -setfeeassetrate=: set the assetID:multiplier-script applied to relay and mempool policy for this node. 
 
-The multiplier is supplied as a floating point number (issued_asset_fee_rate_multiplier) which is converted to fixed point integer by multiplication with ASSET_MULTIPLIER_SCALE_FACTOR. Optional script to set destination for coinbase fees for each asset. If mandatory_coinbase_destination is set as a consensus rule, then this is not currently possible and will return an error.
+The multiplier is supplied as a floating point number (issued_asset_fee_rate_multiplier) which is converted to fixed point integer by ceiling multiplication with ASSET_MULTIPLIER_SCALE_FACTOR. 
 
 This can be repeated for additional fee assets and rates. The maximum number of issued asset fee multipliers allowed is set by MAX_ISSUED_ASSET_FEE_SIZE (default 10).
 
@@ -94,11 +89,11 @@ A new accepted fee asset can be added on demand at runtime with the RPC, or an e
 
 getassetfeerate get all assetID, multiplier, script applied by this client.
 
-Note: Consensus enforces that any individual fee output, and the total sum of fee values in a block do not exceed MAX_MONEY. Issued asset fees could in theory exceed this, as individual assets might have much larger issuance amounts than MAX_MONEY. Therefore, currently in BlockAssember the inclusion of issued asset fee paying transactions in a block will be limited so that the total value of fee outputs will not exceed MAX_MONEY when generating a new block. 
+Note: Consensus enforces that any individual fee output, and the total sum of fee values in a block do not exceed MAX_MONEY. Issued asset fees could in theory exceed this, as individual assets might have much larger issuance amounts than MAX_MONEY. Therefore, currently in BlockAssembler the inclusion of issued asset fee paying transactions in a block will be limited so that the total value of fee outputs will not exceed MAX_MONEY when generating a new block. 
 
 ====Fee rate publication====
 
-Asset fee rates (and optional destination) encoded in coinbase OP_RETURN as follows:
+Asset fee rates multipliers (and optional destination) encoded in additional coinbase OP_RETURN outputs as follows:
 
 * 4 bytes: AFEE
 * 32 bytes: asset ID
@@ -114,10 +109,10 @@ Config option:
 If n is 0 then fee assets and rate multipliers are not written.
 
 If n > 0 then any accepted assets and fee rates set via RPC or config are published every n blocks.
-Any fee rates updated by setassetfeerate or controller script are not applied to policy until the end of an epoch.
+Any fee rates updated by setassetfeerate or controller script are not applied to policy until the end of an epoch of n blocks.
 
-A new RPC getsignerfeerates retrieves the latest published accepted assets and rate multipliers (cached).
-getassetfeerate has a single boolean argument to either retrieve the latest updated fee rate multipliers, or the rates applied in the current epoch.
+A new RPC getsignerfeerates retrieves the latest published accepted assets and rate multipliers.
+getassetfeerate has a single boolean argument to either retrieve the latest updated list of assets and fee rate multipliers, or the list applied in the current epoch.
 
 On node restart, the cached rates are updated from the latest publication.
 

From 3d917cdd4b480b8b247a79778de8daec460b1825 Mon Sep 17 00:00:00 2001
From: Tom Trevethan 
Date: Thu, 2 Oct 2025 12:47:48 +0100
Subject: [PATCH 6/6] change filename temporarily

---
 elip-0202.mediawiki => elip-0000.mediawiki | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename elip-0202.mediawiki => elip-0000.mediawiki (100%)

diff --git a/elip-0202.mediawiki b/elip-0000.mediawiki
similarity index 100%
rename from elip-0202.mediawiki
rename to elip-0000.mediawiki