Solidity smart contracts for cross-chain token swaps with upgradeability and BLS signature verification.
The Router
contract is the central entry point for swap requests and contract upgrades. It manages cross-chain token swap requests, swap execution, and upgrade scheduling. The Router inherits from ScheduledUpgradeable
to support secure, scheduled implementation contract upgrades based on ERC-1822, the Universal Upgradeable Proxy Standard (UUPS).
ScheduledUpgradeable
is an abstract, upgradeable base contract that provides:
- Scheduling logic for contract upgrades, including time delays and BLS signature verification.
- Functions to schedule, execute, and cancel upgrades, ensuring upgrades are authorized and transparent.
Child contracts (like Router
) inherit from ScheduledUpgradeable
and can customize upgrade scheduling logic.
BLSBN254SignatureScheme
is a contract for BLS signature verification on the BN254 curve, used to verify off-chain signatures for swap requests and contract upgrades. The contract enforces domain separation using a unique Domain Separation Tag (DST) that includes the chain ID, contract type, and version (e.g., "swap-v1"
or "upgrade-v1"
), preventing signature replay across different domains or versions.
- The DST is set in the constructor and encodes both the contract type and version for each application.
- The contract exposes functions for verifying BLS signatures and retrieving validator keys.
- Example usage:
application
parameter in the constructor can be set to"upgrade-v1"
for upgrade verification or"swap-v1"
for swap requests.
This repository comes with a comprehensive set of tests written in Solidity, which can be executed using Foundry.
To install Foundry:
curl -L https://foundry.paradigm.xyz | bash
This will download foundryup. To start Foundry, run:
foundryup
To clone the repo:
git clone https://github.com/randa-mu/onlysubs-solidity
npm install
npm run build
npm run test
For the deployment documentation, please see the deployment guide here.
The smart contract tests are written primarily using Hardhat.
To generate a code coverage report, run:
npx hardhat coverage
After running coverage, you can optionally open the generated report to see detailed info:
open coverage/index.html # macOS
xdg-open coverage/index.html # Linux
To correctly format the Solidity and JS code, run the following command:
npm run lint:fix
The OnlySwaps demo script shows how to deploy and interact with contracts across two local Anvil chains with custom chain IDs.
The script automatically spawns two Anvil blockchains at port 8545 (with chain id 31337) and 8546 (with chain id 31338).
To run the demo script, run the following command:
npm run build
npx ts-node demo/onlyswap-e2e-demo.ts
The script will then deploy contracts on both chains, perform a cross-chain token swap, and display logs including chain IDs, transfer parameters, and balances.
Example output:
Anvil instances ready...
Configuring routers...
Recipient balance before swap request: 0.0 RUSD
Swap request created with requestId 0xc8e424bef2a726381716973580834e29713efb17b3af76c0f741bc7ff4a8cc4a
Swap request parameters: {
sender: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
recipient: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
tokenIn: '0x0165878A594ca255338adfa4d48449f69242Eb8F',
tokenOut: '0x8464135c8F25Da09e49BC8782676a84730C318bC',
amountOut: '9.5',
srcChainId: 31337n,
dstChainId: 31338n,
verificationFee: '0.5',
solverFee: '1.0',
nonce: 1n,
executed: false
}
Recipient balance after relay: 9.5 RUSD
Solver balance before rebalance: 0.0 RUSD
Solver balance after rebalance: 10.5 RUSD
Anvil instances stopped.
Several critical functions in the OnlySwaps contracts require BLS (BN254) signature verification to ensure secure, threshold-authorized actions. Each function expects a BLS signature over a specific message, which can be generated off-chain using the helper functions provided in the contracts. The message generated is already hashed to a point on G1 using the correct Domain Separation Tag (DST). Below is a description of each function, how BLS verification is used, and how to obtain the message to sign.
- Purpose: Compensates the solver on the source chain (including solver fees) after the solver has sent liquidity from their wallet to the recipient address on the destination chain to fulfill a swap request.
- BLS Verification: Requires a valid BLS signature from the swap request validator
swapRequestBlsValidator
contract over the swap request parameters. - Message Construction:
- Helper function called:
swapRequestParametersToBytes(bytes32 requestId, address solver)
- Returns:
message
: raw message bytesmessageAsG1Bytes
: marshaled G1 bytes (used for signing)
- To sign: Use
messageAsG1Bytes
as the message for BLS signing.
- Helper function called:
- Purpose: Updates the swap request BLS validator contract. This will also allow switching the public key to a new one in the new validator contract.
- BLS Verification: Requires a valid BLS signature from the current validator
swapRequestBlsValidator
over the update parameters. - Message Construction:
- Helper function called:
swapRequestBlsValidatorUpdateParamsToBytes(address newValidator, uint256 nonce)
- Returns:
message
: raw message bytesmessageAsG1Bytes
: marshaled G1 bytes (used for signing)
- To sign: Use
messageAsG1Bytes
for BLS signing.
- Helper function called:
- Purpose: Schedules a contract upgrade to a new implementation.
- BLS Verification: Requires a valid BLS signature from the contract upgrade validator
contractUpgradeBlsValidator
over the upgrade parameters. - Message Construction:
- Helper function called:
contractUpgradeParamsToBytes(string action, address pendingImplementation, address newImplementation, bytes upgradeCalldata, uint256 upgradeTime, uint256 nonce)
with action =schedule
. - Returns:
message
: raw message bytesmessageAsG1Bytes
: marshaled G1 bytes (used for signing)
- To sign: Use
messageAsG1Bytes
for BLS signing, withaction
set to"schedule"
.
- Helper function called:
- Purpose: Cancels a previously scheduled contract upgrade.
- BLS Verification: Requires a valid BLS signature from the contract upgrade validator
contractUpgradeBlsValidator
over the cancellation parameters. - Message Construction:
- Helper function called:
contractUpgradeParamsToBytes(string action, address pendingImplementation, address newImplementation, bytes upgradeCalldata, uint256 upgradeTime, uint256 nonce)
with action =cancel
. - Returns:
message
: raw message bytesmessageAsG1Bytes
: marshaled G1 bytes (used for signing)
- To sign: Use
messageAsG1Bytes
for BLS signing, withaction
set to"cancel"
.
- Helper function called:
- Purpose: Updates the contract upgrade BLS validator contract. This will also allow switching the public key to a new one in the new validator contract.
- BLS Verification: Requires a valid BLS signature from the current contract upgrade validator
contractUpgradeBlsValidator
over the validator update parameters. - Message Construction:
- Helper function called:
blsValidatorUpdateParamsToBytes(address blsValidator, uint256 nonce)
- Returns:
message
: raw message bytesmessageAsG1Bytes
: marshaled G1 bytes (used for signing)
- To sign: Use
messageAsG1Bytes
for BLS signing.
- Helper function called:
- Call the relevant helper function on the contract to get the message bytes (
messageAsG1Bytes
) for signing. - Sign the message off-chain using your share of the threshold BLS key set in either the
swapRequestBlsValidator
contract orcontractUpgradeBlsValidator
contract and aggregate signatures up to the required threshold. - Submit the aggregated signature as part of the transaction to the contract function.
This design ensures that all critical actions (swap validation, upgrade scheduling/cancellation, validator updates) are authorized by a threshold of BLS signers, and the message format is always available on-chain for off-
This library is licensed under the MIT License which can be accessed here.
Contributions are welcome! If you find a bug, have a feature request, or want to improve the code, feel free to open an issue or submit a pull request.