PizzaSwap - Fractal Swap Service
This document defines a Swap Module for brc-20.
Under the brc-20 Module, we introduce a mechanism that enables users to swap their balances within the module in a seamless and secure way.
All swap-related operations can be seen as a list, updated only by the module’s sequencer, and each event includes the signature of the initiating user.
This ensures maximum security and order throughout the swap process.This proposal defines two operations within the brc-20 module: commit and approve.
Swap Mechanism
Each module instance can include multiple swap pairs, and any user can publicly create a swap pair.
Each module operates independently, with the module’s sequencer responsible for aggregating (committing) all user activities internally and submitting them to the chain.
To prevent users from disrupting aggregation operations with withdrawals, balances in the module are split into two categories: fund balance and trade balance. Deposits into the module are credited to the trade balance, and only the trade balance can participate in aggregation operations.
Only the fund balance can be withdrawn. Users can use the approve operation to transfer the fund balance to the trade balance, or use the decreaseApproval function to convert the trade balance back into the fund balance. The approve inscription can be used to build a brc-20 market within the module.
When the sequencer sends the aggregated events inscription (commit), users will be charged an aggregation fee based on the number of events. The ticker used to pay the commit fee must be specified in the module initialization parameters, and only the fee ticker from the trade balance can be used to pay the commit fee.
Creating a New Swap Module Instance
In the custom parameter section, the following must be set:
Swap transaction fee rate
Liquidity operation fee rate
The ticker used to pay sequencer fees during aggregation
Withdrawing brc-20 Ticker from the Module to a Target Address (Withdraw)
The user must first inscribe a Withdraw inscription, then transfer it to the target address for it to take effect. The inscription can only be used once.
The brc-20 tickers under the user’s account in the module will be withdrawn to the target address.
The inscription must include the module’s ID. If the module doesn’t exist, the withdraw is invalid.
The module must be a white module for withdrawals to be supported. The withdraw inscription does not lock the withdrawable balance when minted, but requires sufficient balance at the time of sending the withdraw transaction. Withdraw inscriptions sent back to the swap module will result in the destruction of the balance.
Approving brc-20 Ticker Balances in the Module to a Target Address (Approve)
The user must first inscribe an Approve inscription, then transfer it to the target address for it to take effect. The inscription can only be used once.
The balance will be assigned to the authorized balance of the specified address in the module.
The inscription must include the module’s ID. If the module doesn’t exist, the approve inscription is invalid.
Only approved balances can be used in the commit event function. For safety, sequencers may reject user requests for approved balances that have fewer than 60 confirmations.
Event Aggregation in Swap
All events that occur in the swap pool are described by functions. To ensure the orderliness of events, these functions are not inscribed individually but are placed in the module's aggregated inscription (commit).The basic rules are as follows:
During aggregation, each operation must be signed by the user’s address for all previous content (serialization including module and parent).
The aggregated inscription must be inscribed by the module sequencer and transferred to the module ID address to take effect. The sequencer is the address specified in the module inscription. The sequencer address can be updated using the update inscription.
For each function aggregated by the module sequencer, a fixed aggregation fee must be charged to the user. The user must deposit and approve sufficient fee tokens in the module, or else the sequencer must reject the user’s event request. If the user has insufficient fees, the commit inscription will be invalid.
To avoid blocking when sending large numbers of commit inscriptions due to the mempool limit of 100KB and the 25-child transaction limit, commit inscriptions can initially be minted to the sequencer using unrelated UTXOs, regardless of the order. The sequencer can then periodically transfer commits in batches to the module address, ensuring that all previous logic is chained together and valid. This process significantly reduces the number of commit inscriptions that need to be transferred, minimizing the impact of the 25-child limit on the commit process.
Aggregated Inscription (Commit)
Aggregated Inscription Structure Validity Details
Inscription field keys must be lowercase characters, and except for tick, values are case-sensitive.
Duplicate fields are allowed, and extra fields are allowed.
User Signature Authorization
Each function in the aggregated inscription must include the user’s address and signature to authorize the sequencer to operate. Initially, only taproot address formats are supported, with other formats such as p2wpkh, p2wsh, p2sh, p2pkh, and hex-encoded arbitrary locking scripts to be supported later.The signature follows the BIP-322 format to sign the following content:
Each function has a function hashid, calculated as the SHA-256 hash of the serialized message (msg) with the following content:
Note: List the data for each field in the specified order, with each field ending in a newline character (\n
).
Parameters in the params field are separated by spaces. If a field value is empty, omit that line in the serialized data.
The function ID is a fully computable intermediate result and it may be omitted from subsequent new inscriptions after testing is completed, meaning it does not need to be recorded again. However, it cannot be removed from inscriptions that have already been issued. The prevs field contains all function IDs submitted by the same user in the current inscription, sorted in the order in which the sequencer accepts them.
In the entire commit inscription, only the last signature for each address needs to be on-chain, as the intermediate function data has been overwritten by the last signature.
Swap Functions Available
DeployPool
Deploy a liquidity swap pool, specifying token0 and token1 as the two tokens participating in the swap pool. token0 and token1 must be different.
Only one liquidity pool is allowed to be deployed for the same pair of tokens within a module. There is no order relationship between the two tokens in the liquidity pool.
The liquidity ticker name will be "token0/token1". It is not possible to use liquidity tokens to deploy another liquidity pool.
Liquidity assets are always confined within the module they were deployed in and cannot be withdrawn. Each pool tracks the distribution of liquidity assets among holders.
Anyone can deploy a liquidity pool, but the deployer has no special permissions.
AddLiquidity
Users add liquidity, operating only on approved module balances.
RemoveLiquidity
Removes liquidity, with the tickers received belonging to the approved balance.
Swap
Executes a swap transaction, using only approved balances, with the received tickers belonging to the approved balance.
DecreaseApproval
Reduces the authorized balance of a ticker, increasing the withdrawable balance.
Send
Sends balances of various tickers within the module. Only authorized balances can be sent. The recipient’s balance will also be authorized. Liquidity tokens cannot be sent.
SendLp
Sends liquidity tickers within the module.
Function declaration
Swap Fees
Every swap incurs a 0.3% transaction fee. 1/6 of this fee goes to the platform, while the rest is distributed among liquidity providers (LPs).
Note: For example, if a user swaps 1000 ORDI for SATS, 1000 ORDI will be deposited into the liquidity pool. A fee of 3 ORDI is deducted, so the actual amount of SATS the user receives is calculated based on 997 ORDI.
Network Fees
The sequencer is responsible for gathering each aggregation function and minting the aggregated inscription on-chain, incurring network fees for miners to package the transaction. We calculate the network fee charged to users based on the current network fee rate and the user’s aggregation operations:
The formula: , where:
gasPrice: Network fee charged per function, depending on two factors: (a) the BTC network rate and (b)the market price of the ticker being charged:
fee: Measured in the ticker being charged. fee: Measured in the Tick being charged.
Optimization of Function Bytes in Commit Inscription
Remove redundant function ID (saves 70 bytes)
Only keep the last signature for the same address (saves 85 bytes)
Number the address in the commit inscription and use the reference number in subsequent functions (saves 40 bytes)
Reduce unnecessary precision in function parameter values, especially those far beyond slippage impact (saves 10 bytes)
The average size of a standard operation is 330 bytes. The theoretical maximum bytes that can be saved is 200 bytes.
Calculation Model
Using the existing Uniswap v2 model, we can define four operations for the pool.Given a swap pool with two tokens, token A and token B, the following notation is used:
A: The balance of token A in the pool.
B: The balance of token B in the pool.
C: The balance of liquidity tokens in the pool.
The constant product formula is:,
Four operations are as follows:
Swap token A for token B:
Swap token B for token A:
Add liquidity by adding token A and token B to receive liquidity token C:
Use amount a and b to swap amount c, taking the smaller of the two: ,
Remove liquidity by consuming liquidity tokens to receive token A and token B:
Use amount c to swap amounts a and b: ,
The fee is deducted from the user’s tickers upfront.
During swap calculations, high-precision libraries are used to avoid precision loss during addition and multiplication. The final division will maintain the correct decimal precision for the ticker.
The user’s received amount will be the result of accurate calculations with truncated decimals. Manipulating precision for profit is not possible.
If the result is zero after truncation, the operation is not allowed, as the amount is too low.
Slippage
The slippage mechanism is similar to Uniswap, allowing users to tolerate a margin of error in the expected result, considering the following:
Reduced Failure Rate: Front-running swap operations can fail due to concurrency. If a user’s signature takes 1 second and another user confirms their signature during that time, the transaction may fail. The longer the signature process, the higher the likelihood of failure.
Lower Backend Pressure: Front-running swaps require frequent updates to swap data, increasing the backend query load.
Uniswap-Consistent Model: Using the same model as Uniswap lowers the learning curve for users and increases adoption.
Summary of Swap Formulas
Slippage:
exactIn:
exactOut:
Swap:
amountIn: Input ticker amount
amountOut: Output ticker amount
reserveIn: Input ticker reserves in the pool
reserveOut: Output ticker reserves in the pool
Assume a 0.3% fee
exactIn
exactOut
AddLiquidity:
amount0: Amount of token0 added
amount1: Amount of token1 added
poolLp: LP token supply in the pool
reserve0: Token0 reserves in the pool
reserve1: Token1 reserves in the pool
For the initial liquidity addition:
For subsequent liquidity additions:
RemoveLiquidity:
Amount of token0 received:
Amount of token1 received:
Platform Fee Calculation
If the platform takes 1/6 of the fees, we need to calculate how much LP should be minted by the platform to capture exactly 1/6 of the incremental wealth.
Minting Timing: Before each liquidity addition/removal, the previously accumulated LP to be minted should be settled.
Assuming the platform collects 1/6 of the fees, it needs to calculate how much additional LP should be minted so that the platform's share is exactly 1/6 of the increment.
Timing of execution: Before each liquidity addition or removal, settle the cumulative LP amount that should be minted.
poolLp: LP token supply in the pool
reserve0: Token0 reserves in the pool
reserve1: Token1 reserves in the pool
kLast: The k value calculated after the last liquidity event
The square root of the current assets in the pool:
The square root of the last liquidity event:
Platform lp amount:
The derivation process is shown below:
As shown above, to achieve 1/6 of the new wealth, the minted LP should satisfy the following equation: , where ∆ = rootK - rootKLast
FAQ
Can anyone deploy a swap module?
Yes, anyone can deploy a swap module, but managing it requires some complexity:
You need to provide an API for users to query the current pool and submit new transactions.
You must regularly mint inscriptions to submit transactions on-chain.
UniSat will parse all swap transactions on-chain.
app-swap.unisat.io is a product of UniSat, where UniSat has deployed a series of trading pair pools for users to participate in swaps.
Can the contract source ID referenced during deployment be changed?
Currently, it cannot be changed. The plugin author specifies the current contract code.
If there’s a bug in the contract, can it be upgraded?
Yes. If a version of the contract has a bug, a new version can be deployed, and liquidity can be transferred to the new version.
Last updated