Skip to content
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f444b7c
feat: added UniswapV3Composer example
St0rmBr3w Dec 3, 2024
e95cd88
fix: remove artifact changes
St0rmBr3w Dec 11, 2024
3f93284
fix: run linter on composer lib
St0rmBr3w Dec 11, 2024
4a32f13
chore: add UniswapV3Composer unit tests
St0rmBr3w Dec 11, 2024
b9cb966
fix: lint
St0rmBr3w Dec 11, 2024
d3ab75a
chore: remove tasks
St0rmBr3w Dec 11, 2024
9761620
chore: update SwapRouterMock
St0rmBr3w Dec 11, 2024
119b75d
chore: remove unused safeERC20
St0rmBr3w Dec 11, 2024
8b19386
chore: update mock to use IOFT instead of IERC20
St0rmBr3w Dec 13, 2024
a2009fa
fix: update lockfile
St0rmBr3w Dec 13, 2024
2016957
chore: address audit feedback
St0rmBr3w Feb 17, 2025
010c95a
chore: update lockfile
St0rmBr3w Feb 17, 2025
50898b8
chore: add fallback for swap failure
St0rmBr3w Mar 6, 2025
54230f8
chore: add optimizer to toml
St0rmBr3w Mar 6, 2025
5ef8557
chore: update README
St0rmBr3w Mar 27, 2025
818b2f1
chore: add readme updates
St0rmBr3w Mar 27, 2025
e84ea84
chore: run linter
St0rmBr3w Mar 28, 2025
6c6fb79
fix: remove changlog
St0rmBr3w Jun 11, 2025
589ba19
chore: add create-lz-oapp example
St0rmBr3w Jun 11, 2025
86b1820
chore: respond to feedback
St0rmBr3w Jun 24, 2025
56bcad7
chore: update code comments
St0rmBr3w Jun 24, 2025
4c11370
chore: update lockfile
St0rmBr3w Jun 24, 2025
483be2d
chore: rename
Nov 12, 2025
12cfb36
chore: move uni example
Nov 13, 2025
ea2d70f
feat: added aave composer example
Nov 13, 2025
2137375
chore: ran lint on contracts
Nov 13, 2025
cf43a00
feat: add aave composer deploy script
Nov 13, 2025
d39b814
feat: add supply aave hh task
Nov 13, 2025
a42a10c
feat: add supply aave foundry script
Nov 13, 2025
b91bfb6
chore: add unit aave composer unit tests
Nov 13, 2025
618decb
chore: added packages
Nov 13, 2025
34df443
chore: add tasks to config
Nov 13, 2025
ddd1155
chore: update lock file
Nov 13, 2025
8ddeba5
chore: clean comments
Nov 13, 2025
03ddcb9
chore: add deploy vars
Nov 17, 2025
447f352
feat: add refund on source
Nov 17, 2025
3ed94f4
chore: update tests
Nov 17, 2025
ff80a12
chore: minor updates
Nov 17, 2025
b9496d8
feat: add uni deployer
Nov 17, 2025
d6c0bfc
chore: add composer example
Nov 18, 2025
2d01144
chore: update readme
Nov 18, 2025
b90bbb5
chore: minor changes + addressed feedback
Nov 18, 2025
739ea88
chore: modify test according contract changes
Nov 18, 2025
579c838
chore: modify default networks
Nov 18, 2025
b57ae3b
chore: clean old composer config
lzJxhn Nov 20, 2025
392bf10
chore: rename
lzJxhn Nov 20, 2025
7947ba9
chore: renamed stargatePool
lzJxhn Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-donkeys-trade.md
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm also not a fan of calling it "oft composer library"

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-lz-oapp": patch
---

Adds OFT composer library
5 changes: 5 additions & 0 deletions .changeset/chilled-fireants-try.md
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't really need this file now

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-lz-oapp": patch
---

Adds lzCompose example
15 changes: 15 additions & 0 deletions examples/oft-composers/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-
# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \
# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-'
#
# Example environment configuration
#
# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-
# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \
# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-'

# By default, the examples support both mnemonic-based and private key-based authentication
#
# You don't need to set both of these values, just pick the one that you prefer and set that one
MNEMONIC=
PRIVATE_KEY=
10 changes: 10 additions & 0 deletions examples/oft-composers/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
artifacts
cache
dist
node_modules
out
*.log
*.sol
*.yaml
*.lock
package-lock.json
10 changes: 10 additions & 0 deletions examples/oft-composers/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require('@rushstack/eslint-patch/modern-module-resolution');

module.exports = {
extends: ['@layerzerolabs/eslint-config-next/recommended'],
rules: {
// @layerzerolabs/eslint-config-next defines rules for turborepo-based projects
// that are not relevant for this particular project
'turbo/no-undeclared-env-vars': 'off',
},
};
24 changes: 24 additions & 0 deletions examples/oft-composers/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts


# LayerZero specific files
.layerzero

# foundry test compilation files
out

# pnpm
pnpm-error.log

# Editor and OS files
.DS_Store
.idea
1 change: 1 addition & 0 deletions examples/oft-composers/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18.18.0
10 changes: 10 additions & 0 deletions examples/oft-composers/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
artifacts/
cache/
dist/
node_modules/
out/
*.log
*ignore
*.yaml
*.lock
package-lock.json
3 changes: 3 additions & 0 deletions examples/oft-composers/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
...require('@layerzerolabs/prettier-config-next'),
};
162 changes: 162 additions & 0 deletions examples/oft-composers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<p align="center">
<a href="https://layerzero.network">
<img alt="LayerZero" style="width: 400px" src="https://docs.layerzero.network/img/LayerZero_Logo_White.svg"/>
</a>
</p>

<p align="center">
<a href="https://layerzero.network" style="color: #a77dff">Homepage</a> | <a href="https://docs.layerzero.network/" style="color: #a77dff">Docs</a> | <a href="https://layerzero.network/developers" style="color: #a77dff">Developers</a>
</p>

<h1 align="center">LayerZero OFT Composer Library</h1>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rename

Copy link

Choose a reason for hiding this comment

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

addressed.


<p align="center">
<a href="https://docs.layerzero.network/v2/developers/evm/oft/quickstart" style="color: #a77dff">Quickstart</a> | <a href="https://docs.layerzero.network/contracts/oapp-configuration" style="color: #a77dff">Configuration</a> | <a href="https://docs.layerzero.network/contracts/options" style="color: #a77dff">Message Execution Options</a> | <a href="https://docs.layerzero.network/v2/developers/evm/composer/overview" style="color: #a77dff">Composer Overview</a>
</p>

<p align="center">
A Composer library to integrate LayerZero composer contracts with the Omnichain Fungible Token (OFT) standard.
</p>

- [What is an Omnichain Fungible Token?](#what-is-an-omnichain-fungible-token)
- [Using Composer with OFTs](#using-composer-with-ofts)
- [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks)
- [Developing & Deploying Contracts](#developing-contracts)
- [Connecting Contracts](#connecting-contracts)

## What is an Omnichain Fungible Token?

The Omnichain Fungible Token (OFT) standard extends the ERC20 interface to enable seamless cross-chain token transfers without the need for asset wrapping or intermediary chains. By combining LayerZero’s OApp Contract Standard with ERC20’s `_burn` and `_mint` methods, OFTs maintain a unified token supply across multiple blockchains.
Copy link
Contributor

Choose a reason for hiding this comment

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


<img alt="LayerZero" src="https://docs.layerzero.network/assets/images/oft_mechanism_light-922b88c364b5156e26edc6def94069f1.jpg#gh-light-mode-only"/>

Learn more about OFTs in the [OFT Quickstart](https://docs.layerzero.network/v2/developers/evm/oft/quickstart).

## Using Composer with OFTs

This repository is not only a template for OFTs, it’s a fully featured **composer library** that empowers you to build composable, cross-chain applications using LayerZero’s composer contracts in combination with the OFT standard.

For example, our repository includes an example contract (see [UniswapV3Composer.sol](./contracts/UniswapV3Composer.sol)) that demonstrates how to:

- Receive cross-chain messages via LayerZero.
- Decode composable messages (using `OFTComposeMsgCodec`) to extract parameters.
- Execute token swaps on Uniswap V3 after an OFT transfer.
- Gracefully handle failures by refunding tokens.

This composability approach lets you extend basic omnichain token transfers with additional logicβ€”such as token swaps, lending, or other decentralized finance features. For more information on composability, visit the [What is Composability?](https://docs.layerzero.network/v2/concepts/applications/composer-standard) documentation and the [EVM Composer Overview](https://docs.layerzero.network/v2/developers/evm/composer/overview).

## LayerZero Hardhat Helper Tasks

LayerZero Devtools offers several helper tasks to deploy, configure, connect, and wire your OFT and composer contracts across multiple chains. These tasks streamline operations like:

- **Deploying Contracts:**

```bash
npx hardhat lz:deploy
```

Deploy your contracts to networks specified in your `hardhat.config.ts`.

- **Initializing Configuration:**

```bash
npx hardhat lz:oapp:config:init --contract-name YOUR_CONTRACT_NAME --oapp-config FILE_NAME
```

Generate a default `layerzero.config.ts` file for setting up cross-chain pathways.

- **Wiring Contracts:**

```bash
npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE
```

Connect your deployed contracts by executing the necessary configuration functions.

- **Viewing Current Configurations:**
```bash
npx hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG
```
Review the active, custom, and default configurations for each pathway.

For more details, refer to the [LayerZero Hardhat Helper Tasks Documentation](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying).

## Developing Contracts

### Installing dependencies

We recommend using `pnpm` (or your preferred package manager):

```bash
pnpm install
```

### Compiling your contracts

This project supports both Hardhat and Forge compilations:

```bash
pnpm compile
```

To compile with a specific tool:

```bash
pnpm compile:hardhat
pnpm compile:forge
```

### Running tests

Both Hardhat and Forge tests are supported:

```bash
pnpm test
```

Or run specific tests:

```bash
pnpm test:hardhat
pnpm test:forge
```

## Deploying Contracts

1. **Set Up Deployer Wallet:**

- Rename `.env.example` to `.env`.
- Configure your mnemonic or private key.

2. **Fund Your Account:**
Ensure your deployer wallet is funded with the appropriate native tokens.

3. **Deploy Contracts:**
```bash
npx hardhat lz:deploy
```
Use the `--help` flag for additional CLI options.

## Connecting Contracts

1. **Configure Connections:**
Generate and customize your `layerzero.config.ts` file:

```bash
npx hardhat lz:oapp:config:init --contract-name [YOUR_CONTRACT_NAME] --oapp-config [CONFIG_NAME]
```

2. **Define Network Pathways:**
Specify contracts and their interconnections (e.g., Ethereum <--> Arbitrum) in your configuration file.

3. **Apply Configuration:**
Connect your deployed contracts by running:
```bash
npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
```

Join our community on [Discord](https://discord-layerzero.netlify.app/discord) or follow us on [Twitter](https://twitter.com/LayerZero_Labs) for updates.

---

By following these steps, you leverage LayerZero’s composer library to build and deploy innovative, composable cross-chain applications powered by the OFT standard.
138 changes: 138 additions & 0 deletions examples/oft-composers/contracts/AaveV3Composer/AaveV3Composer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";
import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol";
import { IOAppCore } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol";
import { IStargate } from "@stargatefinance/stg-evm-v2/src/interfaces/IStargate.sol";

import { IAaveV3Composer } from "./IAaveV3Composer.sol";
import { IPool } from "./IAaveV3Pool.sol";

/**
* @title AaveV3Composer
*
* @notice Supplies the received tokens into Aave V3 liquidity pools after an lzCompose call.
*
* @dev The contract enforces strict authentication, decodes lzCompose payloads, and either supplies tokens into Aave
* or refunds the recipient if the supply reverts.
*/
contract AaveV3Composer is ILayerZeroComposer, IAaveV3Composer {
using SafeERC20 for IERC20;

/// @notice Aave V3 pool that receives supplied liquidity.
IPool public immutable AAVE;

/// @notice LayerZero Endpoint trusted to invoke `lzCompose`.
address public immutable ENDPOINT;

/// @notice Stargate OFT that is authorized to trigger Aave supplies on this chain.
address public immutable STARGATE;

/// @notice Underlying ERC20 token that backs the trusted Stargate OFT.
address public immutable TOKEN_IN;

// ──────────────────────────────────────────────────────────────────────────────
// 0. Setup & Constructor
// β€’ Validates constructor inputs
// β€’ Caches protocol addresses
// β€’ Grants a one-time approval for Aave supplies
//
// ↳ Docs: https://docs.layerzero.network/v2/developers/evm/composer/overview
// https://docs.layerzero.network/v2/developers/evm/oft/quickstart#send-tokens--call-composer
// ──────────────────────────────────────────────────────────────────────────────

/**
* @notice Deploys the composer and connects it to Stargate and Aave pools.
*
* @dev Assuming the Stargate contract is already configured with the correct LayerZero Endpoint.
*
* A **one-time `maxApprove`** is granted to the Aave Pool because:
* 1. Funds only arrive via `lzReceive` β†’ `lzCompose` from the trusted Stargate Pool.
* 2. The pool can only transfer what the composeMsg allows.
* 3. Saves ~5k gas on every compose execution.
*
* @param _aavePool Address of the target Aave V3 pool.
* @param _stargatePool StargatePool expected to receive the supplied tokens.
*/
constructor(address _aavePool, address _stargatePool) {
if (_aavePool == address(0)) revert InvalidAavePool();
if (_stargatePool == address(0)) revert InvalidStargatePool();

// Initialize the Aave pool.
AAVE = IPool(_aavePool);

// Initialize the Stargate Pool.
STARGATE = _stargatePool;

// Gran the endpoint from the StargatePool.
ENDPOINT = address(IOAppCore(STARGATE).endpoint());

// Grab the underlying token from the StargatePool.
TOKEN_IN = IStargate(_stargatePool).token();

// Grant a one-time unlimited allowance so Aave can pull funds during supply.
IERC20(TOKEN_IN).approve(address(AAVE), type(uint256).max);
}

// ──────────────────────────────────────────────────────────────────────────────
// 1. Compose Logic (`lzCompose`)
// Called by the LayerZero Endpoint after a user sends tokens cross-chain
// with a compose message. Decodes the message and performs a Aave V3 supply
// on behalf of the original sender.

// Steps:
// 1. Authenticity checks (trusted Stargate Pool & Endpoint)
// 2. Decode recipient address and amount from `_message`
// 3. Try to execute supply on behalf of the recipient β†’ emit `SupplyExecuted`
// β€’ On failure, refund tokens β†’ emit `SupplyFailedAndRefunded`
// ──────────────────────────────────────────────────────────────────────────────

/**
* @notice Consumes composed messages and supplies the received tokens into the Aave V3 pool.
* @dev `_message` is encoded by the OFT.send() caller on the source chain via
* `OFTComposeMsgCodec.encode()` and has the following layout:
*
* ```
* | srcNonce (uint64) | srcEid (uint32) | amountLD (uint128) |
* | composeFrom (bytes32) | composeMsg (bytes) |
* ```
*
* `composeMsg` (last field) is expected to be:
* `abi.encode(address onBehalfOf)`.
*
*
* @param _oft Address of the originating OFT; must equal the trusted `stargate`.
* @dev _guid Message hash (unused, but kept for future extensibility).
* @param _message ABI-encoded compose payload containing recipient address.
* @dev _executor Executor that relayed the message (unused).
* @dev _extraData Extra data from executor (unused).
*/
function lzCompose(
address _oft,
bytes32 /* _guid */,
bytes calldata _message,
address /* _executor */,
bytes calldata /* _extraData */
) external payable {
// Step 1️: Authenticate call logic source.
if (_oft != STARGATE) revert UnauthorizedStargatePool();
if (msg.sender != ENDPOINT) revert UnauthorizedEndpoint();

// Step 2️: Decode the recipient address and amount from the message.
address _to = abi.decode(OFTComposeMsgCodec.composeMsg(_message), (address));
uint256 amountLD = OFTComposeMsgCodec.amountLD(_message);

// Step 3: Execute the supply or refund to target recipient.
try AAVE.supply(TOKEN_IN, amountLD, _to, 0) {
// 0 is the referral code
emit SupplyExecuted(_to, amountLD);
} catch {
IERC20(TOKEN_IN).safeTransfer(_to, amountLD);
emit SupplyFailedAndRefunded(_to, amountLD);
}
}
}
Loading
Loading