diff --git a/docs/taproot-assets/rfq-and-decimal-display.md b/docs/taproot-assets/rfq-and-decimal-display.md
new file mode 100644
index 00000000..e54fe4e3
--- /dev/null
+++ b/docs/taproot-assets/rfq-and-decimal-display.md
@@ -0,0 +1,480 @@
+# Asset Decimal Display
+
+Within the Taproot Assets Protocol, an asset's unit is an integer (`uint64`).
+That means, the protocol cannot represent fractions of an asset.
+Therefore, any asset that represents a fiat currency would need to issue assets
+equivalent to at least the smallest unit in use.
+For example, an asset that represents the US-Dollar would need to be minted in a
+way that one asset unit would represent one USD **cent**.
+Or in other words, 100 units of such an asset would represent 1 US-Dollar.
+Beyond the smallest unit, additional breathing room should be added to ensure
+minimal precision loss during conversion arithmetic (see next chapter).
+
+Because wallet software user interfaces aren't expected to know what
+"resolution" or precision any asset in the wild represents, a new field called
+`decimal_display` was added to the JSON metadata of new assets minted with
+`tapd v0.4.0-alpha` and later (this field is encoded in the metadata field as a
+JSON field, therefore it is only compatible with assets that have a JSON
+metadata field).
+
+An issuer can specify `tapcli assets mint --decimal_display X` to specify the
+number of decimal places the comma should be shifted to the left when displaying
+a sum of asset units.
+
+For the example above, a USD asset would choose `--decimal_display 2` to
+indicate that 100 units ($10^2$) should be displayed as `$1.00` in the wallet
+UI. Or another example: 1234 USD cent asset units would be displayed as `$12.34`
+in the UI.
+
+An asset's decimal display can be viewed with the `tapcli assets list` command
+(for an asset that's owned):
+```shell
+$ tapcli assets list
+
+{
+ "assets": [
+ {
+ "version": "ASSET_VERSION_V0",
+ "asset_genesis": {
+ ...
+ },
+ "amount": "10000000",
+ ...
+ "decimal_display": {
+ "decimal_display": 3
+ }
+ }
+ ]
+}
+```
+
+If the `decimal_display` field is missing or showing as
+`"decimal_display": null`, it means the asset is using a value of 0 (which means
+no shift in decimal places).
+
+For an asset that isn't owned by the local node (but its issuance information
+was synced from a universe server), the decimal display value can be retrieved
+from the asset's metadata:
+
+```shell
+$ tapcli assets meta --asset_id xyz
+
+{
+ "data": "7b22646563696d616c5f646973706c6179223a357d",
+ "type": "META_TYPE_JSON",
+ "meta_hash": "2ae3dc4e0430e7e19134adb516d9a59237efa0c580479c5e983ca0c1b6777c65"
+}
+
+$ tapcli assets meta --asset_id xyz | jq -r '.data' | xxd -p -r
+
+{"decimal_display":5}
+```
+
+## Precision requirement for assets in the Lightning Network
+
+Due to the non-divisible (integer) nature of Taproot Asset units, the smallest
+asset amount that can be transferred with a Lightning Network HTLC is one asset
+unit (partial units or zero units aren't possible).
+
+If one such asset unit represents significantly more than a couple of
+milli-satoshi or even full satoshi, then in some cases, due to integer division
+and rounding up, the user might end up spending noticeably more assets than
+necessary when paying an invoice.
+
+**Example 1: Paying a 1 satoshi invoice**:
+
+While writing this article, one USD cent is roughly equivalent to 19 satoshi.
+
+So if a user with USD cent assets in their wallet attempted to pay an invoice
+denominated over 1 satoshi, they would need to send a full USD cent to satisfy
+the invoice (again, only full asset units can be transported over an HTLC).
+
+Even if one cent isn't much, the overpayment would still be roughly `19x`.
+
+**Example 2: Paying an invoice with MPP**:
+
+Multi-Part Payments (MPP) allow a single payment to be split up into multiple
+parts/paths, resulting in multiple HTLCs per payment.
+
+Assuming a `decimal_display` value of `2` (1 unit represents 1 USD cent), if a
+user wants to pay an invoice over `1,000,000` satoshi, that would be
+equivalent to `$526.315789 USD` or `52,631.5789` cents
+(`1 million satoshi / 19 satoshi`, showing extra decimal places to demonstrate
+loss of precision).
+If the user were to pay this amount in a single HTLC, they would send `52,632`
+asset units (need to round up to satisfy integer asset amount and invoice
+minimum amount requirements), overpaying by `0.4211` cents.
+
+If the user's wallet decided to split up the payment into 16 parts for example,
+then each part would correspond to `3,289.4737` cents. To satisfy the integer
+asset amount and invoice minimum amount requirement, each of the 16 HTLCs would
+send out `3290` cents. That's a full `8.4211` cents of overpayment.
+
+## What precision should I choose when minting an asset for the Lightning Network?
+
+To address the issue of rounding up when splitting payments or representing
+small satoshi amounts as asset units, an issuer of assets should use a high
+enough value for `decimal_display` when minting.
+
+**But what is a good value for `decimal_display`?**
+
+We recommend to use a `decimal_display` value of `6` for currencies which
+use a smaller subunit with two decimal places (such as cents for USD or EUR,
+penny for GBP and so on).
+
+For currencies without smaller units (for example JPY or VND), a
+`decimal_display` value of `4` is recommended.
+
+## What if I made an asset with the wrong amount of precision?
+
+The `decimal_display` value is stored in the `asset_meta` field of the
+`genesis_asset` that creates a particular `group_key` (or `asset_id`). As a
+result, the value of the `asset_meta` actually determined the original
+`asset_id` and `group_key` used, therefore these values are strongly bound.
+
+The only way to "fix" the `decimal_display` value is to burn all the existing
+assets, creating new assets with the proper decimal display value. It's possible
+to do this in a _single atomic transaction_ using the gRPC/REST interface.
+
+Such a transaction would:
+1. Burn referenced asset inputs
+2. Create new asset units under a new group key
+
+# RFQ
+
+The RFQ system is responsible for acquiring real-time price quotes for
+converting between asset units and satoshi (both directions) or between
+different asset types.
+
+It's important to note that the direction of "inbound/in/buy" and
+"outbound/out/sell" is always seen from the point of view of the **wallet end
+user**. So what is outbound for the end user would be inbound for the RFQ peer
+(edge node) and vice versa.
+
+There are two main user stories, as seen from the point of view of the wallet
+end user:
+
+1. Sending out assets: The user wants to pay a Lightning Network invoice that
+ is denominated in satoshi. The user only has assets in their wallet, they
+ (or their wallet software) want to find out how many asset units they need to
+ send in order to satisfy the invoice amount in satoshi.
+2. Receiving assets: The user wants to get paid in a specific asset. The user
+ only knows about the asset, so they (or their wallet software) want to find
+ out what the asset amount corresponds to in satoshi.
+
+**NOTE**: All arithmetic conversions in the section below always use
+_fixed point_ arithmetic. A `scale` (equivalent to a decimal display, but just
+for computations) of either `11`, or the `decimal_display` value (which ever is
+greater is used).
+
+## Sell Order (Paying Invoices)
+
+The sell order covers the first user story: The user wants to pay a
+satoshi-denominated invoice with assets.
+
+The end result is that the user uses the pre-image referenced by the payment
+hash in the invoice to atomically sell some of their assets units in their
+channel to the RFQ per (edge node), ensuring the payment is only complete is the
+receiver receives their funds.
+Note that from the PoV of the edge node, they're effectively paid a routing fee
+to buy asset units (more asset unit inbound) by also sending out BTC outbound
+(less BTC outbound).
+
+Formal definition:
+- Use case: sending assets as a payment, selling `buxx` for `msat`
+- User query: `Q = how many out_asset units for in_asset amount?` (how many
+ `buxx` do I need to sell/send to pay this payment denominated in `msat`?)
+- `out_asset`: `buxx` (user sells `buxx` asset to RFQ peer, sending that value
+ to the edge node)
+- `in_asset`: `msat` (user "receives" `msat` from RFQ peer, which are then
+ routed to the network)
+- `max_amount`: `in_asset` (what is the maximum amount of `msat` the RFQ peer
+ has to forward to the network? Equal to invoice amount plus user-defined max
+ routing fee limit)
+- `price_out_asset`: `out_asset_units_per_btc` (`buxx per BTC`)
+- `price_in_asset`: `in_asset_units_per_btc` (`msat per BTC`)
+
+### Calculating asset units to send
+
+In this case, we have an invoice denominated in mSAT, and want to convert to
+asset units `U`. Given the total amount of mSAT to send (`X`), the number of
+assets units per BTC (`Y`), and the total amount of mSAT in 1 BTC (`M`), we can
+convert from mSAT to asset units as follows:
+* U = (X / M) * Y
+* where
+ * `U` is the result, the number of asset units to send
+ * `X` is the invoice amount in mSAT
+ * `M` is the number of mSAT in a BTC (100,000,000,000), specified by
+ `price_in_asset`
+ * `Y` is the number of asset units per BTC, specified by `price_out_asset`
+
+### Price oracle interaction
+
+```mermaid
+sequenceDiagram
+ actor User
+ box Seller (user)
+ participant NodeA as Node A
+ participant OracleA as Price Oracle A
+ end
+ box Buyer (edge node)
+ participant NodeB as Node B
+ participant OracleB as Price Oracle B
+ end
+
+ User->>+NodeA: SendPayment
+
+ NodeA->>+NodeA: AddAssetSellOrder
+
+ NodeA->>+OracleA: Get price rate hint
(QueryAssetRate[type=SALE,intent=PAY_INVOICE_HINT,peer=NodeB])
+ OracleA-->>-NodeA: Price rate hint
+
+ NodeA->>+NodeB: Send sell request with price
rate hint over p2p
+
+ NodeB->>+OracleB: Determine actual price
rate using suggested price
(QueryAssetRate[type=PURCHASE,intent=PAY_INVOICE,peer=NodeA])
+ OracleB-->>-NodeB: Actual price rate
+
+ NodeB-->>-NodeA: Return actual price rate
+
+ NodeA->>+OracleA: Validate actual price rate
(QueryAssetRate[type=SALE,intent=PAY_INVOICE_QUALIFY,peer=NodeB])
+ OracleA-->>-NodeA: Approve actual price rate
+
+ NodeA->>-NodeA: Send payment over LN using approved actual price rate
+
+ NodeA->>-User: Payment result
+```
+
+## Buy Order (Receiving via an Invoice)
+
+The buy order covers the second user story: The user wants to get paid, they
+create an invoice specifying the number of asset units they want to receive,
+which is then mapped to a normal, satoshi-denominated invoice.
+The end result is that the user uses the satoshis sent by the sender through the
+normal LN network to _buy_ enough asset units to satisfy their invoice, using
+the edge node and the atomic exchange of the pre-image.
+
+Formal definition:
+- Use case: receiving assets through an invoice, selling `msat` for `buxx`
+- User query: `Q = how many out_asset units for in_asset amount?` (how many
+ `msat` should I denominate my invoice with to receive a given amount of
+ `buxx`?)
+- `out_asset`: `msat` (user sells sats to RFQ peer, which are routed to them by
+ the network)
+- `in_asset`: `buxx` (user buys `buxx` from RFQ peer)
+- `max_amount`: `in_asset` (what is the maximum number of `buxx` the RFQ peer
+ has to sell? Equal to the amount in the user query)
+- `price_out_asset`: `out_asset_units_per_btc` (`msat per BTC`)
+- `price_in_asset`: `in_asset_units_per_btc` (`buxx per BTC`)
+
+### Calculating satoshi to receive
+
+For the receiving case, we perform the opposite computation that we did for
+sending: we want to receive `U` asset units, given a rate of (`Y`) units per
+BTC, we can compute the amount of satoshis that must be paid (`X`) into the edge
+node as:
+
+* `X = (U / Y) * M`
+* where
+ * `X` is the result, the number of mSAT to receive
+ * `U` is the desired number of asset units to receive
+ * `Y` is the number of asset units per BTC, specified by `price_out_asset`
+ * `M` is the number of mSAT in a BTC (100,000,000,000), specified by
+ `price_in_asset`
+
+### Price oracle interaction
+
+```mermaid
+sequenceDiagram
+ actor User
+ box Buyer (user)
+ participant NodeA as Node A
+ participant OracleA as Price Oracle A
+ end
+ box Seller (edge node)
+ participant NodeB as Node B
+ participant OracleB as Price Oracle B
+ end
+
+ User->>+NodeA: AddInvoice
+
+ NodeA->>+NodeA: AddAssetBuyOrder
+
+ NodeA->>+OracleA: Get price rate hint
(QueryAssetRate[type=PURCHASE,intent=RECV_PAYMENT_HINT,peer=NodeB])
+ OracleA-->>-NodeA: Price rate hint
+
+ NodeA->>+NodeB: Send buy request with price
rate hint over p2p
+
+ NodeB->>+OracleB: Determine actual price
rate using suggested price
(QueryAssetRate[type=SALE,intent=RECV_PAYMENT,peer=NodeA])
+ OracleB-->>-NodeB: Actual price rate
+
+ NodeB-->>-NodeA: Return actual price rate
+
+ NodeA->>+OracleA: Validate actual price rate
(QueryAssetRate[type=PURCHASE,intent=RECV_PAYMENT_QUALIFY,peer=NodeB])
+ OracleA-->>-NodeA: Approve actual price rate
+
+ NodeA->>-NodeA: Create invoice using actual price rate
+
+ NodeA->>-User: Invoice
+```
+
+
+## Examples
+
+See `TestFindDecimalDisplayBoundaries` and `TestUsdToJpy` in
+`rfqmath/convert_test.go` for how these examples are constructed.
+
+**Case 1**: Buying/selling USD against BTC.
+
+```text
+In Asset: USD with decimal display = 6 (1_000_000 asset units = 1 USD)
+Out Asset: satoshi / milli-satoshi
+
+Example 1:
+----------
+
+What is price rate when 1 BTC = 20,000.00 USD?
+
+decimalDisplay: 6 1000000 units = 1 USD, 1 BTC = 20000000000 units
+Max issuable units: can represent 922337203 BTC
+Min payable invoice amount: 5 mSAT
+Max MPP rounding error: 80 mSAT (@16 shards)
+Satoshi per USD: 5000
+Satoshi per Asset Unit: 0.00500
+Asset Units per Satoshi: 200
+Price In Asset: 20000000000
+Price Out Asset: 100000000000
+
+
+Example 2:
+----------
+
+What is price rate when 1 BTC = 1,000,000.00 USD?
+
+decimalDisplay: 6 1000000 units = 1 USD, 1 BTC = 1000000000000 units
+Max issuable units: can represent 18446744 BTC
+Min payable invoice amount: 1 mSAT
+Max MPP rounding error: 1 mSAT (@16 shards)
+Satoshi per USD: 100
+Satoshi per Asset Unit: 0.00010
+Asset Units per Satoshi: 10000
+Price In Asset: 1000000000000
+Price Out Asset: 100000000000
+
+
+Example 3:
+----------
+
+What is price rate when 1 BTC = 10,000,000.00 USD?
+
+decimalDisplay: 6 1000000 units = 1 USD, 1 BTC = 10000000000000 units
+Max issuable units: can represent 1844674 BTC
+Min payable invoice amount: 1 mSAT
+Max MPP rounding error: 0 mSAT (@16 shards)
+Satoshi per USD: 10
+Satoshi per Asset Unit: 0.00001
+Asset Units per Satoshi: 100000
+Price In Asset: 10000000000000
+Price Out Asset: 100000000000
+```
+
+**Case 2**: Buying/selling USD against JPY.
+
+```text
+In Asset: USD with decimal display = 6 (1_000_000 asset units = 1 USD)
+Out Asset: JPY with decimal display = 4 (10_000 asset units = 1 JPY)
+
+Assumption: 1 USD = 142 JPY
+
+Example 1:
+----------
+
+What is price rate when 1 BTC = 20,000.00 USD (1 BTC = 2,840,000 JPY)?
+
+Satoshi per USD: 5000
+Satoshi per USD Asset Unit: 0.00500
+USD Asset Units per Satoshi: 200
+Satoshi per JPY: 35
+Satoshi per JPY Asset Unit: 0.35211
+JPY Asset Units per Satoshi: 284
+Price In Asset: 20000000000
+Price Out Asset: 28400000000
+ 1 USD in JPY: 142
+
+
+Example 2:
+----------
+
+What is price rate when 1 BTC = 1,000,000.00 USD (1 BTC = 142,000,000 JPY)?
+
+Satoshi per USD: 100
+Satoshi per USD Asset Unit: 0.00010
+USD Asset Units per Satoshi: 10000
+Satoshi per JPY: 0
+Satoshi per JPY Asset Unit: 0.00704
+JPY Asset Units per Satoshi: 14199
+Price In Asset: 1000000000000
+Price Out Asset: 1420000000000
+500 USD in JPY: 71000
+```
+
+# Price Oracle
+
+The price oracle is an important component in the RFQ system, as it provides the
+values for the exchange rates mentioned above.
+
+Both parties of an asset channel (the wallet end user and the edge node) might
+use a price oracle, but its role is different for those parties:
+* The **Price Oracle for the edge node** is responsible for putting a price tag
+ on the service that is offered by the edge node, which is an atomic swap
+ between two types of assets (often one of them being BTC). In other words,
+ the oracle is responsible for calculating a concrete exchange rate for a
+ specific atomic swap. The inputs to that calculation are variables such as
+ the official exchange rate between the asset and BTC ("official" market rate,
+ potentially obtained from a third party exchange API), the size/volume of the
+ swap and the requested validity duration (expiry). The output of the
+ calculation is again an exchange rate, but one that is adjusted to include a
+ spread vs. the input exchange rate. The spread is what allows the edge node
+ to be compensated for offering the swap service, including potential exchange
+ rate fluctuation risks. It is expected that the spread is adjusted by the
+ price oracle implementation based on the size and validity duration of the
+ swap, because those values directly correlate with the exchange rate risk.
+* The **Price Oracle for the wallet end user** on the other hand is simply
+ tasked with validating exchange rates offered to them by the edge node, to
+ make sure they aren't proposing absurd rates (by accident or on purpose).
+
+**NOTE**: By default, the _minimum_ quote expiry at tapd node will accept is _10
+seconds_.
+
+Due to the fundamentally different roles of the price oracle for both parties,
+it is expected that the actual implementation for the price oracle is also
+different among the parties.
+
+## Edge node
+
+Given that an edge node might want to implement their specific business logic
+and rules, no default implementation for a price oracle for edge nodes is
+provided. Edge node operators need to implement the RPC interface defined in
+`taprpc/priceoraclerpc` and point their `tapd` to use their custom
+implementation with the
+`experimental.rfq.priceoracleaddress=rfqrpc://:` configuration
+value.
+An example implementation of a price oracle server implementing that RPC
+interface with Golang can be found in
+[`docs/examples/basic-price-oracle`](examples/basic-price-oracle).
+
+## Wallet end user
+
+The wallet end user's price oracle implementation can be quite simple. All it
+needs to do is to query an exchange provider's API for the current exchange
+rate of an asset. Then the maximum deviation from that "official" market rate
+that is accepted from edge nodes can be configured using the
+`experimental.rfq.acceptpricedeviationppm=` configuration value (which is in
+parts per million and the default value is `50000` which is equal to `5%`).
+
+Because the API endpoints of public exchange platforms aren't standardized,
+there also isn't a default implementation of an end user price oracle available.
+It is expected that third party developers (or at some point even the exchange
+platforms themselves) will offer a gRPC (`rfqrpc`) compatible price oracle
+endpoint that can directly be plugged into the
+`experimental.rfq.priceoracleaddress=rfqrpc://:` configuration
+value on the wallet end user side.