From 480eb0025e810b2b79519c11b3593178156b8e0e Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Wed, 9 Apr 2025 16:28:45 -0700 Subject: [PATCH 01/11] feat: add solana composer example --- .../oft-solana-composer-library/.env.example | 22 + .../oft-solana-composer-library/.eslintignore | 13 + .../oft-solana-composer-library/.eslintrc.js | 12 + .../oft-solana-composer-library/.gitignore | 26 + examples/oft-solana-composer-library/.nvmrc | 1 + .../.prettierignore | 13 + .../.prettierrc.js | 3 + .../oft-solana-composer-library/.solhintrc.js | 3 + .../oft-solana-composer-library/Anchor.toml | 19 + .../oft-solana-composer-library/CHANGELOG.md | 215 ++ .../oft-solana-composer-library/Cargo.lock | 2756 +++++++++++++++++ .../oft-solana-composer-library/Cargo.toml | 16 + .../oft-solana-composer-library/README.md | 376 +++ .../contracts/MyOFT.sol | 14 + .../contracts/mocks/MyOFTMock.sol | 18 + .../deploy/MyOFT.ts | 53 + .../oft-solana-composer-library/foundry.toml | 30 + .../hardhat.config.ts | 79 + .../jest.config.ts | 12 + .../oft-solana-composer-library/junk-id.json | 6 + .../layerzero.config.ts | 57 + .../oft-solana-composer-library/package.json | 117 + .../programs/composer/Cargo.toml | 25 + .../programs/composer/Xargo.toml | 2 + .../programs/composer/build.rs | 3 + .../programs/composer/src/lib.rs | 164 + .../programs/endpoint-mock/Cargo.toml | 22 + .../programs/endpoint-mock/Xargo.toml | 2 + .../endpoint-mock/src/instructions/mod.rs | 3 + .../src/instructions/oapp/mod.rs | 3 + .../src/instructions/oapp/register_oapp.rs | 35 + .../programs/endpoint-mock/src/lib.rs | 19 + .../endpoint-mock/src/state/endpoint.rs | 8 + .../programs/endpoint-mock/src/state/mod.rs | 3 + .../programs/oft/Cargo.toml | 24 + .../programs/oft/Xargo.toml | 2 + .../programs/oft/build.rs | 3 + .../programs/oft/src/compose_msg_codec.rs | 51 + .../programs/oft/src/errors.rs | 14 + .../programs/oft/src/events.rs | 18 + .../programs/oft/src/instructions/init_oft.rs | 86 + .../oft/src/instructions/lz_receive.rs | 183 ++ .../oft/src/instructions/lz_receive_types.rs | 139 + .../programs/oft/src/instructions/mod.rs | 21 + .../oft/src/instructions/quote_oft.rs | 92 + .../oft/src/instructions/quote_send.rs | 193 ++ .../programs/oft/src/instructions/send.rs | 175 ++ .../oft/src/instructions/set_oft_config.rs | 60 + .../oft/src/instructions/set_pause.rs | 35 + .../oft/src/instructions/set_peer_config.rs | 99 + .../oft/src/instructions/withdraw_fee.rs | 67 + .../programs/oft/src/lib.rs | 101 + .../programs/oft/src/msg_codec.rs | 46 + .../programs/oft/src/state/mod.rs | 5 + .../programs/oft/src/state/oft.rs | 50 + .../programs/oft/src/state/peer_config.rs | 92 + .../programs/oft/tests/msg_codec.rs | 55 + .../rust-toolchain.toml | 2 + .../solhint.config.js | 1 + .../tasks/common/config.get.ts | 200 ++ .../tasks/common/taskHelper.ts | 56 + .../tasks/common/types.ts | 19 + .../tasks/common/utils.ts | 203 ++ .../tasks/common/wire.ts | 203 ++ .../tasks/evm/send.ts | 60 + .../tasks/index.ts | 17 + .../tasks/solana/base58.ts | 36 + .../tasks/solana/createOFT.ts | 339 ++ .../tasks/solana/createOFTAdapter.ts | 106 + .../tasks/solana/debug.ts | 281 ++ .../tasks/solana/getPrioFees.ts | 33 + .../tasks/solana/getRateLimits.ts | 35 + .../tasks/solana/index.ts | 391 +++ .../tasks/solana/initConfig.ts | 31 + .../tasks/solana/multisig.ts | 158 + .../tasks/solana/retryPayload.ts | 100 + .../tasks/solana/sendOFT.ts | 136 + .../tasks/solana/setAuthority.ts | 206 ++ .../tasks/solana/setInboundRateLimit.ts | 80 + .../tasks/solana/setOutboundRateLimit.ts | 81 + .../tasks/solana/setUpdateAuthority.ts | 99 + .../tasks/solana/updateMetadata.ts | 66 + .../tasks/solana/utils.ts | 77 + .../tasks/utils/getFee.ts | 75 + .../test/foundry/MyOFT.t.sol | 160 + .../test/hardhat/MyOFT.test.ts | 101 + .../test/mocks/ERC20Mock.sol | 12 + .../test/mocks/OFTComposerMock.sol | 27 + .../test/mocks/OFTMock.sol | 59 + .../oft-solana-composer-library/tsconfig.json | 13 + .../oft-solana-composer-library/turbo.json | 8 + pnpm-lock.yaml | 4 +- 92 files changed, 9233 insertions(+), 3 deletions(-) create mode 100644 examples/oft-solana-composer-library/.env.example create mode 100644 examples/oft-solana-composer-library/.eslintignore create mode 100644 examples/oft-solana-composer-library/.eslintrc.js create mode 100644 examples/oft-solana-composer-library/.gitignore create mode 100644 examples/oft-solana-composer-library/.nvmrc create mode 100644 examples/oft-solana-composer-library/.prettierignore create mode 100644 examples/oft-solana-composer-library/.prettierrc.js create mode 100644 examples/oft-solana-composer-library/.solhintrc.js create mode 100644 examples/oft-solana-composer-library/Anchor.toml create mode 100644 examples/oft-solana-composer-library/CHANGELOG.md create mode 100644 examples/oft-solana-composer-library/Cargo.lock create mode 100644 examples/oft-solana-composer-library/Cargo.toml create mode 100644 examples/oft-solana-composer-library/README.md create mode 100644 examples/oft-solana-composer-library/contracts/MyOFT.sol create mode 100644 examples/oft-solana-composer-library/contracts/mocks/MyOFTMock.sol create mode 100644 examples/oft-solana-composer-library/deploy/MyOFT.ts create mode 100644 examples/oft-solana-composer-library/foundry.toml create mode 100644 examples/oft-solana-composer-library/hardhat.config.ts create mode 100644 examples/oft-solana-composer-library/jest.config.ts create mode 100644 examples/oft-solana-composer-library/junk-id.json create mode 100644 examples/oft-solana-composer-library/layerzero.config.ts create mode 100644 examples/oft-solana-composer-library/package.json create mode 100644 examples/oft-solana-composer-library/programs/composer/Cargo.toml create mode 100644 examples/oft-solana-composer-library/programs/composer/Xargo.toml create mode 100644 examples/oft-solana-composer-library/programs/composer/build.rs create mode 100644 examples/oft-solana-composer-library/programs/composer/src/lib.rs create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/Cargo.toml create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/Xargo.toml create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/mod.rs create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/mod.rs create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/register_oapp.rs create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/src/state/endpoint.rs create mode 100644 examples/oft-solana-composer-library/programs/endpoint-mock/src/state/mod.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/Cargo.toml create mode 100644 examples/oft-solana-composer-library/programs/oft/Xargo.toml create mode 100644 examples/oft-solana-composer-library/programs/oft/build.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/compose_msg_codec.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/errors.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/events.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/init_oft.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive_types.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/mod.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/quote_oft.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/quote_send.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/send.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/set_oft_config.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/set_pause.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/set_peer_config.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/instructions/withdraw_fee.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/lib.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/msg_codec.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/state/mod.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/state/oft.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/src/state/peer_config.rs create mode 100644 examples/oft-solana-composer-library/programs/oft/tests/msg_codec.rs create mode 100644 examples/oft-solana-composer-library/rust-toolchain.toml create mode 100644 examples/oft-solana-composer-library/solhint.config.js create mode 100644 examples/oft-solana-composer-library/tasks/common/config.get.ts create mode 100644 examples/oft-solana-composer-library/tasks/common/taskHelper.ts create mode 100644 examples/oft-solana-composer-library/tasks/common/types.ts create mode 100644 examples/oft-solana-composer-library/tasks/common/utils.ts create mode 100644 examples/oft-solana-composer-library/tasks/common/wire.ts create mode 100644 examples/oft-solana-composer-library/tasks/evm/send.ts create mode 100644 examples/oft-solana-composer-library/tasks/index.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/base58.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/createOFT.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/createOFTAdapter.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/debug.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/getPrioFees.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/getRateLimits.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/index.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/initConfig.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/multisig.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/retryPayload.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/sendOFT.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/setAuthority.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/setInboundRateLimit.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/setOutboundRateLimit.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/setUpdateAuthority.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/updateMetadata.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/utils.ts create mode 100644 examples/oft-solana-composer-library/tasks/utils/getFee.ts create mode 100644 examples/oft-solana-composer-library/test/foundry/MyOFT.t.sol create mode 100644 examples/oft-solana-composer-library/test/hardhat/MyOFT.test.ts create mode 100644 examples/oft-solana-composer-library/test/mocks/ERC20Mock.sol create mode 100644 examples/oft-solana-composer-library/test/mocks/OFTComposerMock.sol create mode 100644 examples/oft-solana-composer-library/test/mocks/OFTMock.sol create mode 100644 examples/oft-solana-composer-library/tsconfig.json create mode 100644 examples/oft-solana-composer-library/turbo.json diff --git a/examples/oft-solana-composer-library/.env.example b/examples/oft-solana-composer-library/.env.example new file mode 100644 index 000000000..a2ee9642c --- /dev/null +++ b/examples/oft-solana-composer-library/.env.example @@ -0,0 +1,22 @@ +# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.- +# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ +# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' +# +# 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 +# +# By default, the Solana example will use the default cluster RPC URL if no other value is provided +# For SOLANA_PRIVATE_KEY use base58 encoding +MNEMONIC= +PRIVATE_KEY= # Private key for EVM contract owner/delegate +SOLANA_PRIVATE_KEY= +SOLANA_KEYPAIR_PATH= +RPC_URL_SOLANA= +RPC_URL_SOLANA_TESTNET= \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.eslintignore b/examples/oft-solana-composer-library/.eslintignore new file mode 100644 index 000000000..50166580a --- /dev/null +++ b/examples/oft-solana-composer-library/.eslintignore @@ -0,0 +1,13 @@ +.anchor +.turbo +node_modules +target +artifacts +cache +dist +out +*.log +*.sol +*.yaml +*.lock +package-lock.json \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.eslintrc.js b/examples/oft-solana-composer-library/.eslintrc.js new file mode 100644 index 000000000..bd7363593 --- /dev/null +++ b/examples/oft-solana-composer-library/.eslintrc.js @@ -0,0 +1,12 @@ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + root: true, + 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', + 'import/no-unresolved': 'warn', + }, +}; diff --git a/examples/oft-solana-composer-library/.gitignore b/examples/oft-solana-composer-library/.gitignore new file mode 100644 index 000000000..0f7da464c --- /dev/null +++ b/examples/oft-solana-composer-library/.gitignore @@ -0,0 +1,26 @@ +.anchor +node_modules +.env +coverage +coverage.json +target +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 diff --git a/examples/oft-solana-composer-library/.nvmrc b/examples/oft-solana-composer-library/.nvmrc new file mode 100644 index 000000000..3462e8c26 --- /dev/null +++ b/examples/oft-solana-composer-library/.nvmrc @@ -0,0 +1 @@ +v18.19.0 \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.prettierignore b/examples/oft-solana-composer-library/.prettierignore new file mode 100644 index 000000000..fbb1e9c85 --- /dev/null +++ b/examples/oft-solana-composer-library/.prettierignore @@ -0,0 +1,13 @@ +.anchor +.turbo +node_modules/ +target +artifacts/ +cache/ +dist/ +out/ +*.log +*ignore +*.yaml +*.lock +package-lock.json \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.prettierrc.js b/examples/oft-solana-composer-library/.prettierrc.js new file mode 100644 index 000000000..6f55b4019 --- /dev/null +++ b/examples/oft-solana-composer-library/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('@layerzerolabs/prettier-config-next'), +}; diff --git a/examples/oft-solana-composer-library/.solhintrc.js b/examples/oft-solana-composer-library/.solhintrc.js new file mode 100644 index 000000000..102eae347 --- /dev/null +++ b/examples/oft-solana-composer-library/.solhintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['solhint:recommended', require.resolve('@layerzerolabs/solhint-config')], +}; diff --git a/examples/oft-solana-composer-library/Anchor.toml b/examples/oft-solana-composer-library/Anchor.toml new file mode 100644 index 000000000..8bf60abcb --- /dev/null +++ b/examples/oft-solana-composer-library/Anchor.toml @@ -0,0 +1,19 @@ +[toolchain] +anchor_version = "0.29.0" + +[features] +seeds = false +skip-lint = false + +[programs.localnet] +oft = "G2BYTnfGCMQAErMZkTBCFSapKevzf6QCjizjXi8hFEtJ" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "Localnet" +wallet = "./junk-id.json" + +[scripts] +test = "npx jest test/anchor" diff --git a/examples/oft-solana-composer-library/CHANGELOG.md b/examples/oft-solana-composer-library/CHANGELOG.md new file mode 100644 index 000000000..c0fbbffb7 --- /dev/null +++ b/examples/oft-solana-composer-library/CHANGELOG.md @@ -0,0 +1,215 @@ +# @layerzerolabs/oft-solana-example + +## 0.7.14 + +### Patch Changes + +- eba3669: Rename clear script to retry-payload + +## 0.7.13 + +### Patch Changes + +- 4ff1db8: feat(oft-solana): remove need for manual solana endpoint ID input +- 28eb8be: feat(oft-solana): support loading keypair via path +- 0385135: fix import in setInboundRateLimit + +## 0.7.12 + +### Patch Changes + +- 292803d: introduce fix suggestions, starting with when Solana init-config is skipped + +## 0.7.11 + +### Patch Changes + +- 8817095: Add the ability to filter out connections from specified EndpointIds + +## 0.7.10 + +### Patch Changes + +- 8b6c422: Bump monorepo dependencies to latest patch version + +## 0.7.9 + +### Patch Changes + +- a843edf: Debugging Hardhat tasks + +## 0.7.8 + +### Patch Changes + +- ce3a36b: introduce getSolanaDeploymentFunction and simplify solana task params + +## 0.7.7 + +### Patch Changes + +- e4f8538: feat(oft-solana): remove need to pass in solana secret key flag + +## 0.7.6 + +### Patch Changes + +- e256387: Updating packages + +## 0.7.5 + +### Patch Changes + +- fe71c0e: fix typo and mention how to transfer ownership + +## 0.7.4 + +### Patch Changes + +- 6bbe466: move solana init to own script and update task name + +## 0.7.3 + +### Patch Changes + +- e5fffc3: fix import in createOFT.ts +- fcf924d: docs: default to using solana 1.18 + +## 0.7.2 + +### Patch Changes + +- 274b8aa: Add task to get Solana rate limits + +## 0.7.1 + +### Patch Changes + +- 213a76b: Enable optimizer explicitly + +## 0.7.0 + +### Minor Changes + +- 57a80a8: added script to update metaplex metadata + +## 0.6.0 + +### Minor Changes + +- 1ce802a: oft-solana - throw if trying to send more than owned + +## 0.5.1 + +### Patch Changes + +- 12eaa61: oft-solana(getSimulationComputeUnits): increase backoff max delay from 3s to 10s + +## 0.5.0 + +### Minor Changes + +- 4af9800: fallback for getSimulationComputeUnits + +## 0.4.9 + +### Patch Changes + +- a2ecefd: fix missing return statement when user chooses no to 'continue with onlyOftStore' + +## 0.4.8 + +### Patch Changes + +- af91805: Bump to lz-definitions 3.0.59+ + +## 0.4.7 + +### Patch Changes + +- ce03876: Get latest EndpointIds by bumping lz-definitions + +## 0.4.6 + +### Patch Changes + +- d1d51ef: Bump ua-devtools-evm-hardhat dependency to 6.0.6+ + +## 0.4.5 + +### Patch Changes + +- 1d2abff: new SDK methods, tests in devtools-ton, upgraded lz-definitions + +## 0.4.4 + +### Patch Changes + +- 1bb0524: Upgraded dependency (@layerzerolabs/lz-definitions 3.0.12->3.0.21) + +## 0.4.3 + +### Patch Changes + +- 447af65: Use concurrently for parallel compilation task + +## 0.4.2 + +### Patch Changes + +- 59cd485: Fix add additional minters to createOFT + +## 0.4.1 + +### Patch Changes + +- 63238e9: Add ability to swap out the mint authority with a new SPL multisig + +## 0.4.0 + +### Minor Changes + +- e2395b5: Add OApp Read Example + +## 0.3.1 + +### Patch Changes + +- 2540bb1: solana support for lz:oapp:config:get + +## 0.3.0 + +### Minor Changes + +- aa37daf: Update layerzerolabs packages to 3.0.12 + +## 0.2.1 + +### Patch Changes + +- ff5972b: Fix createOFT should allow 0 amount + +## 0.2.0 + +### Minor Changes + +- ccba37b: Bump for solana oftv2 + +### Patch Changes + +- 019cd52: Bump for new Solana Implementation Version +- 4ca5233: Enable squadsv4 CLI support + +## 0.1.5 + +### Patch Changes + +- f34f4fe: Add support for Token2022 and housecleaning +- 33ff07d: Fix refund address for EVM send script +- e59f693: Resolves Issue 926, allowing createOFT to have amount=0 + +## 0.1.4 + +### Patch Changes + +- 6a07bb7: New solana OFT reference diff --git a/examples/oft-solana-composer-library/Cargo.lock b/examples/oft-solana-composer-library/Cargo.lock new file mode 100644 index 000000000..b2eda3001 --- /dev/null +++ b/examples/oft-solana-composer-library/Cargo.lock @@ -0,0 +1,2756 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anchor-attribute-access-control" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" +dependencies = [ + "anchor-syn", + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +dependencies = [ + "anchor-syn", + "borsh-derive-internal 0.10.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-space" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-program", + "anchor-derive-accounts", + "anchor-derive-serde", + "anchor-derive-space", + "anchor-syn", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.10.4", + "bytemuck", + "getrandom 0.2.15", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-spl" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a" +dependencies = [ + "anchor-lang", + "mpl-token-metadata", + "solana-program", + "spl-associated-token-account", + "spl-token", + "spl-token-2022", +] + +[[package]] +name = "anchor-syn" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" +dependencies = [ + "anyhow", + "bs58 0.5.1", + "heck", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake3" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" +dependencies = [ + "borsh-derive 0.10.4", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831213f80d9423998dd696e2c5345aba6be7a0bd8cd19e31c5243e13df1cef89" +dependencies = [ + "borsh-derive-internal 0.10.4", + "borsh-schema-derive-internal 0.10.4", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65d6ba50644c98714aa2a70d13d7df3cd75cd2b523a2b452bf010443800976b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276691d96f063427be83e6692b86148e488ebba9f48f77788724ca027ba3b6d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "composer" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "oapp", + "raydium-clmm-cpi", + "solana-helper", + "utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpi-helper" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.100", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.8", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "endpoint" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", + "cpi-helper", + "messagelib-interface", + "solana-helper", + "solana-program", + "utils", +] + +[[package]] +name = "endpoint-mock" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "cpi-helper", + "solana-program", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.5", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.2", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint", + "thiserror", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "messagelib-interface" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", +] + +[[package]] +name = "mpl-token-metadata" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8ee05284d79b367ae8966d558e1a305a781fc80c9df51f37775169117ba64f" +dependencies = [ + "borsh 0.10.4", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", +] + +[[package]] +name = "num_enum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +dependencies = [ + "num_enum_derive 0.7.0", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "oapp" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", + "endpoint", +] + +[[package]] +name = "oft" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "oapp", + "solana-helper", + "utils", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.24", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "raydium-clmm-cpi" +version = "0.1.0" +source = "git+https://github.com/St0rmBr3w/raydium-cpi/?branch=anchor-0.29.0#3354f36cddfaedf38a2f313dcf0e5221c0cb8419" +dependencies = [ + "ahash 0.8.5", + "anchor-lang", + "anchor-spl", + "num_enum 0.7.0", + "solana-program", + "spl-memo", + "spl-token", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "solana-frozen-abi" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96734b05823c8b515f8e3cc02641a27aee2c9760b1a43c74cb20f2a1ab0ab76c" +dependencies = [ + "ahash 0.8.5", + "blake3", + "block-buffer 0.10.4", + "bs58 0.4.0", + "bv", + "byteorder", + "cc", + "either", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "solana-frozen-abi-macro", + "subtle", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a0f1291a464fd046135d019d57a81be165ee3d23aa7df880b47dac683a0582a" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.100", +] + +[[package]] +name = "solana-helper" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c6deff8c48efb84b5828db064ad9873ef3445f129f888b4b6a664bd5220e35" +dependencies = [ + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "solana-logger" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5977c8f24b83cf50e7139ffdb25d70bad6a177f18ccc79ca2293d6a987fa81c" +dependencies = [ + "env_logger", + "lazy_static", + "log", +] + +[[package]] +name = "solana-program" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6412447793f8a3ef7526655906728325093b472e481791ac5c584e8d272166dc" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", + "bincode", + "bitflags", + "blake3", + "borsh 0.10.4", + "borsh 0.9.3", + "bs58 0.4.0", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.15", + "itertools", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1", + "light-poseidon", + "log", + "memoffset", + "num-bigint", + "num-derive 0.3.3", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3 0.10.8", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-sdk" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de1ce8848de4198f9bc7e4574252be02b1ed86ecbc2fff506780d5f8d6e4c4a8" +dependencies = [ + "assert_matches", + "base64 0.21.7", + "bincode", + "bitflags", + "borsh 0.10.4", + "bs58 0.4.0", + "bytemuck", + "byteorder", + "chrono", + "derivation-path", + "digest 0.10.7", + "ed25519-dalek", + "ed25519-dalek-bip32", + "generic-array", + "hmac 0.12.1", + "itertools", + "js-sys", + "lazy_static", + "libsecp256k1", + "log", + "memmap2", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.6.1", + "pbkdf2 0.11.0", + "qstring", + "qualifier_attr", + "rand 0.7.3", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "serde_with", + "sha2 0.10.8", + "sha3 0.10.8", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-program", + "solana-sdk-macro", + "thiserror", + "uriparse", + "wasm-bindgen", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cc46bbda0a5472d8d0a4c846b22941436ac45c31456d3e885a387a5f264f7" +dependencies = [ + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597dddc8ab46852dea7fc3d22e031fa4ffdb1b2291ac24d960605424a510a5f5" +dependencies = [ + "aes-gcm-siv", + "base64 0.21.7", + "bincode", + "bytemuck", + "byteorder", + "curve25519-dalek", + "getrandom 0.1.16", + "itertools", + "lazy_static", + "merlin", + "num-derive 0.3.3", + "num-traits", + "rand 0.7.3", + "serde", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "spl-associated-token-account" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" +dependencies = [ + "assert_matches", + "borsh 0.10.4", + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-token", + "spl-token-2022", + "thiserror", +] + +[[package]] +name = "spl-discriminator" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" +dependencies = [ + "quote", + "spl-discriminator-syn", + "syn 2.0.100", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.100", + "thiserror", +] + +[[package]] +name = "spl-memo" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" +dependencies = [ + "solana-program", +] + +[[package]] +name = "spl-pod" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error", +] + +[[package]] +name = "spl-program-error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-program-error-derive", + "thiserror", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.100", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-token" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.6.1", + "solana-program", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.0", + "solana-program", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod", + "spl-token", + "spl-token-metadata-interface", + "spl-transfer-hook-interface", + "spl-type-length-value", + "thiserror", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" +dependencies = [ + "borsh 0.10.4", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution", + "spl-type-length-value", +] + +[[package]] +name = "spl-type-length-value" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + +[[package]] +name = "utils" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] diff --git a/examples/oft-solana-composer-library/Cargo.toml b/examples/oft-solana-composer-library/Cargo.toml new file mode 100644 index 000000000..ae8c8729c --- /dev/null +++ b/examples/oft-solana-composer-library/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +members = ["programs/*"] +resolver = "2" + +# [features] +# idl-build = ["anchor-lang/idl-build"] + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 + +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/examples/oft-solana-composer-library/README.md b/examples/oft-solana-composer-library/README.md new file mode 100644 index 000000000..e1ab56126 --- /dev/null +++ b/examples/oft-solana-composer-library/README.md @@ -0,0 +1,376 @@ +

+ + LayerZero + +

+ +

+ Homepage | Docs | Developers +

+ +

Omnichain Fungible Token (OFT) Solana Example

+ +## Requirements + +- Rust `v1.75.0` +- Anchor `v0.29` +- Solana CLI `v1.17.31` +- Docker +- Node.js + +## Setup + +We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice). + +[Docker](https://docs.docker.com/get-started/get-docker/) is required to build using anchor. We highly recommend that you use the most up-to-date Docker version to avoid any issues with anchor +builds. + +:warning: You need anchor version `0.29` and solana version `1.17.31` specifically to compile the build artifacts. Using higher Anchor and Solana versions can introduce unexpected issues during compilation. See the following issues in Anchor's repo: [1](https://github.com/coral-xyz/anchor/issues/3089), [2](https://github.com/coral-xyz/anchor/issues/2835). After compiling the correct build artifacts, you can change the Solana version to higher versions. + +### Install Rust + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +``` + +### Install Solana + +```bash +sh -c "$(curl -sSfL https://release.solana.com/v1.17.31/install)" +``` + +### Install Anchor + +Install and use the correct version + +```bash +cargo install --git https://github.com/coral-xyz/anchor --tag v0.29.0 anchor-cli --locked +``` + +### Get the code + +```bash +LZ_ENABLE_SOLANA_OFT_EXAMPLE=1 npx create-lz-oapp@latest +``` + +### Installing Dependencies + +```bash +pnpm install +``` + +### Running tests + +```bash +pnpm test +``` + +### Get Devnet SOL + +```bash +solana airdrop 5 -u devnet +``` + +We recommend that you request 5 devnet SOL, which should be sufficient for this walkthrough. For the example here, we will be using Solana Devnet. If you hit rate limits, you can also use the [official Solana faucet](https://faucet.solana.com/). + +### Prepare `.env` + +```bash +cp .env.example .env +``` + +#### Solana Keypair + +By default, the scripts will use the keypair at the default location `~/.config/solana/id.json`. If you want to use this keypair, there is no need to set any environment variable. There will, however, be a prompt when running certain commands to confirm that you want to use the default keypair. + +If you wish to use a different keypair, then you can set either of the following in the `.env`: + +1. `SOLANA_PRIVATE_KEY` - this can be either in base58 string format (i.e. when imported from a wallet) or the Uint8 Array in string format (all in one line, e.g. `[1,1,...1]`). + +2. `SOLANA_KEYPAIR_PATH` - the location to the keypair file that you want to use. + +#### Solana RPC + +Also set the `RPC_URL_SOLANA_TESTNET` value. Note that while the naming used here is `TESTNET`, it refers to the [Solana Devnet](https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts#solana-testnet). We use `TESTNET` to keep it consistent with the existing EVM testnets. + +## Deploy + +### Prepare the OFT Program ID + +Create `programId` keypair files by running: + +```bash +solana-keygen new -o target/deploy/endpoint-keypair.json --force +solana-keygen new -o target/deploy/oft-keypair.json --force + +anchor keys sync +``` + +:warning: `--force` flag overwrites the existing keys with the ones you generate. + +Run + +``` +anchor keys list +``` + +to view the generated programIds (public keys). The output should look something like this: + +``` +endpoint: +oft: +``` + +Copy the OFT's program ID, which you will use in the build step. + +### Building and Deploying the Solana OFT Program + +Ensure you have Docker running before running the build command. + +#### Build the Solana OFT program + +```bash +anchor build -v -e OFT_ID= +``` + +Where `` is replaced with your OFT Program ID copied from the previous step. + + + +#### Preview Rent Costs for the Solana OFT + +:information_source: The majority of the SOL required to deploy your program will be for [**rent**](https://solana.com/docs/core/fees#rent) (specifically, for the minimum balance of SOL required for [rent-exemption](https://solana.com/docs/core/fees#rent-exempt)), which is calculated based on the amount of bytes the program or account uses. Programs typically require more rent than PDAs as more bytes are required to store the program's executable code. + +In our case, the OFT Program's rent accounts for roughly 99% of the SOL needed during deployment, while the other accounts' rent, OFT Store, Mint, Mint Authority Multisig and Escrow make up for only a fraction of the SOL needed. + +You can preview how much SOL would be needed for the program account. Note that the total SOL required would to be slightly higher than just this, to account for the other accounts that need to be created. + +```bash +solana rent $(wc -c < target/verifiable/oft.so) +``` + +You should see an output such as + +```bash +Rent-exempt minimum: 3.87415872 SOL +``` + +:information_source: LayerZero's default deployment path for Solana OFTs require you to deploy your own OFT program as this means you own the Upgrade Authority and don't rely on LayerZero to manage that authority for you. Read [this](https://neodyme.io/en/blog/solana_upgrade_authority/) to understand more no why this is important. + +#### Deploy the Solana OFT + +While for building, we must use Solana `v1.17.31`, for deploying, we will be using `v1.18.26` as it provides an improved program deployment experience (i.e. ability to attach priority fees and also exact-sized on-chain program length which prevents needing to provide 2x the rent as in `v1.17.31`). + +##### Temporarily switch to Solana `v1.18.26` + +First, we switch to Solana `v1.18.26` (remember to switch back to `v1.17.31` later) + +```bash +sh -c "$(curl -sSfL https://release.solana.com/v1.18.26/install)" +``` + +##### (Recommended) Deploying with a priority fee + +This section applies if you are unable to land your deployment transaction due to network congestion. + +:information_source: [Priority Fees](https://solana.com/developers/guides/advanced/how-to-use-priority-fees) are Solana's mechanism to allow transactions to be prioritized during periods of network congestion. When the network is busy, transactions without priority fees might never be processed. It is then necessary to include priority fees, or wait until the network is less congested. Priority fees are calculated as follows: `priorityFee = compute budget * compute unit price`. We can make use of priority fees by attaching the `--with-compute-unit-price` flag to our `solana program deploy` command. Note that the flag takes in a value in micro lamports, where 1 micro lamport = 0.000001 lamport. + +You can run refer QuickNode's [Solana Priority Fee Tracker](https://www.quicknode.com/gas-tracker/solana) to know what value you'd need to pass into the `--with-compute-unit-price` flag. + +##### Run the deploy command + +```bash +solana program deploy --program-id target/deploy/oft-keypair.json target/verifiable/oft.so -u devnet --with-compute-unit-price +``` + +:information_source: the `-u` flag specifies the RPC URL that should be used. The options are `mainnet-beta, devnet, testnet, localhost`, which also have their respective shorthands: `-um, -ud, -ut, -ul` + +:warning: If the deployment is slow, it could be that the network is congested and you might need to increase the priority fee. + +##### Switch back to Solana `1.17.31` + +:warning: After deploying, make sure to switch back to v1.17.31 after deploying. If you need to rebuild artifacts, you must use Solana CLI version `1.17.31` and Anchor version `0.29.0` + +```bash +sh -c "$(curl -sSfL https://release.solana.com/v1.17.31/install)" +``` + +### Create the Solana OFT + +:information_source: For **OFT** and **OFT Mint-and-Burn Adapter**, the SPL token's Mint Authority is set to the **Mint Authority Multisig**, which always has the **OFT Store** as a signer. The multisig is fixed to needing 1 of N signatures. + +:information_source: For **OFT** and **OFT Mint-And-Burn Adapter**, you have the option to specify additional signers through the `--additional-minters` flag. If you choose not to, you must pass in `--only-oft-store true`, which means only the **OFT Store** will be a signer for the \_Mint Authority Multisig\*. + +:warning: If you choose to go with `--only-oft-store`, you will not be able to add in other signers/minters or update the Mint Authority, and the Freeze Authority will be immediately renounced. The token Mint Authority will be fixed Mint Authority Multisig address while the Freeze Authority will be set to None. + +#### For OFT: + +```bash +pnpm hardhat lz:oft:solana:create --eid 40168 --program-id +``` + +:warning: Use `--additional-minters` flag to add a CSV of additional minter addresses to the Mint Authority Multisig. If you do not want to, you must specify `--only-oft-store true`. + +:information_source: You can also specify `--amount ` to have the OFT minted to your deployer address upon token creation. + +#### For OFTAdapter: + +```bash +pnpm hardhat lz:oft-adapter:solana:create --eid 40168 --program-id --mint --token-program +``` + +:information_source: You can use OFT Adapter if you want to use an existing token on Solana. For OFT Adapter, tokens will be locked when sending to other chains and unlocked when receiving from other chains. + +#### For OFT Mint-And-Burn Adapter (MABA): + +```bash +pnpm hardhat lz:oft:solana:create --eid 40168 --program-id --mint --token-program +``` + +:information_source: You can use OFT Mint-And-Burn Adapter if you want to use an existing token on Solana. For OFT Mint-And-Burn Adapter, tokens will be burned when sending to other chains and minted when receiving from other chains. + +:warning: You cannot use this option if your token's Mint Authority has been renounced. + +:warning: Note that for MABA mode, before attempting any cross-chain transfers, **you must transfer the Mint Authority** for `lz_receive` to work, as that is not handled in the script (since you are using an existing token). If you opted for `--additional-minters`, then you must transfer the Mint Authority to the newly created multisig (this is the `mintAuthority` value in the `/deployments/solana-/OFT.json`). If not, then it should be set to the OFT Store address, which is `oftStore` in the same file. + +### Note on the LZ Config file, [layerzero.config.ts](./layerzero.config.ts) + +In [layerzero.config.ts](./layerzero.config.ts), the `solanaContract.address` is auto-populated with the `oftStore` address from the deployment file, which has the default path of `deployments/solana-`. + +```typescript +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET, + address: getOftStoreAddress(EndpointId.SOLANA_V2_TESTNET), +}; +``` + +:warning: Ensure that you `address` is specified only for the solana contract object. Do not specify addresses for the EVM chain contract objects. Under the hood, we use `hardhat-deploy` to retrieve the contract addresses of the deployed EVM chain contracts. You will run into an error if you specify `address` for an EVM chain contract object. + +### Deploy a sepolia OFT peer + +```bash +pnpm hardhat lz:deploy # follow the prompts +``` + +Note: If you are on testnet, consider using `MyOFTMock` to allow test token minting. If you do use `MyOFTMock`, make sure to update the `sepoliaContract.contractName` in [layerzero.config.ts](./layerzero.config.ts) to `MyOFTMock`. + +#### Initialize the OFT Program's SendConfig and ReceiveConfig Accounts + +:warning: Do this only when initializing the OFT for the first time. The only exception is if a new pathway is added later. If so, run this again to properly initialize the pathway. + +Run the following command to init the pathway config. This step is unique to pathways that involve Solana. + +```bash +npx hardhat lz:oft:solana:init-config --oapp-config layerzero.config.ts +``` + +### Wire + +Run the following to wire the pathways specified in your `layerzero.config.ts` + +```bash +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +With a squads multisig, you can simply append the `--multisig-key` flag to the end of the above command. + +### Mint OFT on Solana + +This is only relevant for **OFT**. If you opted to include the `--amount` flag in the create step, that means you already have minted some Solana OFT and you can skip this section. + +:information_source: This is only possible if you specified your deployer address as part of the `--additional-minters` flag when creating the Solana OFT. If you had chosen `--only-oft-store true`, you will not be able to mint your OFT on Solana. + +First, you need to create the Associated Token Account for your address. + +```bash +spl-token create-account +``` + +Then, you can mint. Remember this is in local decimals, so with local decimals of 9, you would need to pass in `--amount 1000000000` to mint 1 OFT. + +```bash +spl-token mint --multisig-signer ~/.config/solana/id.json --owner +``` + +:information_source: `~/.config/solana/id.json` assumes that you will use the keypair in the default location. To verify if this path applies to you, run `solana config get` and not the keypair path value. + +:information_source: You can get the `` address from [deployments/solana-testnet/OFT.json](deployments/solana-testnet/OFT.json). + +### Set Message Execution Options + +Refer to [Generating Execution Options](https://docs.layerzero.network/v2/developers/solana/gas-settings/options#generating-options) to learn how to build the options param for send transactions. + +Note that you will need to either enable `enforcedOptions` in [./layerzero.config.ts](./layerzero.config.ts) or pass in a value for `_options` when calling `send()`. Having neither will cause a revert when calling send(). + +For this example, we have already included `enforcedOptions` by default in the `layerzero.config.ts`, which will take effect in the wiring step. + +#### (Optional) If specifying the `_options` value when calling `send()` + +It's only necessary to specify `_options` if you do not have `enforcedOptions`. + +For Sepolia -> Solana, you should pass in the options value into the script at [tasks/evm/send.ts](./tasks/evm/send.ts) as the value for `sendParam.extraOptions`. + +For Solana -> Sepolia, you should pass in the options value into the script at [tasks/solana/sendOFT.ts](./tasks/solana/sendOFT.ts) as the value for `options` for both in `quote` and `send`. + +### Send + +#### Send SOL -> Sepolia + +```bash +npx hardhat lz:oft:solana:send --amount --from-eid 40168 --to --to-eid 40161 +``` + +#### Send Sepolia -> SOL + +```bash +npx hardhat --network sepolia-testnet send --dst-eid 40168 --amount --to +``` + +:information_source: If you encounter an error such as `No Contract deployed with name`, ensure that the `tokenName` in the task defined in `tasks/evm/send.ts` matches the deployed contract name. + +### Set a new Mint Authority Multisig + +If you are not happy with the deployer being a mint authority, you can create and set a new mint authority by running: + +```bash +pnpm hardhat lz:oft:solana:setauthority --eid --mint --program-id --escrow --additional-minters +``` + +The `OFTStore` is automatically added as a mint authority to the newly created mint authority, and does not need to be +included in the `--additional-minters` list. + +## Appendix + +### Solana Program Verification + +Refer to [Verify the OFT Program](https://docs.layerzero.network/v2/developers/solana/oft/program#optional-verify-the-oft-program). + +### Transferring ownership + +Ownership of OFTs can be transferred via running the wire command after the appropriate changes are made to the LZ Config file (`layerzero.config.ts`). You need to first set the `delegate` value, and then only the `owner` value. + +**First, set the `delegate`.** + +How to set delegate: https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/configuring-pathways#adding-delegate + +Now run + +``` +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +and execute the transactions. + +**Then, set the `owner`.** + +How to set owner: https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/configuring-pathways#adding-owner + +Now, run + +``` +npx hardhat lz:ownable:transfer-ownership --oapp-config layerzero.config.ts +``` + +### Troubleshooting + +Refer to the [Solana Troubleshooting page on the LayerZero Docs](https://docs.layerzero.network/v2/developers/solana/troubleshooting/common-errors) to see how to solve common error when deploying Solana OFTs. diff --git a/examples/oft-solana-composer-library/contracts/MyOFT.sol b/examples/oft-solana-composer-library/contracts/MyOFT.sol new file mode 100644 index 000000000..f8bc7b47f --- /dev/null +++ b/examples/oft-solana-composer-library/contracts/MyOFT.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; + +contract MyOFT is OFT { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} +} diff --git a/examples/oft-solana-composer-library/contracts/mocks/MyOFTMock.sol b/examples/oft-solana-composer-library/contracts/mocks/MyOFTMock.sol new file mode 100644 index 000000000..3ebb888d4 --- /dev/null +++ b/examples/oft-solana-composer-library/contracts/mocks/MyOFTMock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { MyOFT } from "../MyOFT.sol"; + +// @dev WARNING: This is for testing purposes only +contract MyOFTMock is MyOFT { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) MyOFT(_name, _symbol, _lzEndpoint, _delegate) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} diff --git a/examples/oft-solana-composer-library/deploy/MyOFT.ts b/examples/oft-solana-composer-library/deploy/MyOFT.ts new file mode 100644 index 000000000..bcbdeb19b --- /dev/null +++ b/examples/oft-solana-composer-library/deploy/MyOFT.ts @@ -0,0 +1,53 @@ +import assert from 'assert' + +import { type DeployFunction } from 'hardhat-deploy/types' + +const contractName = 'MyOFT' + +const deploy: DeployFunction = async (hre) => { + const { getNamedAccounts, deployments } = hre + + const { deploy } = deployments + const { deployer } = await getNamedAccounts() + + assert(deployer, 'Missing named deployer account') + + console.log(`Network: ${hre.network.name}`) + console.log(`Deployer: ${deployer}`) + + // This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2 + // + // @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments + // from @layerzerolabs packages based on the configuration in your hardhat config + // + // For this to work correctly, your network config must define an eid property + // set to `EndpointId` as defined in @layerzerolabs/lz-definitions + // + // For example: + // + // networks: { + // fuji: { + // ... + // eid: EndpointId.AVALANCHE_V2_TESTNET + // } + // } + const endpointV2Deployment = await hre.deployments.get('EndpointV2') + + const { address } = await deploy(contractName, { + from: deployer, + args: [ + 'MyOFT', // name + 'MOFT', // symbol + endpointV2Deployment.address, // LayerZero's EndpointV2 address + deployer, // owner + ], + log: true, + skipIfAlreadyDeployed: false, + }) + + console.log(`Deployed contract: ${contractName}, network: ${hre.network.name}, address: ${address}`) +} + +deploy.tags = [contractName] + +export default deploy diff --git a/examples/oft-solana-composer-library/foundry.toml b/examples/oft-solana-composer-library/foundry.toml new file mode 100644 index 000000000..9819f073e --- /dev/null +++ b/examples/oft-solana-composer-library/foundry.toml @@ -0,0 +1,30 @@ +[profile.default] +solc-version = '0.8.22' +src = 'contracts' +out = 'out' +test = 'test/foundry' +cache_path = 'cache/foundry' +optimizer = true +optimizer_runs = 20_000 + +libs = [ + # We provide a set of useful contract utilities + # in the lib directory of @layerzerolabs/toolbox-foundry: + # + # - forge-std + # - ds-test + # - solidity-bytes-utils + 'node_modules/@layerzerolabs/toolbox-foundry/lib', + 'node_modules', +] + +remappings = [ + # Due to a misconfiguration of solidity-bytes-utils, an outdated version + # of forge-std is being dragged in + # + # To remedy this, we'll remap the ds-test and forge-std imports to our own versions + 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test', + 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std', + '@layerzerolabs/=node_modules/@layerzerolabs/', + '@openzeppelin/=node_modules/@openzeppelin/', +] diff --git a/examples/oft-solana-composer-library/hardhat.config.ts b/examples/oft-solana-composer-library/hardhat.config.ts new file mode 100644 index 000000000..e298b44c3 --- /dev/null +++ b/examples/oft-solana-composer-library/hardhat.config.ts @@ -0,0 +1,79 @@ +// Get the environment configuration from .env file +// +// To make use of automatic environment setup: +// - Duplicate .env.example file and name it .env +// - Fill in the environment variables +import 'dotenv/config' + +import 'hardhat-deploy' +import '@nomicfoundation/hardhat-ethers' +import '@nomiclabs/hardhat-waffle' +import 'hardhat-deploy-ethers' +import 'hardhat-contract-sizer' +import '@nomiclabs/hardhat-ethers' +import '@layerzerolabs/toolbox-hardhat' + +import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types' + +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import './tasks/index' + +// Set your preferred authentication method +// +// If you prefer using a mnemonic, set a MNEMONIC environment variable +// to a valid mnemonic +const MNEMONIC = process.env.MNEMONIC + +// If you prefer to be authenticated using a private key, set a PRIVATE_KEY environment variable +const PRIVATE_KEY = process.env.PRIVATE_KEY + +const accounts: HttpNetworkAccountsUserConfig | undefined = MNEMONIC + ? { mnemonic: MNEMONIC } + : PRIVATE_KEY + ? [PRIVATE_KEY] + : undefined + +if (accounts == null) { + console.warn( + 'Could not find MNEMONIC or PRIVATE_KEY environment variables. It will not be possible to execute transactions in your example.' + ) +} + +const config: HardhatUserConfig = { + paths: { + cache: 'cache/hardhat', + tests: 'test/hardhat', + }, + solidity: { + compilers: [ + { + version: '0.8.22', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], + }, + networks: { + 'sepolia-testnet': { + eid: EndpointId.SEPOLIA_V2_TESTNET, + url: process.env.RPC_URL_SEPOLIA || 'https://gateway.tenderly.co/public/sepolia', + accounts, + }, + hardhat: { + // Need this for testing because TestHelperOz5.sol is exceeding the compiled contract size limit + allowUnlimitedContractSize: true, + }, + }, + namedAccounts: { + deployer: { + default: 0, // wallet address of index[0], of the mnemonic in .env + }, + }, +} + +export default config diff --git a/examples/oft-solana-composer-library/jest.config.ts b/examples/oft-solana-composer-library/jest.config.ts new file mode 100644 index 000000000..a5634529d --- /dev/null +++ b/examples/oft-solana-composer-library/jest.config.ts @@ -0,0 +1,12 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + reporters: [['github-actions', { silent: false }], 'default'], + testEnvironment: 'node', + testTimeout: 15000, + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + transform: { + '^.+\\.(t|j)sx?$': '@swc/jest', + }, +} diff --git a/examples/oft-solana-composer-library/junk-id.json b/examples/oft-solana-composer-library/junk-id.json new file mode 100644 index 000000000..8ccf579ce --- /dev/null +++ b/examples/oft-solana-composer-library/junk-id.json @@ -0,0 +1,6 @@ +[ + 101, 96, 5, 237, 143, 245, 198, 118, 241, 242, 185, 196, 246, 72, 152, 231, + 30, 170, 168, 48, 19, 92, 179, 54, 175, 98, 167, 177, 62, 91, 162, 83, 255, + 175, 71, 42, 217, 187, 228, 197, 222, 137, 131, 197, 89, 69, 190, 209, 113, + 186, 78, 149, 158, 115, 255, 26, 162, 25, 122, 247, 1, 33, 92, 96 +] diff --git a/examples/oft-solana-composer-library/layerzero.config.ts b/examples/oft-solana-composer-library/layerzero.config.ts new file mode 100644 index 000000000..7e4b46ed8 --- /dev/null +++ b/examples/oft-solana-composer-library/layerzero.config.ts @@ -0,0 +1,57 @@ +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { ExecutorOptionType } from '@layerzerolabs/lz-v2-utilities' +import { generateConnectionsConfig } from '@layerzerolabs/metadata-tools' +import { OAppEnforcedOption, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' + +import { getOftStoreAddress } from './tasks/solana' + +// Note: Do not use address for EVM OmniPointHardhat contracts. Contracts are loaded using hardhat-deploy. +// If you do use an address, ensure artifacts exists. +const sepoliaContract: OmniPointHardhat = { + eid: EndpointId.SEPOLIA_V2_TESTNET, + contractName: 'MyOFT', +} + +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET, + address: getOftStoreAddress(EndpointId.SOLANA_V2_TESTNET), +} + +const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 80000, + value: 0, + }, +] + +const SOLANA_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 200000, + value: 2500000, + }, +] + +// Learn about Message Execution Options: https://docs.layerzero.network/v2/developers/solana/oft/account#message-execution-options +// Learn more about the Simple Config Generator - https://docs.layerzero.network/v2/developers/evm/technical-reference/simple-config +export default async function () { + // note: pathways declared here are automatically bidirectional + // if you declare A,B there's no need to declare B,A + const connections = await generateConnectionsConfig([ + [ + sepoliaContract, // Chain A contract + solanaContract, // Chain B contract + [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] + [15, 32], // [A to B confirmations, B to A confirmations] + [SOLANA_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain B enforcedOptions, Chain A enforcedOptions + ], + ]) + + return { + contracts: [{ contract: sepoliaContract }, { contract: solanaContract }], + connections, + } +} diff --git a/examples/oft-solana-composer-library/package.json b/examples/oft-solana-composer-library/package.json new file mode 100644 index 000000000..5e0f95e0e --- /dev/null +++ b/examples/oft-solana-composer-library/package.json @@ -0,0 +1,117 @@ +{ + "name": "@layerzerolabs/oft-solana-composer-library-example", + "version": "0.0.1", + "private": true, + "scripts": { + "clean": "rm -rf target artifacts cache out .anchor", + "compile": "concurrently -c auto --names forge,hardhat,anchor '$npm_execpath run compile:forge' '$npm_execpath run compile:hardhat' '$npm_execpath run compile:anchor'", + "compile:anchor": "anchor build", + "compile:forge": "forge build", + "compile:hardhat": "hardhat compile", + "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", + "lint:sol": "solhint 'contracts/**/*.sol'", + "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", + "test:anchor": "anchor test", + "test:forge": "forge test", + "test:hardhat": "hardhat test" + }, + "resolutions": { + "@solana/web3.js": "^1.98.0", + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + }, + "devDependencies": { + "@coral-xyz/anchor": "^0.29.0", + "@ethersproject/bytes": "^5.7.0", + "@layerzerolabs/devtools": "~0.4.8", + "@layerzerolabs/devtools-evm": "~1.0.6", + "@layerzerolabs/devtools-evm-hardhat": "^2.0.9", + "@layerzerolabs/devtools-solana": "~1.0.8", + "@layerzerolabs/eslint-config-next": "~2.3.39", + "@layerzerolabs/io-devtools": "~0.1.16", + "@layerzerolabs/lz-definitions": "^3.0.75", + "@layerzerolabs/lz-evm-messagelib-v2": "^3.0.75", + "@layerzerolabs/lz-evm-protocol-v2": "^3.0.75", + "@layerzerolabs/lz-evm-v1-0.7": "^3.0.75", + "@layerzerolabs/lz-solana-sdk-v2": "3.0.0", + "@layerzerolabs/lz-v2-utilities": "^3.0.75", + "@layerzerolabs/metadata-tools": "^0.4.1", + "@layerzerolabs/oapp-evm": "^0.3.2", + "@layerzerolabs/oft-evm": "^3.1.3", + "@layerzerolabs/oft-v2-solana-sdk": "^3.0.59", + "@layerzerolabs/prettier-config-next": "^2.3.39", + "@layerzerolabs/protocol-devtools": "^1.1.6", + "@layerzerolabs/protocol-devtools-evm": "~3.0.7", + "@layerzerolabs/protocol-devtools-solana": "^4.0.9", + "@layerzerolabs/solhint-config": "^3.0.12", + "@layerzerolabs/test-devtools-evm-foundry": "~6.0.3", + "@layerzerolabs/test-devtools-evm-hardhat": "~0.5.2", + "@layerzerolabs/toolbox-foundry": "~0.1.12", + "@layerzerolabs/toolbox-hardhat": "~0.6.9", + "@layerzerolabs/ua-devtools": "~3.0.6", + "@layerzerolabs/ua-devtools-evm": "~5.0.7", + "@layerzerolabs/ua-devtools-evm-hardhat": "~6.0.11", + "@layerzerolabs/ua-devtools-solana": "~4.1.2", + "@metaplex-foundation/mpl-token-metadata": "^3.2.1", + "@metaplex-foundation/mpl-toolbox": "^0.9.4", + "@metaplex-foundation/umi": "^0.9.2", + "@metaplex-foundation/umi-bundle-defaults": "^0.9.2", + "@metaplex-foundation/umi-eddsa-web3js": "^0.9.2", + "@metaplex-foundation/umi-public-keys": "^0.8.9", + "@metaplex-foundation/umi-web3js-adapters": "^0.9.2", + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@nomiclabs/hardhat-ethers": "^2.2.3", + "@nomiclabs/hardhat-waffle": "^2.0.6", + "@openzeppelin/contracts": "^5.0.2", + "@openzeppelin/contracts-upgradeable": "^5.0.2", + "@rushstack/eslint-patch": "^1.7.0", + "@solana-developers/helpers": "~2.8.1", + "@solana/spl-token": "^0.4.8", + "@solana/web3.js": "~1.95.8", + "@sqds/sdk": "^2.0.4", + "@swc/core": "^1.4.0", + "@swc/jest": "^0.2.36", + "@types/chai": "^4.3.11", + "@types/jest": "^29.5.12", + "@types/mocha": "^10.0.6", + "@types/node": "~18.18.14", + "bs58": "^6.0.0", + "chai": "^4.4.1", + "concurrently": "~9.1.0", + "dotenv": "^16.4.5", + "eslint": "^8.55.0", + "eslint-plugin-jest-extended": "~2.0.0", + "ethereumjs-util": "^7.1.5", + "ethers": "^5.7.2", + "exponential-backoff": "~3.1.1", + "fp-ts": "^2.16.2", + "hardhat": "^2.22.10", + "hardhat-contract-sizer": "^2.10.0", + "hardhat-deploy": "^0.12.1", + "hardhat-deploy-ethers": "^0.4.2", + "jest": "^29.7.0", + "mocha": "^10.2.0", + "prettier": "^3.2.5", + "solhint": "^4.1.1", + "solidity-bytes-utils": "^0.8.2", + "ts-node": "^10.9.2", + "typescript": "^5.4.4" + }, + "engines": { + "node": ">=18.16.0" + }, + "pnpm": { + "overrides": { + "@solana/web3.js": "^1.98.0", + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + } + }, + "overrides": { + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1", + "@solana/web3.js": "^1.98.0" + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/Cargo.toml b/examples/oft-solana-composer-library/programs/composer/Cargo.toml new file mode 100644 index 000000000..2dcbfbaf6 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "composer" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "composer" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build", "oapp/idl-build"] + +[dependencies] +anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } +anchor-spl = "0.29.0" +raydium-clmm-cpi = { git = "https://github.com/St0rmBr3w/raydium-cpi/", package = "raydium-clmm-cpi", branch = "anchor-0.29.0" } +oapp = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +utils = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +solana-helper = "0.1.0" \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/composer/Xargo.toml b/examples/oft-solana-composer-library/programs/composer/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/oft-solana-composer-library/programs/composer/build.rs b/examples/oft-solana-composer-library/programs/composer/build.rs new file mode 100644 index 000000000..8859933ee --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-env-changed=OFT_ID"); +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs new file mode 100644 index 000000000..dc94808b6 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -0,0 +1,164 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; + +// LayerZero / OApp endpoint CPI helpers +use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; +use oapp::endpoint_cpi::{clear_compose, get_accounts_for_clear_compose, LzAccount}; +use oapp::LzComposeParams; + +// Raydium CLMM CPI (concentrated liquidity swap) +// NOTE: ensure these imports match the raydium_clmm_cpi crate version you're using +use raydium_clmm_cpi::cpi::swap_v2 as swap; +use raydium_clmm_cpi::cpi::accounts::SwapSingleV2 as RaydiumSwapAccounts; + +declare_id!("3NJ7AUBaj9N8kBsRqA7SYWJ1poEUsEW36gCG1EfLDkW2"); + +#[program] +pub mod composer { + use super::*; + + /// Return all accounts needed by `lz_compose`. + pub fn lz_compose_types( + ctx: Context, + params: LzComposeParams, + ) -> Result> { + // 1) LayerZero endpoint accounts + let mut accounts = get_accounts_for_clear_compose( + ENDPOINT_ID, + ¶ms.from, // origin sender (32 bytes) + &ctx.accounts.lz_program.key(), + ¶ms.guid, // GUID of message + params.index, // index in the queue + ¶ms.message, // raw message bytes + ); + + // 2) Raydium CLMM CPI accounts (same order as in LzCompose below) + accounts.push(LzAccount { pubkey: ctx.accounts.payer.key(), is_signer: true, is_writable: false }); + accounts.push(LzAccount { pubkey: ctx.accounts.authority.key(), is_signer: false, is_writable: false }); + accounts.push(LzAccount { pubkey: ctx.accounts.amm_config.key(), is_signer: false, is_writable: false }); + accounts.push(LzAccount { pubkey: ctx.accounts.pool_state.key(), is_signer: false, is_writable: true }); + accounts.push(LzAccount { pubkey: ctx.accounts.source_token_account.key(), is_signer: false, is_writable: true }); + accounts.push(LzAccount { pubkey: ctx.accounts.dest_token_account.key(), is_signer: false, is_writable: true }); + accounts.push(LzAccount { pubkey: ctx.accounts.input_vault.key(), is_signer: false, is_writable: true }); + accounts.push(LzAccount { pubkey: ctx.accounts.output_vault.key(), is_signer: false, is_writable: true }); + accounts.push(LzAccount { pubkey: ctx.accounts.input_token_program.key(), is_signer: false, is_writable: false }); + accounts.push(LzAccount { pubkey: ctx.accounts.output_token_program.key(),is_signer: false, is_writable: false }); + accounts.push(LzAccount { pubkey: ctx.accounts.input_token_mint.key(), is_signer: false, is_writable: false }); + accounts.push(LzAccount { pubkey: ctx.accounts.output_token_mint.key(), is_signer: false, is_writable: false }); + accounts.push(LzAccount { pubkey: ctx.accounts.observation_state.key(), is_signer: false, is_writable: true }); + accounts.push(LzAccount { pubkey: ctx.accounts.token_program.key(), is_signer: false, is_writable: false }); + + Ok(accounts) + } + + /// Process the OFT message: swap then clear. + pub fn lz_compose( + ctx: Context, + params: LzComposeParams, + ) -> Result<()> { + // Parse OFT payload + let msg = ¶ms.message; + let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); + let mut compose_from = [0u8; 32]; + compose_from.copy_from_slice(&msg[40..72]); + let inner = &msg[72..]; + + // Decode swap params (first 8 bytes = min_amount_out) + require!(inner.len() >= 8, CustomError::InvalidPayload); + let min_amount_out = u64::from_be_bytes(inner[0..8].try_into().unwrap()); + + // Raydium CLMM CPI call + let signer_seeds: &[&[&[u8]]] = &[&[b"oft_seed"]]; + let cpi_accounts = RaydiumSwapAccounts { + payer: ctx.accounts.payer.to_account_info(), + authority: ctx.accounts.authority.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + input_token_account: ctx.accounts.source_token_account.to_account_info(), + output_token_account:ctx.accounts.dest_token_account.to_account_info(), + input_vault: ctx.accounts.input_vault.to_account_info(), + output_vault: ctx.accounts.output_vault.to_account_info(), + input_token_program: ctx.accounts.input_token_program.to_account_info(), + output_token_program:ctx.accounts.output_token_program.to_account_info(), + input_token_mint: ctx.accounts.input_token_mint.to_account_info(), + output_token_mint: ctx.accounts.output_token_mint.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + token_program_2022: ctx.accounts.token_program.to_account_info(), + memo_program: ctx.accounts.token_program.to_account_info(), + }; + let cpi_ctx = CpiContext::new_with_signer( + ctx.accounts.raydium_program.to_account_info(), + cpi_accounts, + signer_seeds, + ); + swap(cpi_ctx, amount_ld, min_amount_out, 0u128, true)?; + + // Clear LayerZero compose + let clear_params = ClearComposeParams { + from: compose_from.into(), + guid: params.guid, + index: params.index, + message: params.message.clone(), + }; + clear_compose( + ENDPOINT_ID, + ctx.accounts.lz_program.key(), + &ctx.remaining_accounts, + &[b"composer"], + clear_params, + ) + } +} + +#[derive(Accounts)] +pub struct LzComposeTypes<'info> { + #[account(seeds = [b"composer"], bump)] + pub lz_program: AccountInfo<'info>, + pub payer: Signer<'info>, + #[account(seeds = [b"authority"], bump)] pub authority: UncheckedAccount<'info>, + pub amm_config: AccountInfo<'info>, + #[account(mut)] pub pool_state: AccountInfo<'info>, + #[account(mut)] pub source_token_account:Account<'info, TokenAccount>, + #[account(mut)] pub dest_token_account: Account<'info, TokenAccount>, + #[account(mut)] pub input_vault: AccountInfo<'info>, + #[account(mut)] pub output_vault: AccountInfo<'info>, + pub input_token_program: AccountInfo<'info>, + pub output_token_program:AccountInfo<'info>, + #[account(mut)] pub input_token_mint: AccountInfo<'info>, + #[account(mut)] pub output_token_mint: AccountInfo<'info>, + #[account(mut)] pub observation_state: AccountInfo<'info>, + pub raydium_program: Program<'info, RaydiumCpmm>, + pub token_program: Program<'info, Token>, +} + +#[derive(Accounts)] +pub struct LzCompose<'info> { + #[account(seeds = [b"composer"], bump)] pub lz_program: AccountInfo<'info>, + pub payer: Signer<'info>, + #[account(seeds = [b"authority"], bump)] pub authority: UncheckedAccount<'info>, + pub amm_config: AccountInfo<'info>, + #[account(mut)] pub pool_state: AccountInfo<'info>, + #[account(mut)] pub source_token_account:Account<'info, TokenAccount>, + #[account(mut)] pub dest_token_account: Account<'info, TokenAccount>, + #[account(mut)] pub input_vault: AccountInfo<'info>, + #[account(mut)] pub output_vault: AccountInfo<'info>, + pub input_token_program: AccountInfo<'info>, + pub output_token_program:AccountInfo<'info>, + #[account(mut)] pub input_token_mint: AccountInfo<'info>, + #[account(mut)] pub output_token_mint: AccountInfo<'info>, + #[account(mut)] pub observation_state: AccountInfo<'info>, + pub raydium_program: Program<'info, RaydiumCpmm>, + pub token_program: Program<'info, Token>, +} + +#[error_code] +pub enum CustomError { + #[msg("compose_msg too short")] + InvalidPayload, +} + +#[derive(Clone)] +pub struct RaydiumCpmm; +impl anchor_lang::Id for RaydiumCpmm { + fn id() -> Pubkey { Pubkey::new_from_array([1u8; 32]) } +} \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/Cargo.toml b/examples/oft-solana-composer-library/programs/endpoint-mock/Cargo.toml new file mode 100644 index 000000000..756ebf747 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "endpoint-mock" +version = "0.1.0" +description = "Endpoint Mock" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "endpoint" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build"] + +[dependencies] +anchor-lang = { version = "0.29.0", features = ["event-cpi"] } +solana-program = "=1.17.31" +cpi-helper = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/Xargo.toml b/examples/oft-solana-composer-library/programs/endpoint-mock/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/mod.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/mod.rs new file mode 100644 index 000000000..7e79a19da --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/mod.rs @@ -0,0 +1,3 @@ +pub mod oapp; + +pub use oapp::*; diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/mod.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/mod.rs new file mode 100644 index 000000000..54d7fe213 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/mod.rs @@ -0,0 +1,3 @@ +pub mod register_oapp; + +pub use register_oapp::*; diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/register_oapp.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/register_oapp.rs new file mode 100644 index 000000000..5da8dfd3e --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/register_oapp.rs @@ -0,0 +1,35 @@ +use crate::*; + +use cpi_helper::CpiContext; + +#[event_cpi] +#[derive(CpiContext, Accounts)] +#[instruction(params: RegisterOAppParams)] +pub struct RegisterOApp<'info> { + #[account(mut)] + pub payer: Signer<'info>, + /// The PDA of the OApp + pub oapp: Signer<'info>, + #[account( + init, + payer = payer, + space = 8 + OAppRegistry::INIT_SPACE, + seeds = [OAPP_SEED, oapp.key.as_ref()], + bump + )] + pub oapp_registry: Account<'info, OAppRegistry>, + pub system_program: Program<'info, System>, +} + +impl RegisterOApp<'_> { + pub fn apply(ctx: &mut Context, params: &RegisterOAppParams) -> Result<()> { + ctx.accounts.oapp_registry.delegate = params.delegate; + ctx.accounts.oapp_registry.bump = ctx.bumps.oapp_registry; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct RegisterOAppParams { + pub delegate: Pubkey, +} diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs new file mode 100644 index 000000000..2afd6555e --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs @@ -0,0 +1,19 @@ +pub mod instructions; +pub mod state; + +use anchor_lang::prelude::*; +use instructions::*; +use state::*; + +declare_id!("6xmPjYnXyxz36xcKkv2zCrZc72LK5hQ9xzY3EjeZ59MV"); + +pub const OAPP_SEED: &[u8] = b"OApp"; + +#[program] +pub mod endpoint { + use super::*; + + pub fn register_oapp(mut ctx: Context, params: RegisterOAppParams) -> Result<()> { + RegisterOApp::apply(&mut ctx, ¶ms) + } +} diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/endpoint.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/endpoint.rs new file mode 100644 index 000000000..42ff5cbd6 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/endpoint.rs @@ -0,0 +1,8 @@ +use crate::*; + +#[account] +#[derive(InitSpace)] +pub struct OAppRegistry { + pub delegate: Pubkey, + pub bump: u8, +} diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/mod.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/mod.rs new file mode 100644 index 000000000..86edb11f4 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod endpoint; + +pub use endpoint::*; diff --git a/examples/oft-solana-composer-library/programs/oft/Cargo.toml b/examples/oft-solana-composer-library/programs/oft/Cargo.toml new file mode 100644 index 000000000..457f6ac54 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "oft" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "oft" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build", "oapp/idl-build"] + +[dependencies] +anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } +anchor-spl = "0.29.0" +oapp = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +utils = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +solana-helper = "0.1.0" \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/oft/Xargo.toml b/examples/oft-solana-composer-library/programs/oft/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/oft-solana-composer-library/programs/oft/build.rs b/examples/oft-solana-composer-library/programs/oft/build.rs new file mode 100644 index 000000000..8859933ee --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-env-changed=OFT_ID"); +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/compose_msg_codec.rs b/examples/oft-solana-composer-library/programs/oft/src/compose_msg_codec.rs new file mode 100644 index 000000000..7716b3732 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/compose_msg_codec.rs @@ -0,0 +1,51 @@ +const NONCE_OFFSET: usize = 0; +const SRC_EID_OFFSET: usize = 8; +const AMOUNT_LD_OFFSET: usize = 12; +const COMPOSE_FROM_OFFSET: usize = 20; +const COMPOSE_MSG_OFFSET: usize = 52; + +pub fn encode( + nonce: u64, + src_eid: u32, + amount_ld: u64, + compose_msg: &Vec, // [composeFrom][composeMsg] +) -> Vec { + let mut encoded = Vec::with_capacity(20 + compose_msg.len()); // 8 + 4 + 8 + encoded.extend_from_slice(&nonce.to_be_bytes()); + encoded.extend_from_slice(&src_eid.to_be_bytes()); + encoded.extend_from_slice(&amount_ld.to_be_bytes()); + encoded.extend_from_slice(&compose_msg); + encoded +} + +pub fn nonce(message: &[u8]) -> u64 { + let mut nonce_bytes = [0; 8]; + nonce_bytes.copy_from_slice(&message[NONCE_OFFSET..SRC_EID_OFFSET]); + u64::from_be_bytes(nonce_bytes) +} + +pub fn src_eid(message: &[u8]) -> u32 { + let mut src_eid_bytes = [0; 4]; + src_eid_bytes.copy_from_slice(&message[SRC_EID_OFFSET..AMOUNT_LD_OFFSET]); + u32::from_be_bytes(src_eid_bytes) +} + +pub fn amount_ld(message: &[u8]) -> u64 { + let mut amount_ld_bytes = [0; 8]; + amount_ld_bytes.copy_from_slice(&message[AMOUNT_LD_OFFSET..COMPOSE_FROM_OFFSET]); + u64::from_be_bytes(amount_ld_bytes) +} + +pub fn compose_from(message: &[u8]) -> [u8; 32] { + let mut compose_from = [0; 32]; + compose_from.copy_from_slice(&message[COMPOSE_FROM_OFFSET..COMPOSE_MSG_OFFSET]); + compose_from +} + +pub fn compose_msg(message: &[u8]) -> Vec { + if message.len() > COMPOSE_MSG_OFFSET { + message[COMPOSE_MSG_OFFSET..].to_vec() + } else { + Vec::new() + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/errors.rs b/examples/oft-solana-composer-library/programs/oft/src/errors.rs new file mode 100644 index 000000000..92d764507 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/errors.rs @@ -0,0 +1,14 @@ +use anchor_lang::prelude::error_code; + +#[error_code] +pub enum OFTError { + Unauthorized, + InvalidSender, + InvalidDecimals, + SlippageExceeded, + InvalidTokenDest, + RateLimitExceeded, + InvalidFee, + InvalidMintAuthority, + Paused, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/events.rs b/examples/oft-solana-composer-library/programs/oft/src/events.rs new file mode 100644 index 000000000..367df4572 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/events.rs @@ -0,0 +1,18 @@ +use crate::*; + +#[event] +pub struct OFTSent { + pub guid: [u8; 32], + pub dst_eid: u32, + pub from: Pubkey, + pub amount_sent_ld: u64, + pub amount_received_ld: u64, +} + +#[event] +pub struct OFTReceived { + pub guid: [u8; 32], + pub src_eid: u32, + pub to: Pubkey, + pub amount_received_ld: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/init_oft.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/init_oft.rs new file mode 100644 index 000000000..95ba9966f --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/init_oft.rs @@ -0,0 +1,86 @@ +use crate::*; +use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; +use oapp::endpoint::{instructions::RegisterOAppParams, ID as ENDPOINT_ID}; + +#[derive(Accounts)] +pub struct InitOFT<'info> { + #[account(mut)] + pub payer: Signer<'info>, + #[account( + init, + payer = payer, + space = 8 + OFTStore::INIT_SPACE, + seeds = [OFT_SEED, token_escrow.key().as_ref()], + bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + init, + payer = payer, + space = 8 + LzReceiveTypesAccounts::INIT_SPACE, + seeds = [LZ_RECEIVE_TYPES_SEED, oft_store.key().as_ref()], + bump + )] + pub lz_receive_types_accounts: Account<'info, LzReceiveTypesAccounts>, + #[account(mint::token_program = token_program)] + pub token_mint: InterfaceAccount<'info, Mint>, + #[account( + init, + payer = payer, + token::authority = oft_store, + token::mint = token_mint, + token::token_program = token_program, + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + pub token_program: Interface<'info, TokenInterface>, + pub system_program: Program<'info, System>, +} + +impl InitOFT<'_> { + pub fn apply(ctx: &mut Context, params: &InitOFTParams) -> Result<()> { + // Initialize the oft_store + ctx.accounts.oft_store.oft_type = params.oft_type.clone(); + require!( + ctx.accounts.token_mint.decimals >= params.shared_decimals, + OFTError::InvalidDecimals + ); + ctx.accounts.oft_store.ld2sd_rate = + 10u64.pow((ctx.accounts.token_mint.decimals - params.shared_decimals) as u32); + ctx.accounts.oft_store.token_mint = ctx.accounts.token_mint.key(); + ctx.accounts.oft_store.token_escrow = ctx.accounts.token_escrow.key(); + ctx.accounts.oft_store.endpoint_program = + if let Some(endpoint_program) = params.endpoint_program { + endpoint_program + } else { + ENDPOINT_ID + }; + ctx.accounts.oft_store.bump = ctx.bumps.oft_store; + ctx.accounts.oft_store.tvl_ld = 0; + ctx.accounts.oft_store.admin = params.admin; + ctx.accounts.oft_store.default_fee_bps = 0; + ctx.accounts.oft_store.paused = false; + ctx.accounts.oft_store.pauser = None; + ctx.accounts.oft_store.unpauser = None; + + // Initialize the lz_receive_types_accounts + ctx.accounts.lz_receive_types_accounts.oft_store = ctx.accounts.oft_store.key(); + ctx.accounts.lz_receive_types_accounts.token_mint = ctx.accounts.token_mint.key(); + + // Register the oapp + oapp::endpoint_cpi::register_oapp( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + ctx.remaining_accounts, + &[OFT_SEED, ctx.accounts.token_escrow.key().as_ref(), &[ctx.bumps.oft_store]], + RegisterOAppParams { delegate: params.admin }, + ) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct InitOFTParams { + pub oft_type: OFTType, + pub admin: Pubkey, + pub shared_decimals: u8, + pub endpoint_program: Option, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive.rs new file mode 100644 index 000000000..2059d7adf --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive.rs @@ -0,0 +1,183 @@ +use crate::*; +use anchor_lang::solana_program; +use anchor_spl::{ + associated_token::AssociatedToken, + token_2022::spl_token_2022::{self, solana_program::program_option::COption}, + token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked}, +}; +use oapp::endpoint::{ + cpi::accounts::Clear, + instructions::{ClearParams, SendComposeParams}, + ConstructCPIContext, +}; + +#[event_cpi] +#[derive(Accounts)] +#[instruction(params: LzReceiveParams)] +pub struct LzReceive<'info> { + #[account(mut)] + pub payer: Signer<'info>, + #[account( + mut, + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.src_eid.to_be_bytes() + ], + bump = peer.bump, + constraint = peer.peer_address == params.sender @OFTError::InvalidSender + )] + pub peer: Account<'info, PeerConfig>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + mut, + address = oft_store.token_escrow, + token::authority = oft_store, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + /// CHECK: the wallet address to receive the token + #[account(address = Pubkey::from(msg_codec::send_to(¶ms.message)) @OFTError::InvalidTokenDest)] + pub to_address: AccountInfo<'info>, + #[account( + init_if_needed, + payer = payer, + associated_token::mint = token_mint, + associated_token::authority = to_address, + associated_token::token_program = token_program + )] + pub token_dest: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + address = oft_store.token_mint, + mint::token_program = token_program + )] + pub token_mint: InterfaceAccount<'info, Mint>, + // Only used for native mint, the mint authority can be: + // 1. a spl-token multisig account with oft_store as one of the signers, and the quorum **MUST** be 1-of-n. (recommended) + // 2. or the mint_authority is oft_store itself. + #[account(constraint = token_mint.mint_authority == COption::Some(mint_authority.key()) @OFTError::InvalidMintAuthority)] + pub mint_authority: Option>, + pub token_program: Interface<'info, TokenInterface>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, +} + +impl LzReceive<'_> { + pub fn apply(ctx: &mut Context, params: &LzReceiveParams) -> Result<()> { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let oft_store_seed = ctx.accounts.token_escrow.key(); + let seeds: &[&[u8]] = &[OFT_SEED, oft_store_seed.as_ref(), &[ctx.accounts.oft_store.bump]]; + + // Validate and clear the payload + let accounts_for_clear = &ctx.remaining_accounts[0..Clear::MIN_ACCOUNTS_LEN]; + let _ = oapp::endpoint_cpi::clear( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + accounts_for_clear, + seeds, + ClearParams { + receiver: ctx.accounts.oft_store.key(), + src_eid: params.src_eid, + sender: params.sender, + nonce: params.nonce, + guid: params.guid, + message: params.message.clone(), + }, + )?; + + // Convert the amount from sd to ld + let amount_sd = msg_codec::amount_sd(¶ms.message); + let mut amount_received_ld = ctx.accounts.oft_store.sd2ld(amount_sd); + + // Consume the inbound rate limiter + if let Some(rate_limiter) = ctx.accounts.peer.inbound_rate_limiter.as_mut() { + rate_limiter.try_consume(amount_received_ld)?; + } + // Refill the outbound rate limiter + if let Some(rate_limiter) = ctx.accounts.peer.outbound_rate_limiter.as_mut() { + rate_limiter.refill(amount_received_ld)?; + } + + if ctx.accounts.oft_store.oft_type == OFTType::Adapter { + // unlock from escrow + ctx.accounts.oft_store.tvl_ld -= amount_received_ld; + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_escrow.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_dest.to_account_info(), + authority: ctx.accounts.oft_store.to_account_info(), + }, + ) + .with_signer(&[&seeds]), + amount_received_ld, + ctx.accounts.token_mint.decimals, + )?; + + // update the amount_received_ld with the post transfer fee amount + amount_received_ld = + get_post_fee_amount_ld(&ctx.accounts.token_mint, amount_received_ld)? + } else if let Some(mint_authority) = &ctx.accounts.mint_authority { + // Native type + // mint + let ix = spl_token_2022::instruction::mint_to( + ctx.accounts.token_program.key, + &ctx.accounts.token_mint.key(), + &ctx.accounts.token_dest.key(), + mint_authority.key, + &[&ctx.accounts.oft_store.key()], + amount_received_ld, + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.token_dest.to_account_info(), + ctx.accounts.token_mint.to_account_info(), + mint_authority.to_account_info(), + ctx.accounts.oft_store.to_account_info(), + ], + &[&seeds], + )?; + } else { + return Err(OFTError::InvalidMintAuthority.into()); + } + + if let Some(message) = msg_codec::compose_msg(¶ms.message) { + oapp::endpoint_cpi::send_compose( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + &ctx.remaining_accounts[Clear::MIN_ACCOUNTS_LEN..], + seeds, + SendComposeParams { + to: ctx.accounts.to_address.key(), + guid: params.guid, + index: 0, // only 1 compose msg per lzReceive + message: compose_msg_codec::encode( + params.nonce, + params.src_eid, + amount_received_ld, + &message, + ), + }, + )?; + } + + emit_cpi!(OFTReceived { + guid: params.guid, + src_eid: params.src_eid, + to: ctx.accounts.to_address.key(), + amount_received_ld, + }); + Ok(()) + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive_types.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive_types.rs new file mode 100644 index 000000000..a63ee6133 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive_types.rs @@ -0,0 +1,139 @@ +use crate::*; +use anchor_lang::solana_program; +use anchor_spl::{ + associated_token::{get_associated_token_address_with_program_id, ID as ASSOCIATED_TOKEN_ID}, + token_2022::spl_token_2022::solana_program::program_option::COption, + token_interface::Mint, +}; +use oapp::endpoint_cpi::LzAccount; + +#[derive(Accounts)] +pub struct LzReceiveTypes<'info> { + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account(address = oft_store.token_mint)] + pub token_mint: InterfaceAccount<'info, Mint>, +} + +// account structure +// account 0 - payer (executor) +// account 1 - peer +// account 2 - oft store +// account 3 - token escrow +// account 4 - to address / wallet address +// account 5 - token dest +// account 6 - token mint +// account 7 - mint authority (optional) +// account 8 - token program +// account 9 - associated token program +// account 10 - system program +// account 11 - event authority +// account 12 - this program +// account remaining accounts +// 0..9 - accounts for clear +// 9..16 - accounts for compose +impl LzReceiveTypes<'_> { + pub fn apply( + ctx: &Context, + params: &LzReceiveParams, + ) -> Result> { + let (peer, _) = Pubkey::find_program_address( + &[PEER_SEED, ctx.accounts.oft_store.key().as_ref(), ¶ms.src_eid.to_be_bytes()], + ctx.program_id, + ); + + // account 0..3 + let mut accounts = vec![ + LzAccount { pubkey: Pubkey::default(), is_signer: true, is_writable: true }, // 0 + LzAccount { pubkey: peer, is_signer: false, is_writable: true }, // 1 + LzAccount { pubkey: ctx.accounts.oft_store.key(), is_signer: false, is_writable: true }, // 2 + LzAccount { + pubkey: ctx.accounts.oft_store.token_escrow.key(), + is_signer: false, + is_writable: true, + }, // 3 + ]; + + // account 4..9 + let to_address = Pubkey::from(msg_codec::send_to(¶ms.message)); + let token_program = ctx.accounts.token_mint.to_account_info().owner; + let token_dest = get_associated_token_address_with_program_id( + &to_address, + &ctx.accounts.oft_store.token_mint, + token_program, + ); + let mint_authority = + if let COption::Some(mint_authority) = ctx.accounts.token_mint.mint_authority { + mint_authority + } else { + ctx.program_id.key() + }; + accounts.extend_from_slice(&[ + LzAccount { pubkey: to_address, is_signer: false, is_writable: false }, // 4 + LzAccount { pubkey: token_dest, is_signer: false, is_writable: true }, // 5 + LzAccount { + pubkey: ctx.accounts.token_mint.key(), + is_signer: false, + is_writable: true, + }, // 6 + LzAccount { pubkey: mint_authority, is_signer: false, is_writable: false }, // 7 + LzAccount { pubkey: *token_program, is_signer: false, is_writable: false }, // 8 + LzAccount { pubkey: ASSOCIATED_TOKEN_ID, is_signer: false, is_writable: false }, // 9 + ]); + + // account 10..12 + let (event_authority_account, _) = + Pubkey::find_program_address(&[oapp::endpoint_cpi::EVENT_SEED], &ctx.program_id); + accounts.extend_from_slice(&[ + LzAccount { + pubkey: solana_program::system_program::ID, + is_signer: false, + is_writable: false, + }, // 10 + LzAccount { pubkey: event_authority_account, is_signer: false, is_writable: false }, // 11 + LzAccount { pubkey: ctx.program_id.key(), is_signer: false, is_writable: false }, // 12 + ]); + + let endpoint_program = ctx.accounts.oft_store.endpoint_program; + // remaining accounts 0..9 + let accounts_for_clear = oapp::endpoint_cpi::get_accounts_for_clear( + endpoint_program, + &ctx.accounts.oft_store.key(), + params.src_eid, + ¶ms.sender, + params.nonce, + ); + accounts.extend(accounts_for_clear); + + // remaining accounts 9..16 + if let Some(message) = msg_codec::compose_msg(¶ms.message) { + let amount_sd = msg_codec::amount_sd(¶ms.message); + let amount_ld = ctx.accounts.oft_store.sd2ld(amount_sd); + let amount_received_ld = if ctx.accounts.oft_store.oft_type == OFTType::Native { + amount_ld + } else { + get_post_fee_amount_ld(&ctx.accounts.token_mint, amount_ld)? + }; + + let accounts_for_composing = oapp::endpoint_cpi::get_accounts_for_send_compose( + endpoint_program, + &ctx.accounts.oft_store.key(), + &to_address, + ¶ms.guid, + 0, + &compose_msg_codec::encode( + params.nonce, + params.src_eid, + amount_received_ld, + &message, + ), + ); + accounts.extend(accounts_for_composing); + } + + Ok(accounts) + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/mod.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/mod.rs new file mode 100644 index 000000000..7030177db --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/mod.rs @@ -0,0 +1,21 @@ +pub mod init_oft; +pub mod lz_receive; +pub mod lz_receive_types; +pub mod quote_oft; +pub mod quote_send; +pub mod send; +pub mod set_oft_config; +pub mod set_pause; +pub mod set_peer_config; +pub mod withdraw_fee; + +pub use init_oft::*; +pub use lz_receive::*; +pub use lz_receive_types::*; +pub use quote_oft::*; +pub use quote_send::*; +pub use send::*; +pub use set_oft_config::*; +pub use set_pause::*; +pub use set_peer_config::*; +pub use withdraw_fee::*; diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_oft.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_oft.rs new file mode 100644 index 000000000..aba5707a7 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_oft.rs @@ -0,0 +1,92 @@ +use crate::*; +use anchor_spl::token_interface::Mint; + +#[derive(Accounts)] +#[instruction(params: QuoteOFTParams)] +pub struct QuoteOFT<'info> { + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.dst_eid.to_be_bytes() + ], + bump = peer.bump + )] + pub peer: Account<'info, PeerConfig>, + #[account(address = oft_store.token_mint)] + pub token_mint: InterfaceAccount<'info, Mint>, +} + +impl QuoteOFT<'_> { + pub fn apply(ctx: &Context, params: &QuoteOFTParams) -> Result { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let (amount_sent_ld, amount_received_ld, oft_fee_ld) = compute_fee_and_adjust_amount( + params.amount_ld, + &ctx.accounts.oft_store, + &ctx.accounts.token_mint, + ctx.accounts.peer.fee_bps, + )?; + require!(amount_received_ld >= params.min_amount_ld, OFTError::SlippageExceeded); + + let oft_limits = OFTLimits { min_amount_ld: 0, max_amount_ld: 0xffffffffffffffff }; + let mut oft_fee_details = if amount_received_ld + oft_fee_ld < amount_sent_ld { + vec![OFTFeeDetail { + fee_amount_ld: amount_sent_ld - oft_fee_ld - amount_received_ld, + description: "Token2022 Transfer Fee".to_string(), + }] + } else { + vec![] + }; + // cross chain fee + if oft_fee_ld > 0 { + oft_fee_details.push(OFTFeeDetail { + fee_amount_ld: oft_fee_ld, + description: "Cross Chain Fee".to_string(), + }); + } + let oft_receipt = OFTReceipt { amount_sent_ld, amount_received_ld }; + Ok(QuoteOFTResult { oft_limits, oft_fee_details, oft_receipt }) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct QuoteOFTParams { + pub dst_eid: u32, + pub to: [u8; 32], + pub amount_ld: u64, + pub min_amount_ld: u64, + pub options: Vec, + pub compose_msg: Option>, + pub pay_in_lz_token: bool, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct QuoteOFTResult { + pub oft_limits: OFTLimits, + pub oft_fee_details: Vec, + pub oft_receipt: OFTReceipt, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct OFTFeeDetail { + pub fee_amount_ld: u64, + pub description: String, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct OFTReceipt { + pub amount_sent_ld: u64, + pub amount_received_ld: u64, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct OFTLimits { + pub min_amount_ld: u64, + pub max_amount_ld: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_send.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_send.rs new file mode 100644 index 000000000..efedefb9b --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_send.rs @@ -0,0 +1,193 @@ +use crate::*; +use oapp::endpoint::{instructions::QuoteParams, MessagingFee}; + +use anchor_spl::{ + token_2022::spl_token_2022::{ + extension::{ + transfer_fee::{TransferFee, TransferFeeConfig}, + BaseStateWithExtensions, StateWithExtensions, + }, + state::Mint as MintState, + }, + token_interface::Mint, +}; + +#[derive(Accounts)] +#[instruction(params: QuoteSendParams)] +pub struct QuoteSend<'info> { + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.dst_eid.to_be_bytes() + ], + bump = peer.bump + )] + pub peer: Account<'info, PeerConfig>, + #[account(address = oft_store.token_mint)] + pub token_mint: InterfaceAccount<'info, Mint>, +} + +impl QuoteSend<'_> { + pub fn apply(ctx: &Context, params: &QuoteSendParams) -> Result { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let (_, amount_received_ld, _) = compute_fee_and_adjust_amount( + params.amount_ld, + &ctx.accounts.oft_store, + &ctx.accounts.token_mint, + ctx.accounts.peer.fee_bps, + )?; + require!(amount_received_ld >= params.min_amount_ld, OFTError::SlippageExceeded); + + // calling endpoint cpi + oapp::endpoint_cpi::quote( + ctx.accounts.oft_store.endpoint_program, + ctx.remaining_accounts, + QuoteParams { + sender: ctx.accounts.oft_store.key(), + dst_eid: params.dst_eid, + receiver: ctx.accounts.peer.peer_address, + message: msg_codec::encode( + params.to, + amount_received_ld, + Pubkey::default(), + ¶ms.compose_msg, + ), + pay_in_lz_token: params.pay_in_lz_token, + options: ctx + .accounts + .peer + .enforced_options + .combine_options(¶ms.compose_msg, ¶ms.options)?, + }, + ) + } +} + +pub fn compute_fee_and_adjust_amount( + amount_ld: u64, + oft_store: &OFTStore, + token_mint: &InterfaceAccount, + fee_bps: Option, +) -> Result<(u64, u64, u64)> { + let (amount_sent_ld, amount_received_ld, oft_fee_ld) = if OFTType::Adapter == oft_store.oft_type + { + let mut amount_received_ld = + oft_store.remove_dust(get_post_fee_amount_ld(token_mint, amount_ld)?); + let amount_sent_ld = get_pre_fee_amount_ld(token_mint, amount_received_ld)?; + + // remove the oft fee from the amount_received_ld + let oft_fee_ld = oft_store.remove_dust(calculate_fee( + amount_received_ld, + oft_store.default_fee_bps, + fee_bps, + )); + amount_received_ld -= oft_fee_ld; + (amount_sent_ld, amount_received_ld, oft_fee_ld) + } else { + // if it is Native OFT, there is no transfer fee + let amount_sent_ld = oft_store.remove_dust(amount_ld); + let oft_fee_ld = oft_store.remove_dust(calculate_fee( + amount_sent_ld, + oft_store.default_fee_bps, + fee_bps, + )); + let amount_received_ld = amount_sent_ld - oft_fee_ld; + (amount_sent_ld, amount_received_ld, oft_fee_ld) + }; + Ok((amount_sent_ld, amount_received_ld, oft_fee_ld)) +} + +fn calculate_fee(pre_fee_amount: u64, default_fee_bps: u16, fee_bps: Option) -> u64 { + let final_fee_bps = if let Some(bps) = fee_bps { bps as u128 } else { default_fee_bps as u128 }; + if final_fee_bps == 0 || pre_fee_amount == 0 { + 0 + } else { + // pre_fee_amount * final_fee_bps / ONE_IN_BASIS_POINTS + let fee = (pre_fee_amount as u128) * final_fee_bps; + (fee / ONE_IN_BASIS_POINTS) as u64 + } +} + +pub fn get_post_fee_amount_ld(token_mint: &InterfaceAccount, amount_ld: u64) -> Result { + let token_mint_info = token_mint.to_account_info(); + let token_mint_data = token_mint_info.try_borrow_data()?; + let token_mint_ext = StateWithExtensions::::unpack(&token_mint_data)?; + let post_amount_ld = + if let Ok(transfer_fee_config) = token_mint_ext.get_extension::() { + transfer_fee_config + .get_epoch_fee(Clock::get()?.epoch) + .calculate_post_fee_amount(amount_ld) + .ok_or(ProgramError::InvalidArgument)? + } else { + amount_ld + }; + Ok(post_amount_ld) +} + +// Calculate the amount_sent_ld necessary to receive amount_received_ld +// Does *not* de-dust any inputs or outputs. +fn get_pre_fee_amount_ld(token_mint: &InterfaceAccount, amount_ld: u64) -> Result { + let token_mint_info = token_mint.to_account_info(); + let token_mint_data = token_mint_info.try_borrow_data()?; + let token_mint_ext = StateWithExtensions::::unpack(&token_mint_data)?; + let pre_amount_ld = + if let Ok(transfer_fee) = token_mint_ext.get_extension::() { + calculate_pre_fee_amount(transfer_fee.get_epoch_fee(Clock::get()?.epoch), amount_ld) + .ok_or(ProgramError::InvalidArgument)? + } else { + amount_ld + }; + Ok(pre_amount_ld) +} + +// DO NOT CHANGE THIS CODE!!! +// bug reported on token2022: https://github.com/solana-labs/solana-program-library/pull/6704/files +// copy code over as fix has not been published +pub const MAX_FEE_BASIS_POINTS: u16 = 10_000; +const ONE_IN_BASIS_POINTS: u128 = MAX_FEE_BASIS_POINTS as u128; +fn calculate_pre_fee_amount(fee: &TransferFee, post_fee_amount: u64) -> Option { + let maximum_fee = u64::from(fee.maximum_fee); + let transfer_fee_basis_points = u16::from(fee.transfer_fee_basis_points) as u128; + match (transfer_fee_basis_points, post_fee_amount) { + // no fee, same amount + (0, _) => Some(post_fee_amount), + // 0 zero out, 0 in + (_, 0) => Some(0), + // 100%, cap at max fee + (ONE_IN_BASIS_POINTS, _) => maximum_fee.checked_add(post_fee_amount), + _ => { + let numerator = (post_fee_amount as u128).checked_mul(ONE_IN_BASIS_POINTS)?; + let denominator = ONE_IN_BASIS_POINTS.checked_sub(transfer_fee_basis_points)?; + let raw_pre_fee_amount = ceil_div(numerator, denominator)?; + + if raw_pre_fee_amount.checked_sub(post_fee_amount as u128)? >= maximum_fee as u128 { + post_fee_amount.checked_add(maximum_fee) + } else { + // should return `None` if `pre_fee_amount` overflows + u64::try_from(raw_pre_fee_amount).ok() + } + }, + } +} + +fn ceil_div(numerator: u128, denominator: u128) -> Option { + numerator.checked_add(denominator)?.checked_sub(1)?.checked_div(denominator) +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct QuoteSendParams { + pub dst_eid: u32, + pub to: [u8; 32], + pub amount_ld: u64, + pub min_amount_ld: u64, + pub options: Vec, + pub compose_msg: Option>, + pub pay_in_lz_token: bool, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/send.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/send.rs new file mode 100644 index 000000000..4a839ca18 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/send.rs @@ -0,0 +1,175 @@ +use crate::*; +use anchor_spl::token_interface::{ + self, Burn, Mint, TokenAccount, TokenInterface, TransferChecked, +}; +use oapp::endpoint::{instructions::SendParams as EndpointSendParams, MessagingReceipt}; + +#[event_cpi] +#[derive(Accounts)] +#[instruction(params: SendParams)] +pub struct Send<'info> { + pub signer: Signer<'info>, + #[account( + mut, + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.dst_eid.to_be_bytes() + ], + bump = peer.bump + )] + pub peer: Account<'info, PeerConfig>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + mut, + token::authority = signer, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_source: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + address = oft_store.token_escrow, + token::authority = oft_store.key(), + token::mint = token_mint, + token::token_program = token_program + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + address = oft_store.token_mint, + mint::token_program = token_program + )] + pub token_mint: InterfaceAccount<'info, Mint>, + pub token_program: Interface<'info, TokenInterface>, +} + +impl Send<'_> { + pub fn apply( + ctx: &mut Context, + params: &SendParams, + ) -> Result<(MessagingReceipt, OFTReceipt)> { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let (amount_sent_ld, amount_received_ld, oft_fee_ld) = compute_fee_and_adjust_amount( + params.amount_ld, + &ctx.accounts.oft_store, + &ctx.accounts.token_mint, + ctx.accounts.peer.fee_bps, + )?; + require!(amount_received_ld >= params.min_amount_ld, OFTError::SlippageExceeded); + + if let Some(rate_limiter) = ctx.accounts.peer.outbound_rate_limiter.as_mut() { + rate_limiter.try_consume(amount_received_ld)?; + } + if let Some(rate_limiter) = ctx.accounts.peer.inbound_rate_limiter.as_mut() { + rate_limiter.refill(amount_received_ld)?; + } + + if ctx.accounts.oft_store.oft_type == OFTType::Adapter { + // transfer all tokens to escrow with fee + ctx.accounts.oft_store.tvl_ld += amount_received_ld; + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_source.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_escrow.to_account_info(), + authority: ctx.accounts.signer.to_account_info(), + }, + ), + amount_sent_ld, + ctx.accounts.token_mint.decimals, + )?; + } else { + // Native type + // burn + token_interface::burn( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + Burn { + mint: ctx.accounts.token_mint.to_account_info(), + from: ctx.accounts.token_source.to_account_info(), + authority: ctx.accounts.signer.to_account_info(), + }, + ), + amount_sent_ld - oft_fee_ld, + )?; + + // transfer fee to escrow + if oft_fee_ld > 0 { + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_source.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_escrow.to_account_info(), + authority: ctx.accounts.signer.to_account_info(), + }, + ), + oft_fee_ld, + ctx.accounts.token_mint.decimals, + )?; + } + } + + // send message to endpoint + require!( + ctx.accounts.oft_store.key() == ctx.remaining_accounts[1].key(), + OFTError::InvalidSender + ); + let amount_sd = ctx.accounts.oft_store.ld2sd(amount_received_ld); + let msg_receipt = oapp::endpoint_cpi::send( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + ctx.remaining_accounts, + &[OFT_SEED, ctx.accounts.token_escrow.key().as_ref(), &[ctx.accounts.oft_store.bump]], + EndpointSendParams { + dst_eid: params.dst_eid, + receiver: ctx.accounts.peer.peer_address, + message: msg_codec::encode( + params.to, + amount_sd, + ctx.accounts.signer.key(), + ¶ms.compose_msg, + ), + options: ctx + .accounts + .peer + .enforced_options + .combine_options(¶ms.compose_msg, ¶ms.options)?, + native_fee: params.native_fee, + lz_token_fee: params.lz_token_fee, + }, + )?; + + emit_cpi!(OFTSent { + guid: msg_receipt.guid, + dst_eid: params.dst_eid, + from: ctx.accounts.token_source.key(), + amount_sent_ld, + amount_received_ld + }); + + Ok((msg_receipt, OFTReceipt { amount_sent_ld, amount_received_ld })) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct SendParams { + pub dst_eid: u32, + pub to: [u8; 32], + pub amount_ld: u64, + pub min_amount_ld: u64, + pub options: Vec, + pub compose_msg: Option>, + pub native_fee: u64, + pub lz_token_fee: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/set_oft_config.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_oft_config.rs new file mode 100644 index 000000000..d554a69ab --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_oft_config.rs @@ -0,0 +1,60 @@ +use crate::*; +use oapp::endpoint::instructions::SetDelegateParams; + +#[derive(Accounts)] +pub struct SetOFTConfig<'info> { + pub admin: Signer<'info>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + has_one = admin @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, +} + +impl SetOFTConfig<'_> { + pub fn apply(ctx: &mut Context, params: &SetOFTConfigParams) -> Result<()> { + match params.clone() { + SetOFTConfigParams::Admin(admin) => { + ctx.accounts.oft_store.admin = admin; + }, + SetOFTConfigParams::Delegate(delegate) => { + let oft_store_seed = ctx.accounts.oft_store.token_escrow.key(); + let seeds: &[&[u8]] = + &[OFT_SEED, &oft_store_seed.to_bytes(), &[ctx.accounts.oft_store.bump]]; + let _ = oapp::endpoint_cpi::set_delegate( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + &ctx.remaining_accounts, + seeds, + SetDelegateParams { delegate }, + )?; + }, + SetOFTConfigParams::DefaultFee(fee_bps) => { + require!(fee_bps < MAX_FEE_BASIS_POINTS, OFTError::InvalidFee); + ctx.accounts.oft_store.default_fee_bps = fee_bps; + }, + SetOFTConfigParams::Paused(paused) => { + ctx.accounts.oft_store.paused = paused; + }, + SetOFTConfigParams::Pauser(pauser) => { + ctx.accounts.oft_store.pauser = pauser; + }, + SetOFTConfigParams::Unpauser(unpauser) => { + ctx.accounts.oft_store.unpauser = unpauser; + }, + } + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub enum SetOFTConfigParams { + Admin(Pubkey), + Delegate(Pubkey), // OApp delegate for the endpoint + DefaultFee(u16), + Paused(bool), + Pauser(Option), + Unpauser(Option), +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/set_pause.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_pause.rs new file mode 100644 index 000000000..dfac3a113 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_pause.rs @@ -0,0 +1,35 @@ +use crate::*; + +#[derive(Accounts)] +#[instruction(params: SetPauseParams)] +pub struct SetPause<'info> { + /// pauser or unpauser + pub signer: Signer<'info>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + constraint = is_valid_signer(signer.key(), &oft_store, params.paused) @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, +} + +impl SetPause<'_> { + pub fn apply(ctx: &mut Context, params: &SetPauseParams) -> Result<()> { + ctx.accounts.oft_store.paused = params.paused; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct SetPauseParams { + pub paused: bool, +} + +fn is_valid_signer(signer: Pubkey, oft_store: &OFTStore, paused: bool) -> bool { + if paused { + oft_store.pauser == Some(signer) + } else { + oft_store.unpauser == Some(signer) + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/set_peer_config.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_peer_config.rs new file mode 100644 index 000000000..d4d4ee58c --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_peer_config.rs @@ -0,0 +1,99 @@ +use crate::*; + +#[derive(Accounts)] +#[instruction(params: SetPeerConfigParams)] +pub struct SetPeerConfig<'info> { + #[account(mut)] + pub admin: Signer<'info>, + #[account( + init_if_needed, + payer = admin, + space = 8 + PeerConfig::INIT_SPACE, + seeds = [PEER_SEED, oft_store.key().as_ref(), ¶ms.remote_eid.to_be_bytes()], + bump + )] + pub peer: Account<'info, PeerConfig>, + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + has_one = admin @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, + pub system_program: Program<'info, System>, +} + +impl SetPeerConfig<'_> { + pub fn apply(ctx: &mut Context, params: &SetPeerConfigParams) -> Result<()> { + match params.config.clone() { + PeerConfigParam::PeerAddress(peer_address) => { + ctx.accounts.peer.peer_address = peer_address; + }, + PeerConfigParam::FeeBps(fee_bps) => { + if let Some(fee_bps) = fee_bps { + require!(fee_bps < MAX_FEE_BASIS_POINTS, OFTError::InvalidFee); + } + ctx.accounts.peer.fee_bps = fee_bps; + }, + PeerConfigParam::EnforcedOptions { send, send_and_call } => { + oapp::options::assert_type_3(&send)?; + ctx.accounts.peer.enforced_options.send = send; + oapp::options::assert_type_3(&send_and_call)?; + ctx.accounts.peer.enforced_options.send_and_call = send_and_call; + }, + PeerConfigParam::OutboundRateLimit(rate_limit_params) => { + Self::update_rate_limiter( + &mut ctx.accounts.peer.outbound_rate_limiter, + &rate_limit_params, + )?; + }, + PeerConfigParam::InboundRateLimit(rate_limit_params) => { + Self::update_rate_limiter( + &mut ctx.accounts.peer.inbound_rate_limiter, + &rate_limit_params, + )?; + }, + } + ctx.accounts.peer.bump = ctx.bumps.peer; + Ok(()) + } + + fn update_rate_limiter( + rate_limiter: &mut Option, + params: &Option, + ) -> Result<()> { + if let Some(param) = params { + let mut limiter = rate_limiter.clone().unwrap_or_default(); + if let Some(capacity) = param.capacity { + limiter.set_capacity(capacity)?; + } + if let Some(refill_rate) = param.refill_per_second { + limiter.set_rate(refill_rate)?; + } + *rate_limiter = Some(limiter); + } else { + *rate_limiter = None; + } + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct SetPeerConfigParams { + pub remote_eid: u32, + pub config: PeerConfigParam, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub enum PeerConfigParam { + PeerAddress([u8; 32]), + FeeBps(Option), + EnforcedOptions { send: Vec, send_and_call: Vec }, + OutboundRateLimit(Option), + InboundRateLimit(Option), +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct RateLimitParams { + pub refill_per_second: Option, + pub capacity: Option, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/withdraw_fee.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/withdraw_fee.rs new file mode 100644 index 000000000..762c46ae6 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/withdraw_fee.rs @@ -0,0 +1,67 @@ +use crate::*; +use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked}; + +#[derive(Accounts)] +pub struct WithdrawFee<'info> { + pub admin: Signer<'info>, + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + has_one = admin @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + address = oft_store.token_mint, + mint::token_program = token_program + )] + pub token_mint: InterfaceAccount<'info, Mint>, + #[account( + mut, + address = oft_store.token_escrow, + token::authority = oft_store, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_dest: InterfaceAccount<'info, TokenAccount>, + pub token_program: Interface<'info, TokenInterface>, +} + +impl WithdrawFee<'_> { + pub fn apply(ctx: &mut Context, params: &WithdrawFeeParams) -> Result<()> { + require!( + ctx.accounts.token_escrow.amount - ctx.accounts.oft_store.tvl_ld >= params.fee_ld, + OFTError::InvalidFee + ); + let seeds: &[&[u8]] = &[ + OFT_SEED, + &ctx.accounts.token_escrow.key().to_bytes(), + &[ctx.accounts.oft_store.bump], + ]; + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_escrow.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_dest.to_account_info(), + authority: ctx.accounts.oft_store.to_account_info(), + }, + ) + .with_signer(&[&seeds]), + params.fee_ld, + ctx.accounts.token_mint.decimals, + )?; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct WithdrawFeeParams { + pub fee_ld: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/lib.rs b/examples/oft-solana-composer-library/programs/oft/src/lib.rs new file mode 100644 index 000000000..68b54b928 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/lib.rs @@ -0,0 +1,101 @@ +use anchor_lang::prelude::*; + +pub mod compose_msg_codec; +pub mod errors; +pub mod events; +pub mod instructions; +pub mod msg_codec; +pub mod state; + +use errors::*; +use events::*; +use instructions::*; +use oapp::{ + endpoint::{MessagingFee, MessagingReceipt}, + LzReceiveParams, +}; +use solana_helper::program_id_from_env; +use state::*; + +declare_id!(Pubkey::new_from_array(program_id_from_env!( + "OFT_ID", + "9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT" +))); + +pub const OFT_SEED: &[u8] = b"OFT"; +pub const PEER_SEED: &[u8] = b"Peer"; +pub const ENFORCED_OPTIONS_SEED: &[u8] = b"EnforcedOptions"; +pub const LZ_RECEIVE_TYPES_SEED: &[u8] = oapp::LZ_RECEIVE_TYPES_SEED; + +#[program] +pub mod oft { + use super::*; + + pub fn oft_version(_ctx: Context) -> Result { + Ok(Version { interface: 2, message: 1 }) + } + + pub fn init_oft(mut ctx: Context, params: InitOFTParams) -> Result<()> { + InitOFT::apply(&mut ctx, ¶ms) + } + + // ============================== Admin ============================== + pub fn set_oft_config( + mut ctx: Context, + params: SetOFTConfigParams, + ) -> Result<()> { + SetOFTConfig::apply(&mut ctx, ¶ms) + } + + pub fn set_peer_config( + mut ctx: Context, + params: SetPeerConfigParams, + ) -> Result<()> { + SetPeerConfig::apply(&mut ctx, ¶ms) + } + + pub fn set_pause(mut ctx: Context, params: SetPauseParams) -> Result<()> { + SetPause::apply(&mut ctx, ¶ms) + } + + pub fn withdraw_fee(mut ctx: Context, params: WithdrawFeeParams) -> Result<()> { + WithdrawFee::apply(&mut ctx, ¶ms) + } + + // ============================== Public ============================== + + pub fn quote_oft(ctx: Context, params: QuoteOFTParams) -> Result { + QuoteOFT::apply(&ctx, ¶ms) + } + + pub fn quote_send(ctx: Context, params: QuoteSendParams) -> Result { + QuoteSend::apply(&ctx, ¶ms) + } + + pub fn send( + mut ctx: Context, + params: SendParams, + ) -> Result<(MessagingReceipt, OFTReceipt)> { + Send::apply(&mut ctx, ¶ms) + } + + pub fn lz_receive(mut ctx: Context, params: LzReceiveParams) -> Result<()> { + LzReceive::apply(&mut ctx, ¶ms) + } + + pub fn lz_receive_types( + ctx: Context, + params: LzReceiveParams, + ) -> Result> { + LzReceiveTypes::apply(&ctx, ¶ms) + } +} + +#[derive(Accounts)] +pub struct OFTVersion {} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct Version { + pub interface: u64, + pub message: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/msg_codec.rs b/examples/oft-solana-composer-library/programs/oft/src/msg_codec.rs new file mode 100644 index 000000000..47771d590 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/msg_codec.rs @@ -0,0 +1,46 @@ +use crate::*; + +const SEND_TO_OFFSET: usize = 0; +const SEND_AMOUNT_SD_OFFSET: usize = 32; +const COMPOSE_MSG_OFFSET: usize = 40; + +pub fn encode( + send_to: [u8; 32], + amount_sd: u64, + sender: Pubkey, + compose_msg: &Option>, +) -> Vec { + if let Some(msg) = compose_msg { + let mut encoded = Vec::with_capacity(72 + msg.len()); // 32 + 8 + 32 + encoded.extend_from_slice(&send_to); + encoded.extend_from_slice(&amount_sd.to_be_bytes()); + encoded.extend_from_slice(sender.to_bytes().as_ref()); + encoded.extend_from_slice(&msg); + encoded + } else { + let mut encoded = Vec::with_capacity(40); // 32 + 8 + encoded.extend_from_slice(&send_to); + encoded.extend_from_slice(&amount_sd.to_be_bytes()); + encoded + } +} + +pub fn send_to(message: &[u8]) -> [u8; 32] { + let mut send_to = [0; 32]; + send_to.copy_from_slice(&message[SEND_TO_OFFSET..SEND_AMOUNT_SD_OFFSET]); + send_to +} + +pub fn amount_sd(message: &[u8]) -> u64 { + let mut amount_sd_bytes = [0; 8]; + amount_sd_bytes.copy_from_slice(&message[SEND_AMOUNT_SD_OFFSET..COMPOSE_MSG_OFFSET]); + u64::from_be_bytes(amount_sd_bytes) +} + +pub fn compose_msg(message: &[u8]) -> Option> { + if message.len() > COMPOSE_MSG_OFFSET { + Some(message[COMPOSE_MSG_OFFSET..].to_vec()) + } else { + None + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/state/mod.rs b/examples/oft-solana-composer-library/programs/oft/src/state/mod.rs new file mode 100644 index 000000000..66db5699b --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/state/mod.rs @@ -0,0 +1,5 @@ +pub mod oft; +pub mod peer_config; + +pub use oft::*; +pub use peer_config::*; diff --git a/examples/oft-solana-composer-library/programs/oft/src/state/oft.rs b/examples/oft-solana-composer-library/programs/oft/src/state/oft.rs new file mode 100644 index 000000000..6cb97b5da --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/state/oft.rs @@ -0,0 +1,50 @@ +use crate::*; + +#[account] +#[derive(InitSpace)] +pub struct OFTStore { + // immutable + pub oft_type: OFTType, + pub ld2sd_rate: u64, + pub token_mint: Pubkey, + pub token_escrow: Pubkey, // this account is used to hold TVL and fees + pub endpoint_program: Pubkey, + pub bump: u8, + // mutable + pub tvl_ld: u64, // total value locked. if oft_type is Native, it is always 0. + // configurable + pub admin: Pubkey, + pub default_fee_bps: u16, + pub paused: bool, + pub pauser: Option, + pub unpauser: Option, +} + +#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub enum OFTType { + Native, + Adapter, +} + +impl OFTStore { + pub fn ld2sd(&self, amount_ld: u64) -> u64 { + amount_ld / self.ld2sd_rate + } + + pub fn sd2ld(&self, amount_sd: u64) -> u64 { + amount_sd * self.ld2sd_rate + } + + pub fn remove_dust(&self, amount_ld: u64) -> u64 { + amount_ld - amount_ld % self.ld2sd_rate + } +} + +/// LzReceiveTypesAccounts includes accounts that are used in the LzReceiveTypes +/// instruction. +#[account] +#[derive(InitSpace)] +pub struct LzReceiveTypesAccounts { + pub oft_store: Pubkey, + pub token_mint: Pubkey, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/state/peer_config.rs b/examples/oft-solana-composer-library/programs/oft/src/state/peer_config.rs new file mode 100644 index 000000000..2a6434087 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/state/peer_config.rs @@ -0,0 +1,92 @@ +use crate::*; + +pub const ENFORCED_OPTIONS_SEND_MAX_LEN: usize = 512; +pub const ENFORCED_OPTIONS_SEND_AND_CALL_MAX_LEN: usize = 1024; + +#[account] +#[derive(InitSpace)] +pub struct PeerConfig { + pub peer_address: [u8; 32], + pub enforced_options: EnforcedOptions, + pub outbound_rate_limiter: Option, + pub inbound_rate_limiter: Option, + pub fee_bps: Option, + pub bump: u8, +} + +#[derive(Clone, Default, AnchorSerialize, AnchorDeserialize, InitSpace)] +pub struct RateLimiter { + pub capacity: u64, + pub tokens: u64, + pub refill_per_second: u64, + pub last_refill_time: u64, +} + +impl RateLimiter { + pub fn set_rate(&mut self, refill_per_second: u64) -> Result<()> { + self.refill(0)?; + self.refill_per_second = refill_per_second; + Ok(()) + } + + pub fn set_capacity(&mut self, capacity: u64) -> Result<()> { + self.capacity = capacity; + self.tokens = capacity; + self.last_refill_time = Clock::get()?.unix_timestamp.try_into().unwrap(); + Ok(()) + } + + pub fn refill(&mut self, extra_tokens: u64) -> Result<()> { + let mut new_tokens = extra_tokens; + let current_time: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); + if current_time > self.last_refill_time { + let time_elapsed_in_seconds = current_time - self.last_refill_time; + new_tokens = new_tokens + .saturating_add(time_elapsed_in_seconds.saturating_mul(self.refill_per_second)); + } + self.tokens = std::cmp::min(self.capacity, self.tokens.saturating_add(new_tokens)); + + self.last_refill_time = current_time; + Ok(()) + } + + pub fn try_consume(&mut self, amount: u64) -> Result<()> { + self.refill(0)?; + match self.tokens.checked_sub(amount) { + Some(new_tokens) => { + self.tokens = new_tokens; + Ok(()) + }, + None => Err(error!(OFTError::RateLimitExceeded)), + } + } +} + +#[derive(Clone, Default, AnchorSerialize, AnchorDeserialize, InitSpace)] +pub struct EnforcedOptions { + #[max_len(ENFORCED_OPTIONS_SEND_MAX_LEN)] + pub send: Vec, + #[max_len(ENFORCED_OPTIONS_SEND_AND_CALL_MAX_LEN)] + pub send_and_call: Vec, +} + +impl EnforcedOptions { + pub fn get_enforced_options(&self, composed_msg: &Option>) -> Vec { + if composed_msg.is_none() { + self.send.clone() + } else { + self.send_and_call.clone() + } + } + + pub fn combine_options( + &self, + compose_msg: &Option>, + extra_options: &Vec, + ) -> Result> { + let enforced_options = self.get_enforced_options(compose_msg); + oapp::options::combine_options(enforced_options, extra_options) + } +} + +utils::generate_account_size_test!(EnforcedOptions, enforced_options_test); diff --git a/examples/oft-solana-composer-library/programs/oft/tests/msg_codec.rs b/examples/oft-solana-composer-library/programs/oft/tests/msg_codec.rs new file mode 100644 index 000000000..edf8f236c --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/tests/msg_codec.rs @@ -0,0 +1,55 @@ +#[cfg(test)] +mod test_msg_codec { + use anchor_lang::prelude::Pubkey; + use oft::compose_msg_codec; + use oft::msg_codec; + + #[test] + fn test_msg_codec_with_compose_msg() { + let send_to: [u8; 32] = [1; 32]; + let amount_sd: u64 = 123456789; + let sender: Pubkey = Pubkey::new_unique(); + let compose_msg: Option> = Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + let encoded = msg_codec::encode(send_to, amount_sd, sender, &compose_msg); + assert_eq!(encoded.len(), 72 + compose_msg.clone().unwrap().len()); + assert_eq!(msg_codec::send_to(&encoded), send_to); + assert_eq!(msg_codec::amount_sd(&encoded), amount_sd); + assert_eq!( + msg_codec::compose_msg(&encoded), + Some([sender.to_bytes().as_ref(), compose_msg.unwrap().as_slice()].concat()) + ); + } + + #[test] + fn test_msg_codec_without_compose_msg() { + let send_to: [u8; 32] = [1; 32]; + let amount_sd: u64 = 123456789; + let sender: Pubkey = Pubkey::new_unique(); + let compose_msg: Option> = None; + let encoded = msg_codec::encode(send_to, amount_sd, sender, &compose_msg); + assert_eq!(encoded.len(), 40); + assert_eq!(msg_codec::send_to(&encoded), send_to); + assert_eq!(msg_codec::amount_sd(&encoded), amount_sd); + assert_eq!(msg_codec::compose_msg(&encoded), None); + } + + #[test] + fn test_compose_msg_codec() { + let nonce: u64 = 123456789; + let src_eid: u32 = 987654321; + let amount_ld: u64 = 123456789; + let compose_from: [u8; 32] = [1; 32]; + let compose_msg: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + let encoded = compose_msg_codec::encode( + nonce, + src_eid, + amount_ld, + &[&compose_from[..], &compose_msg].concat(), + ); + assert_eq!(encoded.len(), 20 + [&compose_from[..], &compose_msg].concat().len()); + assert_eq!(compose_msg_codec::nonce(&encoded), nonce); + assert_eq!(compose_msg_codec::src_eid(&encoded), src_eid); + assert_eq!(compose_msg_codec::amount_ld(&encoded), amount_ld); + assert_eq!(compose_msg_codec::compose_msg(&encoded), compose_msg); + } +} diff --git a/examples/oft-solana-composer-library/rust-toolchain.toml b/examples/oft-solana-composer-library/rust-toolchain.toml new file mode 100644 index 000000000..7897a24d1 --- /dev/null +++ b/examples/oft-solana-composer-library/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.75.0" diff --git a/examples/oft-solana-composer-library/solhint.config.js b/examples/oft-solana-composer-library/solhint.config.js new file mode 100644 index 000000000..52efe629c --- /dev/null +++ b/examples/oft-solana-composer-library/solhint.config.js @@ -0,0 +1 @@ +module.exports = require('@layerzerolabs/solhint-config'); diff --git a/examples/oft-solana-composer-library/tasks/common/config.get.ts b/examples/oft-solana-composer-library/tasks/common/config.get.ts new file mode 100644 index 000000000..9b2f3e411 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/config.get.ts @@ -0,0 +1,200 @@ +import { PublicKey } from '@solana/web3.js' +import { task } from 'hardhat/config' +import { ActionType } from 'hardhat/types' + +import { OmniPoint, createDefaultApplicative } from '@layerzerolabs/devtools' +import { createConnectedContractFactory, getNetworkNameForEid } from '@layerzerolabs/devtools-evm-hardhat' +import { createLogger, printCrossTable, setDefaultLogLevel } from '@layerzerolabs/io-devtools' +import { ChainType, EndpointId, endpointIdToChainType, getNetworkForChainId } from '@layerzerolabs/lz-definitions' +import { EndpointV2 } from '@layerzerolabs/protocol-devtools-solana' +import { OAppOmniGraph } from '@layerzerolabs/ua-devtools' +import { createOAppFactory } from '@layerzerolabs/ua-devtools-evm' +import { + OAppOmniGraphHardhatSchema, + SUBTASK_LZ_OAPP_CONFIG_LOAD, + type SubtaskLoadConfigTaskArgs, + TASK_LZ_OAPP_CONFIG_GET, +} from '@layerzerolabs/ua-devtools-evm-hardhat' +import { getReceiveConfig, getSendConfig } from '@layerzerolabs/ua-devtools-evm-hardhat' + +import { getSolanaReceiveConfig, getSolanaSendConfig } from './taskHelper' +import { createSolanaConnectionFactory } from './utils' + +interface TaskArgs { + logLevel?: string + oappConfig: string +} + +/** + * Helper function to determine if the point is Solana + * @param point {OmniPoint} + */ +const isSolana = (point: OmniPoint) => endpointIdToChainType(point.eid) === ChainType.SOLANA + +/** + * Helper function to get the hardhat.config.ts network name for a given endpoint id, or use the convention of + * networkName-environment for Solana. + * @param eid {EndpointId} + */ +const getEid = (eid: EndpointId) => { + switch (eid) { + // In the case of solana-testnet and solana-mainnet, we'll use the convention of networkName-environment + case EndpointId.SOLANA_V2_TESTNET: + case EndpointId.SOLANA_V2_MAINNET: { + const { chainName, env } = getNetworkForChainId(eid) + return `${chainName}-${env}` + } + default: + // For all other chains, we'll use the network name from hardhat.config.ts + return getNetworkNameForEid(eid) + } +} + +const action: ActionType = async ({ logLevel = 'info', oappConfig }, hre) => { + setDefaultLogLevel(logLevel) + const logger = createLogger(logLevel) + + const graph: OAppOmniGraph = await hre.run(SUBTASK_LZ_OAPP_CONFIG_LOAD, { + configPath: oappConfig, + schema: OAppOmniGraphHardhatSchema, + task: TASK_LZ_OAPP_CONFIG_GET, + } satisfies SubtaskLoadConfigTaskArgs) + + const evmSdkFactory = createOAppFactory(createConnectedContractFactory()) + const configs: Record> = {} + + // Iterate over the graph of connections not from Solana + const tasks = graph.connections + .filter(({ vector: { from } }) => !isSolana(from)) + .map(({ vector: { from, to } }) => async () => { + const endpointV2Sdk = await (await evmSdkFactory(from)).getEndpointSDK() + + // OApp User Set Config + const receiveCustomConfig = await getReceiveConfig(endpointV2Sdk, to.eid, from.address, true) + const sendCustomConfig = await getSendConfig(endpointV2Sdk, to.eid, from.address, true) + const [sendCustomLibrary, sendCustomUlnConfig, sendCustomExecutorConfig] = sendCustomConfig ?? [] + const [receiveCustomLibrary, receiveCustomUlnConfig] = receiveCustomConfig ?? [] + + // Default Config + const receiveDefaultConfig = await getReceiveConfig(endpointV2Sdk, to.eid) + const sendDefaultConfig = await getSendConfig(endpointV2Sdk, to.eid) + const [sendDefaultLibrary, sendDefaultUlnConfig, sendDefaultExecutorConfig] = sendDefaultConfig ?? [] + const [receiveDefaultLibrary, receiveDefaultUlnConfig] = receiveDefaultConfig ?? [] + + // OApp Config + const receiveOAppConfig = await getReceiveConfig(endpointV2Sdk, to.eid, from.address) + const sendOAppConfig = await getSendConfig(endpointV2Sdk, to.eid, from.address) + const [sendOAppLibrary, sendOAppUlnConfig, sendOAppExecutorConfig] = sendOAppConfig ?? [] + const [receiveOAppLibrary, receiveOAppUlnConfig] = receiveOAppConfig ?? [] + + const localNetworkName = getEid(from.eid) + const remoteNetworkName = getEid(to.eid) + + // Update the global state + configs[localNetworkName] = { + ...configs[localNetworkName], + [remoteNetworkName]: { + defaultSendLibrary: sendOAppLibrary, + defaultReceiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + } + + console.log( + printCrossTable( + [ + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendCustomLibrary, + receiveLibrary: receiveCustomLibrary, + sendUlnConfig: sendCustomUlnConfig, + sendExecutorConfig: sendCustomExecutorConfig, + receiveUlnConfig: receiveCustomUlnConfig, + }, + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendDefaultLibrary, + receiveLibrary: receiveDefaultLibrary, + sendUlnConfig: sendDefaultUlnConfig, + sendExecutorConfig: sendDefaultExecutorConfig, + receiveUlnConfig: receiveDefaultUlnConfig, + }, + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendOAppLibrary, + receiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + ], + ['', 'Custom OApp Config', 'Default OApp Config', 'Active OApp Config'] + ) + ) + }) + // Iterate over the graph of connections from Solana + const solTasks = graph.connections + .filter(({ vector: { from } }) => isSolana(from)) + .map(({ vector: { from, to } }) => async () => { + const endpointV2Sdk = new EndpointV2( + await createSolanaConnectionFactory()(from.eid), + from, + new PublicKey(from.address) // doesn't matter as we are not sending transactions + ) + // OApp Config + const receiveOAppConfig = await getSolanaReceiveConfig(endpointV2Sdk, to.eid, from.address) + const sendOAppConfig = await getSolanaSendConfig(endpointV2Sdk, to.eid, from.address) + const [sendOAppLibrary, sendOAppUlnConfig, sendOAppExecutorConfig] = sendOAppConfig ?? [] + const [receiveOAppLibrary, receiveOAppUlnConfig] = receiveOAppConfig ?? [] + + const localNetworkName = getEid(from.eid) + const remoteNetworkName = getEid(to.eid) + + // Update the global state + configs[localNetworkName] = { + ...configs[localNetworkName], + [remoteNetworkName]: { + defaultSendLibrary: sendOAppLibrary, + defaultReceiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + } + // Defaults are treated much differently in Solana, so we only output the active OApp config. + console.log( + printCrossTable( + [ + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendOAppLibrary, + receiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + ], + ['', 'Active OApp Config'] + ) + ) + }) + + // We allow this script to be executed either in parallel or in series + const applicative = createDefaultApplicative(logger) + await applicative(tasks) + await applicative(solTasks) + + return configs +} + +task( + TASK_LZ_OAPP_CONFIG_GET, + 'Outputs Custom OApp Config, Default OApp Config, and Active OApp Config. Each config contains Send & Receive Libraries, Send Uln & Executor Configs, and Receive Executor Configs', + action +) diff --git a/examples/oft-solana-composer-library/tasks/common/taskHelper.ts b/examples/oft-solana-composer-library/tasks/common/taskHelper.ts new file mode 100644 index 000000000..ca763a8fe --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/taskHelper.ts @@ -0,0 +1,56 @@ +import { OmniAddress } from '@layerzerolabs/devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { UlnProgram } from '@layerzerolabs/lz-solana-sdk-v2' +import { + IEndpointV2, + Timeout, + Uln302ConfigType, + Uln302ExecutorConfig, + Uln302UlnConfig, +} from '@layerzerolabs/protocol-devtools' + +/** + * Get the receive config for a Solana OApp + * @param endpointV2Sdk {IEndpointV2} SDK for the endpoint + * @param remoteEid {EndpointId} remote eid + * @param address {OmniAddress} address of the OApp + */ +export async function getSolanaReceiveConfig( + endpointV2Sdk: IEndpointV2, + remoteEid: EndpointId, + address: OmniAddress +): Promise<[OmniAddress, Uln302UlnConfig, Timeout] | undefined> { + const [receiveLibrary] = await endpointV2Sdk.getReceiveLibrary(address, remoteEid) + return [ + receiveLibrary ?? UlnProgram.PROGRAM_ADDRESS, + await endpointV2Sdk.getAppUlnConfig( + address, + UlnProgram.PROGRAM_ID.toBase58(), + remoteEid, + Uln302ConfigType.Receive + ), + { + lib: UlnProgram.PROGRAM_ID.toBase58(), + expiry: 0n, // unsupported for Solana + }, + ] +} + +/** + * Get the send config for a Solana OApp. + * @param endpointV2Sdk {IEndpointV2} SDK for the endpoint + * @param eid {EndpointId} remote eid + * @param address {OmniAddress} address of the OApp + */ +export async function getSolanaSendConfig( + endpointV2Sdk: IEndpointV2, + eid: EndpointId, + address: OmniAddress +): Promise<[OmniAddress, Uln302UlnConfig, Uln302ExecutorConfig] | undefined> { + const sendLibrary = (await endpointV2Sdk.getSendLibrary(address, eid)) ?? UlnProgram.PROGRAM_ADDRESS + return [ + sendLibrary, + await endpointV2Sdk.getAppUlnConfig(address, UlnProgram.PROGRAM_ID.toBase58(), eid, Uln302ConfigType.Send), + await endpointV2Sdk.getAppExecutorConfig(address, sendLibrary, eid), + ] +} diff --git a/examples/oft-solana-composer-library/tasks/common/types.ts b/examples/oft-solana-composer-library/tasks/common/types.ts new file mode 100644 index 000000000..bf0b4bc87 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/types.ts @@ -0,0 +1,19 @@ +import { decode } from '@coral-xyz/anchor/dist/cjs/utils/bytes/bs58' +import { Keypair, PublicKey } from '@solana/web3.js' +import { CLIArgumentType } from 'hardhat/types' + +export const keyPair: CLIArgumentType = { + name: 'keyPair', + parse(name: string, value: string) { + return Keypair.fromSecretKey(decode(value)) + }, + validate() {}, +} + +export const publicKey: CLIArgumentType = { + name: 'keyPair', + parse(name: string, value: string) { + return new PublicKey(value) + }, + validate() {}, +} diff --git a/examples/oft-solana-composer-library/tasks/common/utils.ts b/examples/oft-solana-composer-library/tasks/common/utils.ts new file mode 100644 index 000000000..48a13e64b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/utils.ts @@ -0,0 +1,203 @@ +import assert from 'assert' + +import { Connection, Keypair, PublicKey } from '@solana/web3.js' + +import { + OmniPoint, + OmniSigner, + OmniTransactionReceipt, + OmniTransactionResponse, + firstFactory, + formatEid, +} from '@layerzerolabs/devtools' +import { createConnectedContractFactory } from '@layerzerolabs/devtools-evm-hardhat' +import { + OmniSignerSolana, + OmniSignerSolanaSquads, + createConnectionFactory, + createRpcUrlFactory, +} from '@layerzerolabs/devtools-solana' +import { ChainType, EndpointId, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +import { UlnProgram } from '@layerzerolabs/lz-solana-sdk-v2' +import { Options } from '@layerzerolabs/lz-v2-utilities' +import { IOApp } from '@layerzerolabs/ua-devtools' +import { createOAppFactory } from '@layerzerolabs/ua-devtools-evm' +import { createOFTFactory } from '@layerzerolabs/ua-devtools-solana' + +export const createSolanaConnectionFactory = () => + createConnectionFactory( + createRpcUrlFactory({ + [EndpointId.SOLANA_V2_MAINNET]: process.env.RPC_URL_SOLANA, + [EndpointId.SOLANA_V2_TESTNET]: process.env.RPC_URL_SOLANA_TESTNET, + }) + ) + +export const createSdkFactory = ( + userAccount: PublicKey, + programId: PublicKey, + connectionFactory = createSolanaConnectionFactory() +) => { + // To create a EVM/Solana SDK factory we need to merge the EVM and the Solana factories into one + // + // We do this by using the firstFactory helper function that is provided by the devtools package. + // This function will try to execute the factories one by one and return the first one that succeeds. + const evmSdkfactory = createOAppFactory(createConnectedContractFactory()) + const solanaSdkFactory = createOFTFactory( + // The first parameter to createOFTFactory is a user account factory + // + // This is a function that receives an OmniPoint ({ eid, address } object) + // and returns a user account to be used with that SDK. + // + // For our purposes this will always be the user account coming from the secret key passed in + () => userAccount, + // The second parameter is a program ID factory + // + // This is a function that receives an OmniPoint ({ eid, address } object) + // and returns a program ID to be used with that SDK. + // + // Since we only have one OFT deployed, this will always be the program ID passed as a CLI parameter. + // + // In situations where we might have multiple configs with OFTs using multiple program IDs, + // this function needs to decide which one to use. + () => programId, + // Last but not least the SDK will require a connection + connectionFactory + ) + + // We now "merge" the two SDK factories into one. + // + // We do this by using the firstFactory helper function that is provided by the devtools package. + // This function will try to execute the factories one by one and return the first one that succeeds. + return firstFactory<[OmniPoint], IOApp>(evmSdkfactory, solanaSdkFactory) +} + +export const createSolanaSignerFactory = ( + wallet: Keypair, + connectionFactory = createSolanaConnectionFactory(), + multisigKey?: PublicKey +) => { + return async (eid: EndpointId): Promise>> => { + assert( + endpointIdToChainType(eid) === ChainType.SOLANA, + `Solana signer factory can only create signers for Solana networks. Received ${formatEid(eid)}` + ) + + return multisigKey + ? new OmniSignerSolanaSquads(eid, await connectionFactory(eid), multisigKey, wallet) + : new OmniSignerSolana(eid, await connectionFactory(eid), wallet) + } +} + +export function uint8ArrayToHex(uint8Array: Uint8Array, prefix = false): string { + const hexString = Buffer.from(uint8Array).toString('hex') + return prefix ? `0x${hexString}` : hexString +} + +function formatBigIntForDisplay(n: bigint) { + return n.toLocaleString().replace(/,/g, '_') +} + +export function decodeLzReceiveOptions(hex: string): string { + try { + // Handle empty/undefined values first + if (!hex || hex === '0x') return 'No options set' + const options = Options.fromOptions(hex) + const lzReceiveOpt = options.decodeExecutorLzReceiveOption() + return lzReceiveOpt + ? `gas: ${formatBigIntForDisplay(lzReceiveOpt.gas)} , value: ${formatBigIntForDisplay(lzReceiveOpt.value)} wei` + : 'No executor options' + } catch (e) { + return `Invalid options (${hex.slice(0, 12)}...)` + } +} + +export async function getSolanaUlnConfigPDAs( + remote: EndpointId, + connection: Connection, + ulnAddress: PublicKey, + oftStore: PublicKey +) { + const uln = new UlnProgram.Uln(new PublicKey(ulnAddress)) + const sendConfig = uln.getSendConfigState(connection, new PublicKey(oftStore), remote) + + const receiveConfig = uln.getReceiveConfigState(connection, new PublicKey(oftStore), remote) + + return await Promise.all([sendConfig, receiveConfig]) +} + +export class DebugLogger { + static keyValue(key: string, value: any, indentLevel = 0) { + const indent = ' '.repeat(indentLevel * 2) + console.log(`${indent}\x1b[33m${key}:\x1b[0m ${value}`) + } + + static keyHeader(key: string, indentLevel = 0) { + const indent = ' '.repeat(indentLevel * 2) + console.log(`${indent}\x1b[33m${key}:\x1b[0m`) + } + + static header(text: string) { + console.log(`\x1b[36m${text}\x1b[0m`) + } + + static separator() { + console.log('\x1b[90m----------------------------------------\x1b[0m') + } + + /** + * Logs an error (in red) and corresponding fix suggestion (in blue). + * Uses the ERRORS_FIXES_MAP to retrieve text based on the known error type. + * + * @param type Required KnownErrors enum member + * @param errorMsg Optional string message to append to the error. + */ + static printErrorAndFixSuggestion(type: KnownErrors, errorMsg?: string) { + const fixInfo = ERRORS_FIXES_MAP[type] + if (!fixInfo) { + // Fallback if the error type is not recognized + console.log(`\x1b[31mError:\x1b[0m Unknown error type "${type}"`) + return + } + + // If errorMsg is specified, append it in parentheses + const errorOutput = errorMsg ? `${type}: (${errorMsg})` : type + + // Print the error type in red + console.log(`\x1b[31mError:\x1b[0m ${errorOutput}`) + + // Print the tip in green + console.log(`\x1b[32mFix suggestion:\x1b[0m ${fixInfo.tip}`) + + // Print the info in blue + if (fixInfo.info) { + console.log(`\x1b[34mElaboration:\x1b[0m ${fixInfo.info}`) + } + + // log empty line to separate error messages + console.log() + } +} + +export enum KnownErrors { + // variable name format: _ + // e.g. If the user forgets to deploy the OFT Program, the variable name should be: + // FIX_SUGGESTION_OFT_PROGRAM_NOT_DEPLOYED + ULN_INIT_CONFIG_SKIPPED = 'ULN_INIT_CONFIG_SKIPPED', + SOLANA_DEPLOYMENT_NOT_FOUND = 'SOLANA_DEPLOYMENT_NOT_FOUND', +} + +interface ErrorFixInfo { + tip: string + info?: string +} + +export const ERRORS_FIXES_MAP: Record = { + [KnownErrors.ULN_INIT_CONFIG_SKIPPED]: { + tip: 'Did you run `npx hardhat lz:oft:solana:init-config --oapp-config --solana-eid ` ?', + info: 'You must run lz:oft:solana:init-config once before you run lz:oapp:wire. If you have added new pathways, you must also run lz:oft:solana:init-config again.', + }, + [KnownErrors.SOLANA_DEPLOYMENT_NOT_FOUND]: { + tip: 'Did you run `npx hardhat lz:oft:solana:create` ?', + info: 'The Solana deployment file is required to run config tasks. The default path is ./deployments/solana-/OFT.json', + }, +} diff --git a/examples/oft-solana-composer-library/tasks/common/wire.ts b/examples/oft-solana-composer-library/tasks/common/wire.ts new file mode 100644 index 000000000..1b280760a --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/wire.ts @@ -0,0 +1,203 @@ +import { PublicKey } from '@solana/web3.js' +import { subtask, task } from 'hardhat/config' + +import { firstFactory } from '@layerzerolabs/devtools' +import { SUBTASK_LZ_SIGN_AND_SEND, types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { setTransactionSizeBuffer } from '@layerzerolabs/devtools-solana' +import { type LogLevel, createLogger } from '@layerzerolabs/io-devtools' +import { ChainType, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +import { type IOApp, type OAppConfigurator, type OAppOmniGraph, configureOwnable } from '@layerzerolabs/ua-devtools' +import { + SUBTASK_LZ_OAPP_WIRE_CONFIGURE, + type SubtaskConfigureTaskArgs, + TASK_LZ_OAPP_WIRE, + TASK_LZ_OWNABLE_TRANSFER_OWNERSHIP, +} from '@layerzerolabs/ua-devtools-evm-hardhat' + +import { getSolanaDeployment, useWeb3Js } from '../solana' +import { findSolanaEndpointIdInGraph } from '../solana/utils' + +import { publicKey as publicKeyType } from './types' +import { + DebugLogger, + KnownErrors, + createSdkFactory, + createSolanaConnectionFactory, + createSolanaSignerFactory, + getSolanaUlnConfigPDAs, +} from './utils' + +import type { SignAndSendTaskArgs } from '@layerzerolabs/devtools-evm-hardhat/tasks' + +/** + * Additional CLI arguments for our custom wire task + */ +interface Args { + logLevel: LogLevel + multisigKey?: PublicKey + isSolanaInitConfig: boolean // For internal use only. This helps us to control which code runs depdending on whether the task ran is wire or init-config + oappConfig: string + internalConfigurator?: OAppConfigurator +} + +/** + * We extend the default wiring task to add functionality required by Solana + */ +task(TASK_LZ_OAPP_WIRE) + .addParam('multisigKey', 'The MultiSig key', undefined, publicKeyType, true) + // We use this argument to get around the fact that we want to both override the task action for the wiring task + // and wrap this task with custom configurators + // + // By default, this argument will be left empty and the default OApp configurator will be used. + // The tasks that are using custom configurators will override this argument with the configurator of their choice + .addParam('internalConfigurator', 'FOR INTERNAL USE ONLY', undefined, devtoolsTypes.fn, true) + .addParam('isSolanaInitConfig', 'FOR INTERNAL USE ONLY', undefined, devtoolsTypes.boolean, true) + .setAction(async (args: Args, hre, runSuper) => { + const logger = createLogger(args.logLevel) + + // + // + // ENVIRONMENT SETUP + // + // + + // The Solana transaction size estimation algorithm is not very accurate, so we increase its tolerance by 192 bytes + setTransactionSizeBuffer(192) + + // + // + // USER INPUT + // + // + + // construct the user's keypair via the SOLANA_PRIVATE_KEY env var + const keypair = (await useWeb3Js()).web3JsKeypair // note: this can be replaced with getSolanaKeypair() if we are okay to export that + const userAccount = keypair.publicKey + + const solanaEid = await findSolanaEndpointIdInGraph(hre, args.oappConfig) + const solanaDeployment = getSolanaDeployment(solanaEid) + + // Then we grab the programId from the args + const programId = new PublicKey(solanaDeployment.programId) + + // TODO: refactor to instead use a function such as verifySolanaDeployment that also checks for oftStore key + if (!programId) { + logger.error('Missing programId in solana deployment') + return + } + + const configurator = args.internalConfigurator + + // + // + // TOOLING SETUP + // + // + + // We'll need a connection factory to be able to query the Solana network + // + // If you haven't set RPC_URL_SOLANA and/or RPC_URL_SOLANA_TESTNET environment variables, + // the factory will use the default public RPC URLs + const connectionFactory = createSolanaConnectionFactory() + + // We'll need SDKs to be able to use devtools + const sdkFactory = createSdkFactory(userAccount, programId, connectionFactory) + + // We'll also need a signer factory + const solanaSignerFactory = createSolanaSignerFactory(keypair, connectionFactory, args.multisigKey) + + // + // + // SUBTASK OVERRIDES + // + // + + // We'll need to override the default implementation of the configure subtask + // (responsible for collecting the on-chain configuration of the contracts + // and coming up with the transactions that need to be sent to the network) + // + // The only thing we are overriding is the sdkFactory parameter - we supply the SDK factory we created above + subtask( + SUBTASK_LZ_OAPP_WIRE_CONFIGURE, + 'Configure OFT', + async (subtaskArgs: SubtaskConfigureTaskArgs, _hre, runSuper) => { + // start of pre-wiring checks. we only do this when the current task is wire. if the current task is init-config, we shouldn't run this. + if (!args.isSolanaInitConfig) { + logger.debug('Running pre-wiring checks...') + const { graph } = subtaskArgs + for (const connection of graph.connections) { + // check if from Solana Endpoint + if (endpointIdToChainType(connection.vector.from.eid) === ChainType.SOLANA) { + if (connection.config?.sendLibrary) { + // if from Solana Endpoint, ensure the PeerConfig account was already initialized + const solanaConnection = await connectionFactory(connection.vector.from.eid) + + const [sendConfig, receiveConfig] = await getSolanaUlnConfigPDAs( + connection.vector.to.eid, + solanaConnection, + new PublicKey(connection.config.sendLibrary), + new PublicKey(solanaDeployment.oftStore) + ) + + if (sendConfig == null) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ULN_INIT_CONFIG_SKIPPED, + `SendConfig on ${connection.vector.from.eid} not initialized for remote ${connection.vector.to.eid}.` + ) + } + + if (receiveConfig == null) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ULN_INIT_CONFIG_SKIPPED, + `ReceiveConfig on ${connection.vector.from.eid} not initialized for remote ${connection.vector.to.eid}.` + ) + } + + if (sendConfig == null || receiveConfig == null) { + throw new Error('SendConfig or ReceiveConfig not initialized. ') + } + } else { + logger.debug( + `No sendLibrary found in connection config for ${connection.vector.from.eid} -> ${connection.vector.to.eid}` + ) + } + } + } + // end of pre-wiring checks + } + + return runSuper({ + ...subtaskArgs, + configurator: configurator ?? subtaskArgs.configurator, + sdkFactory, + }) + } + ) + + // We'll also need to override the default implementation of the signAndSend subtask + // (responsible for sending transactions to the network and waiting for confirmations) + // + // In this subtask we need to override the createSigner function so that it uses the Solana + // signer for all Solana transactions + subtask(SUBTASK_LZ_SIGN_AND_SEND, 'Sign OFT transactions', (args: SignAndSendTaskArgs, _hre, runSuper) => + runSuper({ + ...args, + createSigner: firstFactory(solanaSignerFactory, args.createSigner), + }) + ) + + return runSuper(args) + }) + +// We'll change the default ownership transfer task to use our wire implementation +// +// The reason for this is the fact that the ownership transfer task has a deficiency +// and that is the fact that it does not support a custom SDK factory as of yet +// +// The two tasks are identical and the only drawback of this approach is the fact +// that the logs will say "Wiring OApp" instead of "Transferring ownership" +task(TASK_LZ_OWNABLE_TRANSFER_OWNERSHIP) + .addParam('multisigKey', 'The MultiSig key', undefined, publicKeyType, true) + .setAction(async (args: Args, hre) => { + return hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: configureOwnable }) + }) diff --git a/examples/oft-solana-composer-library/tasks/evm/send.ts b/examples/oft-solana-composer-library/tasks/evm/send.ts new file mode 100644 index 000000000..2253e6314 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/evm/send.ts @@ -0,0 +1,60 @@ +import bs58 from 'bs58' +import { BigNumber } from 'ethers' +import { task, types } from 'hardhat/config' +import { ActionType, HardhatRuntimeEnvironment } from 'hardhat/types' + +import { makeBytes32 } from '@layerzerolabs/devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { getLayerZeroScanLink } from '../solana' + +interface TaskArguments { + dstEid: number + amount: string + to: string + contractName: string +} + +const action: ActionType = async ( + { dstEid, amount, to, contractName }, + hre: HardhatRuntimeEnvironment +) => { + const signer = await hre.ethers.getNamedSigner('deployer') + // @ts-ignore + const token = (await hre.ethers.getContract(contractName)).connect(signer) + + // if (isSepolia(hre.network.name)) { + // // @ts-ignore + // const erc20Token = (await hre.ethers.getContractAt(IERC20, address)).connect(signer) + // const approvalTxResponse = await erc20Token.approve(token.address, amount) + // const approvalTxReceipt = await approvalTxResponse.wait() + // console.log(`approve: ${amount}: ${approvalTxReceipt.transactionHash}`) + // } + + const amountLD = BigNumber.from(amount) + const sendParam = { + dstEid, + to: makeBytes32(bs58.decode(to)), + amountLD: amountLD.toString(), + minAmountLD: amountLD.mul(9_000).div(10_000).toString(), + extraOptions: '0x', + composeMsg: '0x', + oftCmd: '0x', + } + const [msgFee] = await token.functions.quoteSend(sendParam, false) + const txResponse = await token.functions.send(sendParam, msgFee, signer.address, { + value: msgFee.nativeFee, + gasLimit: 500_000, + }) + const txReceipt = await txResponse.wait() + console.log(`send: ${amount} to ${to}: ${txReceipt.transactionHash}`) + console.log( + `Track cross-chain transfer here: ${getLayerZeroScanLink(txReceipt.transactionHash, dstEid == EndpointId.SOLANA_V2_TESTNET)}` + ) +} + +task('send', 'Sends a transaction', action) + .addParam('dstEid', 'Destination endpoint ID', undefined, types.int, false) + .addParam('amount', 'Amount to send in wei', undefined, types.string, false) + .addParam('to', 'Recipient address', undefined, types.string, false) + .addOptionalParam('contractName', 'Name of the contract in deployments folder', 'MyOFT', types.string) diff --git a/examples/oft-solana-composer-library/tasks/index.ts b/examples/oft-solana-composer-library/tasks/index.ts new file mode 100644 index 000000000..cda96e8e6 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/index.ts @@ -0,0 +1,17 @@ +import './common/config.get' +import './common/wire' +import './evm/send' +import './solana/initConfig' +import './solana/createOFT' +import './solana/createOFTAdapter' +import './solana/debug' +import './solana/getRateLimits' +import './solana/retryPayload' +import './solana/sendOFT' +import './solana/setAuthority' +import './solana/updateMetadata' +import './solana/setUpdateAuthority' +import './solana/getPrioFees' +import './solana/base58' +import './solana/setInboundRateLimit' +import './solana/setOutboundRateLimit' diff --git a/examples/oft-solana-composer-library/tasks/solana/base58.ts b/examples/oft-solana-composer-library/tasks/solana/base58.ts new file mode 100644 index 000000000..ea7ed20a4 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/base58.ts @@ -0,0 +1,36 @@ +import assert from 'assert' +import fs from 'fs' +import path from 'path' + +import { Keypair } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' + +interface Base58FeesTaskArgs { + /** + * The path to the keypair file to be used. + */ + keypairFile: string +} + +assert(process.env.HOME != undefined, 'process.env.HOME needs to be defined') + +const defaultKeypairFile = path.resolve(process.env.HOME, '.config/solana/id.json') + +task('lz:solana:base-58', 'Outputs the base58 string for a keypair') + .addParam( + 'keypairFile', + 'The path to the keypair file to be used. Defaults to ~/.config/solana/id.json', + defaultKeypairFile, + devtoolsTypes.string + ) + .setAction(async ({ keypairFile }: Base58FeesTaskArgs) => { + assert(fs.existsSync(keypairFile), `Keypair file not found: ${keypairFile}`) + const data = fs.readFileSync(keypairFile, 'utf8') + const keypairJson = JSON.parse(data) + const keypair = Keypair.fromSecretKey(Uint8Array.from(keypairJson)) + const base58EncodedPrivateKey = bs58.encode(keypair.secretKey) + console.log(`Base58 encoded private key: ${base58EncodedPrivateKey}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/createOFT.ts b/examples/oft-solana-composer-library/tasks/solana/createOFT.ts new file mode 100644 index 000000000..fc9187a72 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/createOFT.ts @@ -0,0 +1,339 @@ +import { + CreateV1InstructionAccounts, + CreateV1InstructionArgs, + TokenStandard, + createV1, + mintV1, +} from '@metaplex-foundation/mpl-token-metadata' +import { AuthorityType, setAuthority } from '@metaplex-foundation/mpl-toolbox' +import { + createNoopSigner, + createSignerFromKeypair, + percentAmount, + publicKey, + transactionBuilder, +} from '@metaplex-foundation/umi' +import { fromWeb3JsPublicKey, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { promptToContinue } from '@layerzerolabs/io-devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OFT_DECIMALS as DEFAULT_SHARED_DECIMALS, oft } from '@layerzerolabs/oft-v2-solana-sdk' + +import { checkMultisigSigners, createMintAuthorityMultisig } from './multisig' +import { assertAccountInitialized } from './utils' + +import { + TransactionType, + addComputeUnitInstructions, + deriveConnection, + deriveKeys, + getExplorerTxLink, + saveSolanaDeployment, +} from './index' + +const DEFAULT_LOCAL_DECIMALS = 9 + +interface CreateOFTTaskArgs { + /** + * The initial supply to mint on solana. + */ + amount: number + + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + + /** + * The number of decimal places to use for the token. + */ + localDecimals: number + + /** + * OFT shared decimals. + */ + sharedDecimals: number + + /** + * The optional token mint ID, for Mint-And-Burn-Adapter only. + */ + mint?: string + + /** + * The name of the token. + */ + name: string + + /** + * The program ID for the OFT program. + */ + programId: string + + /** + * The seller fee basis points. + */ + sellerFeeBasisPoints: number + + /** + * The symbol of the token. + */ + symbol: string + + /** + * Whether the token metadata is mutable. + */ + tokenMetadataIsMutable: boolean + + /** + * The CSV list of additional minters. + */ + additionalMinters?: string[] + + /** + * The token program ID, for Mint-And-Burn-Adapter only. + */ + tokenProgram: string + + /** + * If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in + * losing the ability to mint new tokens for everything but the OFTStore. You should really be intentional about + * using this flag, as it is not reversible. + */ + onlyOftStore: boolean + + /** + * The URI for the token metadata. + */ + uri: string + + computeUnitPriceScaleFactor: number +} + +// Define a Hardhat task for creating OFT on Solana +// * Create the SPL Multisig account for mint authority +// * Mint the new SPL Token +// * Initialize the OFT Store account +// * Set the mint authority to the multisig account. If not in only OFT Store mode, also set the freeze authority to the multisig account. +// Note: Only supports SPL Token Standard. +task('lz:oft:solana:create', 'Mints new SPL Token and creates new OFT Store account') + .addOptionalParam('amount', 'The initial supply to mint on solana', undefined, devtoolsTypes.int) + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addOptionalParam('localDecimals', 'Token local decimals (default=9)', DEFAULT_LOCAL_DECIMALS, devtoolsTypes.int) + .addOptionalParam('sharedDecimals', 'OFT shared decimals (default=6)', DEFAULT_SHARED_DECIMALS, devtoolsTypes.int) + .addParam('name', 'Token Name', 'MockOFT', devtoolsTypes.string) + .addParam('mint', 'The Token mint public key (used for MABA only)', '', devtoolsTypes.string) + .addParam('programId', 'The OFT Program id') + .addParam('sellerFeeBasisPoints', 'Seller fee basis points', 0, devtoolsTypes.int) + .addParam('symbol', 'Token Symbol', 'MOFT', devtoolsTypes.string) + .addParam('tokenMetadataIsMutable', 'Token metadata is mutable', true, devtoolsTypes.boolean) + .addParam('additionalMinters', 'Comma-separated list of additional minters', undefined, devtoolsTypes.csv, true) + .addOptionalParam( + 'onlyOftStore', + 'If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in losing the ability to mint new tokens by everything but the OFTStore.', + false, + devtoolsTypes.boolean + ) + .addParam( + 'tokenProgram', + 'The Token Program public key (used for MABA only)', + TOKEN_PROGRAM_ID.toBase58(), + devtoolsTypes.string + ) + .addParam('uri', 'URI for token metadata', '', devtoolsTypes.string) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction( + async ({ + amount, + eid, + localDecimals: decimals, + sharedDecimals, + mint: mintStr, + name, + programId: programIdStr, + sellerFeeBasisPoints, + symbol, + tokenMetadataIsMutable: isMutable, + additionalMinters: additionalMintersAsStrings, + onlyOftStore, + tokenProgram: tokenProgramStr, + uri, + computeUnitPriceScaleFactor, + }: CreateOFTTaskArgs) => { + const isMABA = !!mintStr // the difference between MABA and OFT Adapter is that MABA uses mint/burn mechanism whereas OFT Adapter uses lock/unlock mechanism + if (tokenProgramStr !== TOKEN_PROGRAM_ID.toBase58() && !isMABA) { + throw new Error('Non-Mint-And-Burn-Adapter does not support custom token programs') + } + if (isMABA && amount) { + throw new Error('Mint-And-Burn-Adapter does not support minting tokens') + } + if (decimals < sharedDecimals) { + throw new Error('Solana token local decimals must be greater than or equal to OFT shared decimals') + } + const tokenProgramId = publicKey(tokenProgramStr) + const { connection, umi, umiWalletKeyPair, umiWalletSigner } = await deriveConnection(eid) + const { programId, lockBox, escrowPK, oftStorePda, eddsa } = deriveKeys(programIdStr) + if (!additionalMintersAsStrings) { + if (!onlyOftStore) { + throw new Error( + 'If you want to proceed with only the OFT Store having the ability to mint, please specify --only-oft-store true. Note that this also means the Freeze Authority will be immediately renounced.' + ) + } + } + + if (onlyOftStore) { + const continueWithOnlyOftStore = await promptToContinue( + 'You have chosen `--only-oft-store true`. This means that only the OFT Store will be able to mint new tokens and that the Freeze Authority will be immediately renounced. Continue?' + ) + if (!continueWithOnlyOftStore) { + return + } + } + + const additionalMinters = additionalMintersAsStrings?.map((minter) => new PublicKey(minter)) ?? [] + + let mintAuthorityPublicKey: PublicKey = toWeb3JsPublicKey(oftStorePda) // we default to the OFT Store as the Mint Authority when there are no additional minters + + if (additionalMintersAsStrings) { + // we only need a multisig when we have additional minters + mintAuthorityPublicKey = await createMintAuthorityMultisig( + connection, + umi, + eid, + umiWalletSigner, + toWeb3JsPublicKey(oftStorePda), + toWeb3JsPublicKey(tokenProgramId), // Only configurable for MABA + additionalMinters, + computeUnitPriceScaleFactor + ) + console.log(`created SPL multisig @ ${mintAuthorityPublicKey.toBase58()}`) + await checkMultisigSigners(connection, mintAuthorityPublicKey, [ + toWeb3JsPublicKey(oftStorePda), + ...additionalMinters, + ]) + } + + const mint = isMABA + ? createNoopSigner(publicKey(mintStr)) + : createSignerFromKeypair(umi, eddsa.generateKeypair()) + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + if (!isMABA) { + const createV1Args: CreateV1InstructionAccounts & CreateV1InstructionArgs = { + mint, + name, + symbol, + decimals, + uri, + isMutable, + sellerFeeBasisPoints: percentAmount(sellerFeeBasisPoints), + authority: umiWalletSigner, // authority is transferred later + tokenStandard: TokenStandard.Fungible, + } + let txBuilder = transactionBuilder().add(createV1(umi, createV1Args)) + if (amount) { + // recreate txBuilder since it is immutable + txBuilder = transactionBuilder() + .add(txBuilder) + .add( + mintV1(umi, { + ...createV1Args, + mint: publicKey(createV1Args.mint), + authority: umiWalletSigner, + amount, + tokenOwner: umiWalletSigner.publicKey, + tokenStandard: TokenStandard.Fungible, + }) + ) + } + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.CreateToken + ) + const createTokenTx = await txBuilder.sendAndConfirm(umi) + await assertAccountInitialized(connection, toWeb3JsPublicKey(mint.publicKey)) + console.log(`createTokenTx: ${getExplorerTxLink(bs58.encode(createTokenTx.signature), isTestnet)}`) + } + + const lockboxSigner = createSignerFromKeypair({ eddsa: eddsa }, lockBox) + let txBuilder = transactionBuilder().add( + oft.initOft( + { + payer: umiWalletSigner, + admin: umiWalletKeyPair.publicKey, + mint: mint.publicKey, + escrow: lockboxSigner, + }, + oft.types.OFTType.Native, + sharedDecimals, + { + oft: programId, + token: tokenProgramId, + } + ) + ) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.InitOft + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log(`initOftTx: ${getExplorerTxLink(bs58.encode(signature), isTestnet)}`) + + if (!isMABA) { + let txBuilder = transactionBuilder() + .add( + setAuthority(umi, { + owned: mint.publicKey, + owner: umiWalletSigner, + newAuthority: fromWeb3JsPublicKey(mintAuthorityPublicKey), + authorityType: AuthorityType.MintTokens, + }) + ) + .add( + setAuthority(umi, { + owned: mint.publicKey, + owner: umiWalletSigner, + newAuthority: onlyOftStore ? null : fromWeb3JsPublicKey(mintAuthorityPublicKey), + authorityType: AuthorityType.FreezeAccount, + }) + ) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.SetAuthority + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log(`setAuthorityTx: ${getExplorerTxLink(bs58.encode(signature), isTestnet)}`) + } + if (isMABA) { + console.log( + `Please note that for MABA mode, you must carry out the change of Mint Authority before making any cross-chain transfers. For more details: https://github.com/LayerZero-Labs/devtools/tree/main/examples/oft-solana#for-oft-mint-and-burn-adapter-maba` + ) + } + saveSolanaDeployment( + eid, + programIdStr, + mint.publicKey, + mintAuthorityPublicKey.toBase58(), + escrowPK, + oftStorePda + ) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/createOFTAdapter.ts b/examples/oft-solana-composer-library/tasks/solana/createOFTAdapter.ts new file mode 100644 index 000000000..219cdf4d9 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/createOFTAdapter.ts @@ -0,0 +1,106 @@ +import { createSignerFromKeypair, publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import { TOKEN_PROGRAM_ID, getMint } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OFT_DECIMALS, oft } from '@layerzerolabs/oft-v2-solana-sdk' + +import { + TransactionType, + addComputeUnitInstructions, + deriveConnection, + deriveKeys, + getExplorerTxLink, + saveSolanaDeployment, +} from './index' + +interface CreateOFTAdapterTaskArgs { + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + + /** + * The token mint public key. + */ + mint: string + + /** + * The OFT Program id. + */ + programId: string + + /** + * The Token Program public key. + */ + tokenProgram: string + + computeUnitPriceScaleFactor: number +} + +// Define a Hardhat task for creating OFTAdapter on Solana +task('lz:oft-adapter:solana:create', 'Creates new OFT Adapter (OFT Store PDA)') + .addParam('mint', 'The Token Mint public key') + .addParam('programId', 'The OFT program ID') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('tokenProgram', 'The Token Program public key', TOKEN_PROGRAM_ID.toBase58(), devtoolsTypes.string, true) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction( + async ({ + eid, + mint: mintStr, + programId: programIdStr, + tokenProgram: tokenProgramStr, + computeUnitPriceScaleFactor, + }: CreateOFTAdapterTaskArgs) => { + const { connection, umi, umiWalletKeyPair, umiWalletSigner } = await deriveConnection(eid) + const { programId, lockBox, escrowPK, oftStorePda, eddsa } = deriveKeys(programIdStr) + + const tokenProgram = publicKey(tokenProgramStr) + const mint = publicKey(mintStr) + + const mintPDA = await getMint(connection, new PublicKey(mintStr), undefined, new PublicKey(tokenProgramStr)) + + const mintAuthority = mintPDA.mintAuthority + + let txBuilder = transactionBuilder().add( + oft.initOft( + { + payer: createSignerFromKeypair({ eddsa: eddsa }, umiWalletKeyPair), + admin: umiWalletKeyPair.publicKey, + mint: mint, + escrow: createSignerFromKeypair({ eddsa: eddsa }, lockBox), + }, + oft.types.OFTType.Adapter, + OFT_DECIMALS, + { + oft: programId, + token: tokenProgram ? publicKey(tokenProgram) : undefined, + } + ) + ) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.InitOft + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log(`initOftTx: ${getExplorerTxLink(bs58.encode(signature), eid == EndpointId.SOLANA_V2_TESTNET)}`) + + saveSolanaDeployment( + eid, + programIdStr, + mint, + mintAuthority ? mintAuthority.toBase58() : '', + escrowPK, + oftStorePda + ) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/debug.ts b/examples/oft-solana-composer-library/tasks/solana/debug.ts new file mode 100644 index 000000000..a2a50c613 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/debug.ts @@ -0,0 +1,281 @@ +import { fetchMint } from '@metaplex-foundation/mpl-toolbox' +import { publicKey, unwrapOption } from '@metaplex-foundation/umi' +import { toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Keypair, PublicKey } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import { OmniPoint, denormalizePeer } from '@layerzerolabs/devtools' +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId, getNetworkForChainId } from '@layerzerolabs/lz-definitions' +import { EndpointPDADeriver, EndpointProgram } from '@layerzerolabs/lz-solana-sdk-v2' +import { OftPDA, oft } from '@layerzerolabs/oft-v2-solana-sdk' +import { EndpointV2 } from '@layerzerolabs/protocol-devtools-solana' + +import { getSolanaReceiveConfig, getSolanaSendConfig } from '../common/taskHelper' +import { DebugLogger, createSolanaConnectionFactory, decodeLzReceiveOptions, uint8ArrayToHex } from '../common/utils' + +import { deriveConnection, getSolanaDeployment } from './index' + +const DEBUG_ACTIONS = { + OFT_STORE: 'oft-store', + GET_ADMIN: 'admin', + GET_DELEGATE: 'delegate', + CHECKS: 'checks', + GET_TOKEN: 'token', + GET_PEERS: 'peers', +} + +/** + * Get the OFTStore account from the task arguments, the deployment file, or throw an error. + * @param {EndpointId} eid + * @param {string} oftStore + */ +const getOftStore = (eid: EndpointId, oftStore?: string) => publicKey(oftStore ?? getSolanaDeployment(eid).oftStore) + +type DebugTaskArgs = { + eid: EndpointId + oftStore?: string + endpoint: string + dstEids: EndpointId[] + action?: string +} + +task('lz:oft:solana:debug', 'Manages OFTStore and OAppRegistry information') + .addParam( + 'eid', + 'Solana mainnet (30168) or testnet (40168). Defaults to mainnet.', + EndpointId.SOLANA_V2_MAINNET, + types.eid + ) + .addParam( + 'oftStore', + 'The OFTStore public key. Derived from deployments if not provided.', + undefined, + types.string, + true + ) + .addParam('endpoint', 'The Endpoint public key', EndpointProgram.PROGRAM_ID.toBase58(), types.string) + .addOptionalParam('dstEids', 'Destination eids to check (comma-separated list)', [], types.csv) + .addOptionalParam( + 'action', + `The action to perform: ${Object.keys(DEBUG_ACTIONS).join(', ')} (defaults to all)`, + undefined, + types.string + ) + .setAction(async (taskArgs: DebugTaskArgs) => { + const { eid, oftStore: oftStoreArg, endpoint, dstEids, action } = taskArgs + const { umi, connection } = await deriveConnection(eid, true) + const oftStore = getOftStore(eid, oftStoreArg) + + let oftStoreInfo + try { + oftStoreInfo = await oft.accounts.fetchOFTStore(umi, oftStore) + } catch (e) { + console.error(`Failed to fetch OFTStore at ${oftStore.toString()}:`, e) + return + } + + const mintAccount = await fetchMint(umi, publicKey(oftStoreInfo.tokenMint)) + + const epDeriver = new EndpointPDADeriver(new PublicKey(endpoint)) + const [oAppRegistry] = epDeriver.oappRegistry(toWeb3JsPublicKey(oftStore)) + const oAppRegistryInfo = await EndpointProgram.accounts.OAppRegistry.fromAccountAddress( + connection, + oAppRegistry + ) + + if (!oAppRegistryInfo) { + console.warn('OAppRegistry info not found.') + return + } + + const oftDeriver = new OftPDA(oftStoreInfo.header.owner) + + const printOftStore = async () => { + DebugLogger.header('OFT Store Information') + DebugLogger.keyValue('Owner', oftStoreInfo.header.owner) + DebugLogger.keyValue('OFT Type', oft.types.OFTType[oftStoreInfo.oftType]) + DebugLogger.keyValue('Admin', oftStoreInfo.admin) + DebugLogger.keyValue('Token Mint', oftStoreInfo.tokenMint) + DebugLogger.keyValue('Token Escrow', oftStoreInfo.tokenEscrow) + DebugLogger.keyValue('Endpoint Program', oftStoreInfo.endpointProgram) + DebugLogger.separator() + } + + const printAdmin = async () => { + const admin = oftStoreInfo.admin + DebugLogger.keyValue('Admin', admin) + } + + const printDelegate = async () => { + const delegate = oAppRegistryInfo?.delegate?.toBase58() + DebugLogger.header('OApp Registry Information') + DebugLogger.keyValue('Delegate', delegate) + DebugLogger.separator() + } + + const printToken = async () => { + DebugLogger.header('Token Information') + DebugLogger.keyValue('Mint Authority', unwrapOption(mintAccount.mintAuthority)) + DebugLogger.keyValue( + 'Freeze Authority', + unwrapOption(mintAccount.freezeAuthority, () => 'None') + ) + DebugLogger.separator() + } + + const printChecks = async () => { + const delegate = oAppRegistryInfo?.delegate?.toBase58() + + DebugLogger.header('Checks') + DebugLogger.keyValue('Admin (Owner) same as Delegate', oftStoreInfo.admin === delegate) + DebugLogger.keyValue( + 'Token Mint Authority is OFT Store', + unwrapOption(mintAccount.mintAuthority) === oftStore + ) + DebugLogger.separator() + } + + const printPeerConfigs = async () => { + const peerConfigs = dstEids.map((dstEid) => { + const peerConfig = oftDeriver.peer(oftStore, dstEid) + return publicKey(peerConfig) + }) + const mockKeypair = new Keypair() + const point: OmniPoint = { + eid, + address: oftStore.toString(), + } + const endpointV2Sdk = new EndpointV2( + await createSolanaConnectionFactory()(eid), + point, + mockKeypair.publicKey // doesn't matter as we are not sending transactions + ) + + DebugLogger.header('Peer Configurations') + + const peerConfigInfos = await oft.accounts.safeFetchAllPeerConfig(umi, peerConfigs) + for (let index = 0; index < dstEids.length; index++) { + const dstEid = dstEids[index] + const info = peerConfigInfos[index] + const network = getNetworkForChainId(dstEid) + const oAppReceiveConfig = await getSolanaReceiveConfig(endpointV2Sdk, dstEid, oftStore) + const oAppSendConfig = await getSolanaSendConfig(endpointV2Sdk, dstEid, oftStore) + + // Show the chain info + DebugLogger.header(`${dstEid} (${network.chainName})`) + + if (info) { + // Existing PeerConfig info + DebugLogger.keyValue('PeerConfig Account', peerConfigs[index].toString()) + DebugLogger.keyValue('Peer Address', denormalizePeer(info.peerAddress, dstEid)) + DebugLogger.keyHeader('Enforced Options') + DebugLogger.keyValue( + 'Send', + decodeLzReceiveOptions(uint8ArrayToHex(info.enforcedOptions.send, true)), + 2 + ) + DebugLogger.keyValue( + 'SendAndCall', + decodeLzReceiveOptions(uint8ArrayToHex(info.enforcedOptions.sendAndCall, true)), + 2 + ) + + printOAppReceiveConfigs(oAppReceiveConfig, network.chainName) + printOAppSendConfigs(oAppSendConfig, network.chainName) + } else { + // No PeerConfig account + console.log(`No PeerConfig account found for ${dstEid} (${network.chainName}).`) + } + + DebugLogger.separator() + } + } + if (action) { + switch (action) { + case DEBUG_ACTIONS.OFT_STORE: + await printOftStore() + break + case DEBUG_ACTIONS.GET_ADMIN: + await printAdmin() + break + case DEBUG_ACTIONS.GET_DELEGATE: + await printDelegate() + break + case DEBUG_ACTIONS.CHECKS: + await printChecks() + break + case DEBUG_ACTIONS.GET_TOKEN: + await printToken() + break + case DEBUG_ACTIONS.GET_PEERS: + await printPeerConfigs() + break + default: + console.error(`Invalid action specified. Use any of ${Object.keys(DEBUG_ACTIONS)}.`) + } + } else { + await printOftStore() + await printDelegate() + await printToken() + if (dstEids.length > 0) await printPeerConfigs() + await printChecks() + } + }) + +function printOAppReceiveConfigs( + oAppReceiveConfig: Awaited>, + peerChainName: string +) { + const oAppReceiveConfigIndexesToKeys: Record = { + 0: 'receiveLibrary', + 1: 'receiveUlnConfig', + 2: 'receiveLibraryTimeoutConfig', + } + + if (!oAppReceiveConfig) { + console.log('No receive configs found.') + return + } + + DebugLogger.keyValue(`Receive Configs (${peerChainName} to solana)`, '') + for (let i = 0; i < oAppReceiveConfig.length; i++) { + const item = oAppReceiveConfig[i] + if (typeof item === 'object' && item !== null) { + // Print each property in the object + DebugLogger.keyValue(`${oAppReceiveConfigIndexesToKeys[i]}`, '', 2) + for (const [propKey, propVal] of Object.entries(item)) { + DebugLogger.keyValue(`${propKey}`, String(propVal), 3) + } + } else { + // Print a primitive (string, number, etc.) + DebugLogger.keyValue(`${oAppReceiveConfigIndexesToKeys[i]}`, String(item), 2) + } + } +} + +function printOAppSendConfigs(oAppSendConfig: Awaited>, peerChainName: string) { + const sendOappConfigIndexesToKeys: Record = { + 0: 'sendLibrary', + 1: 'sendUlnConfig', + 2: 'sendExecutorConfig', + } + + if (!oAppSendConfig) { + console.log('No send configs found.') + return + } + + DebugLogger.keyValue(`Send Configs (solana to ${peerChainName})`, '') + for (let i = 0; i < oAppSendConfig.length; i++) { + const item = oAppSendConfig[i] + if (typeof item === 'object' && item !== null) { + DebugLogger.keyValue(`${sendOappConfigIndexesToKeys[i]}`, '', 2) + for (const [propKey, propVal] of Object.entries(item)) { + DebugLogger.keyValue(`${propKey}`, String(propVal), 3) + } + } else { + DebugLogger.keyValue(`${sendOappConfigIndexesToKeys[i]}`, String(item), 2) + } + } +} diff --git a/examples/oft-solana-composer-library/tasks/solana/getPrioFees.ts b/examples/oft-solana-composer-library/tasks/solana/getPrioFees.ts new file mode 100644 index 000000000..d3dff02c8 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getPrioFees.ts @@ -0,0 +1,33 @@ +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import getPrioritizationFees from '../utils/getFee' + +import { deriveConnection } from './index' + +interface GetPrioFeesTaskArgs { + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + /** + * The program ID or account address that will be written to. + */ + address: string +} + +task('lz:solana:get-priority-fees', 'Fetches prioritization fees from the Solana network') + .addParam('eid', 'The endpoint ID for the Solana network', undefined, devtoolsTypes.eid) + .addOptionalParam( + 'address', + 'The address (program ID or account address) that will be written to', + undefined, + devtoolsTypes.string + ) + .setAction(async ({ eid, address }: GetPrioFeesTaskArgs) => { + const { connection } = await deriveConnection(eid) + const fees = await getPrioritizationFees(connection, address) + console.log('Prioritization Fees:', fees) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/getRateLimits.ts b/examples/oft-solana-composer-library/tasks/solana/getRateLimits.ts new file mode 100644 index 000000000..4448ad671 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getRateLimits.ts @@ -0,0 +1,35 @@ +import { mplToolbox } from '@metaplex-foundation/mpl-toolbox' +import { publicKey } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA, accounts } from '@layerzerolabs/oft-v2-solana-sdk' + +import { createSolanaConnectionFactory } from '../common/utils' + +interface Args { + mint: string + eid: EndpointId + dstEid: EndpointId + programId: string + oftStore: string +} + +task('lz:oft:solana:get-rate-limits', 'Gets the Solana inbound / outbound rate limits') + .addParam('mint', 'The OFT token mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, types.eid) + .addParam('dstEid', 'The destination endpoint ID', undefined, types.eid) + .addParam('oftStore', 'The OFTStore account') + .setAction(async (taskArgs: Args, _) => { + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + + const [peer] = new OftPDA(publicKey(taskArgs.programId)).peer(publicKey(taskArgs.oftStore), taskArgs.dstEid) + const peerInfo = await accounts.fetchPeerConfig({ rpc: umi.rpc }, peer) + console.log(`Peer info between ${taskArgs.eid} and ${taskArgs.dstEid}`) + console.dir({ peerInfo }, { depth: null }) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/index.ts b/examples/oft-solana-composer-library/tasks/solana/index.ts new file mode 100644 index 000000000..df3b1a58b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/index.ts @@ -0,0 +1,391 @@ +import assert from 'assert' +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import path from 'node:path' + +import { + fetchAddressLookupTable, + mplToolbox, + setComputeUnitLimit, + setComputeUnitPrice, +} from '@metaplex-foundation/mpl-toolbox' +import { + AddressLookupTableInput, + EddsaInterface, + Instruction, + KeypairSigner, + PublicKey, + TransactionBuilder, + Umi, + createSignerFromKeypair, + publicKey, + signerIdentity, + transactionBuilder, +} from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { createWeb3JsEddsa } from '@metaplex-foundation/umi-eddsa-web3js' +import { toWeb3JsInstruction, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { AddressLookupTableAccount, Connection, Keypair } from '@solana/web3.js' +import { getKeypairFromEnvironment, getKeypairFromFile, getSimulationComputeUnits } from '@solana-developers/helpers' +import { backOff } from 'exponential-backoff' + +import { formatEid } from '@layerzerolabs/devtools' +import { createLogger, promptToContinue } from '@layerzerolabs/io-devtools' +import { EndpointId, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { OftPDA } from '@layerzerolabs/oft-v2-solana-sdk' + +import { DebugLogger, KnownErrors, createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +const LOOKUP_TABLE_ADDRESS: Partial> = { + [EndpointId.SOLANA_V2_MAINNET]: publicKey('AokBxha6VMLLgf97B5VYHEtqztamWmYERBmmFvjuTzJB'), + [EndpointId.SOLANA_V2_TESTNET]: publicKey('9thqPdbR27A1yLWw2spwJLySemiGMXxPnEvfmXVk4KuK'), +} + +/** + * Extracts the value of the given environment variable. + * @todo consider removing this since it's no longer used (replaced by using @solana-developers/helpers) + * @param key The name of the environment variable to extract. + * @param optional Whether the environment variable is optional or not. + * If it is, the function will return undefined if the variable is not defined. + * Otherwise, it will throw an error if the variable is not defined. + * @returns The value of the environment variable, or undefined if optional and not defined. + */ +const getFromEnv = (key: string, optional = false): string | undefined => { + const value = process.env[key] + if (!value && !optional) { + throw new Error(`${key} is not defined in the environment variables.`) + } + return value +} + +// create a safe version of getKeypairFromFile that returns undefined if the file does not exist, for checking the default keypair +async function safeGetKeypairDefaultPath(filePath?: string) { + try { + return await getKeypairFromFile(filePath) + } catch (error) { + // If the error is due to the file not existing, return undefined + if (error instanceof Error && error.message.includes('Could not read keypair')) { + return undefined + } + throw error // Rethrow if it's a different error + } +} + +// TODO in another PR: consider moving keypair related functions to tasks/solana/utils.ts +async function getSolanaKeypair(readOnly = false): Promise { + const logger = createLogger() + + // Early exit if read-only: ephemeral Keypair is enough. + if (readOnly) { + logger.info('Read-only mode: Using ephemeral (randomly generated) keypair.') + return Keypair.generate() + } + + // Attempt to load from each source + const keypairEnvPrivate = process.env.SOLANA_PRIVATE_KEY + ? getKeypairFromEnvironment('SOLANA_PRIVATE_KEY') + : undefined // #1 SOLANA_PRIVATE_KEY + const keypairEnvPath = process.env.SOLANA_KEYPAIR_PATH + ? await getKeypairFromFile(process.env.SOLANA_KEYPAIR_PATH) + : undefined // #2 SOLANA_KEYPAIR_PATH + const keypairDefaultPath = await safeGetKeypairDefaultPath() // #3 ~/.config/solana/id.json + + // Throw if no keypair is found via all 3 methods + if (!keypairEnvPrivate && !keypairEnvPath && !keypairDefaultPath) { + throw new Error( + 'No Solana keypair found. Provide SOLANA_PRIVATE_KEY, ' + + 'SOLANA_KEYPAIR_PATH, or place a valid keypair at ~/.config/solana/id.json.' + ) + } + + // If both environment-based keys exist, ensure they match + if (keypairEnvPrivate && keypairEnvPath) { + if (keypairEnvPrivate.publicKey.equals(keypairEnvPath.publicKey)) { + logger.info('Both SOLANA_PRIVATE_KEY and SOLANA_KEYPAIR_PATH match. Using environment-based keypair.') + return keypairEnvPrivate + } else { + throw new Error( + `Conflict: SOLANA_PRIVATE_KEY and SOLANA_KEYPAIR_PATH are different keypairs.\n` + + `Path: ${process.env.SOLANA_KEYPAIR_PATH} => ${keypairEnvPath.publicKey.toBase58()}\n` + + `Env : ${keypairEnvPrivate.publicKey.toBase58()}` + ) + } + } + + // If exactly one environment-based keypair is found, use it immediately + if (keypairEnvPrivate) { + logger.info(`Using Solana keypair from SOLANA_PRIVATE_KEY => ${keypairEnvPrivate.publicKey.toBase58()}`) + return keypairEnvPrivate + } + + if (keypairEnvPath) { + logger.info( + `Using Solana keypair from SOLANA_KEYPAIR_PATH (${process.env.SOLANA_KEYPAIR_PATH}) => ${keypairEnvPath.publicKey.toBase58()}` + ) + return keypairEnvPath + } + + // Otherwise, default path is the last fallback + logger.info( + `No environment-based keypair found. Found keypair at default path => ${keypairDefaultPath.publicKey.toBase58()}` + ) + const doContinue = await promptToContinue( + `Defaulting to ~/.config/solana/id.json with address ${keypairDefaultPath.publicKey.toBase58()}. Use this keypair?` + ) + if (!doContinue) process.exit(1) + + return keypairDefaultPath +} + +/** + * Derive common connection and UMI objects for a given endpoint ID. + * @param eid {EndpointId} + */ +export const deriveConnection = async (eid: EndpointId, readOnly = false) => { + const keypair = await getSolanaKeypair(readOnly) + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(eid) + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(keypair.secretKey) + const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair) + umi.use(signerIdentity(umiWalletSigner)) + return { + connection, + umi, + umiWalletKeyPair, + umiWalletSigner, + } +} + +export const useWeb3Js = async () => { + // note: if we are okay with exporting getSolanaKeypair, then useWeb3js can be removed + const keypair = await getSolanaKeypair() + return { + web3JsKeypair: keypair, + } +} + +/** + * Derive the keys needed for the OFT program. + * @param programIdStr {string} + */ +export const deriveKeys = (programIdStr: string) => { + const programId = publicKey(programIdStr) + const eddsa: EddsaInterface = createWeb3JsEddsa() + const oftDeriver = new OftPDA(programId) + const lockBox = eddsa.generateKeypair() + const escrowPK = lockBox.publicKey + const [oftStorePda] = oftDeriver.oftStore(escrowPK) + return { + programId, + lockBox, + escrowPK, + oftStorePda, + eddsa, + } +} + +/** + * Outputs the OFT accounts to a JSON file. + * @param eid {EndpointId} + * @param programId {string} + * @param mint {string} + * @param mintAuthority {string} + * @param escrow {string} + * @param oftStore {string} + */ +export const saveSolanaDeployment = ( + eid: EndpointId, + programId: string, + mint: string, + mintAuthority: string, + escrow: string, + oftStore: string +) => { + const outputDir = `./deployments/${endpointIdToNetwork(eid)}` + if (!existsSync(outputDir)) { + mkdirSync(outputDir, { recursive: true }) + } + writeFileSync( + `${outputDir}/OFT.json`, + JSON.stringify( + { + programId, + mint, + mintAuthority, + escrow, + oftStore, + }, + null, + 4 + ) + ) + console.log(`Accounts have been saved to ${outputDir}/OFT.json`) +} + +/** + * Reads the OFT deployment info from disk for the given endpoint ID. + * @param eid {EndpointId} + * @returns The contents of the OFT.json file as a JSON object. + */ +export const getSolanaDeployment = ( + eid: EndpointId +): { + programId: string + mint: string + mintAuthority: string + escrow: string + oftStore: string +} => { + if (!eid) { + throw new Error('eid is required') + } + const outputDir = path.join('deployments', endpointIdToNetwork(eid)) + const filePath = path.join(outputDir, 'OFT.json') // Note: if you have multiple deployments, change this filename to refer to the desired deployment file + + if (!existsSync(filePath)) { + DebugLogger.printErrorAndFixSuggestion(KnownErrors.SOLANA_DEPLOYMENT_NOT_FOUND) + throw new Error(`Could not find Solana deployment file for eid ${eid} at: ${filePath}`) + } + + const fileContents = readFileSync(filePath, 'utf-8') + return JSON.parse(fileContents) +} + +export const getOftStoreAddress = (eid: EndpointId) => { + const { oftStore } = getSolanaDeployment(eid) + if (!oftStore) { + throw new Error('oftStore not defined in the deployment file') + } + return oftStore +} + +// TODO: move below outside of solana folder since it's generic +export const getLayerZeroScanLink = (hash: string, isTestnet = false) => + isTestnet ? `https://testnet.layerzeroscan.com/tx/${hash}` : `https://layerzeroscan.com/tx/${hash}` + +export const getExplorerTxLink = (hash: string, isTestnet = false) => + `https://solscan.io/tx/${hash}?cluster=${isTestnet ? 'devnet' : 'mainnet-beta'}` + +export const getAddressLookupTable = async (connection: Connection, umi: Umi, fromEid: EndpointId) => { + // Lookup Table Address and Priority Fee Calculation + const lookupTableAddress = LOOKUP_TABLE_ADDRESS[fromEid] + assert(lookupTableAddress != null, `No lookup table found for ${formatEid(fromEid)}`) + const addressLookupTableInput: AddressLookupTableInput = await fetchAddressLookupTable(umi, lookupTableAddress) + if (!addressLookupTableInput) { + throw new Error(`No address lookup table found for ${lookupTableAddress}`) + } + const { value: lookupTableAccount } = await connection.getAddressLookupTable(toWeb3JsPublicKey(lookupTableAddress)) + if (!lookupTableAccount) { + throw new Error(`No address lookup table account found for ${lookupTableAddress}`) + } + return { + lookupTableAddress, + addressLookupTableInput, + lookupTableAccount, + } +} + +export enum TransactionType { + CreateToken = 'CreateToken', + CreateMultisig = 'CreateMultisig', + InitOft = 'InitOft', + SetAuthority = 'SetAuthority', + InitConfig = 'InitConfig', + SendOFT = 'SendOFT', +} + +const TransactionCuEstimates: Record = { + // for the sample values, they are: devnet, mainnet + [TransactionType.CreateToken]: 125_000, // actual sample: (59073, 123539), 55785 (more volatile as it has CPI to Metaplex) + [TransactionType.CreateMultisig]: 5_000, // actual sample: 3,230 + [TransactionType.InitOft]: 70_000, // actual sample: 59207, 65198 (note: this is the only transaction that createOFTAdapter does) + [TransactionType.SetAuthority]: 8_000, // actual sample: 6424, 6472 + [TransactionType.InitConfig]: 42_000, // actual sample: 33157, 40657 + [TransactionType.SendOFT]: 230_000, // actual sample: 217,784 +} + +export const getComputeUnitPriceAndLimit = async ( + connection: Connection, + ixs: Instruction[], + wallet: KeypairSigner, + lookupTableAccount: AddressLookupTableAccount, + transactionType: TransactionType +) => { + const { averageFeeExcludingZeros } = await getFee(connection) + const priorityFee = Math.round(averageFeeExcludingZeros) + const computeUnitPrice = BigInt(priorityFee) + + let computeUnits + + try { + computeUnits = await backOff( + () => + getSimulationComputeUnits( + connection, + ixs.map((ix) => toWeb3JsInstruction(ix)), + toWeb3JsPublicKey(wallet.publicKey), + [lookupTableAccount] + ), + { + maxDelay: 10000, + numOfAttempts: 3, + } + ) + } catch (e) { + console.error(`Error retrieving simulations compute units from RPC:`, e) + const continueByUsingHardcodedEstimate = await promptToContinue( + 'Failed to call simulateTransaction on the RPC. This can happen when the network is congested. Would you like to use hardcoded estimates (TransactionCuEstimates) ? This may result in slightly overpaying for the transaction.' + ) + if (!continueByUsingHardcodedEstimate) { + throw new Error( + 'Failed to call simulateTransaction on the RPC and user chose to not continue with hardcoded estimate.' + ) + } + console.log( + `Falling back to hardcoded estimate for ${transactionType}: ${TransactionCuEstimates[transactionType]} CUs` + ) + computeUnits = TransactionCuEstimates[transactionType] + } + + if (!computeUnits) { + throw new Error('Unable to compute units') + } + + return { + computeUnitPrice, + computeUnits, + } +} + +export const addComputeUnitInstructions = async ( + connection: Connection, + umi: Umi, + eid: EndpointId, + txBuilder: TransactionBuilder, + umiWalletSigner: KeypairSigner, + computeUnitPriceScaleFactor: number, + transactionType: TransactionType +) => { + const computeUnitLimitScaleFactor = 1.1 // hardcoded to 1.1 as the estimations are not perfect and can fall slightly short of the actual CU usage on-chain + const { addressLookupTableInput, lookupTableAccount } = await getAddressLookupTable(connection, umi, eid) + const { computeUnitPrice, computeUnits } = await getComputeUnitPriceAndLimit( + connection, + txBuilder.getInstructions(), + umiWalletSigner, + lookupTableAccount, + transactionType + ) + // Since transaction builders are immutable, we must be careful to always assign the result of the add and prepend + // methods to a new variable. + const newTxBuilder = transactionBuilder() + .add( + setComputeUnitPrice(umi, { + microLamports: computeUnitPrice * BigInt(Math.floor(computeUnitPriceScaleFactor)), + }) + ) + .add(setComputeUnitLimit(umi, { units: computeUnits * computeUnitLimitScaleFactor })) + .setAddressLookupTables([addressLookupTableInput]) + .add(txBuilder) + return newTxBuilder +} diff --git a/examples/oft-solana-composer-library/tasks/solana/initConfig.ts b/examples/oft-solana-composer-library/tasks/solana/initConfig.ts new file mode 100644 index 000000000..202d18a36 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/initConfig.ts @@ -0,0 +1,31 @@ +import { PublicKey } from '@solana/web3.js' +import { ConfigurableTaskDefinition } from 'hardhat/types' + +import { inheritTask } from '@layerzerolabs/devtools-evm-hardhat' +import { type LogLevel } from '@layerzerolabs/io-devtools' +import { type OAppConfigurator } from '@layerzerolabs/ua-devtools' +import { TASK_LZ_OAPP_WIRE } from '@layerzerolabs/ua-devtools-evm-hardhat' +import { initOFTAccounts } from '@layerzerolabs/ua-devtools-solana' + +// We'll create clones of the wire task and only override the configurator argument +const wireLikeTask = inheritTask(TASK_LZ_OAPP_WIRE) + +// TODO: export from wire.ts instead of re-declaring +/** + * Additional CLI arguments for our custom wire task + */ +interface Args { + logLevel: LogLevel + multisigKey?: PublicKey + internalConfigurator?: OAppConfigurator +} + +// This task will use the `initOFTAccounts` configurator that initializes the Solana accounts +const initConfigTask = wireLikeTask('lz:oft:solana:init-config') as ConfigurableTaskDefinition + +// TODO: currently the message for 'already done' state is "OApp is already wired." which is misleading -> should be changed to "Pathway Config already initialized" +initConfigTask + .setDescription('Initialize OFT accounts for Solana') + .setAction(async (args: Args, hre) => + hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: initOFTAccounts, isSolanaInitConfig: true }) + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/multisig.ts b/examples/oft-solana-composer-library/tasks/solana/multisig.ts new file mode 100644 index 000000000..2031a1a67 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/multisig.ts @@ -0,0 +1,158 @@ +import { createAccount, initializeMultisig } from '@metaplex-foundation/mpl-toolbox' +import { + KeypairSigner, + Umi, + createSignerFromKeypair, + transactionBuilder, + publicKey as umiPublicKey, +} from '@metaplex-foundation/umi' +import { toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { MULTISIG_SIZE, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Connection, PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' + +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { assertAccountInitialized } from './utils' + +import { TransactionType, addComputeUnitInstructions, getExplorerTxLink } from '.' + +export async function createMultisig( + connection: Connection, + umi: Umi, + eid: EndpointId, + umiWalletSigner: KeypairSigner, + signers: PublicKey[], + m: number, + keypair = umi.eddsa.generateKeypair(), + programId = TOKEN_PROGRAM_ID, + computeUnitPriceScaleFactor?: number +): Promise { + let txBuilder = transactionBuilder() + .add( + createAccount(umi, { + newAccount: createSignerFromKeypair(umi, keypair), + lamports: await umi.rpc.getRent(MULTISIG_SIZE), + space: MULTISIG_SIZE, + programId: umiPublicKey(programId.toBase58()), + }) + ) + .add( + initializeMultisig(umi, { + multisig: keypair.publicKey, + rent: undefined, + m, + }).addRemainingAccounts( + signers.map((signer) => ({ + pubkey: umiPublicKey(signer.toBase58()), + isWritable: false, + isSigner: false, + })) + ) + ) + + if (computeUnitPriceScaleFactor) { + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.CreateMultisig + ) + } + + const multisigPublicKey = toWeb3JsPublicKey(keypair.publicKey) + + const tx = await txBuilder.sendAndConfirm(umi) + await assertAccountInitialized(connection, multisigPublicKey) + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + console.log(`createMultisigTx: ${getExplorerTxLink(bs58.encode(tx.signature), isTestnet)}`) + + return multisigPublicKey +} + +/** + * Creates a (1/N) multisig account for use as the mint authority. + * @param connection {Connection} + * @param payer {Signer} + * @param oftStorePda {PublicKey} will be included as a signer + * @param tokenProgramId {PublicKey} defaults to SPL token program ID + * @param additionalSigners {PublicKey[]} the additionalSigners for the multisig account + */ +export const createMintAuthorityMultisig = async ( + connection: Connection, + umi: Umi, + eid: EndpointId, + umiWalletSigner: KeypairSigner, + oftStorePda: PublicKey, + tokenProgramId: PublicKey = TOKEN_PROGRAM_ID, + additionalSigners: PublicKey[], + computeUnitPriceScaleFactor: number +) => { + return createMultisig( + connection, + umi, + eid, + umiWalletSigner, + [oftStorePda, ...additionalSigners], + 1, // quorum 1/N + undefined, + tokenProgramId, + computeUnitPriceScaleFactor + ) +} + +/** + * Decode the signers of a multisig account and check if the expected signers + * are present and the quorum is 1/N. + * @param connection {Connection} + * @param multisigAddress {PublicKey} + * @param expectedSigners {PublicKey[]} the expected signers + */ +export const checkMultisigSigners = async ( + connection: Connection, + multisigAddress: PublicKey, + expectedSigners: PublicKey[] +) => { + const accountInfo = await assertAccountInitialized(connection, multisigAddress) + + if (!accountInfo.owner.equals(TOKEN_PROGRAM_ID) && !accountInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { + throw new Error('Provided address is not an SPL Token multisig account') + } + + // Multisig accounts have a specific layout based on the Multisig interface: + const data = accountInfo.data + + // Extract the number of required signers (m) and total possible signers (n) + const numRequiredSigners = data[0] + const numTotalSigners = data[1] + + if (numRequiredSigners !== 1) { + throw new Error('Multisig account must have 1 required signer') + } + + // Initialize an array to hold the signers + const signers: PublicKey[] = [] + + // Extract each signer public key based on the Multisig interface + const signerOffset = 3 // Offset to the first signer in the data + const signerSize = 32 // Each signer address is 32 bytes + + for (let i = 0; i < numTotalSigners; i++) { + const start = signerOffset + i * signerSize + const end = start + signerSize + const signerPublicKey = new PublicKey(data.slice(start, end)) + if (!signerPublicKey.equals(PublicKey.default)) { + signers.push(signerPublicKey) + } + } + + for (const signer of expectedSigners) { + if (!signers.find((s) => s.toBase58() == signer.toBase58())) { + throw new Error(`Signer ${signer.toBase58()} not found in multisig account`) + } + } + return signers +} diff --git a/examples/oft-solana-composer-library/tasks/solana/retryPayload.ts b/examples/oft-solana-composer-library/tasks/solana/retryPayload.ts new file mode 100644 index 000000000..0035d0248 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/retryPayload.ts @@ -0,0 +1,100 @@ +import { web3 } from '@coral-xyz/anchor' +import { toWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' +import { ComputeBudgetProgram, Keypair, sendAndConfirmTransaction } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { makeBytes32 } from '@layerzerolabs/devtools' +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { lzReceive } from '@layerzerolabs/lz-solana-sdk-v2' + +import { deriveConnection, getExplorerTxLink } from './index' + +interface Args { + srcEid: EndpointId + nonce: bigint + sender: string + dstEid: EndpointId + receiver: string + guid: string + payload: string + computeUnits: number + lamports: number + withPriorityFee: number +} + +task('lz:oft:solana:retry-payload', 'Retry a stored payload on Solana') + .addParam('srcEid', 'The source EndpointId', undefined, types.eid) + .addParam('nonce', 'The nonce of the payload', undefined, types.bigint) + .addParam('sender', 'The source OApp address (hex)', undefined, types.string) + .addParam('dstEid', 'The destination EndpointId (Solana chain)', undefined, types.eid) + .addParam('receiver', 'The receiver address on the destination Solana chain (bytes58)', undefined, types.string) + .addParam('guid', 'The GUID of the message (hex)', undefined, types.string) + .addParam('payload', 'The message payload (hex)', undefined, types.string) + .addParam('computeUnits', 'The CU for the lzReceive instruction', undefined, types.int) + .addParam('lamports', 'The lamports for the lzReceive instruction', undefined, types.int) + .addParam('withPriorityFee', 'The priority fee in microLamports', undefined, types.int) + .setAction( + async ({ + srcEid, + nonce, + sender, + dstEid, + receiver, + guid, + payload, + computeUnits, + lamports, + withPriorityFee, + }: Args) => { + if (!process.env.SOLANA_PRIVATE_KEY) { + throw new Error('SOLANA_PRIVATE_KEY is not defined in the environment variables.') + } + + const { connection, umiWalletKeyPair } = await deriveConnection(dstEid) + const signer = toWeb3JsKeypair(umiWalletKeyPair) + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash() + const tx = new web3.Transaction({ + feePayer: signer.publicKey, + blockhash, + lastValidBlockHeight, + }) + + const instruction = await lzReceive( + connection, + signer.publicKey, + { + nonce: nonce.toString(), + srcEid, + sender: makeBytes32(sender), + dstEid, + receiver, + payload: '', // unused; just added to satisfy typing + guid, + message: payload, // referred to as "payload" in scan-api + version: 1, // unused; just added to satisfy typing + }, + Uint8Array.from([computeUnits, lamports]), + 'confirmed' + ) + + if (withPriorityFee) { + tx.add( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: withPriorityFee, + }) + ) + } + tx.add(instruction) + tx.recentBlockhash = blockhash + + const keypair = Keypair.fromSecretKey(bs58.decode(process.env.SOLANA_PRIVATE_KEY)) + tx.sign(keypair) + + const signature = await sendAndConfirmTransaction(connection, tx, [keypair], { skipPreflight: true }) + console.log( + `View Solana transaction here: ${getExplorerTxLink(signature.toString(), dstEid == EndpointId.SOLANA_V2_TESTNET)}` + ) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/sendOFT.ts b/examples/oft-solana-composer-library/tasks/solana/sendOFT.ts new file mode 100644 index 000000000..1c91dba8b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/sendOFT.ts @@ -0,0 +1,136 @@ +import { fetchToken, findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox' +import { publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import { fromWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { addressToBytes32 } from '@layerzerolabs/lz-v2-utilities' +import { oft } from '@layerzerolabs/oft-v2-solana-sdk' + +import { + TransactionType, + addComputeUnitInstructions, + deriveConnection, + getExplorerTxLink, + getLayerZeroScanLink, + getSolanaDeployment, +} from './index' + +interface Args { + amount: bigint + to: string + fromEid: EndpointId + toEid: EndpointId + tokenProgram: string + computeUnitPriceScaleFactor: number +} + +// Define a Hardhat task for sending OFT from Solana +task('lz:oft:solana:send', 'Send tokens from Solana to a target EVM chain') + .addParam('amount', 'The amount of tokens to send', undefined, devtoolsTypes.bigint) + .addParam('fromEid', 'The source endpoint ID', undefined, devtoolsTypes.eid) + .addParam('to', 'The recipient address on the destination chain') + .addParam('toEid', 'The destination endpoint ID', undefined, devtoolsTypes.eid) + .addParam('tokenProgram', 'The Token Program public key', TOKEN_PROGRAM_ID.toBase58(), devtoolsTypes.string, true) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction(async (args: Args) => { + const { amount, fromEid, to, toEid, tokenProgram: tokenProgramStr, computeUnitPriceScaleFactor } = args + const { connection, umi, umiWalletSigner } = await deriveConnection(fromEid) + + const solanaDeployment = getSolanaDeployment(fromEid) + + const oftProgramId = publicKey(solanaDeployment.programId) + const mint = publicKey(solanaDeployment.mint) + const umiEscrowPublicKey = publicKey(solanaDeployment.escrow) + const tokenProgramId = tokenProgramStr ? publicKey(tokenProgramStr) : fromWeb3JsPublicKey(TOKEN_PROGRAM_ID) + + const tokenAccount = findAssociatedTokenPda(umi, { + mint, + owner: umiWalletSigner.publicKey, + tokenProgramId, + }) + + if (!tokenAccount) { + throw new Error( + `No token account found for mint ${mint.toString()} and owner ${umiWalletSigner.publicKey} in program ${tokenProgramId}` + ) + } + + const tokenAccountData = await fetchToken(umi, tokenAccount) + const balance = tokenAccountData.amount + + if (amount == BigInt(0) || amount > balance) { + throw new Error( + `Attempting to send ${amount}, but ${umiWalletSigner.publicKey} only has balance of ${balance}` + ) + } + + const recipientAddressBytes32 = addressToBytes32(to) + + const { nativeFee } = await oft.quote( + umi.rpc, + { + payer: umiWalletSigner.publicKey, + tokenMint: mint, + tokenEscrow: umiEscrowPublicKey, + }, + { + payInLzToken: false, + to: Buffer.from(recipientAddressBytes32), + dstEid: toEid, + amountLd: BigInt(amount), + minAmountLd: 1n, + options: Buffer.from(''), + composeMsg: undefined, + }, + { + oft: oftProgramId, + } + ) + + const ix = await oft.send( + umi.rpc, + { + payer: umiWalletSigner, + tokenMint: mint, + tokenEscrow: umiEscrowPublicKey, + tokenSource: tokenAccount[0], + }, + { + to: Buffer.from(recipientAddressBytes32), + dstEid: toEid, + amountLd: BigInt(amount), + minAmountLd: (BigInt(amount) * BigInt(9)) / BigInt(10), + options: Buffer.from(''), + composeMsg: undefined, + nativeFee, + }, + { + oft: oftProgramId, + token: tokenProgramId, + } + ) + + let txBuilder = transactionBuilder().add([ix]) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + fromEid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.SendOFT + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + const transactionSignatureBase58 = bs58.encode(signature) + + console.log(`✅ Sent ${amount} token(s) to destination EID: ${toEid}!`) + const isTestnet = fromEid == EndpointId.SOLANA_V2_TESTNET + console.log( + `View Solana transaction here: ${getExplorerTxLink(transactionSignatureBase58.toString(), isTestnet)}` + ) + console.log(`Track cross-chain transfer here: ${getLayerZeroScanLink(transactionSignatureBase58, isTestnet)}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/setAuthority.ts b/examples/oft-solana-composer-library/tasks/solana/setAuthority.ts new file mode 100644 index 000000000..56d8b8407 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setAuthority.ts @@ -0,0 +1,206 @@ +import { AccountMeta, publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import { fromWeb3JsPublicKey, toWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { AuthorityType, TOKEN_PROGRAM_ID, createSetAuthorityInstruction, getMint } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA } from '@layerzerolabs/oft-v2-solana-sdk' + +import { checkMultisigSigners, createMintAuthorityMultisig } from './multisig' + +import { TransactionType, addComputeUnitInstructions, deriveConnection, getExplorerTxLink } from './index' + +interface SetAuthorityTaskArgs { + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + + /** + * The escrow public key. + */ + escrow: string + + /** + * The token mint ID, for Mint-And-Burn-Adapter only. + */ + mint: string + + /** + * The program ID for the OFT program. + */ + programId: string + + /** + * The CSV list of additional minters. + */ + additionalMinters?: string[] + + /** + * The token program ID, for Mint-And-Burn-Adapter only. + */ + tokenProgram: string + + /** + * If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in + * losing the ability to mint new tokens for everything but the OFTStore. You should really be intentional about + * using this flag, as it is not reversible. + */ + onlyOftStore: boolean + + computeUnitPriceScaleFactor: number +} + +/** + * Derive the OFT Store account for a given program and escrow. + * @param programId {string} + * @param escrow {string} + */ +const getOftStore = (programId: string, escrow: string) => { + const oftDeriver = new OftPDA(publicKey(programId)) + const escrowPK = publicKey(escrow) + const [oftStorePda] = oftDeriver.oftStore(escrowPK) + return oftStorePda +} + +/** + * Get the string representation of the authority type. + * @param authorityType {AuthorityType} + */ +const getAuthorityTypeString = (authorityType: AuthorityType) => { + switch (authorityType) { + case AuthorityType.MintTokens: + return 'MintTokens' + case AuthorityType.FreezeAccount: + return 'FreezeAccount' + default: + throw Error(`Unknown authority type: ${authorityType}`) + } +} + +// Define a Hardhat task for creating and setting a new Mint/Freeze Authority +// for OFT on Solana +// * Create SPL Multisig account for mint authority +// * Sanity check the new Multisig account +// * Set Mint Authority +// * Set Freeze Authority +// Note: Only supports SPL Token Standard. +task('lz:oft:solana:setauthority', 'Create a new Mint Authority SPL multisig and set the mint/freeze authority') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168) eid', undefined, devtoolsTypes.eid) + .addParam('mint', 'The Token Mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('escrow', 'The OFT Escrow public key') + .addParam('additionalMinters', 'Comma-separated list of additional minters', undefined, devtoolsTypes.csv, true) + .addOptionalParam( + 'onlyOftStore', + 'If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in losing the ability to mint new tokens by everything but the OFTStore.', + false, + devtoolsTypes.boolean + ) + .addParam( + 'tokenProgram', + 'The Token Program public key (used for MABA only)', + TOKEN_PROGRAM_ID.toBase58(), + devtoolsTypes.string + ) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction( + async ({ + eid, + escrow: escrowStr, + mint: mintStr, + programId: programIdStr, + tokenProgram: tokenProgramStr, + additionalMinters: additionalMintersAsStrings, + onlyOftStore, + computeUnitPriceScaleFactor, + }: SetAuthorityTaskArgs) => { + const { connection, umi, umiWalletKeyPair, umiWalletSigner } = await deriveConnection(eid) + const oftStorePda = getOftStore(programIdStr, escrowStr) + const tokenProgram = publicKey(tokenProgramStr) + if (!additionalMintersAsStrings) { + if (!onlyOftStore) { + throw new Error( + 'If you want to proceed with only the OFTStore, please specify --only-oft-store true' + ) + } + console.log( + 'No additional minters specified. This will result in only the OFTStore being able to mint new tokens.' + ) + } + const additionalMinters = additionalMintersAsStrings?.map((minter) => new PublicKey(minter)) ?? [] + const mint = new PublicKey(mintStr) + const newMintAuthority = await createMintAuthorityMultisig( + connection, + umi, + eid, + umiWalletSigner, + new PublicKey(oftStorePda.toString()), + new PublicKey(tokenProgram.toString()), + additionalMinters, + computeUnitPriceScaleFactor + ) + console.log(`New Mint Authority: ${newMintAuthority.toBase58()}`) + const signers = await checkMultisigSigners(connection, newMintAuthority, [ + toWeb3JsPublicKey(oftStorePda), + ...additionalMinters, + ]) + console.log(`New Mint Authority Signers: ${signers.map((s) => s.toBase58()).join(', ')}`) + for (const authorityType of [AuthorityType.MintTokens, AuthorityType.FreezeAccount]) { + const mintAuthRet = await getMint(connection, mint, undefined, toWeb3JsPublicKey(tokenProgram)) + let currentAuthority + if (authorityType == AuthorityType.MintTokens) { + if (!mintAuthRet.mintAuthority) { + throw new Error(`Mint ${mintStr} has no mint authority`) + } + currentAuthority = fromWeb3JsPublicKey(mintAuthRet.mintAuthority) + } else { + if (!mintAuthRet.freezeAuthority) { + throw new Error(`Mint ${mintStr} has no freeze authority`) + } + currentAuthority = fromWeb3JsPublicKey(mintAuthRet.freezeAuthority) + } + if (authorityType == AuthorityType.FreezeAccount && !mintAuthRet.freezeAuthority) { + throw new Error(`Mint ${mintStr} has no freeze authority`) + } + console.log(`Current ${getAuthorityTypeString(authorityType)} Authority: ${currentAuthority}`) + const ix = createSetAuthorityInstruction( + new PublicKey(mintStr), + toWeb3JsPublicKey(currentAuthority), + authorityType, + newMintAuthority, + [toWeb3JsKeypair(umiWalletKeyPair)] + ) + const umiInstruction = { + programId: publicKey(ix.programId.toBase58()), + keys: ix.keys.map((key) => ({ + pubkey: key.pubkey, + isSigner: key.isSigner, + isWritable: key.isWritable, + })) as unknown as AccountMeta[], + data: ix.data, + } + let txBuilder = transactionBuilder().add({ + instruction: umiInstruction, + signers: [umiWalletSigner], // Include all required signers here + bytesCreatedOnChain: 0, + }) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.SetAuthority + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log( + `SetAuthorityTx(${getAuthorityTypeString(authorityType)}): ${getExplorerTxLink(bs58.encode(signature), eid == EndpointId.SOLANA_V2_TESTNET)}` + ) + } + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/setInboundRateLimit.ts b/examples/oft-solana-composer-library/tasks/solana/setInboundRateLimit.ts new file mode 100644 index 000000000..a4f668aed --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setInboundRateLimit.ts @@ -0,0 +1,80 @@ +import assert from 'assert' + +import { mplToolbox } from '@metaplex-foundation/mpl-toolbox' +import { createSignerFromKeypair, publicKey, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Keypair, PublicKey, sendAndConfirmTransaction } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { deserializeTransactionMessage } from '@layerzerolabs/devtools-solana' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA, oft } from '@layerzerolabs/oft-v2-solana-sdk' +import { createOFTFactory } from '@layerzerolabs/ua-devtools-solana' + +import { createSolanaConnectionFactory } from '../common/utils' + +interface Args { + mint: string + eid: EndpointId + srcEid: EndpointId + programId: string + oftStore: string + capacity: bigint + refillPerSecond: bigint +} + +task( + 'lz:oft:solana:inbound-rate-limit', + "Sets the Solana and EVM rate limits from './scripts/solana/utils/constants.ts'" +) + .addParam('mint', 'The OFT token mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, types.eid) + .addParam('srcEid', 'The source endpoint ID', undefined, types.eid) + .addParam('oftStore', 'The OFTStore account') + .addParam('capacity', 'The capacity of the rate limit', undefined, types.bigint) + .addParam('refillPerSecond', 'The refill rate of the rate limit', undefined, types.bigint) + .setAction(async (taskArgs: Args, hre) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + const keypair = Keypair.fromSecretKey(bs58.decode(privateKey)) + const umiKeypair = fromWeb3JsKeypair(keypair) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) + umi.use(signerIdentity(umiWalletSigner)) + + const solanaSdkFactory = createOFTFactory( + () => toWeb3JsPublicKey(umiWalletSigner.publicKey), + () => new PublicKey(taskArgs.programId), + connectionFactory + ) + const sdk = await solanaSdkFactory({ + address: new PublicKey(taskArgs.oftStore).toBase58(), + eid: taskArgs.eid, + }) + const solanaRateLimits = { + capacity: taskArgs.capacity, + refillPerSecond: taskArgs.refillPerSecond, + } + try { + const tx = deserializeTransactionMessage( + (await sdk.setInboundRateLimit(taskArgs.srcEid, solanaRateLimits)).data + ) + tx.sign(keypair) + const txId = await sendAndConfirmTransaction(connection, tx, [keypair]) + console.log(`Transaction successful with ID: ${txId}`) + const [peer] = new OftPDA(publicKey(taskArgs.programId)).peer(publicKey(taskArgs.oftStore), taskArgs.srcEid) + const peerInfo = await oft.accounts.fetchPeerConfig({ rpc: umi.rpc }, peer) + console.dir({ peerInfo }, { depth: null }) + } catch (error) { + console.error(`setInboundRateLimit failed:`, error) + } + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/setOutboundRateLimit.ts b/examples/oft-solana-composer-library/tasks/solana/setOutboundRateLimit.ts new file mode 100644 index 000000000..4d5abbf51 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setOutboundRateLimit.ts @@ -0,0 +1,81 @@ +import assert from 'assert' + +import { mplToolbox } from '@metaplex-foundation/mpl-toolbox' +import { createSignerFromKeypair, publicKey, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsKeypair, toWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Keypair, PublicKey, sendAndConfirmTransaction } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { deserializeTransactionMessage } from '@layerzerolabs/devtools-solana' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA, oft } from '@layerzerolabs/oft-v2-solana-sdk' +import { createOFTFactory } from '@layerzerolabs/ua-devtools-solana' + +import { createSolanaConnectionFactory } from '../common/utils' + +interface Args { + mint: string + eid: EndpointId + dstEid: EndpointId + programId: string + oftStore: string + capacity: bigint + refillPerSecond: bigint +} + +task( + 'lz:oft:solana:outbound-rate-limit', + "Sets the Solana and EVM rate limits from './scripts/solana/utils/constants.ts'" +) + .addParam('mint', 'The OFT token mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, types.eid) + .addParam('dstEid', 'The destination endpoint ID', undefined, types.eid) + .addParam('oftStore', 'The OFTStore account') + .addParam('capacity', 'The capacity of the rate limit', undefined, types.bigint) + .addParam('refillPerSecond', 'The refill rate of the rate limit', undefined, types.bigint) + .setAction(async (taskArgs: Args, hre) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + const keypair = Keypair.fromSecretKey(bs58.decode(privateKey)) + const umiKeypair = fromWeb3JsKeypair(keypair) + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) + const web3WalletKeyPair = toWeb3JsKeypair(umiKeypair) + umi.use(signerIdentity(umiWalletSigner)) + + const solanaSdkFactory = createOFTFactory( + () => toWeb3JsPublicKey(umiWalletSigner.publicKey), + () => new PublicKey(taskArgs.programId), + connectionFactory + ) + + const sdk = await solanaSdkFactory({ + address: new PublicKey(taskArgs.oftStore).toBase58(), + eid: taskArgs.eid, + }) + const solanaRateLimits = { + capacity: taskArgs.capacity, + refillPerSecond: taskArgs.refillPerSecond, + } + // for (const peer of graph.connections.filter((connection) => connection.vector.from.eid === solanaEid)) { + try { + const tx = deserializeTransactionMessage( + (await sdk.setOutboundRateLimit(EndpointId.SEPOLIA_V2_TESTNET, solanaRateLimits)).data + ) + tx.sign(keypair) + const txId = await sendAndConfirmTransaction(connection, tx, [keypair]) + console.log(`Transaction successful with ID: ${txId}`) + const [peer] = new OftPDA(publicKey(taskArgs.programId)).peer(publicKey(taskArgs.oftStore), taskArgs.dstEid) + const peerInfo = await oft.accounts.fetchPeerConfig({ rpc: umi.rpc }, peer) + console.dir({ peerInfo }, { depth: null }) + } catch (error) { + console.error(`setOutboundRateLimit failed:`, error) + } + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/setUpdateAuthority.ts b/examples/oft-solana-composer-library/tasks/solana/setUpdateAuthority.ts new file mode 100644 index 000000000..a042469de --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setUpdateAuthority.ts @@ -0,0 +1,99 @@ +import { fetchMetadataFromSeeds, updateV1 } from '@metaplex-foundation/mpl-token-metadata' +import { publicKey } from '@metaplex-foundation/umi' +import { SystemProgram } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { promptToContinue } from '@layerzerolabs/io-devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { deriveConnection, getExplorerTxLink } from '.' + +interface Args { + mint: string + newUpdateAuthority?: string + renounceUpdateAuthority?: boolean + eid: EndpointId +} + +// sets the update authority via Metaplex +task('lz:oft:solana:set-update-authority', 'Updates the metaplex update authority of the SPL Token') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('mint', 'The Token mint public key', undefined, devtoolsTypes.string) + .addOptionalParam('newUpdateAuthority', 'The new update authority', undefined, devtoolsTypes.string) + .addOptionalParam('renounceUpdateAuthority', 'Renounce update authority', false, devtoolsTypes.boolean) + .setAction( + async ({ eid, mint: mintStr, newUpdateAuthority: newUpdateAuthorityStr, renounceUpdateAuthority }: Args) => { + // if not renouncing, must provide new update authority + if (!renounceUpdateAuthority && !newUpdateAuthorityStr) { + throw new Error( + 'Either specify the new update authority via --new-update-authority or renounce via --renounce-update-authority true' + ) + } + + // if renouncing, must not provide new update authority + if (renounceUpdateAuthority && newUpdateAuthorityStr) { + throw new Error('Cannot provide new update authority if renouncing') + } + + /* + * On why the update authority is set to SystemProgram.programId ("11111111111111111111111111111111") when renouncing: + * The Metaplex Token Metadata program defines the update_authority strictly as a Pubkey: + * https://github.com/metaplex-foundation/mpl-token-metadata/blob/23aee718e723578ee5df411f045184e0ac9a9e63/programs/token-metadata/program/src/state/metadata.rs#L73 + * Hence, the value must always be a Pubkey + * To renounce the update authority, we can to set its value to SystemProgram ID ("11111111111111111111111111111111") + * This is done on top of setting `isMutable` to false + */ + + const updateAuthority = renounceUpdateAuthority + ? publicKey(SystemProgram.programId) + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + publicKey(newUpdateAuthorityStr!) // we already checked that this is defined + + const { umi, umiWalletSigner } = await deriveConnection(eid) + + const mint = publicKey(mintStr) + const initialMetadata = await fetchMetadataFromSeeds(umi, { mint }) + + if (initialMetadata.updateAuthority === SystemProgram.programId.toString()) { + console.log('\nThe update authority has already been renounced\n') + return + } + + if (initialMetadata.updateAuthority !== umiWalletSigner.publicKey.toString()) { + throw new Error('Only the update authority can update the metadata') + } + + console.log(`\nMint Address: ${mintStr}\n`) + console.log(`\nCurrent update authority: ${initialMetadata.updateAuthority}\n`) + console.log(`\nNew update authority: ${updateAuthority.toString()}\n`) + + if (renounceUpdateAuthority) { + const doContinue = await promptToContinue( + 'You have chosen `--renounce-update-authority true`. This means that the Update Authority will be immediately renounced. This is irreversible. Continue?' + ) + if (!doContinue) { + return + } + } + + const isMutable = renounceUpdateAuthority ? false : initialMetadata.isMutable + + // Verify that isMutable is true when not renouncing, can't be too safe. + if (!renounceUpdateAuthority && !isMutable) { + throw new Error('When not renouncing, `isMutable` must be true') + } + + const txn = await updateV1(umi, { + mint, + newUpdateAuthority: updateAuthority, + authority: umiWalletSigner, + isMutable: renounceUpdateAuthority ? false : isMutable, + }).sendAndConfirm(umi) + + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + + console.log(`Txn link: ${getExplorerTxLink(bs58.encode(txn.signature), isTestnet)}`) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/updateMetadata.ts b/examples/oft-solana-composer-library/tasks/solana/updateMetadata.ts new file mode 100644 index 000000000..07a59703c --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/updateMetadata.ts @@ -0,0 +1,66 @@ +import { + UpdateV1InstructionAccounts, + UpdateV1InstructionArgs, + fetchMetadataFromSeeds, + updateV1, +} from '@metaplex-foundation/mpl-token-metadata' +import { publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { deriveConnection, getExplorerTxLink } from './index' + +interface UpdateMetadataTaskArgs { + eid: EndpointId + name: string + mint: string + sellerFeeBasisPoints: number + symbol: string + uri: string +} + +// note that if URI is specified, then the name and symbol in there would be used and will override the 'outer' name and symbol +task('lz:oft:solana:update-metadata', 'Updates the metaplex metadata of the SPL Token') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('mint', 'The Token mint public key', undefined, devtoolsTypes.string) + .addOptionalParam('name', 'Token Name', undefined, devtoolsTypes.string) + .addOptionalParam('symbol', 'Token Symbol', undefined, devtoolsTypes.string) + .addOptionalParam('sellerFeeBasisPoints', 'Seller fee basis points', undefined, devtoolsTypes.int) + .addOptionalParam('uri', 'URI for token metadata', undefined, devtoolsTypes.string) + .setAction(async ({ eid, name, mint: mintStr, sellerFeeBasisPoints, symbol, uri }: UpdateMetadataTaskArgs) => { + const { umi, umiWalletSigner } = await deriveConnection(eid) + + const mint = publicKey(mintStr) + + const initialMetadata = await fetchMetadataFromSeeds(umi, { mint }) + + if (initialMetadata.updateAuthority !== umiWalletSigner.publicKey.toString()) { + throw new Error('Only the update authority can update the metadata') + } + + if (initialMetadata.isMutable == false) { + throw new Error('Metadata is not mutable') + } + + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + + const updateV1Args: UpdateV1InstructionAccounts & UpdateV1InstructionArgs = { + mint, + authority: umiWalletSigner, + data: { + ...initialMetadata, + name: name || initialMetadata.name, + symbol: symbol || initialMetadata.symbol, + uri: uri || initialMetadata.uri, + sellerFeeBasisPoints: + sellerFeeBasisPoints != undefined ? sellerFeeBasisPoints : initialMetadata.sellerFeeBasisPoints, + }, + } + + const txBuilder = transactionBuilder().add(updateV1(umi, updateV1Args)) + const createTokenTx = await txBuilder.sendAndConfirm(umi) + console.log(`createTokenTx: ${getExplorerTxLink(bs58.encode(createTokenTx.signature), isTestnet)}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/utils.ts b/examples/oft-solana-composer-library/tasks/solana/utils.ts new file mode 100644 index 000000000..fd9a33b0b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/utils.ts @@ -0,0 +1,77 @@ +import { Connection, PublicKey } from '@solana/web3.js' +import { backOff } from 'exponential-backoff' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { ChainType, EndpointId, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +import { OAppOmniGraph } from '@layerzerolabs/ua-devtools' +import { + OAppOmniGraphHardhatSchema, + SUBTASK_LZ_OAPP_CONFIG_LOAD, + SubtaskLoadConfigTaskArgs, + TASK_LZ_OAPP_CONFIG_GET, +} from '@layerzerolabs/ua-devtools-evm-hardhat' + +/** + * Assert that the account is initialized on the Solana blockchain. Due to eventual consistency, there is a race + * between account creation and initialization. This function will retry 10 times with backoff to ensure the account is + * initialized. + * @param connection {Connection} + * @param publicKey {PublicKey} + */ +export const assertAccountInitialized = async (connection: Connection, publicKey: PublicKey) => { + return backOff( + async () => { + const accountInfo = await connection.getAccountInfo(publicKey) + if (!accountInfo) { + throw new Error('Multisig account not found') + } + return accountInfo + }, + { + maxDelay: 30000, + numOfAttempts: 10, + startingDelay: 5000, + } + ) +} + +export const findSolanaEndpointIdInGraph = async ( + hre: HardhatRuntimeEnvironment, + oappConfig: string +): Promise => { + if (!oappConfig) throw new Error('Missing oappConfig') + + let graph: OAppOmniGraph + try { + graph = await hre.run(SUBTASK_LZ_OAPP_CONFIG_LOAD, { + configPath: oappConfig, + schema: OAppOmniGraphHardhatSchema, + task: TASK_LZ_OAPP_CONFIG_GET, + } satisfies SubtaskLoadConfigTaskArgs) + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to load OApp configuration: ${error.message}`) + } else { + throw new Error('Failed to load OApp configuration: Unknown error') + } + } + + let solanaEid: EndpointId | null = null + + const checkSolanaEndpoint = (eid: EndpointId) => { + if (endpointIdToChainType(eid) === ChainType.SOLANA) { + if (solanaEid && solanaEid !== eid) { + throw new Error(`Multiple Solana Endpoint IDs found: ${solanaEid}, ${eid}`) + } + solanaEid = eid + } + } + + for (const { vector } of graph.connections) { + checkSolanaEndpoint(vector.from.eid) + checkSolanaEndpoint(vector.to.eid) + if (solanaEid) return solanaEid + } + + throw new Error('No Solana Endpoint ID found. Ensure your OApp configuration includes a valid Solana endpoint.') +} diff --git a/examples/oft-solana-composer-library/tasks/utils/getFee.ts b/examples/oft-solana-composer-library/tasks/utils/getFee.ts new file mode 100644 index 000000000..ef83ec4cd --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/utils/getFee.ts @@ -0,0 +1,75 @@ +import { Connection, PublicKey } from '@solana/web3.js' + +// Define interfaces for more explicit typing +interface PrioritizationFeeObject { + slot: number + prioritizationFee: number +} + +interface Config { + lockedWritableAccounts: PublicKey[] +} + +const getPrioritizationFees = async ( + connection: Connection, + programId?: string // TODO: change to array of addresses / public keys to match lockedWritableAccounts' type +): Promise<{ + averageFeeIncludingZeros: number + averageFeeExcludingZeros: number + medianFee: number +}> => { + try { + const publicKey = new PublicKey(programId || PublicKey.default) // the account that will be written to + + const config: Config = { + lockedWritableAccounts: [publicKey], + } + + const prioritizationFeeObjects = (await connection.getRecentPrioritizationFees( + config + )) as PrioritizationFeeObject[] + + if (prioritizationFeeObjects.length === 0) { + console.log('No prioritization fee data available.') + return { averageFeeIncludingZeros: 0, averageFeeExcludingZeros: 0, medianFee: 0 } + } + + // Extract slots and sort them + // const slots = prioritizationFeeObjects.map((feeObject) => feeObject.slot).sort((a, b) => a - b) + + // Calculate the average including zero fees + const averageFeeIncludingZeros = + prioritizationFeeObjects.length > 0 + ? Math.floor( + prioritizationFeeObjects.reduce((acc, feeObject) => acc + feeObject.prioritizationFee, 0) / + prioritizationFeeObjects.length + ) + : 0 + + // Filter out prioritization fees that are equal to 0 for other calculations + const nonZeroFees = prioritizationFeeObjects + .map((feeObject) => feeObject.prioritizationFee) + .filter((fee) => fee !== 0) + + // Calculate the average of the non-zero fees + const averageFeeExcludingZeros = + nonZeroFees.length > 0 ? Math.floor(nonZeroFees.reduce((acc, fee) => acc + fee, 0) / nonZeroFees.length) : 0 + + // Calculate the median of the non-zero fees + const sortedFees = nonZeroFees.sort((a, b) => a - b) + let medianFee = 0 + if (sortedFees.length > 0) { + const midIndex = Math.floor(sortedFees.length / 2) + medianFee = + sortedFees.length % 2 !== 0 + ? sortedFees[midIndex] + : Math.floor((sortedFees[midIndex - 1] + sortedFees[midIndex]) / 2) + } + return { averageFeeIncludingZeros, averageFeeExcludingZeros, medianFee } + } catch (error) { + console.error('Error fetching prioritization fees:', error) + return { averageFeeIncludingZeros: 0, averageFeeExcludingZeros: 0, medianFee: 0 } + } +} + +export default getPrioritizationFees diff --git a/examples/oft-solana-composer-library/test/foundry/MyOFT.t.sol b/examples/oft-solana-composer-library/test/foundry/MyOFT.t.sol new file mode 100644 index 000000000..f285c0a53 --- /dev/null +++ b/examples/oft-solana-composer-library/test/foundry/MyOFT.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +// Mock imports +import { OFTMock } from "../mocks/OFTMock.sol"; +import { ERC20Mock } from "../mocks/ERC20Mock.sol"; +import { OFTComposerMock } from "../mocks/OFTComposerMock.sol"; + +// OApp imports +import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol"; +import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; + +// OFT imports +import { IOFT, SendParam, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; +import { MessagingFee, MessagingReceipt } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol"; +import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol"; +import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol"; + +// OZ imports +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +// Forge imports +//import "forge-std/console.sol"; + +// DevTools imports +import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; + +contract MyOFTTest is TestHelperOz5 { + using OptionsBuilder for bytes; + + uint32 private aEid = 1; + uint32 private bEid = 2; + + OFTMock private aOFT; + OFTMock private bOFT; + + address private userA = address(0x1); + address private userB = address(0x2); + uint256 private initialBalance = 100 ether; + + function setUp() public virtual override { + vm.deal(userA, 1000 ether); + vm.deal(userB, 1000 ether); + + super.setUp(); + setUpEndpoints(2, LibraryType.UltraLightNode); + + aOFT = OFTMock( + _deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this))) + ); + + bOFT = OFTMock( + _deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this))) + ); + + // config and wire the ofts + address[] memory ofts = new address[](2); + ofts[0] = address(aOFT); + ofts[1] = address(bOFT); + this.wireOApps(ofts); + + // mint tokens + aOFT.mint(userA, initialBalance); + bOFT.mint(userB, initialBalance); + } + + function test_constructor() public { + assertEq(aOFT.owner(), address(this)); + assertEq(bOFT.owner(), address(this)); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + assertEq(aOFT.token(), address(aOFT)); + assertEq(bOFT.token(), address(bOFT)); + } + + function test_send_oft() public { + uint256 tokensToSend = 1 ether; + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + SendParam memory sendParam = SendParam( + bEid, + addressToBytes32(userB), + tokensToSend, + tokensToSend, + options, + "", + "" + ); + MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + vm.prank(userA); + aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this))); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend); + } + + function test_send_oft_compose_msg() public { + uint256 tokensToSend = 1 ether; + + OFTComposerMock composer = new OFTComposerMock(); + + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(200000, 0) + .addExecutorLzComposeOption(0, 500000, 0); + bytes memory composeMsg = hex"1234"; + SendParam memory sendParam = SendParam( + bEid, + addressToBytes32(address(composer)), + tokensToSend, + tokensToSend, + options, + composeMsg, + "" + ); + MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(address(composer)), 0); + + vm.prank(userA); + (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = aOFT.send{ value: fee.nativeFee }( + sendParam, + fee, + payable(address(this)) + ); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + // lzCompose params + uint32 dstEid_ = bEid; + address from_ = address(bOFT); + bytes memory options_ = options; + bytes32 guid_ = msgReceipt.guid; + address to_ = address(composer); + bytes memory composerMsg_ = OFTComposeMsgCodec.encode( + msgReceipt.nonce, + aEid, + oftReceipt.amountReceivedLD, + abi.encodePacked(addressToBytes32(userA), composeMsg) + ); + this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_); + + assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + assertEq(bOFT.balanceOf(address(composer)), tokensToSend); + + assertEq(composer.from(), from_); + assertEq(composer.guid(), guid_); + assertEq(composer.message(), composerMsg_); + assertEq(composer.executor(), address(this)); + assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test + } + + // TODO import the rest of oft tests? +} diff --git a/examples/oft-solana-composer-library/test/hardhat/MyOFT.test.ts b/examples/oft-solana-composer-library/test/hardhat/MyOFT.test.ts new file mode 100644 index 000000000..257bc5218 --- /dev/null +++ b/examples/oft-solana-composer-library/test/hardhat/MyOFT.test.ts @@ -0,0 +1,101 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { Contract, ContractFactory } from 'ethers' +import { deployments, ethers } from 'hardhat' + +import { Options } from '@layerzerolabs/lz-v2-utilities' + +describe('MyOFT Test', function () { + // Constant representing a mock Endpoint ID for testing purposes + const eidA = 1 + const eidB = 2 + // Declaration of variables to be used in the test suite + let MyOFT: ContractFactory + let EndpointV2Mock: ContractFactory + let ownerA: SignerWithAddress + let ownerB: SignerWithAddress + let endpointOwner: SignerWithAddress + let myOFTA: Contract + let myOFTB: Contract + let mockEndpointV2A: Contract + let mockEndpointV2B: Contract + + // Before hook for setup that runs once before all tests in the block + before(async function () { + // Contract factory for our tested contract + // + // We are using a derived contract that exposes a mint() function for testing purposes + MyOFT = await ethers.getContractFactory('MyOFTMock') + + // Fetching the first three signers (accounts) from Hardhat's local Ethereum network + const signers = await ethers.getSigners() + + ;[ownerA, ownerB, endpointOwner] = signers + + // The EndpointV2Mock contract comes from @layerzerolabs/test-devtools-evm-hardhat package + // and its artifacts are connected as external artifacts to this project + // + // Unfortunately, hardhat itself does not yet provide a way of connecting external artifacts, + // so we rely on hardhat-deploy to create a ContractFactory for EndpointV2Mock + // + // See https://github.com/NomicFoundation/hardhat/issues/1040 + const EndpointV2MockArtifact = await deployments.getArtifact('EndpointV2Mock') + EndpointV2Mock = new ContractFactory(EndpointV2MockArtifact.abi, EndpointV2MockArtifact.bytecode, endpointOwner) + }) + + // beforeEach hook for setup that runs before each test in the block + beforeEach(async function () { + // Deploying a mock LZEndpoint with the given Endpoint ID + mockEndpointV2A = await EndpointV2Mock.deploy(eidA) + mockEndpointV2B = await EndpointV2Mock.deploy(eidB) + + // Deploying two instances of MyOFT contract with different identifiers and linking them to the mock LZEndpoint + myOFTA = await MyOFT.deploy('aOFT', 'aOFT', mockEndpointV2A.address, ownerA.address) + myOFTB = await MyOFT.deploy('bOFT', 'bOFT', mockEndpointV2B.address, ownerB.address) + + // Setting destination endpoints in the LZEndpoint mock for each MyOFT instance + await mockEndpointV2A.setDestLzEndpoint(myOFTB.address, mockEndpointV2B.address) + await mockEndpointV2B.setDestLzEndpoint(myOFTA.address, mockEndpointV2A.address) + + // Setting each MyOFT instance as a peer of the other in the mock LZEndpoint + await myOFTA.connect(ownerA).setPeer(eidB, ethers.utils.zeroPad(myOFTB.address, 32)) + await myOFTB.connect(ownerB).setPeer(eidA, ethers.utils.zeroPad(myOFTA.address, 32)) + }) + + // A test case to verify token transfer functionality + it('should send a token from A address to B address via each OFT', async function () { + // Minting an initial amount of tokens to ownerA's address in the myOFTA contract + const initialAmount = ethers.utils.parseEther('100') + await myOFTA.mint(ownerA.address, initialAmount) + + // Defining the amount of tokens to send and constructing the parameters for the send operation + const tokensToSend = ethers.utils.parseEther('1') + + // Defining extra message execution options for the send operation + const options = Options.newOptions().addExecutorLzReceiveOption(200000, 0).toHex().toString() + + const sendParam = [ + eidB, + ethers.utils.zeroPad(ownerB.address, 32), + tokensToSend, + tokensToSend, + options, + '0x', + '0x', + ] + + // Fetching the native fee for the token send operation + const [nativeFee] = await myOFTA.quoteSend(sendParam, false) + + // Executing the send operation from myOFTA contract + await myOFTA.send(sendParam, [nativeFee, 0], ownerA.address, { value: nativeFee }) + + // Fetching the final token balances of ownerA and ownerB + const finalBalanceA = await myOFTA.balanceOf(ownerA.address) + const finalBalanceB = await myOFTB.balanceOf(ownerB.address) + + // Asserting that the final balances are as expected after the send operation + expect(finalBalanceA).eql(initialAmount.sub(tokensToSend)) + expect(finalBalanceB).eql(tokensToSend) + }) +}) diff --git a/examples/oft-solana-composer-library/test/mocks/ERC20Mock.sol b/examples/oft-solana-composer-library/test/mocks/ERC20Mock.sol new file mode 100644 index 000000000..6ce17e418 --- /dev/null +++ b/examples/oft-solana-composer-library/test/mocks/ERC20Mock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} diff --git a/examples/oft-solana-composer-library/test/mocks/OFTComposerMock.sol b/examples/oft-solana-composer-library/test/mocks/OFTComposerMock.sol new file mode 100644 index 000000000..fdd5c2426 --- /dev/null +++ b/examples/oft-solana-composer-library/test/mocks/OFTComposerMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol"; + +contract OFTComposerMock is IOAppComposer { + // default empty values for testing a lzCompose received message + address public from; + bytes32 public guid; + bytes public message; + address public executor; + bytes public extraData; + + function lzCompose( + address _from, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata /*_extraData*/ + ) external payable { + from = _from; + guid = _guid; + message = _message; + executor = _executor; + extraData = _message; + } +} diff --git a/examples/oft-solana-composer-library/test/mocks/OFTMock.sol b/examples/oft-solana-composer-library/test/mocks/OFTMock.sol new file mode 100644 index 000000000..cef8770f5 --- /dev/null +++ b/examples/oft-solana-composer-library/test/mocks/OFTMock.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; +import { SendParam } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol"; + +contract OFTMock is OFT { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) Ownable(_delegate) OFT(_name, _symbol, _lzEndpoint, _delegate) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } + + // @dev expose internal functions for testing purposes + function debit( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debit(msg.sender, _amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function debitView( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public view returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) { + return _removeDust(_amountLD); + } + + function toLD(uint64 _amountSD) public view returns (uint256 amountLD) { + return _toLD(_amountSD); + } + + function toSD(uint256 _amountLD) public view returns (uint64 amountSD) { + return _toSD(_amountLD); + } + + function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) { + return _credit(_to, _amountToCreditLD, _srcEid); + } + + function buildMsgAndOptions( + SendParam calldata _sendParam, + uint256 _amountToCreditLD + ) public view returns (bytes memory message, bytes memory options) { + return _buildMsgAndOptions(_sendParam, _amountToCreditLD); + } +} diff --git a/examples/oft-solana-composer-library/tsconfig.json b/examples/oft-solana-composer-library/tsconfig.json new file mode 100644 index 000000000..5fcf450fd --- /dev/null +++ b/examples/oft-solana-composer-library/tsconfig.json @@ -0,0 +1,13 @@ +{ + "exclude": ["node_modules"], + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/examples/oft-solana-composer-library/turbo.json b/examples/oft-solana-composer-library/turbo.json new file mode 100644 index 000000000..b38dc23a0 --- /dev/null +++ b/examples/oft-solana-composer-library/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "pipeline": { + "compile": { + "outputs": ["target/**"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97cd1e61b..86c251b53 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2195,9 +2195,6 @@ importers: '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 - '@layerzerolabs/metadata-tools': - specifier: ^0.4.1 - version: link:../../packages/metadata-tools '@layerzerolabs/oapp-evm': specifier: ^0.3.2 version: link:../../packages/oapp-evm @@ -11025,6 +11022,7 @@ packages: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - typescript + dev: false /@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.98.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} From 306b73093b28c93be0671eb639f57555a3210b76 Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Thu, 10 Apr 2025 11:58:00 -0700 Subject: [PATCH 02/11] chore: continuing to improve compiler runs --- .../oft-solana-composer-library/Cargo.lock | 2 +- .../programs/composer/src/lib.rs | 320 ++++++++++++------ 2 files changed, 226 insertions(+), 96 deletions(-) diff --git a/examples/oft-solana-composer-library/Cargo.lock b/examples/oft-solana-composer-library/Cargo.lock index b2eda3001..e8c5edebe 100644 --- a/examples/oft-solana-composer-library/Cargo.lock +++ b/examples/oft-solana-composer-library/Cargo.lock @@ -1648,7 +1648,7 @@ dependencies = [ [[package]] name = "raydium-clmm-cpi" version = "0.1.0" -source = "git+https://github.com/St0rmBr3w/raydium-cpi/?branch=anchor-0.29.0#3354f36cddfaedf38a2f313dcf0e5221c0cb8419" +source = "git+https://github.com/St0rmBr3w/raydium-cpi/?branch=anchor-0.29.0#586486b3ee307c157ac12bec5a779717bbdac489" dependencies = [ "ahash 0.8.5", "anchor-lang", diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index dc94808b6..4ae453358 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -1,15 +1,27 @@ use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount}; +use anchor_spl::token_interface::{Token2022}; -// LayerZero / OApp endpoint CPI helpers +/// LayerZero / OApp endpoint CPI helpers use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; use oapp::endpoint_cpi::{clear_compose, get_accounts_for_clear_compose, LzAccount}; use oapp::LzComposeParams; -// Raydium CLMM CPI (concentrated liquidity swap) -// NOTE: ensure these imports match the raydium_clmm_cpi crate version you're using -use raydium_clmm_cpi::cpi::swap_v2 as swap; -use raydium_clmm_cpi::cpi::accounts::SwapSingleV2 as RaydiumSwapAccounts; +/// Raydium CLMM CPI (concentrated liquidity swap) +use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; +use raydium_clmm_cpi::cpi::swap_v2; + +use std::str::FromStr; + +// Instead of importing AmmV3 from the raydium_amm_v3 crate, we define our own minimal type. +#[derive(Clone, Debug)] +pub struct AmmV3; + +impl anchor_lang::Id for AmmV3 { + fn id() -> Pubkey { + Pubkey::from_str("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK").unwrap() + } +} declare_id!("3NJ7AUBaj9N8kBsRqA7SYWJ1poEUsEW36gCG1EfLDkW2"); @@ -22,84 +34,175 @@ pub mod composer { ctx: Context, params: LzComposeParams, ) -> Result> { - // 1) LayerZero endpoint accounts + // LayerZero endpoint accounts first. let mut accounts = get_accounts_for_clear_compose( ENDPOINT_ID, - ¶ms.from, // origin sender (32 bytes) + ¶ms.from, &ctx.accounts.lz_program.key(), - ¶ms.guid, // GUID of message - params.index, // index in the queue - ¶ms.message, // raw message bytes + ¶ms.guid, + params.index, + ¶ms.message, ); - // 2) Raydium CLMM CPI accounts (same order as in LzCompose below) - accounts.push(LzAccount { pubkey: ctx.accounts.payer.key(), is_signer: true, is_writable: false }); - accounts.push(LzAccount { pubkey: ctx.accounts.authority.key(), is_signer: false, is_writable: false }); - accounts.push(LzAccount { pubkey: ctx.accounts.amm_config.key(), is_signer: false, is_writable: false }); - accounts.push(LzAccount { pubkey: ctx.accounts.pool_state.key(), is_signer: false, is_writable: true }); - accounts.push(LzAccount { pubkey: ctx.accounts.source_token_account.key(), is_signer: false, is_writable: true }); - accounts.push(LzAccount { pubkey: ctx.accounts.dest_token_account.key(), is_signer: false, is_writable: true }); - accounts.push(LzAccount { pubkey: ctx.accounts.input_vault.key(), is_signer: false, is_writable: true }); - accounts.push(LzAccount { pubkey: ctx.accounts.output_vault.key(), is_signer: false, is_writable: true }); - accounts.push(LzAccount { pubkey: ctx.accounts.input_token_program.key(), is_signer: false, is_writable: false }); - accounts.push(LzAccount { pubkey: ctx.accounts.output_token_program.key(),is_signer: false, is_writable: false }); - accounts.push(LzAccount { pubkey: ctx.accounts.input_token_mint.key(), is_signer: false, is_writable: false }); - accounts.push(LzAccount { pubkey: ctx.accounts.output_token_mint.key(), is_signer: false, is_writable: false }); - accounts.push(LzAccount { pubkey: ctx.accounts.observation_state.key(), is_signer: false, is_writable: true }); - accounts.push(LzAccount { pubkey: ctx.accounts.token_program.key(), is_signer: false, is_writable: false }); + // Append the Raydium CLMM CPI accounts *in the exact order* required by the CPI instruction. + accounts.push(LzAccount { + pubkey: ctx.accounts.payer.key(), + is_signer: true, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.authority.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.amm_config.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.pool_state.key(), + is_signer: false, + is_writable: true, + }); + // Note: We assume here that the "source" account is the user’s input token account. + accounts.push(LzAccount { + pubkey: ctx.accounts.source_token_account.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.dest_token_account.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.input_vault.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.output_vault.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.token_program.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.token_program_2022.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.memo_program.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.input_token_mint.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.output_token_mint.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.observation_state.key(), + is_signer: false, + is_writable: true, + }); + // Include tick arrays for swap range (if required) + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_lower.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_current.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_upper.key(), + is_signer: false, + is_writable: true, + }); Ok(accounts) } /// Process the OFT message: swap then clear. - pub fn lz_compose( - ctx: Context, - params: LzComposeParams, - ) -> Result<()> { - // Parse OFT payload + pub fn lz_compose(ctx: Context, params: LzComposeParams) -> Result<()> { let msg = ¶ms.message; + // Decode input amount (bytes 32..40) let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); let mut compose_from = [0u8; 32]; compose_from.copy_from_slice(&msg[40..72]); let inner = &msg[72..]; - // Decode swap params (first 8 bytes = min_amount_out) + // Ensure payload is long enough for swap parameters. require!(inner.len() >= 8, CustomError::InvalidPayload); let min_amount_out = u64::from_be_bytes(inner[0..8].try_into().unwrap()); - // Raydium CLMM CPI call - let signer_seeds: &[&[&[u8]]] = &[&[b"oft_seed"]]; - let cpi_accounts = RaydiumSwapAccounts { - payer: ctx.accounts.payer.to_account_info(), - authority: ctx.accounts.authority.to_account_info(), - amm_config: ctx.accounts.amm_config.to_account_info(), - pool_state: ctx.accounts.pool_state.to_account_info(), + // Build the CPI accounts struct as expected by Raydium’s SwapSingleV2. + let cpi_accounts = SwapSingleV2 { + payer: ctx.accounts.payer.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.source_token_account.to_account_info(), - output_token_account:ctx.accounts.dest_token_account.to_account_info(), - input_vault: ctx.accounts.input_vault.to_account_info(), - output_vault: ctx.accounts.output_vault.to_account_info(), - input_token_program: ctx.accounts.input_token_program.to_account_info(), - output_token_program:ctx.accounts.output_token_program.to_account_info(), - input_token_mint: ctx.accounts.input_token_mint.to_account_info(), - output_token_mint: ctx.accounts.output_token_mint.to_account_info(), - observation_state: ctx.accounts.observation_state.to_account_info(), - token_program_2022: ctx.accounts.token_program.to_account_info(), - memo_program: ctx.accounts.token_program.to_account_info(), + output_token_account: ctx.accounts.dest_token_account.to_account_info(), + input_vault: ctx.accounts.input_vault.to_account_info(), + output_vault: ctx.accounts.output_vault.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + memo_program: ctx.accounts.memo_program.to_account_info(), + input_vault_mint: ctx.accounts.input_token_mint.to_account_info(), + output_vault_mint: ctx.accounts.output_token_mint.to_account_info(), }; - let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.raydium_program.to_account_info(), - cpi_accounts, - signer_seeds, - ); - swap(cpi_ctx, amount_ld, min_amount_out, 0u128, true)?; - // Clear LayerZero compose + // Build the CPI context with the Raydium CLMM program. + let mut cpi_ctx = + CpiContext::new(ctx.accounts.raydium_program.to_account_info(), cpi_accounts); + + // Attach remaining accounts in the exact order that Raydium CLMM's SwapSingleV2 expects. + cpi_ctx = cpi_ctx.with_remaining_accounts(vec![ + ctx.accounts.payer.to_account_info(), // 1 + ctx.accounts.authority.to_account_info(), // 2 + ctx.accounts.amm_config.to_account_info(), // 3 + ctx.accounts.pool_state.to_account_info(), // 4 + ctx.accounts.source_token_account.to_account_info(), // 5 + ctx.accounts.dest_token_account.to_account_info(), // 6 + ctx.accounts.input_vault.to_account_info(), // 7 + ctx.accounts.output_vault.to_account_info(), // 8 + ctx.accounts.observation_state.to_account_info(), // 9 + ctx.accounts.tick_array_lower.to_account_info(), // 10 + ctx.accounts.tick_array_current.to_account_info(), // 11 + ctx.accounts.tick_array_upper.to_account_info(), // 12 + ]); + + // Execute the CPI swap call. + swap_v2( + cpi_ctx, + amount_ld, + min_amount_out, + 0u128, // sqrt_price_limit_x64 (adjust if needed) + true, // is_base_input: true if input token is base token + )?; + + // Clear the LayerZero compose message to finalize processing. let clear_params = ClearComposeParams { - from: compose_from.into(), - guid: params.guid, - index: params.index, + from: compose_from.into(), + guid: params.guid, + index: params.index, message: params.message.clone(), }; + clear_compose( ENDPOINT_ID, ctx.accounts.lz_program.key(), @@ -114,41 +217,74 @@ pub mod composer { pub struct LzComposeTypes<'info> { #[account(seeds = [b"composer"], bump)] pub lz_program: AccountInfo<'info>, - pub payer: Signer<'info>, - #[account(seeds = [b"authority"], bump)] pub authority: UncheckedAccount<'info>, - pub amm_config: AccountInfo<'info>, - #[account(mut)] pub pool_state: AccountInfo<'info>, - #[account(mut)] pub source_token_account:Account<'info, TokenAccount>, - #[account(mut)] pub dest_token_account: Account<'info, TokenAccount>, - #[account(mut)] pub input_vault: AccountInfo<'info>, - #[account(mut)] pub output_vault: AccountInfo<'info>, - pub input_token_program: AccountInfo<'info>, - pub output_token_program:AccountInfo<'info>, - #[account(mut)] pub input_token_mint: AccountInfo<'info>, - #[account(mut)] pub output_token_mint: AccountInfo<'info>, - #[account(mut)] pub observation_state: AccountInfo<'info>, - pub raydium_program: Program<'info, RaydiumCpmm>, - pub token_program: Program<'info, Token>, + pub payer: Signer<'info>, + #[account(seeds = [b"authority"], bump)] + pub authority: AccountInfo<'info>, + pub amm_config: AccountInfo<'info>, + #[account(mut)] + pub pool_state: AccountInfo<'info>, + #[account(mut)] + pub source_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub dest_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub input_vault: AccountInfo<'info>, + #[account(mut)] + pub output_vault: AccountInfo<'info>, + pub token_program: Program<'info, Token>, + pub token_program_2022: Program<'info, Token2022>, + #[account(mut)] + pub input_token_mint: AccountInfo<'info>, + #[account(mut)] + pub output_token_mint: AccountInfo<'info>, + #[account(mut)] + pub observation_state: AccountInfo<'info>, + /// CHECK: Required for memo instructions. Must match spl_memo::id(). + pub memo_program: AccountInfo<'info>, + #[account(mut)] + pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] + pub tick_array_current: AccountInfo<'info>, + #[account(mut)] + pub tick_array_upper: AccountInfo<'info>, + pub raydium_program: Program<'info, AmmV3>, } #[derive(Accounts)] pub struct LzCompose<'info> { - #[account(seeds = [b"composer"], bump)] pub lz_program: AccountInfo<'info>, - pub payer: Signer<'info>, - #[account(seeds = [b"authority"], bump)] pub authority: UncheckedAccount<'info>, - pub amm_config: AccountInfo<'info>, - #[account(mut)] pub pool_state: AccountInfo<'info>, - #[account(mut)] pub source_token_account:Account<'info, TokenAccount>, - #[account(mut)] pub dest_token_account: Account<'info, TokenAccount>, - #[account(mut)] pub input_vault: AccountInfo<'info>, - #[account(mut)] pub output_vault: AccountInfo<'info>, - pub input_token_program: AccountInfo<'info>, - pub output_token_program:AccountInfo<'info>, - #[account(mut)] pub input_token_mint: AccountInfo<'info>, - #[account(mut)] pub output_token_mint: AccountInfo<'info>, - #[account(mut)] pub observation_state: AccountInfo<'info>, - pub raydium_program: Program<'info, RaydiumCpmm>, - pub token_program: Program<'info, Token>, + #[account(seeds = [b"composer"], bump)] + pub lz_program: AccountInfo<'info>, + pub payer: Signer<'info>, + #[account(seeds = [b"authority"], bump)] + pub authority: AccountInfo<'info>, + pub amm_config: AccountInfo<'info>, + #[account(mut)] + pub pool_state: AccountInfo<'info>, + #[account(mut)] + pub source_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub dest_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub input_vault: AccountInfo<'info>, + #[account(mut)] + pub output_vault: AccountInfo<'info>, + pub token_program: Program<'info, Token>, + pub token_program_2022: Program<'info, Token2022>, + #[account(mut)] + pub input_token_mint: AccountInfo<'info>, + #[account(mut)] + pub output_token_mint: AccountInfo<'info>, + #[account(mut)] + pub observation_state: AccountInfo<'info>, + /// CHECK: Required for memo instructions. + pub memo_program: AccountInfo<'info>, + #[account(mut)] + pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] + pub tick_array_current: AccountInfo<'info>, + #[account(mut)] + pub tick_array_upper: AccountInfo<'info>, + pub raydium_program: Program<'info, AmmV3>, } #[error_code] @@ -156,9 +292,3 @@ pub enum CustomError { #[msg("compose_msg too short")] InvalidPayload, } - -#[derive(Clone)] -pub struct RaydiumCpmm; -impl anchor_lang::Id for RaydiumCpmm { - fn id() -> Pubkey { Pubkey::new_from_array([1u8; 32]) } -} \ No newline at end of file From badb58bf2276b9e79828895e57c76cdc720fdf7f Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Thu, 10 Apr 2025 15:51:43 -0700 Subject: [PATCH 03/11] chore: move functionality into instructions + state --- .../src/instructions/init_composer.rs | 38 +++ .../composer/src/instructions/lz_compose.rs | 134 ++++++++ .../src/instructions/lz_compose_types.rs | 159 ++++++++++ .../programs/composer/src/instructions/mod.rs | 7 + .../programs/composer/src/lib.rs | 297 +----------------- .../programs/composer/src/state/composer.rs | 25 ++ .../programs/composer/src/state/mod.rs | 3 + 7 files changed, 383 insertions(+), 280 deletions(-) create mode 100644 examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs create mode 100644 examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs create mode 100644 examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs create mode 100644 examples/oft-solana-composer-library/programs/composer/src/instructions/mod.rs create mode 100644 examples/oft-solana-composer-library/programs/composer/src/state/composer.rs create mode 100644 examples/oft-solana-composer-library/programs/composer/src/state/mod.rs diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs new file mode 100644 index 000000000..91f1f2e53 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs @@ -0,0 +1,38 @@ +use crate::*; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +#[instruction(params: InitComposerParams)] +pub struct InitComposer<'info> { + /// The Composer configuration account, initialized as a PDA. + #[account( + init, + payer = payer, + space = Composer::SIZE, + seeds = [LZ_COMPOSE_TYPES_SEED, payer.key().as_ref(), &[params.id]], + bump + )] + pub composer: Account<'info, Composer>, + + #[account(mut)] + pub payer: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct InitComposerParams { + pub id: u8, + pub oft: Pubkey, + pub endpoint_program: Pubkey, +} + +impl InitComposer<'_> { + /// Processes the init_composer instruction by writing the provided values to the Composer account. + pub fn apply(ctx: &mut Context, params: &InitComposerParams) -> Result<()> { + let composer = &mut ctx.accounts.composer; + composer.oft = params.oft; + composer.endpoint_program = params.endpoint_program; + Ok(()) + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs new file mode 100644 index 000000000..ad9ec9f09 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs @@ -0,0 +1,134 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; +use anchor_spl::token_interface::{Token2022}; +use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; +use raydium_clmm_cpi::cpi::swap_v2; +use std::str::FromStr; + +// LayerZero / OApp endpoint CPI helpers: +use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; +use oapp::endpoint_cpi::{clear_compose}; +use oapp::LzComposeParams; + +// Use our minimal AmmV3 type instead of importing from the raydium_amm_v3 crate. +#[derive(Clone, Debug)] +pub struct AmmV3; + +impl anchor_lang::Id for AmmV3 { + fn id() -> Pubkey { + Pubkey::from_str("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK").unwrap() + } +} + +#[derive(Accounts)] +pub struct LzCompose<'info> { + #[account(seeds = [b"composer"], bump)] + pub lz_program: AccountInfo<'info>, + pub payer: Signer<'info>, + #[account(seeds = [b"authority"], bump)] + pub authority: AccountInfo<'info>, + pub amm_config: AccountInfo<'info>, + #[account(mut)] + pub pool_state: AccountInfo<'info>, + #[account(mut)] + pub source_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub dest_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub input_vault: AccountInfo<'info>, + #[account(mut)] + pub output_vault: AccountInfo<'info>, + pub token_program: Program<'info, Token>, + pub token_program_2022: Program<'info, Token2022>, + #[account(mut)] + pub input_token_mint: AccountInfo<'info>, + #[account(mut)] + pub output_token_mint: AccountInfo<'info>, + #[account(mut)] + pub observation_state: AccountInfo<'info>, + /// CHECK: Required for memo instructions. + pub memo_program: AccountInfo<'info>, + #[account(mut)] + pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] + pub tick_array_current: AccountInfo<'info>, + #[account(mut)] + pub tick_array_upper: AccountInfo<'info>, + pub raydium_program: Program<'info, AmmV3>, +} + +/// Processes the lz_compose instruction. +impl LzCompose<'_> { + pub fn apply(ctx: &mut Context, params: &LzComposeParams) -> Result<()> { + let msg = ¶ms.message; + // Decode input amount from bytes 32..40. + let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); + let mut compose_from = [0u8; 32]; + compose_from.copy_from_slice(&msg[40..72]); + let inner = &msg[72..]; + + let min_amount_out = u64::from_be_bytes(inner[0..8].try_into().unwrap()); + + // Build the CPI accounts struct for Raydium's SwapSingleV2. + let cpi_accounts = SwapSingleV2 { + payer: ctx.accounts.payer.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + input_token_account: ctx.accounts.source_token_account.to_account_info(), + output_token_account: ctx.accounts.dest_token_account.to_account_info(), + input_vault: ctx.accounts.input_vault.to_account_info(), + output_vault: ctx.accounts.output_vault.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + memo_program: ctx.accounts.memo_program.to_account_info(), + input_vault_mint: ctx.accounts.input_token_mint.to_account_info(), + output_vault_mint: ctx.accounts.output_token_mint.to_account_info(), + }; + + // Build the CPI context with the Raydium CLMM program. + let mut cpi_ctx = + CpiContext::new(ctx.accounts.raydium_program.to_account_info(), cpi_accounts); + + // Attach remaining accounts in the exact order required. + cpi_ctx = cpi_ctx.with_remaining_accounts(vec![ + ctx.accounts.payer.to_account_info(), + ctx.accounts.authority.to_account_info(), + ctx.accounts.amm_config.to_account_info(), + ctx.accounts.pool_state.to_account_info(), + ctx.accounts.source_token_account.to_account_info(), + ctx.accounts.dest_token_account.to_account_info(), + ctx.accounts.input_vault.to_account_info(), + ctx.accounts.output_vault.to_account_info(), + ctx.accounts.observation_state.to_account_info(), + ctx.accounts.tick_array_lower.to_account_info(), + ctx.accounts.tick_array_current.to_account_info(), + ctx.accounts.tick_array_upper.to_account_info(), + ]); + + // Execute the CPI swap call. + swap_v2( + cpi_ctx, + amount_ld, + min_amount_out, + 0u128, // sqrt_price_limit_x64 (adjust as needed) + true, // is_base_input + )?; + + // Clear the LayerZero compose message. + let clear_params = ClearComposeParams { + from: compose_from.into(), + guid: params.guid, + index: params.index, + message: params.message.clone(), + }; + + clear_compose( + ENDPOINT_ID, + ctx.accounts.lz_program.key(), + &ctx.remaining_accounts, + &[b"composer"], + clear_params, + ) + } +} \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs new file mode 100644 index 000000000..6a2f961bc --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs @@ -0,0 +1,159 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; +use anchor_spl::token_interface::Token2022; + +// LayerZero / OApp endpoint CPI helpers: +use oapp::endpoint_cpi::{get_accounts_for_clear_compose, LzAccount}; +use oapp::{endpoint::ID as ENDPOINT_ID, LzComposeParams}; + +#[derive(Accounts)] +pub struct LzComposeTypes<'info> { + #[account(seeds = [b"composer"], bump)] + pub lz_program: AccountInfo<'info>, + pub payer: Signer<'info>, + #[account(seeds = [b"authority"], bump)] + pub authority: AccountInfo<'info>, + pub amm_config: AccountInfo<'info>, + #[account(mut)] + pub pool_state: AccountInfo<'info>, + #[account(mut)] + pub source_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub dest_token_account: Account<'info, TokenAccount>, + #[account(mut)] + pub input_vault: AccountInfo<'info>, + #[account(mut)] + pub output_vault: AccountInfo<'info>, + pub token_program: Program<'info, Token>, + pub token_program_2022: Program<'info, Token2022>, + #[account(mut)] + pub input_token_mint: AccountInfo<'info>, + #[account(mut)] + pub output_token_mint: AccountInfo<'info>, + #[account(mut)] + pub observation_state: AccountInfo<'info>, + /// CHECK: Required for memo instructions. Must match spl_memo::id(). + pub memo_program: AccountInfo<'info>, + #[account(mut)] + pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] + pub tick_array_current: AccountInfo<'info>, + #[account(mut)] + pub tick_array_upper: AccountInfo<'info>, + pub raydium_program: Program<'info, crate::AmmV3>, +} + +impl LzComposeTypes<'_> { + /// Processes the lz_compose_types instruction and returns a vector of LzAccount. + /// The list of accounts should follow the rules below: + /// 1. Include all the accounts that are used in the LzCompose instruction, including the + /// accounts that are used by the Endpoint program. + /// 2. Set the account is a signer with ZERO address if the LzCompose instruction needs a payer + /// to pay fee, like rent. + /// 3. Set the account is writable if the LzCompose instruction needs to modify the account. + pub fn apply( + ctx: &Context, + params: &LzComposeParams, + ) -> Result> { + // LayerZero endpoint accounts first. + let mut accounts = get_accounts_for_clear_compose( + ENDPOINT_ID, + ¶ms.from, + &ctx.accounts.lz_program.key(), + ¶ms.guid, + params.index, + ¶ms.message, + ); + + // Append the Raydium CLMM CPI accounts in the exact order required. + accounts.push(LzAccount { + pubkey: ctx.accounts.payer.key(), + is_signer: true, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.authority.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.amm_config.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.pool_state.key(), + is_signer: false, + is_writable: true, + }); + // Assumes that the source is the user's input token account. + accounts.push(LzAccount { + pubkey: ctx.accounts.source_token_account.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.dest_token_account.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.input_vault.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.output_vault.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.token_program.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.token_program_2022.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.memo_program.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.input_token_mint.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.output_token_mint.key(), + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.observation_state.key(), + is_signer: false, + is_writable: true, + }); + // Include tick arrays for swap range (if required) + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_lower.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_current.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_upper.key(), + is_signer: false, + is_writable: true, + }); + + Ok(accounts) + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/mod.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/mod.rs new file mode 100644 index 000000000..7712604e4 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/mod.rs @@ -0,0 +1,7 @@ +pub mod lz_compose; +pub mod lz_compose_types; +pub mod init_composer; + +pub use lz_compose::*; +pub use lz_compose_types::*; +pub use init_composer::*; diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index 4ae453358..ce356880e 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -1,294 +1,31 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{Token, TokenAccount}; -use anchor_spl::token_interface::{Token2022}; - -/// LayerZero / OApp endpoint CPI helpers -use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; -use oapp::endpoint_cpi::{clear_compose, get_accounts_for_clear_compose, LzAccount}; -use oapp::LzComposeParams; - -/// Raydium CLMM CPI (concentrated liquidity swap) -use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; -use raydium_clmm_cpi::cpi::swap_v2; - -use std::str::FromStr; +mod instructions; +mod state; -// Instead of importing AmmV3 from the raydium_amm_v3 crate, we define our own minimal type. -#[derive(Clone, Debug)] -pub struct AmmV3; - -impl anchor_lang::Id for AmmV3 { - fn id() -> Pubkey { - Pubkey::from_str("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK").unwrap() - } -} +use anchor_lang::prelude::*; +use instructions::*; +use oapp::{endpoint_cpi::LzAccount, LzComposeParams}; +use state::*; declare_id!("3NJ7AUBaj9N8kBsRqA7SYWJ1poEUsEW36gCG1EfLDkW2"); +const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; + #[program] pub mod composer { use super::*; - /// Return all accounts needed by `lz_compose`. + pub fn init_composer(mut ctx: Context, params: InitComposerParams) -> Result<()> { + InitComposer::apply(&mut ctx, ¶ms) + } + + pub fn lz_compose(mut ctx: Context, params: LzComposeParams) -> Result<()> { + LzCompose::apply(&mut ctx, ¶ms) + } + pub fn lz_compose_types( ctx: Context, params: LzComposeParams, ) -> Result> { - // LayerZero endpoint accounts first. - let mut accounts = get_accounts_for_clear_compose( - ENDPOINT_ID, - ¶ms.from, - &ctx.accounts.lz_program.key(), - ¶ms.guid, - params.index, - ¶ms.message, - ); - - // Append the Raydium CLMM CPI accounts *in the exact order* required by the CPI instruction. - accounts.push(LzAccount { - pubkey: ctx.accounts.payer.key(), - is_signer: true, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.authority.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.amm_config.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.pool_state.key(), - is_signer: false, - is_writable: true, - }); - // Note: We assume here that the "source" account is the user’s input token account. - accounts.push(LzAccount { - pubkey: ctx.accounts.source_token_account.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.dest_token_account.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.input_vault.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.output_vault.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.token_program.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.token_program_2022.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.memo_program.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.input_token_mint.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.output_token_mint.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.observation_state.key(), - is_signer: false, - is_writable: true, - }); - // Include tick arrays for swap range (if required) - accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_lower.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_current.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_upper.key(), - is_signer: false, - is_writable: true, - }); - - Ok(accounts) + LzComposeTypes::apply(&ctx, ¶ms) } - - /// Process the OFT message: swap then clear. - pub fn lz_compose(ctx: Context, params: LzComposeParams) -> Result<()> { - let msg = ¶ms.message; - // Decode input amount (bytes 32..40) - let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); - let mut compose_from = [0u8; 32]; - compose_from.copy_from_slice(&msg[40..72]); - let inner = &msg[72..]; - - // Ensure payload is long enough for swap parameters. - require!(inner.len() >= 8, CustomError::InvalidPayload); - let min_amount_out = u64::from_be_bytes(inner[0..8].try_into().unwrap()); - - // Build the CPI accounts struct as expected by Raydium’s SwapSingleV2. - let cpi_accounts = SwapSingleV2 { - payer: ctx.accounts.payer.to_account_info(), - amm_config: ctx.accounts.amm_config.to_account_info(), - pool_state: ctx.accounts.pool_state.to_account_info(), - input_token_account: ctx.accounts.source_token_account.to_account_info(), - output_token_account: ctx.accounts.dest_token_account.to_account_info(), - input_vault: ctx.accounts.input_vault.to_account_info(), - output_vault: ctx.accounts.output_vault.to_account_info(), - observation_state: ctx.accounts.observation_state.to_account_info(), - token_program: ctx.accounts.token_program.to_account_info(), - token_program_2022: ctx.accounts.token_program_2022.to_account_info(), - memo_program: ctx.accounts.memo_program.to_account_info(), - input_vault_mint: ctx.accounts.input_token_mint.to_account_info(), - output_vault_mint: ctx.accounts.output_token_mint.to_account_info(), - }; - - // Build the CPI context with the Raydium CLMM program. - let mut cpi_ctx = - CpiContext::new(ctx.accounts.raydium_program.to_account_info(), cpi_accounts); - - // Attach remaining accounts in the exact order that Raydium CLMM's SwapSingleV2 expects. - cpi_ctx = cpi_ctx.with_remaining_accounts(vec![ - ctx.accounts.payer.to_account_info(), // 1 - ctx.accounts.authority.to_account_info(), // 2 - ctx.accounts.amm_config.to_account_info(), // 3 - ctx.accounts.pool_state.to_account_info(), // 4 - ctx.accounts.source_token_account.to_account_info(), // 5 - ctx.accounts.dest_token_account.to_account_info(), // 6 - ctx.accounts.input_vault.to_account_info(), // 7 - ctx.accounts.output_vault.to_account_info(), // 8 - ctx.accounts.observation_state.to_account_info(), // 9 - ctx.accounts.tick_array_lower.to_account_info(), // 10 - ctx.accounts.tick_array_current.to_account_info(), // 11 - ctx.accounts.tick_array_upper.to_account_info(), // 12 - ]); - - // Execute the CPI swap call. - swap_v2( - cpi_ctx, - amount_ld, - min_amount_out, - 0u128, // sqrt_price_limit_x64 (adjust if needed) - true, // is_base_input: true if input token is base token - )?; - - // Clear the LayerZero compose message to finalize processing. - let clear_params = ClearComposeParams { - from: compose_from.into(), - guid: params.guid, - index: params.index, - message: params.message.clone(), - }; - - clear_compose( - ENDPOINT_ID, - ctx.accounts.lz_program.key(), - &ctx.remaining_accounts, - &[b"composer"], - clear_params, - ) - } -} - -#[derive(Accounts)] -pub struct LzComposeTypes<'info> { - #[account(seeds = [b"composer"], bump)] - pub lz_program: AccountInfo<'info>, - pub payer: Signer<'info>, - #[account(seeds = [b"authority"], bump)] - pub authority: AccountInfo<'info>, - pub amm_config: AccountInfo<'info>, - #[account(mut)] - pub pool_state: AccountInfo<'info>, - #[account(mut)] - pub source_token_account: Account<'info, TokenAccount>, - #[account(mut)] - pub dest_token_account: Account<'info, TokenAccount>, - #[account(mut)] - pub input_vault: AccountInfo<'info>, - #[account(mut)] - pub output_vault: AccountInfo<'info>, - pub token_program: Program<'info, Token>, - pub token_program_2022: Program<'info, Token2022>, - #[account(mut)] - pub input_token_mint: AccountInfo<'info>, - #[account(mut)] - pub output_token_mint: AccountInfo<'info>, - #[account(mut)] - pub observation_state: AccountInfo<'info>, - /// CHECK: Required for memo instructions. Must match spl_memo::id(). - pub memo_program: AccountInfo<'info>, - #[account(mut)] - pub tick_array_lower: AccountInfo<'info>, - #[account(mut)] - pub tick_array_current: AccountInfo<'info>, - #[account(mut)] - pub tick_array_upper: AccountInfo<'info>, - pub raydium_program: Program<'info, AmmV3>, -} - -#[derive(Accounts)] -pub struct LzCompose<'info> { - #[account(seeds = [b"composer"], bump)] - pub lz_program: AccountInfo<'info>, - pub payer: Signer<'info>, - #[account(seeds = [b"authority"], bump)] - pub authority: AccountInfo<'info>, - pub amm_config: AccountInfo<'info>, - #[account(mut)] - pub pool_state: AccountInfo<'info>, - #[account(mut)] - pub source_token_account: Account<'info, TokenAccount>, - #[account(mut)] - pub dest_token_account: Account<'info, TokenAccount>, - #[account(mut)] - pub input_vault: AccountInfo<'info>, - #[account(mut)] - pub output_vault: AccountInfo<'info>, - pub token_program: Program<'info, Token>, - pub token_program_2022: Program<'info, Token2022>, - #[account(mut)] - pub input_token_mint: AccountInfo<'info>, - #[account(mut)] - pub output_token_mint: AccountInfo<'info>, - #[account(mut)] - pub observation_state: AccountInfo<'info>, - /// CHECK: Required for memo instructions. - pub memo_program: AccountInfo<'info>, - #[account(mut)] - pub tick_array_lower: AccountInfo<'info>, - #[account(mut)] - pub tick_array_current: AccountInfo<'info>, - #[account(mut)] - pub tick_array_upper: AccountInfo<'info>, - pub raydium_program: Program<'info, AmmV3>, -} - -#[error_code] -pub enum CustomError { - #[msg("compose_msg too short")] - InvalidPayload, } diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs new file mode 100644 index 000000000..5ee783461 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -0,0 +1,25 @@ +use crate::*; + +#[account] +pub struct Composer { + /// The OFT PDA (unique to this composer instance) + pub oft: Pubkey, + /// The Endpoint PDA (the authorized endpoint for LZ messages) + pub endpoint_program: Pubkey, + /// Bump for PDA derivation. + pub bump: u8, +} + +impl Composer { + pub const SIZE: usize = 8 + std::mem::size_of::(); +} + +/// LzComposeTypesAccounts includes accounts that are used in the LzComposeTypes instruction. +#[account] +pub struct LzComposeTypesAccounts { + pub count: Pubkey, +} + +impl LzComposeTypesAccounts { + pub const SIZE: usize = 8 + std::mem::size_of::(); +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/mod.rs b/examples/oft-solana-composer-library/programs/composer/src/state/mod.rs new file mode 100644 index 000000000..9ffd78ab4 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod composer; + +pub use composer::*; \ No newline at end of file From c129d468fe17eb6fdd52ec0917d0e4ce4fcf3c9b Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Thu, 10 Apr 2025 17:20:32 -0700 Subject: [PATCH 04/11] chore: update composers --- .../oft-solana-composer-library/Cargo.lock | 1 + .../programs/composer/Cargo.toml | 1 + .../composer/src/instructions/lz_compose.rs | 146 ++++++++++-------- .../src/instructions/lz_compose_types.rs | 111 ++++--------- .../programs/composer/src/lib.rs | 4 +- .../programs/composer/src/state/composer.rs | 29 +++- 6 files changed, 145 insertions(+), 147 deletions(-) diff --git a/examples/oft-solana-composer-library/Cargo.lock b/examples/oft-solana-composer-library/Cargo.lock index e8c5edebe..fe600d563 100644 --- a/examples/oft-solana-composer-library/Cargo.lock +++ b/examples/oft-solana-composer-library/Cargo.lock @@ -667,6 +667,7 @@ dependencies = [ "oapp", "raydium-clmm-cpi", "solana-helper", + "spl-memo", "utils", ] diff --git a/examples/oft-solana-composer-library/programs/composer/Cargo.toml b/examples/oft-solana-composer-library/programs/composer/Cargo.toml index 2dcbfbaf6..0dd445b2d 100644 --- a/examples/oft-solana-composer-library/programs/composer/Cargo.toml +++ b/examples/oft-solana-composer-library/programs/composer/Cargo.toml @@ -22,4 +22,5 @@ anchor-spl = "0.29.0" raydium-clmm-cpi = { git = "https://github.com/St0rmBr3w/raydium-cpi/", package = "raydium-clmm-cpi", branch = "anchor-0.29.0" } oapp = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } utils = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +spl-memo = "=4.0.0" solana-helper = "0.1.0" \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs index ad9ec9f09..007ab61d4 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs @@ -1,63 +1,91 @@ +use crate::*; use anchor_lang::prelude::*; -use anchor_spl::token::{Token, TokenAccount}; -use anchor_spl::token_interface::{Token2022}; +use anchor_spl::token::{Token}; +use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; use raydium_clmm_cpi::cpi::swap_v2; -use std::str::FromStr; - -// LayerZero / OApp endpoint CPI helpers: +use raydium_clmm_cpi::{ + program::RaydiumClmm, + states::{AmmConfig, ObservationState, PoolState}, +}; use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; -use oapp::endpoint_cpi::{clear_compose}; +use oapp::endpoint_cpi::clear_compose; use oapp::LzComposeParams; - -// Use our minimal AmmV3 type instead of importing from the raydium_amm_v3 crate. -#[derive(Clone, Debug)] -pub struct AmmV3; - -impl anchor_lang::Id for AmmV3 { - fn id() -> Pubkey { - Pubkey::from_str("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK").unwrap() - } -} +use spl_memo; #[derive(Accounts)] pub struct LzCompose<'info> { - #[account(seeds = [b"composer"], bump)] - pub lz_program: AccountInfo<'info>, + // Use the stored “oft” field as the unique seed. + #[account( + mut, + seeds = [COMPOSER_SEED, &composer.oft.to_bytes()], + bump = composer.bump + )] + pub composer: Account<'info, Composer>, + + pub clmm_program: Program<'info, RaydiumClmm>, + /// The user performing the swap. pub payer: Signer<'info>, - #[account(seeds = [b"authority"], bump)] - pub authority: AccountInfo<'info>, - pub amm_config: AccountInfo<'info>, + + /// The factory state to read protocol fees. + #[account(address = pool_state.load()?.amm_config)] + pub amm_config: Box>, + + /// The program account of the pool in which the swap will be performed. #[account(mut)] - pub pool_state: AccountInfo<'info>, + pub pool_state: AccountLoader<'info, PoolState>, + + /// The user token account for input token. #[account(mut)] - pub source_token_account: Account<'info, TokenAccount>, + pub input_token_account: Box>, + + /// The user token account for output token. #[account(mut)] - pub dest_token_account: Account<'info, TokenAccount>, + pub output_token_account: Box>, + + /// The vault token account for input token. #[account(mut)] - pub input_vault: AccountInfo<'info>, + pub input_vault: Box>, + + /// The vault token account for output token. #[account(mut)] - pub output_vault: AccountInfo<'info>, + pub output_vault: Box>, + + /// The program account for the most recent oracle observation. + #[account(mut, address = pool_state.load()?.observation_key)] + pub observation_state: AccountLoader<'info, ObservationState>, + + /// SPL program for token transfers. pub token_program: Program<'info, Token>, + + /// SPL program 2022 for token transfers. pub token_program_2022: Program<'info, Token2022>, - #[account(mut)] - pub input_token_mint: AccountInfo<'info>, - #[account(mut)] - pub output_token_mint: AccountInfo<'info>, - #[account(mut)] - pub observation_state: AccountInfo<'info>, - /// CHECK: Required for memo instructions. - pub memo_program: AccountInfo<'info>, + + /// CHECK: Must match spl_memo::id(). + #[account(address = spl_memo::id())] + pub memo_program: UncheckedAccount<'info>, + + /// The mint of the input token vault. + #[account(address = input_vault.mint)] + pub input_vault_mint: Box>, + + /// The mint of the output token vault. + #[account(address = output_vault.mint)] + pub output_vault_mint: Box>, + + // Extra accounts required by the CPI. + /// The authority account for the swap. + pub authority: AccountInfo<'info>, + + /// Tick arrays for the swap range. #[account(mut)] pub tick_array_lower: AccountInfo<'info>, #[account(mut)] pub tick_array_current: AccountInfo<'info>, #[account(mut)] pub tick_array_upper: AccountInfo<'info>, - pub raydium_program: Program<'info, AmmV3>, } -/// Processes the lz_compose instruction. impl LzCompose<'_> { pub fn apply(ctx: &mut Context, params: &LzComposeParams) -> Result<()> { let msg = ¶ms.message; @@ -66,38 +94,33 @@ impl LzCompose<'_> { let mut compose_from = [0u8; 32]; compose_from.copy_from_slice(&msg[40..72]); let inner = &msg[72..]; - let min_amount_out = u64::from_be_bytes(inner[0..8].try_into().unwrap()); - // Build the CPI accounts struct for Raydium's SwapSingleV2. + // Build CPI accounts struct. let cpi_accounts = SwapSingleV2 { payer: ctx.accounts.payer.to_account_info(), amm_config: ctx.accounts.amm_config.to_account_info(), pool_state: ctx.accounts.pool_state.to_account_info(), - input_token_account: ctx.accounts.source_token_account.to_account_info(), - output_token_account: ctx.accounts.dest_token_account.to_account_info(), + input_token_account: ctx.accounts.input_token_account.to_account_info(), + output_token_account: ctx.accounts.output_token_account.to_account_info(), input_vault: ctx.accounts.input_vault.to_account_info(), output_vault: ctx.accounts.output_vault.to_account_info(), observation_state: ctx.accounts.observation_state.to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), token_program_2022: ctx.accounts.token_program_2022.to_account_info(), memo_program: ctx.accounts.memo_program.to_account_info(), - input_vault_mint: ctx.accounts.input_token_mint.to_account_info(), - output_vault_mint: ctx.accounts.output_token_mint.to_account_info(), + input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), + output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), }; - // Build the CPI context with the Raydium CLMM program. - let mut cpi_ctx = - CpiContext::new(ctx.accounts.raydium_program.to_account_info(), cpi_accounts); - - // Attach remaining accounts in the exact order required. + let mut cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts); cpi_ctx = cpi_ctx.with_remaining_accounts(vec![ ctx.accounts.payer.to_account_info(), ctx.accounts.authority.to_account_info(), ctx.accounts.amm_config.to_account_info(), ctx.accounts.pool_state.to_account_info(), - ctx.accounts.source_token_account.to_account_info(), - ctx.accounts.dest_token_account.to_account_info(), + ctx.accounts.input_token_account.to_account_info(), + ctx.accounts.output_token_account.to_account_info(), ctx.accounts.input_vault.to_account_info(), ctx.accounts.output_vault.to_account_info(), ctx.accounts.observation_state.to_account_info(), @@ -106,18 +129,17 @@ impl LzCompose<'_> { ctx.accounts.tick_array_upper.to_account_info(), ]); - // Execute the CPI swap call. - swap_v2( - cpi_ctx, - amount_ld, - min_amount_out, - 0u128, // sqrt_price_limit_x64 (adjust as needed) - true, // is_base_input - )?; + swap_v2(cpi_ctx, amount_ld, min_amount_out, 0u128, true)?; + + // Use the stored "oft" field as the seed instead of a non-existent “id”. + let seeds: &[&[u8]] = &[ + COMPOSER_SEED, + &ctx.accounts.composer.oft.to_bytes(), + &[ctx.accounts.composer.bump], + ]; - // Clear the LayerZero compose message. let clear_params = ClearComposeParams { - from: compose_from.into(), + from: params.from, guid: params.guid, index: params.index, message: params.message.clone(), @@ -125,10 +147,10 @@ impl LzCompose<'_> { clear_compose( ENDPOINT_ID, - ctx.accounts.lz_program.key(), + ctx.accounts.composer.key(), &ctx.remaining_accounts, - &[b"composer"], + seeds, clear_params, ) } -} \ No newline at end of file +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs index 6a2f961bc..74650b1f9 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs @@ -1,61 +1,61 @@ use anchor_lang::prelude::*; -use anchor_spl::token::{Token, TokenAccount}; -use anchor_spl::token_interface::Token2022; - -// LayerZero / OApp endpoint CPI helpers: +use anchor_spl::token::{Token}; +use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; +use spl_memo; +use raydium_clmm_cpi::{ + program::RaydiumClmm, + states::{AmmConfig, ObservationState, PoolState}, +}; use oapp::endpoint_cpi::{get_accounts_for_clear_compose, LzAccount}; use oapp::{endpoint::ID as ENDPOINT_ID, LzComposeParams}; #[derive(Accounts)] pub struct LzComposeTypes<'info> { - #[account(seeds = [b"composer"], bump)] - pub lz_program: AccountInfo<'info>, + pub clmm_program: Program<'info, RaydiumClmm>, + /// The user performing the swap. pub payer: Signer<'info>, - #[account(seeds = [b"authority"], bump)] - pub authority: AccountInfo<'info>, - pub amm_config: AccountInfo<'info>, + #[account(address = pool_state.load()?.amm_config)] + pub amm_config: Box>, #[account(mut)] - pub pool_state: AccountInfo<'info>, + pub pool_state: AccountLoader<'info, PoolState>, #[account(mut)] - pub source_token_account: Account<'info, TokenAccount>, + pub input_token_account: Box>, #[account(mut)] - pub dest_token_account: Account<'info, TokenAccount>, + pub output_token_account: Box>, #[account(mut)] - pub input_vault: AccountInfo<'info>, + pub input_vault: Box>, #[account(mut)] - pub output_vault: AccountInfo<'info>, + pub output_vault: Box>, + #[account(mut, address = pool_state.load()?.observation_key)] + pub observation_state: AccountLoader<'info, ObservationState>, pub token_program: Program<'info, Token>, pub token_program_2022: Program<'info, Token2022>, - #[account(mut)] - pub input_token_mint: AccountInfo<'info>, - #[account(mut)] - pub output_token_mint: AccountInfo<'info>, - #[account(mut)] - pub observation_state: AccountInfo<'info>, - /// CHECK: Required for memo instructions. Must match spl_memo::id(). - pub memo_program: AccountInfo<'info>, + #[account(address = spl_memo::id())] + pub memo_program: UncheckedAccount<'info>, + #[account(address = input_vault.mint)] + pub input_vault_mint: Box>, + #[account(address = output_vault.mint)] + pub output_vault_mint: Box>, + // Extra accounts required for the clear_compose call. + /// The LayerZero endpoint program. + pub lz_program: AccountInfo<'info>, + /// The authority account for the swap. + pub authority: AccountInfo<'info>, + /// Tick array accounts for the swap range. #[account(mut)] pub tick_array_lower: AccountInfo<'info>, #[account(mut)] pub tick_array_current: AccountInfo<'info>, #[account(mut)] pub tick_array_upper: AccountInfo<'info>, - pub raydium_program: Program<'info, crate::AmmV3>, } impl LzComposeTypes<'_> { - /// Processes the lz_compose_types instruction and returns a vector of LzAccount. - /// The list of accounts should follow the rules below: - /// 1. Include all the accounts that are used in the LzCompose instruction, including the - /// accounts that are used by the Endpoint program. - /// 2. Set the account is a signer with ZERO address if the LzCompose instruction needs a payer - /// to pay fee, like rent. - /// 3. Set the account is writable if the LzCompose instruction needs to modify the account. + /// Generates the list of LzAccounts expected by the clear_compose call. pub fn apply( ctx: &Context, params: &LzComposeParams, ) -> Result> { - // LayerZero endpoint accounts first. let mut accounts = get_accounts_for_clear_compose( ENDPOINT_ID, ¶ms.from, @@ -64,8 +64,6 @@ impl LzComposeTypes<'_> { params.index, ¶ms.message, ); - - // Append the Raydium CLMM CPI accounts in the exact order required. accounts.push(LzAccount { pubkey: ctx.accounts.payer.key(), is_signer: true, @@ -86,14 +84,13 @@ impl LzComposeTypes<'_> { is_signer: false, is_writable: true, }); - // Assumes that the source is the user's input token account. accounts.push(LzAccount { - pubkey: ctx.accounts.source_token_account.key(), + pubkey: ctx.accounts.input_token_account.key(), is_signer: false, is_writable: true, }); accounts.push(LzAccount { - pubkey: ctx.accounts.dest_token_account.key(), + pubkey: ctx.accounts.output_token_account.key(), is_signer: false, is_writable: true, }); @@ -107,53 +104,11 @@ impl LzComposeTypes<'_> { is_signer: false, is_writable: true, }); - accounts.push(LzAccount { - pubkey: ctx.accounts.token_program.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.token_program_2022.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.memo_program.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.input_token_mint.key(), - is_signer: false, - is_writable: false, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.output_token_mint.key(), - is_signer: false, - is_writable: false, - }); accounts.push(LzAccount { pubkey: ctx.accounts.observation_state.key(), is_signer: false, is_writable: true, }); - // Include tick arrays for swap range (if required) - accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_lower.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_current.key(), - is_signer: false, - is_writable: true, - }); - accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_upper.key(), - is_signer: false, - is_writable: true, - }); - Ok(accounts) } } diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index ce356880e..79f690f23 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -9,19 +9,17 @@ use state::*; declare_id!("3NJ7AUBaj9N8kBsRqA7SYWJ1poEUsEW36gCG1EfLDkW2"); const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; +const COMPOSER_SEED: &[u8] = b"Composer"; #[program] pub mod composer { use super::*; - pub fn init_composer(mut ctx: Context, params: InitComposerParams) -> Result<()> { InitComposer::apply(&mut ctx, ¶ms) } - pub fn lz_compose(mut ctx: Context, params: LzComposeParams) -> Result<()> { LzCompose::apply(&mut ctx, ¶ms) } - pub fn lz_compose_types( ctx: Context, params: LzComposeParams, diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs index 5ee783461..9cbceee1b 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -1,4 +1,4 @@ -use crate::*; +use anchor_lang::prelude::*; #[account] pub struct Composer { @@ -11,13 +11,34 @@ pub struct Composer { } impl Composer { - pub const SIZE: usize = 8 + std::mem::size_of::(); + // Discriminator (8) + 32 + 32 + 1 = 73 bytes. + pub const SIZE: usize = 8 + 32 + 32 + 1; } -/// LzComposeTypesAccounts includes accounts that are used in the LzComposeTypes instruction. +/// LzComposeTypesAccounts includes the pubkeys of all accounts used in the LzComposeTypes instruction. +/// (Adjust the fields as needed.) #[account] pub struct LzComposeTypesAccounts { - pub count: Pubkey, + pub composer: Pubkey, + pub clmm_program: Pubkey, + pub payer: Pubkey, + pub amm_config: Pubkey, + pub pool_state: Pubkey, + pub input_token_account: Pubkey, + pub output_token_account: Pubkey, + pub input_vault: Pubkey, + pub output_vault: Pubkey, + pub observation_state: Pubkey, + pub token_program: Pubkey, + pub token_program_2022: Pubkey, + pub memo_program: Pubkey, + pub input_vault_mint: Pubkey, + pub output_vault_mint: Pubkey, + pub lz_program: Pubkey, + pub authority: Pubkey, + pub tick_array_lower: Pubkey, + pub tick_array_current: Pubkey, + pub tick_array_upper: Pubkey, } impl LzComposeTypesAccounts { From 7f839f2c350f548a66f022e9cab0c005b510bbb9 Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Fri, 11 Apr 2025 08:50:18 -0700 Subject: [PATCH 05/11] chore: anchor build successful with solana-cli v1.17.31 --- .../src/instructions/init_composer.rs | 18 ++++++- .../composer/src/instructions/lz_compose.rs | 9 +++- .../src/instructions/lz_compose_types.rs | 53 ++++++++++++------- .../programs/composer/src/lib.rs | 2 + .../programs/composer/src/state/composer.rs | 10 +++- 5 files changed, 70 insertions(+), 22 deletions(-) diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs index 91f1f2e53..67b13b46d 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs @@ -9,11 +9,21 @@ pub struct InitComposer<'info> { init, payer = payer, space = Composer::SIZE, - seeds = [LZ_COMPOSE_TYPES_SEED, payer.key().as_ref(), &[params.id]], + seeds = [COMPOSER_SEED, params.oft.as_ref()], bump )] pub composer: Account<'info, Composer>, + /// The LzComposeTypesAccounts account, storing extra addresses required for lz_compose_types. + #[account( + init, + payer = payer, + space = LzComposeTypesAccounts::SIZE, + seeds = [LZ_COMPOSE_TYPES_SEED, composer.key().as_ref()], + bump + )] + pub lz_compose_types_accounts: Account<'info, LzComposeTypesAccounts>, + #[account(mut)] pub payer: Signer<'info>, @@ -33,6 +43,12 @@ impl InitComposer<'_> { let composer = &mut ctx.accounts.composer; composer.oft = params.oft; composer.endpoint_program = params.endpoint_program; + composer.bump = ctx.bumps.composer; + + // Initialize lz_compose_types_accounts with the composer address. + let lz_accounts = &mut ctx.accounts.lz_compose_types_accounts; + lz_accounts.composer = composer.key(); + // Other fields in LzComposeTypesAccounts can be set as needed. Ok(()) } } diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs index 007ab61d4..c76d00b8e 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs @@ -2,15 +2,18 @@ use crate::*; use anchor_lang::prelude::*; use anchor_spl::token::{Token}; use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; + use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; use raydium_clmm_cpi::cpi::swap_v2; use raydium_clmm_cpi::{ program::RaydiumClmm, states::{AmmConfig, ObservationState, PoolState}, }; + use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; use oapp::endpoint_cpi::clear_compose; use oapp::LzComposeParams; + use spl_memo; #[derive(Accounts)] @@ -88,6 +91,10 @@ pub struct LzCompose<'info> { impl LzCompose<'_> { pub fn apply(ctx: &mut Context, params: &LzComposeParams) -> Result<()> { + // Validate that the message is coming from the expected OFT and addressed to this composer. + require!(params.from == ctx.accounts.composer.oft, ComposerError::InvalidFrom); + require!(params.to == ctx.accounts.composer.key(), ComposerError::InvalidTo); + let msg = ¶ms.message; // Decode input amount from bytes 32..40. let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); @@ -131,7 +138,7 @@ impl LzCompose<'_> { swap_v2(cpi_ctx, amount_ld, min_amount_out, 0u128, true)?; - // Use the stored "oft" field as the seed instead of a non-existent “id”. + // Use the stored "oft" field as the seed. let seeds: &[&[u8]] = &[ COMPOSER_SEED, &ctx.accounts.composer.oft.to_bytes(), diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs index 74650b1f9..f1c4004d6 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs @@ -2,10 +2,12 @@ use anchor_lang::prelude::*; use anchor_spl::token::{Token}; use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; use spl_memo; + use raydium_clmm_cpi::{ program::RaydiumClmm, states::{AmmConfig, ObservationState, PoolState}, }; + use oapp::endpoint_cpi::{get_accounts_for_clear_compose, LzAccount}; use oapp::{endpoint::ID as ENDPOINT_ID, LzComposeParams}; @@ -16,17 +18,12 @@ pub struct LzComposeTypes<'info> { pub payer: Signer<'info>, #[account(address = pool_state.load()?.amm_config)] pub amm_config: Box>, - #[account(mut)] pub pool_state: AccountLoader<'info, PoolState>, - #[account(mut)] pub input_token_account: Box>, - #[account(mut)] pub output_token_account: Box>, - #[account(mut)] pub input_vault: Box>, - #[account(mut)] pub output_vault: Box>, - #[account(mut, address = pool_state.load()?.observation_key)] + #[account(address = pool_state.load()?.observation_key)] pub observation_state: AccountLoader<'info, ObservationState>, pub token_program: Program<'info, Token>, pub token_program_2022: Program<'info, Token2022>, @@ -36,34 +33,25 @@ pub struct LzComposeTypes<'info> { pub input_vault_mint: Box>, #[account(address = output_vault.mint)] pub output_vault_mint: Box>, - // Extra accounts required for the clear_compose call. /// The LayerZero endpoint program. pub lz_program: AccountInfo<'info>, /// The authority account for the swap. pub authority: AccountInfo<'info>, /// Tick array accounts for the swap range. - #[account(mut)] pub tick_array_lower: AccountInfo<'info>, - #[account(mut)] pub tick_array_current: AccountInfo<'info>, - #[account(mut)] pub tick_array_upper: AccountInfo<'info>, } impl LzComposeTypes<'_> { - /// Generates the list of LzAccounts expected by the clear_compose call. + /// Generates the ordered list of LzAccounts expected by the clear_compose call. + /// Base accounts for the swap are listed first, followed by extra accounts. pub fn apply( ctx: &Context, params: &LzComposeParams, ) -> Result> { - let mut accounts = get_accounts_for_clear_compose( - ENDPOINT_ID, - ¶ms.from, - &ctx.accounts.lz_program.key(), - ¶ms.guid, - params.index, - ¶ms.message, - ); + let mut accounts = Vec::new(); + // Base accounts for swap operations. accounts.push(LzAccount { pubkey: ctx.accounts.payer.key(), is_signer: true, @@ -109,6 +97,33 @@ impl LzComposeTypes<'_> { is_signer: false, is_writable: true, }); + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_lower.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_current.key(), + is_signer: false, + is_writable: true, + }); + accounts.push(LzAccount { + pubkey: ctx.accounts.tick_array_upper.key(), + is_signer: false, + is_writable: true, + }); + + // Append additional accounts required for clear_compose. + let mut extra_accounts = get_accounts_for_clear_compose( + ENDPOINT_ID, + ¶ms.from, + &ctx.accounts.lz_program.key(), + ¶ms.guid, + params.index, + ¶ms.message, + ); + accounts.append(&mut extra_accounts); + Ok(accounts) } } diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index 79f690f23..1e45bf11d 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -3,7 +3,9 @@ mod state; use anchor_lang::prelude::*; use instructions::*; + use oapp::{endpoint_cpi::LzAccount, LzComposeParams}; + use state::*; declare_id!("3NJ7AUBaj9N8kBsRqA7SYWJ1poEUsEW36gCG1EfLDkW2"); diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs index 9cbceee1b..28c754908 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -15,7 +15,7 @@ impl Composer { pub const SIZE: usize = 8 + 32 + 32 + 1; } -/// LzComposeTypesAccounts includes the pubkeys of all accounts used in the LzComposeTypes instruction. +/// LzComposeTypesAccounts includes the pubkeys of all accounts used in the lz_compose_types instruction. /// (Adjust the fields as needed.) #[account] pub struct LzComposeTypesAccounts { @@ -44,3 +44,11 @@ pub struct LzComposeTypesAccounts { impl LzComposeTypesAccounts { pub const SIZE: usize = 8 + std::mem::size_of::(); } + +#[error_code] +pub enum ComposerError { + #[msg("Invalid 'from' address. The message sender is not the expected OFT PDA.")] + InvalidFrom, + #[msg("Invalid 'to' address. The message recipient does not match the composer PDA.")] + InvalidTo, +} From 3e59a463fe47fff40e1d1e338137de2c1ecbc21d Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Fri, 11 Apr 2025 09:18:06 -0700 Subject: [PATCH 06/11] chore: remove CHANGELOG --- .../oft-solana-composer-library/CHANGELOG.md | 215 ------------------ 1 file changed, 215 deletions(-) delete mode 100644 examples/oft-solana-composer-library/CHANGELOG.md diff --git a/examples/oft-solana-composer-library/CHANGELOG.md b/examples/oft-solana-composer-library/CHANGELOG.md deleted file mode 100644 index c0fbbffb7..000000000 --- a/examples/oft-solana-composer-library/CHANGELOG.md +++ /dev/null @@ -1,215 +0,0 @@ -# @layerzerolabs/oft-solana-example - -## 0.7.14 - -### Patch Changes - -- eba3669: Rename clear script to retry-payload - -## 0.7.13 - -### Patch Changes - -- 4ff1db8: feat(oft-solana): remove need for manual solana endpoint ID input -- 28eb8be: feat(oft-solana): support loading keypair via path -- 0385135: fix import in setInboundRateLimit - -## 0.7.12 - -### Patch Changes - -- 292803d: introduce fix suggestions, starting with when Solana init-config is skipped - -## 0.7.11 - -### Patch Changes - -- 8817095: Add the ability to filter out connections from specified EndpointIds - -## 0.7.10 - -### Patch Changes - -- 8b6c422: Bump monorepo dependencies to latest patch version - -## 0.7.9 - -### Patch Changes - -- a843edf: Debugging Hardhat tasks - -## 0.7.8 - -### Patch Changes - -- ce3a36b: introduce getSolanaDeploymentFunction and simplify solana task params - -## 0.7.7 - -### Patch Changes - -- e4f8538: feat(oft-solana): remove need to pass in solana secret key flag - -## 0.7.6 - -### Patch Changes - -- e256387: Updating packages - -## 0.7.5 - -### Patch Changes - -- fe71c0e: fix typo and mention how to transfer ownership - -## 0.7.4 - -### Patch Changes - -- 6bbe466: move solana init to own script and update task name - -## 0.7.3 - -### Patch Changes - -- e5fffc3: fix import in createOFT.ts -- fcf924d: docs: default to using solana 1.18 - -## 0.7.2 - -### Patch Changes - -- 274b8aa: Add task to get Solana rate limits - -## 0.7.1 - -### Patch Changes - -- 213a76b: Enable optimizer explicitly - -## 0.7.0 - -### Minor Changes - -- 57a80a8: added script to update metaplex metadata - -## 0.6.0 - -### Minor Changes - -- 1ce802a: oft-solana - throw if trying to send more than owned - -## 0.5.1 - -### Patch Changes - -- 12eaa61: oft-solana(getSimulationComputeUnits): increase backoff max delay from 3s to 10s - -## 0.5.0 - -### Minor Changes - -- 4af9800: fallback for getSimulationComputeUnits - -## 0.4.9 - -### Patch Changes - -- a2ecefd: fix missing return statement when user chooses no to 'continue with onlyOftStore' - -## 0.4.8 - -### Patch Changes - -- af91805: Bump to lz-definitions 3.0.59+ - -## 0.4.7 - -### Patch Changes - -- ce03876: Get latest EndpointIds by bumping lz-definitions - -## 0.4.6 - -### Patch Changes - -- d1d51ef: Bump ua-devtools-evm-hardhat dependency to 6.0.6+ - -## 0.4.5 - -### Patch Changes - -- 1d2abff: new SDK methods, tests in devtools-ton, upgraded lz-definitions - -## 0.4.4 - -### Patch Changes - -- 1bb0524: Upgraded dependency (@layerzerolabs/lz-definitions 3.0.12->3.0.21) - -## 0.4.3 - -### Patch Changes - -- 447af65: Use concurrently for parallel compilation task - -## 0.4.2 - -### Patch Changes - -- 59cd485: Fix add additional minters to createOFT - -## 0.4.1 - -### Patch Changes - -- 63238e9: Add ability to swap out the mint authority with a new SPL multisig - -## 0.4.0 - -### Minor Changes - -- e2395b5: Add OApp Read Example - -## 0.3.1 - -### Patch Changes - -- 2540bb1: solana support for lz:oapp:config:get - -## 0.3.0 - -### Minor Changes - -- aa37daf: Update layerzerolabs packages to 3.0.12 - -## 0.2.1 - -### Patch Changes - -- ff5972b: Fix createOFT should allow 0 amount - -## 0.2.0 - -### Minor Changes - -- ccba37b: Bump for solana oftv2 - -### Patch Changes - -- 019cd52: Bump for new Solana Implementation Version -- 4ca5233: Enable squadsv4 CLI support - -## 0.1.5 - -### Patch Changes - -- f34f4fe: Add support for Token2022 and housecleaning -- 33ff07d: Fix refund address for EVM send script -- e59f693: Resolves Issue 926, allowing createOFT to have amount=0 - -## 0.1.4 - -### Patch Changes - -- 6a07bb7: New solana OFT reference From 7567e1be1006d0ed3acc08568cc759d6142192da Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Mon, 14 Apr 2025 07:58:23 -0700 Subject: [PATCH 07/11] chore: fix pnpm-lock --- pnpm-lock.yaml | 1080 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 1012 insertions(+), 68 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86c251b53..061180cf3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2040,6 +2040,234 @@ importers: specifier: ^5.4.4 version: 5.5.3 + examples/oft-solana-composer-library: + devDependencies: + '@coral-xyz/anchor': + specifier: ^0.29.0 + version: 0.29.0 + '@ethersproject/bytes': + specifier: ^5.7.0 + version: 5.7.0 + '@layerzerolabs/devtools': + specifier: ~0.4.8 + version: link:../../packages/devtools + '@layerzerolabs/devtools-evm': + specifier: ~1.0.6 + version: link:../../packages/devtools-evm + '@layerzerolabs/devtools-evm-hardhat': + specifier: ^2.0.9 + version: link:../../packages/devtools-evm-hardhat + '@layerzerolabs/devtools-solana': + specifier: ~1.0.8 + version: link:../../packages/devtools-solana + '@layerzerolabs/eslint-config-next': + specifier: ~2.3.39 + version: 2.3.44(typescript@5.5.3) + '@layerzerolabs/io-devtools': + specifier: ~0.1.16 + version: link:../../packages/io-devtools + '@layerzerolabs/lz-definitions': + specifier: ^3.0.75 + version: 3.0.81 + '@layerzerolabs/lz-evm-messagelib-v2': + specifier: ^3.0.75 + version: 3.0.75(@axelar-network/axelar-gmp-sdk-solidity@5.10.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@3.0.75)(@layerzerolabs/lz-evm-v1-0.7@3.0.75)(@openzeppelin/contracts-upgradeable@5.1.0)(@openzeppelin/contracts@5.1.0)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': + specifier: ^3.0.75 + version: 3.0.75(@openzeppelin/contracts-upgradeable@5.1.0)(@openzeppelin/contracts@5.1.0)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': + specifier: ^3.0.75 + version: 3.0.75(@openzeppelin/contracts-upgradeable@5.1.0)(@openzeppelin/contracts@5.1.0)(hardhat-deploy@0.12.4) + '@layerzerolabs/lz-solana-sdk-v2': + specifier: 3.0.0 + version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': + specifier: ^3.0.75 + version: 3.0.81 + '@layerzerolabs/metadata-tools': + specifier: ^0.4.1 + version: link:../../packages/metadata-tools + '@layerzerolabs/oapp-evm': + specifier: ^0.3.2 + version: link:../../packages/oapp-evm + '@layerzerolabs/oft-evm': + specifier: ^3.1.3 + version: link:../../packages/oft-evm + '@layerzerolabs/oft-v2-solana-sdk': + specifier: ^3.0.59 + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/prettier-config-next': + specifier: ^2.3.39 + version: 2.3.44 + '@layerzerolabs/protocol-devtools': + specifier: ^1.1.6 + version: link:../../packages/protocol-devtools + '@layerzerolabs/protocol-devtools-evm': + specifier: ~3.0.7 + version: link:../../packages/protocol-devtools-evm + '@layerzerolabs/protocol-devtools-solana': + specifier: ^4.0.9 + version: link:../../packages/protocol-devtools-solana + '@layerzerolabs/solhint-config': + specifier: ^3.0.12 + version: 3.0.12(typescript@5.5.3) + '@layerzerolabs/test-devtools-evm-foundry': + specifier: ~6.0.3 + version: link:../../packages/test-devtools-evm-foundry + '@layerzerolabs/test-devtools-evm-hardhat': + specifier: ~0.5.2 + version: link:../../packages/test-devtools-evm-hardhat + '@layerzerolabs/toolbox-foundry': + specifier: ~0.1.12 + version: link:../../packages/toolbox-foundry + '@layerzerolabs/toolbox-hardhat': + specifier: ~0.6.9 + version: link:../../packages/toolbox-hardhat + '@layerzerolabs/ua-devtools': + specifier: ~3.0.6 + version: link:../../packages/ua-devtools + '@layerzerolabs/ua-devtools-evm': + specifier: ~5.0.7 + version: link:../../packages/ua-devtools-evm + '@layerzerolabs/ua-devtools-evm-hardhat': + specifier: ~6.0.11 + version: link:../../packages/ua-devtools-evm-hardhat + '@layerzerolabs/ua-devtools-solana': + specifier: ~4.1.2 + version: link:../../packages/ua-devtools-solana + '@metaplex-foundation/mpl-token-metadata': + specifier: ^3.2.1 + version: 3.2.1(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/mpl-toolbox': + specifier: ^0.9.4 + version: 0.9.4(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi': + specifier: ^0.9.2 + version: 0.9.2 + '@metaplex-foundation/umi-bundle-defaults': + specifier: ^0.9.2 + version: 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-eddsa-web3js': + specifier: ^0.9.2 + version: 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-public-keys': + specifier: ^0.8.9 + version: 0.8.9 + '@metaplex-foundation/umi-web3js-adapters': + specifier: ^0.9.2 + version: 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.5 + version: 3.0.5(ethers@5.7.2)(hardhat@2.22.12) + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.12) + '@nomiclabs/hardhat-waffle': + specifier: ^2.0.6 + version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.22.12) + '@openzeppelin/contracts': + specifier: ^5.0.2 + version: 5.1.0 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.2 + version: 5.1.0(@openzeppelin/contracts@5.1.0) + '@rushstack/eslint-patch': + specifier: ^1.7.0 + version: 1.7.0 + '@solana-developers/helpers': + specifier: ~2.8.1 + version: 2.8.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/spl-token': + specifier: ^0.4.8 + version: 0.4.12(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': + specifier: ~1.95.8 + version: 1.95.8 + '@sqds/sdk': + specifier: ^2.0.4 + version: 2.0.4 + '@swc/core': + specifier: ^1.4.0 + version: 1.4.0 + '@swc/jest': + specifier: ^0.2.36 + version: 0.2.36(@swc/core@1.4.0) + '@types/chai': + specifier: ^4.3.11 + version: 4.3.20 + '@types/jest': + specifier: ^29.5.12 + version: 29.5.12 + '@types/mocha': + specifier: ^10.0.6 + version: 10.0.6 + '@types/node': + specifier: ~18.18.14 + version: 18.18.14 + bs58: + specifier: ^6.0.0 + version: 6.0.0 + chai: + specifier: ^4.4.1 + version: 4.5.0 + concurrently: + specifier: ~9.1.0 + version: 9.1.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + eslint: + specifier: ^8.55.0 + version: 8.57.1 + eslint-plugin-jest-extended: + specifier: ~2.0.0 + version: 2.0.0(eslint@8.57.1)(typescript@5.5.3) + ethereumjs-util: + specifier: ^7.1.5 + version: 7.1.5 + ethers: + specifier: ^5.7.2 + version: 5.7.2 + exponential-backoff: + specifier: ~3.1.1 + version: 3.1.1 + fp-ts: + specifier: ^2.16.2 + version: 2.16.2 + hardhat: + specifier: ^2.22.10 + version: 2.22.12(ts-node@10.9.2)(typescript@5.5.3) + hardhat-contract-sizer: + specifier: ^2.10.0 + version: 2.10.0(hardhat@2.22.12) + hardhat-deploy: + specifier: ^0.12.1 + version: 0.12.4 + hardhat-deploy-ethers: + specifier: ^0.4.2 + version: 0.4.2(@nomicfoundation/hardhat-ethers@3.0.5)(hardhat-deploy@0.12.4)(hardhat@2.22.12) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + mocha: + specifier: ^10.2.0 + version: 10.2.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + solhint: + specifier: ^4.1.1 + version: 4.1.1(typescript@5.5.3) + solidity-bytes-utils: + specifier: ^0.8.2 + version: 0.8.2 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + typescript: + specifier: ^5.4.4 + version: 5.5.3 + examples/oft-upgradeable: devDependencies: '@babel/core': @@ -2195,6 +2423,9 @@ importers: '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 + '@layerzerolabs/metadata-tools': + specifier: ^0.4.1 + version: link:../../packages/metadata-tools '@layerzerolabs/oapp-evm': specifier: ^0.3.2 version: link:../../packages/oapp-evm @@ -3295,7 +3526,7 @@ importers: version: 1.95.8 '@sqds/multisig': specifier: ^2.1.3 - version: 2.1.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 2.1.3(typescript@5.5.3) '@swc/core': specifier: ^1.4.0 version: 1.4.0 @@ -3429,7 +3660,7 @@ importers: version: 2.16.2 jest: specifier: ^29.6.2 - version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14) tsup: specifier: ~8.0.1 version: 8.0.1(@swc/core@1.4.0)(typescript@5.5.3) @@ -3532,7 +3763,7 @@ importers: version: 29.5.12 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14) tslib: specifier: ~2.6.2 version: 2.6.3 @@ -4057,13 +4288,13 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.0 - version: 3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 '@layerzerolabs/oft-v2-solana-sdk': specifier: ^3.0.38 - version: 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@layerzerolabs/protocol-devtools': specifier: ~1.1.6 version: link:../protocol-devtools @@ -4684,13 +4915,13 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.59 - version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 '@layerzerolabs/oft-v2-solana-sdk': specifier: ^3.0.59 - version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@layerzerolabs/protocol-devtools': specifier: ~1.1.6 version: link:../protocol-devtools @@ -4795,7 +5026,7 @@ importers: version: 12.6.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14) tsup: specifier: ^8.0.1 version: 8.0.1(@swc/core@1.4.0)(typescript@5.5.3) @@ -6174,6 +6405,7 @@ packages: commander: ~12.1.0 dependencies: commander: 12.1.0 + dev: true /@coral-xyz/anchor-errors@0.30.1: resolution: {integrity: sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==} @@ -6226,6 +6458,7 @@ packages: - bufferutil - encoding - utf-8-validate + dev: true /@coral-xyz/anchor@0.30.1: resolution: {integrity: sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ==} @@ -6271,6 +6504,7 @@ packages: '@solana/web3.js': 1.95.8 bn.js: 5.2.1 buffer-layout: 1.2.2 + dev: true /@coral-xyz/borsh@0.30.1(@solana/web3.js@1.95.8): resolution: {integrity: sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==} @@ -7415,7 +7649,7 @@ packages: '@ledgerhq/hw-transport-webhid': 6.30.0 '@ledgerhq/hw-transport-webusb': 6.29.4 '@mysten/bcs': 1.2.0 - axios: 1.7.9 + axios: 1.8.4 bech32: 2.0.0 bignumber.js: 9.1.2 bip32: 5.0.0-rc.0(typescript@5.5.3) @@ -7700,7 +7934,7 @@ packages: jest-util: 29.7.0 slash: 3.0.0 - /@jest/core@29.7.0(ts-node@10.9.2): + /@jest/core@29.7.0: resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -7733,7 +7967,7 @@ packages: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.5 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -7741,21 +7975,64 @@ packages: - babel-plugin-macros - supports-color - ts-node - - /@jest/create-cache-key-function@29.7.0: - resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 dev: true - /@jest/environment@29.7.0: - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + /@jest/core@29.7.0(ts-node@10.9.2): + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 18.18.14 + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.18.14 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + /@jest/create-cache-key-function@29.7.0: + resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + dev: true + + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.18.14 jest-mock: 29.7.0 /@jest/expect-utils@29.7.0: @@ -7985,6 +8262,7 @@ packages: tiny-invariant: 1.3.3 transitivePeerDependencies: - debug + dev: true /@layerzerolabs/lz-config-types@3.0.59(ethers@5.7.2): resolution: {integrity: sha512-V8hcvabSMG/fCA1pt+mkumVwcT1/uWeH4jFqIcC+Elisw4e6C+ALsWBWKRkiOVzi8yFkKaddE9fPrI3iK46vsQ==} @@ -8002,10 +8280,10 @@ packages: /@layerzerolabs/lz-core@3.0.66: resolution: {integrity: sha512-emp2bo09+cQ8ivLjNSG6vRbV9N8H88yeLRTiR1YcZ1UjLFaElm+8TcqpaHmKhKqGYQCUaz9IfVv7I6VE1pD3Tw==} - dev: true /@layerzerolabs/lz-core@3.0.81: resolution: {integrity: sha512-fY5KOwXEl7V38Nah5AmRsfExy26pTs9e1+5TOTAmvGHScTTOLpqMy+JRVTGMyf/wSbDGmtpysHJy6tGODaIdAw==} + dev: true /@layerzerolabs/lz-corekit-solana@3.0.0: resolution: {integrity: sha512-vJtDiS7QM77BN/VuZJsM/xH6ZJmUY3vDlHOmRMHsjHf5sfXqMafNL4+FKCRIijPVPGR0DCJARiWrRtgZchv9+w==} @@ -8055,6 +8333,36 @@ packages: - utf-8-validate dev: true + /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} + dependencies: + '@layerzerolabs/lz-core': 3.0.66 + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@solana/web3.js': 1.95.8 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} dependencies: @@ -8085,6 +8393,98 @@ packages: - utf-8-validate dev: true + /@layerzerolabs/lz-corekit-solana@3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} + dependencies: + '@layerzerolabs/lz-core': 3.0.66 + '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@solana/web3.js': 1.95.8 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: false + + /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-16rLQA33OyQlfpv8UX4mNnby8XJGNXJEy5okASQJQv3am+N7jLW11dvZxaoSFAlie//w9TbaNAAZe1iuwMLJ6w==} + dependencies: + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@solana/web3.js': 1.95.8 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + memoizee: 0.4.17 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-16rLQA33OyQlfpv8UX4mNnby8XJGNXJEy5okASQJQv3am+N7jLW11dvZxaoSFAlie//w9TbaNAAZe1iuwMLJ6w==} + dependencies: + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@solana/web3.js': 1.95.8 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + memoizee: 0.4.17 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/lz-corekit-solana@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-16rLQA33OyQlfpv8UX4mNnby8XJGNXJEy5okASQJQv3am+N7jLW11dvZxaoSFAlie//w9TbaNAAZe1iuwMLJ6w==} dependencies: @@ -8114,6 +8514,7 @@ packages: - supports-color - typescript - utf-8-validate + dev: true /@layerzerolabs/lz-definitions@3.0.75: resolution: {integrity: sha512-TIbFBCfuElg6WcND4HNUROTSAayBETDC0YrISVadByo3iM2baAi+rpGC1kdrOxOTRlSBetd2khTOUCd7/sZdOQ==} @@ -8566,7 +8967,7 @@ packages: /@layerzerolabs/lz-foundation@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-0WDrlJ+DZ3GGxKPgcConoxQdTB7icpoOYjeTcwmBtJcUnXIE7G3vxzKUAKsUccGegFdgHMEj1K4D+3NPZbNQ2A==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 @@ -8589,7 +8990,7 @@ packages: /@layerzerolabs/lz-foundation@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 @@ -8609,10 +9010,33 @@ packages: - utf-8-validate dev: true + /@layerzerolabs/lz-foundation@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} + dependencies: + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@noble/ed25519': 1.7.3 + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.2.1 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/lz-foundation@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 @@ -8635,7 +9059,7 @@ packages: /@layerzerolabs/lz-foundation@3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 @@ -8655,6 +9079,31 @@ packages: - utf-8-validate dev: false + /@layerzerolabs/lz-foundation@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-rGbjBzm7qeu1OmdaxpSFWsh8QulZGk1DiB+Sow9iUDJPkJKXqAjm40vQ1gj5KQiETGz/hAxOjgmilEjg8MFduA==} + dependencies: + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@noble/ed25519': 1.7.3 + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.2.4 + bech32: 2.0.0 + memoizee: 0.4.17 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/lz-foundation@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-rGbjBzm7qeu1OmdaxpSFWsh8QulZGk1DiB+Sow9iUDJPkJKXqAjm40vQ1gj5KQiETGz/hAxOjgmilEjg8MFduA==} dependencies: @@ -8678,6 +9127,7 @@ packages: - supports-color - typescript - utf-8-validate + dev: true /@layerzerolabs/lz-movevm-sdk-v2@3.0.59(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-qYP5R5nlmO/siy3q68MH3nDnvb0ORzae3KSo2BD3cctNLiEpCjBei+8OcWMo9qZpJDMovNlqtTEaHt1CJmKxBQ==} @@ -8703,6 +9153,34 @@ packages: /@layerzerolabs/lz-serdes@3.0.66: resolution: {integrity: sha512-kk/0pPKuNzIXL1Fh8caH3daYA/Lv2OIPeD7z/VIBANdyCBiYgncF6QK8muoFJ1iIvoQSnpAmgwgww1DlnYa2NA==} + + /@layerzerolabs/lz-serdes@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-edzKOV0sA5+jbqzHYFMX4XOPj7nhnaiZVxl1DbsLlboTBVX8lyJlCmxrEM31+iWB47cuBmyJdO3UkDpaRuQbLQ==} + dependencies: + '@coral-xyz/anchor': 0.29.0 + '@layerzerolabs/lz-aptos-sdk-v1': 3.0.81 + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/tron-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 + tiny-invariant: 1.3.3 + tronweb: 5.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate dev: true /@layerzerolabs/lz-serdes@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): @@ -8732,6 +9210,81 @@ packages: - supports-color - typescript - utf-8-validate + dev: true + + /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/solidity': 5.7.0 + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/solita': 0.20.1 + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + bs58: 5.0.0 + ethereumjs-util: 7.1.5 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/solidity': 5.7.0 + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/solita': 0.20.1 + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + bs58: 5.0.0 + ethereumjs-util: 7.1.5 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true /@layerzerolabs/lz-solana-sdk-v2@3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} @@ -8768,11 +9321,47 @@ packages: resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} dependencies: '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@layerzerolabs/lz-serdes': 3.0.66 '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + bs58: 5.0.0 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} + dependencies: + '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-serdes': 3.0.66 + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -8800,15 +9389,51 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} dependencies: '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@layerzerolabs/lz-serdes': 3.0.66 '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + bs58: 5.0.0 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} + dependencies: + '@layerzerolabs/lz-corekit-solana': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-foundation': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-serdes': 3.0.66 + '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -8834,6 +9459,42 @@ packages: - supports-color - typescript - utf-8-validate + dev: false + + /@layerzerolabs/lz-solana-sdk-v2@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-xUogDY3IhPm65R+7DisfAPGofDTb38gsCW8k+V290zbOmTRSWKVFMsZlNxrVMH6+7jsKsMYQ/2bqMCqlJKkNEQ==} + dependencies: + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-foundation': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-serdes': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + bs58: 5.0.0 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate dev: true /@layerzerolabs/lz-solana-sdk-v2@3.0.81(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): @@ -8866,35 +9527,125 @@ packages: - chai - debug - encoding - - fastestsmallesttextencoderdecoder + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-ton-sdk-v2@3.0.27: + resolution: {integrity: sha512-AU1uOzmLjWvyHdJGTo689bXLsCS/QAmfQSjvZ4544muLfpGVLl3l6lOl8DwmN1UQuwKKK94C5rEvoElVUYf0zQ==} + dependencies: + '@ton/core': 0.59.0(@ton/crypto@3.3.0) + '@ton/crypto': 3.3.0 + bigint-buffer: 1.1.5 + crc-32: 1.2.2 + ethers: 5.7.2 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@layerzerolabs/lz-utilities@3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@initia/initia.js': 0.2.32(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@solana/web3.js': 1.95.8 + '@ton/core': 0.59.0(@ton/crypto@3.3.0) + '@ton/crypto': 3.3.0 + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@jest/globals@29.7.0)(@layerzerolabs/ton@15.2.0-rc.3)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 + winston: 3.11.0 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + + /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@initia/initia.js': 0.2.32(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@solana/web3.js': 1.95.8 + '@ton/core': 0.59.0(@ton/crypto@3.3.0) + '@ton/crypto': 3.3.0 + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 + winston: 3.11.0 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding - supports-color - typescript - utf-8-validate + dev: true - /@layerzerolabs/lz-ton-sdk-v2@3.0.27: - resolution: {integrity: sha512-AU1uOzmLjWvyHdJGTo689bXLsCS/QAmfQSjvZ4544muLfpGVLl3l6lOl8DwmN1UQuwKKK94C5rEvoElVUYf0zQ==} + /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} dependencies: + '@ethersproject/bytes': 5.7.0 + '@initia/initia.js': 0.2.32(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 - bigint-buffer: 1.1.5 - crc-32: 1.2.2 + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 ethers: 5.7.2 - tiny-invariant: 1.3.3 + memoizee: 0.4.17 + winston: 3.11.0 transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript - utf-8-validate dev: true - /@layerzerolabs/lz-utilities@3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} dependencies: '@ethersproject/bytes': 5.7.0 '@initia/initia.js': 0.2.32(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@jest/globals@29.7.0)(@layerzerolabs/ton@15.2.0-rc.3)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 ed25519-hd-key: 1.3.0 @@ -8913,23 +9664,26 @@ packages: - supports-color - typescript - utf-8-validate + dev: true - /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): - resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-QDGV2DAP8qbbAGTOcbsREr6CK8LsmPzHu9q96i7dXTXCvUq9FsibjjFVYfy3ctVMngVbUVOK9fJ+KtOnYLunCA==} dependencies: '@ethersproject/bytes': 5.7.0 '@initia/initia.js': 0.2.32(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 + dayjs: 1.11.13 ed25519-hd-key: 1.3.0 ethers: 5.7.2 memoizee: 0.4.17 - winston: 3.11.0 + picocolors: 1.0.0 + pino: 8.21.0 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -8944,22 +9698,24 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): - resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-QDGV2DAP8qbbAGTOcbsREr6CK8LsmPzHu9q96i7dXTXCvUq9FsibjjFVYfy3ctVMngVbUVOK9fJ+KtOnYLunCA==} dependencies: '@ethersproject/bytes': 5.7.0 '@initia/initia.js': 0.2.32(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 + dayjs: 1.11.13 ed25519-hd-key: 1.3.0 ethers: 5.7.2 memoizee: 0.4.17 - winston: 3.11.0 + picocolors: 1.0.0 + pino: 8.21.0 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -9004,6 +9760,7 @@ packages: - supports-color - typescript - utf-8-validate + dev: true /@layerzerolabs/lz-v2-utilities@3.0.75: resolution: {integrity: sha512-6172reYvXMjBnpQZYlAz+wkxjBJzpBfL5IzAkB0Swc9t27L/ZqDS9e4HLG+lIXqcVaC7jc5cprUw0exbk1002A==} @@ -9035,13 +9792,13 @@ packages: '@layerzerolabs/lz-definitions': 3.0.75 dev: true - /@layerzerolabs/oft-v2-solana-sdk@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + /@layerzerolabs/oft-v2-solana-sdk@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-P06/a5+ixph0u1AQkDZ0P0oFaIAdfGPl/UezMfWXUpiWLth428RT0rrMR6qI7z6X1uxqlUFNIotz2ET1fyFcpQ==} dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -9097,12 +9854,12 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-ijbvj6/Gc4O4WLHfqnrBuKUtIhpaYws/ORj2apZFT1RKSgAX8CCJ9aZmn0ClamEG98i+PpXoroPPU46LMOMZyA==} dependencies: '@ethersproject/bytes': 5.7.0 - '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.75 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -9128,12 +9885,43 @@ packages: - utf-8-validate dev: true + /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-ijbvj6/Gc4O4WLHfqnrBuKUtIhpaYws/ORj2apZFT1RKSgAX8CCJ9aZmn0ClamEG98i+PpXoroPPU46LMOMZyA==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + dotenv: 16.4.7 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-ijbvj6/Gc4O4WLHfqnrBuKUtIhpaYws/ORj2apZFT1RKSgAX8CCJ9aZmn0ClamEG98i+PpXoroPPU46LMOMZyA==} dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.75 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -9237,7 +10025,7 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@jest/globals@29.7.0)(@ton/core@0.59.0)(chai@4.5.0) - axios: 1.7.9 + axios: 1.8.4 dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -9265,7 +10053,36 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@ton/core@0.59.0)(chai@4.4.1) - axios: 1.7.9 + axios: 1.8.4 + dataloader: 2.2.2 + symbol.inspect: 1.0.1 + teslabot: 1.5.0 + zod: 3.22.4 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@ton/ton' + - '@types/node' + - chai + - debug + - encoding + - supports-color + - typescript + dev: true + + /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-Aq7htcWn31DxSYs8l0sJcQ76uAJRPy50lg6M2G+qNLSXpO01sPcUSb8oK/TmCKo+S+rDIFWUNKFjT0B/rll1YQ==} + peerDependencies: + '@ton/core': '>=0.59.0' + '@ton/crypto': '>=3.2.0' + dependencies: + '@ton/blueprint': 0.25.0(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) + '@ton/core': 0.59.0(@ton/crypto@3.3.0) + '@ton/crypto': 3.3.0 + '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) + '@ton/test-utils': 0.4.2(@jest/globals@29.7.0)(@ton/core@0.59.0)(chai@4.5.0) + axios: 1.8.4 dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -9311,6 +10128,27 @@ packages: - supports-color - typescript + /@layerzerolabs/tron-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-bWv3GJJ6J/Wu79zlYEe6Cqv7UwzxC8tkA4rcHv6REnrbR/7iEaLcqLrVA/G1KWhOiUj5Rjqdws0bFpjJHdxXxQ==} + dependencies: + '@ethersproject/providers': 5.7.2 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + ethers: 5.7.2 + tronweb: 5.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/tron-utilities@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-bWv3GJJ6J/Wu79zlYEe6Cqv7UwzxC8tkA4rcHv6REnrbR/7iEaLcqLrVA/G1KWhOiUj5Rjqdws0bFpjJHdxXxQ==} dependencies: @@ -9330,6 +10168,7 @@ packages: - supports-color - typescript - utf-8-validate + dev: true /@ledgerhq/devices@8.4.4: resolution: {integrity: sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A==} @@ -9778,6 +10617,7 @@ packages: /@noble/hashes@1.7.0: resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} engines: {node: ^14.21.3 || >=16} + dev: true /@noble/hashes@1.7.1: resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} @@ -10971,7 +11811,6 @@ packages: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - typescript - dev: false /@solana/spl-token-group@0.0.7(@solana/web3.js@1.98.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==} @@ -11022,7 +11861,19 @@ packages: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - typescript - dev: false + + /@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8)(typescript@5.5.3): + resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.95.3 + dependencies: + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + dev: true /@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.98.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} @@ -11054,6 +11905,25 @@ packages: - typescript - utf-8-validate + /@solana/spl-token@0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3): + resolution: {integrity: sha512-bvohO3rIMSVL24Pb+I4EYTJ6cL82eFpInEXD/I8K8upOGjpqHsKUoAempR/RnUlI1qSFNyFlWJfu6MNUgfbCQQ==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.88.0 + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/buffer-layout-utils': 0.2.0 + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + dev: true + /@solana/spl-token@0.4.12(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-K6CxzSoO1vC+WBys25zlSDaW0w4UFZO/IvEZquEI35A/PjqXNQHeVigmDCZYEJfESvYarKwsr8tYr/29lPtvaw==} engines: {node: '>=16'} @@ -11072,7 +11942,6 @@ packages: - fastestsmallesttextencoderdecoder - typescript - utf-8-validate - dev: false /@solana/spl-token@0.4.12(@solana/web3.js@1.98.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-K6CxzSoO1vC+WBys25zlSDaW0w4UFZO/IvEZquEI35A/PjqXNQHeVigmDCZYEJfESvYarKwsr8tYr/29lPtvaw==} @@ -11181,14 +12050,14 @@ packages: resolution: {integrity: sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==} dev: true - /@sqds/multisig@2.1.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + /@sqds/multisig@2.1.3(typescript@5.5.3): resolution: {integrity: sha512-WOiL7La+RSiJsz7jVO85yhSiiSvNMUthiWuLPeWVOoD6IYa34BEAzanF1RdXRWGglSbRFYCTkyr+Ay1WmXmSRQ==} engines: {node: '>=14'} dependencies: '@metaplex-foundation/beet': 0.7.1 '@metaplex-foundation/beet-solana': 0.4.0 '@metaplex-foundation/cusper': 0.0.2 - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) '@solana/web3.js': 1.95.8 '@types/bn.js': 5.1.6 assert: 2.1.0 @@ -11448,7 +12317,7 @@ packages: '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@jest/globals@29.7.0)(@layerzerolabs/ton@15.2.0-rc.3)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@tonconnect/sdk': 2.2.0 arg: 5.0.2 - axios: 1.7.9 + axios: 1.8.4 chalk: 4.1.2 dotenv: 16.4.7 inquirer: 8.2.6 @@ -11570,6 +12439,7 @@ packages: /@tronweb3/google-protobuf@3.21.4: resolution: {integrity: sha512-joxgV4esCdyZ921AprMIG1T7HjkypquhbJ5qJti/priCBJhRE1z9GOxIEMvayxSVSRbMGIoJNE0Knrg3vpwM1w==} deprecated: This package is deprecated. Please use the official package google-protobuf instead. + dev: true /@trufflesuite/bigint-buffer@1.1.9: resolution: {integrity: sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==} @@ -11666,7 +12536,6 @@ packages: resolution: {integrity: sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==} dependencies: '@types/node': 18.18.14 - dev: true /@types/cacheable-request@6.0.3: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} @@ -12246,6 +13115,7 @@ packages: engines: {node: '>=6.5'} dependencies: event-target-shim: 5.0.1 + dev: true /abortcontroller-polyfill@1.7.5: resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} @@ -12651,6 +13521,7 @@ packages: /atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + dev: true /auto-bind@4.0.0: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} @@ -13902,6 +14773,7 @@ packages: /dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dev: true /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -15247,7 +16119,7 @@ packages: resolution: {integrity: sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==} dependencies: bn.js: 4.12.0 - elliptic: 6.5.4 + elliptic: 6.6.1 xhr-request-promise: 0.1.3 /ethereum-bloom-filters@1.0.10: @@ -15414,9 +16286,11 @@ packages: /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + dev: true /eventemitter3@3.1.2: resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} + dev: true /eventemitter3@4.0.4: resolution: {integrity: sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==} @@ -15546,6 +16420,7 @@ packages: /extended-buffer@6.1.0: resolution: {integrity: sha512-cQwfrYOZEzSqsSkav15ppifOynfJfCPwfPM1wutukoBlvqr2G8ib0uR1EcVmxCWfS4XjYPCywJDAS0s6LeICBQ==} + dev: true /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} @@ -15608,6 +16483,7 @@ packages: /fast-redact@3.5.0: resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} engines: {node: '>=6'} + dev: true /fast-stable-stringify@1.0.0: resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} @@ -16109,6 +16985,7 @@ packages: minimatch: 9.0.5 minipass: 7.1.2 path-scurry: 1.10.1 + dev: true /glob@11.0.1: resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} @@ -16825,6 +17702,7 @@ packages: /injectpromise@1.0.0: resolution: {integrity: sha512-qNq5wy4qX4uWHcVFOEU+RqZkoVG65FhvGkyDWbuBxILMjK6A1LFf5A1mgXZkD4nRx5FCorD81X/XvPKp/zVfPA==} + dev: true /ink-gradient@2.0.0(ink@3.2.0)(react@17.0.2): resolution: {integrity: sha512-d2BK/EzzBRoDL54NWkS3JGE4J8xtzwRVWxDAIkQ/eQ60XIzrFMtT5JlUqgV05Qlt32Jvk50qW51YqxGJggTuqA==} @@ -17446,6 +18324,7 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: true /jackspeak@4.0.1: resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} @@ -17522,6 +18401,34 @@ packages: - babel-plugin-macros - supports-color + /jest-cli@29.7.0(@types/node@18.18.14): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest-cli@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -17865,6 +18772,27 @@ packages: merge-stream: 2.0.0 supports-color: 8.1.1 + /jest@29.7.0(@types/node@18.18.14): + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@18.18.14) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -18357,6 +19285,7 @@ packages: /lru-cache@10.1.0: resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} engines: {node: 14 || >=16.14} + dev: true /lru-cache@11.0.0: resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==} @@ -19066,6 +19995,7 @@ packages: /on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} + dev: true /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -19359,6 +20289,7 @@ packages: dependencies: lru-cache: 10.1.0 minipass: 7.1.2 + dev: true /path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} @@ -19453,9 +20384,11 @@ packages: dependencies: readable-stream: 4.7.0 split2: 4.2.0 + dev: true /pino-std-serializers@6.2.2: resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + dev: true /pino@8.21.0: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} @@ -19472,6 +20405,7 @@ packages: safe-stable-stringify: 2.4.3 sonic-boom: 3.8.1 thread-stream: 2.7.0 + dev: true /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} @@ -19589,6 +20523,7 @@ packages: /process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + dev: true /process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} @@ -19762,6 +20697,7 @@ packages: /querystring-es3@0.2.1: resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} engines: {node: '>=0.4.x'} + dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -19769,6 +20705,7 @@ packages: /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: true /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} @@ -19922,6 +20859,7 @@ packages: events: 3.3.0 process: 0.11.10 string_decoder: 1.3.0 + dev: true /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} @@ -19936,6 +20874,7 @@ packages: /real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + dev: true /reduce-flatten@2.0.0: resolution: {integrity: sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==} @@ -20719,6 +21658,7 @@ packages: resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} dependencies: atomic-sleep: 1.0.0 + dev: true /sort-object-keys@1.1.3: resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} @@ -20805,6 +21745,7 @@ packages: /split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + dev: true /spok@1.5.5: resolution: {integrity: sha512-IrJIXY54sCNFASyHPOY+jEirkiJ26JDqsGiI0Dvhwcnkl0PEWi1PSsrkYql0rzDw8LFVTcA7rdUCAJdE2HE+2Q==} @@ -21302,6 +22243,7 @@ packages: resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} dependencies: real-require: 0.2.0 + dev: true /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -21469,6 +22411,7 @@ packages: - bufferutil - debug - utf-8-validate + dev: true /ts-api-utils@1.3.0(typescript@5.5.3): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} @@ -22066,6 +23009,7 @@ packages: /validator@13.15.0: resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==} engines: {node: '>= 0.10'} + dev: true /varint@5.0.2: resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} @@ -22192,7 +23136,7 @@ packages: resolution: {integrity: sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A==} engines: {node: '>=8.0.0'} dependencies: - '@types/bn.js': 5.1.5 + '@types/bn.js': 5.1.6 web3-core: 1.10.4 web3-core-helpers: 1.10.4 web3-core-method: 1.10.4 From 7096055a1523bc0f25c854308a58c8d7180be2e9 Mon Sep 17 00:00:00 2001 From: Matthew Krak Date: Tue, 15 Apr 2025 09:19:19 -0700 Subject: [PATCH 08/11] chore: add local validator tests --- .../oft-solana-composer-library/Anchor.toml | 9 +- .../oft-solana-composer-library/package.json | 6 + .../src/instructions/init_composer.rs | 11 +- .../programs/composer/src/lib.rs | 2 +- .../programs/composer/src/state/composer.rs | 2 +- .../programs/endpoint-mock/src/lib.rs | 2 +- .../test/anchor/lz_compose_local.test.ts | 125 +++ pnpm-lock.yaml | 786 ++++++------------ 8 files changed, 398 insertions(+), 545 deletions(-) create mode 100644 examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts diff --git a/examples/oft-solana-composer-library/Anchor.toml b/examples/oft-solana-composer-library/Anchor.toml index 8bf60abcb..41c498a66 100644 --- a/examples/oft-solana-composer-library/Anchor.toml +++ b/examples/oft-solana-composer-library/Anchor.toml @@ -6,14 +6,15 @@ seeds = false skip-lint = false [programs.localnet] -oft = "G2BYTnfGCMQAErMZkTBCFSapKevzf6QCjizjXi8hFEtJ" +composer = "2TUdVCMQsefMs28hTeMKAHTjgz8cdMbo7V5oEdrYSu7G" +oft = "BH2YoCQV4i7gagmJqhj6nBByroJJwAQHa76XKV921e9m" [registry] url = "https://api.apr.dev" [provider] -cluster = "Localnet" -wallet = "./junk-id.json" +cluster = "http://localhost:8899" +wallet = "./krak.json" [scripts] -test = "npx jest test/anchor" +test = "npx jest test/anchor/" diff --git a/examples/oft-solana-composer-library/package.json b/examples/oft-solana-composer-library/package.json index 5e0f95e0e..45a5fd3e4 100644 --- a/examples/oft-solana-composer-library/package.json +++ b/examples/oft-solana-composer-library/package.json @@ -22,6 +22,11 @@ "ethers": "^5.7.2", "hardhat-deploy": "^0.12.1" }, + "dependencies": { + "@raydium-io/raydium-sdk-v2": "0.1.120-alpha", + "@solana/spl-memo": "^0.2.5", + "axios": "^1.8.4" + }, "devDependencies": { "@coral-xyz/anchor": "^0.29.0", "@ethersproject/bytes": "^5.7.0", @@ -92,6 +97,7 @@ "hardhat-deploy": "^0.12.1", "hardhat-deploy-ethers": "^0.4.2", "jest": "^29.7.0", + "litesvm": "^0.2.0", "mocha": "^10.2.0", "prettier": "^3.2.5", "solhint": "^4.1.1", diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs index 67b13b46d..7588ffdb2 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs @@ -9,7 +9,7 @@ pub struct InitComposer<'info> { init, payer = payer, space = Composer::SIZE, - seeds = [COMPOSER_SEED, params.oft.as_ref()], + seeds = [COMPOSER_SEED, params.oft_pda.as_ref()], bump )] pub composer: Account<'info, Composer>, @@ -32,17 +32,16 @@ pub struct InitComposer<'info> { #[derive(Clone, AnchorSerialize, AnchorDeserialize)] pub struct InitComposerParams { - pub id: u8, - pub oft: Pubkey, - pub endpoint_program: Pubkey, + pub oft_pda: Pubkey, + pub endpoint_pda: Pubkey, } impl InitComposer<'_> { /// Processes the init_composer instruction by writing the provided values to the Composer account. pub fn apply(ctx: &mut Context, params: &InitComposerParams) -> Result<()> { let composer = &mut ctx.accounts.composer; - composer.oft = params.oft; - composer.endpoint_program = params.endpoint_program; + composer.oft = params.oft_pda; + composer.endpoint = params.endpoint_pda; composer.bump = ctx.bumps.composer; // Initialize lz_compose_types_accounts with the composer address. diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index 1e45bf11d..c9f95df4f 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -8,7 +8,7 @@ use oapp::{endpoint_cpi::LzAccount, LzComposeParams}; use state::*; -declare_id!("3NJ7AUBaj9N8kBsRqA7SYWJ1poEUsEW36gCG1EfLDkW2"); +declare_id!("2TUdVCMQsefMs28hTeMKAHTjgz8cdMbo7V5oEdrYSu7G"); const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; const COMPOSER_SEED: &[u8] = b"Composer"; diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs index 28c754908..24abfd331 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -5,7 +5,7 @@ pub struct Composer { /// The OFT PDA (unique to this composer instance) pub oft: Pubkey, /// The Endpoint PDA (the authorized endpoint for LZ messages) - pub endpoint_program: Pubkey, + pub endpoint: Pubkey, /// Bump for PDA derivation. pub bump: u8, } diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs index 2afd6555e..d3e1d0a22 100644 --- a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use instructions::*; use state::*; -declare_id!("6xmPjYnXyxz36xcKkv2zCrZc72LK5hQ9xzY3EjeZ59MV"); +declare_id!("34Pyq27SBytfMMbK1xJaa5Bg79gxgMG6ovRdZH5Ya9gu"); pub const OAPP_SEED: &[u8] = b"OApp"; diff --git a/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts b/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts new file mode 100644 index 000000000..867b96296 --- /dev/null +++ b/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts @@ -0,0 +1,125 @@ +// Rewritten version of the test using local validator instead of LiteSVM +import * as anchor from '@coral-xyz/anchor' +import { + Raydium, + getPdaObservationAccount, + getPdaPoolVaultId, + getPdaTickArrayAddress, +} from '@raydium-io/raydium-sdk-v2' +import { MEMO_PROGRAM_ID } from '@solana/spl-memo' +import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, getOrCreateAssociatedTokenAccount } from '@solana/spl-token' +import { Connection, Keypair, PublicKey, SystemProgram, Transaction } from '@solana/web3.js' + +import idl from '../../target/idl/composer.json' + +const RAYDIUM_CLMM_ID = new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK') +const COMPOSER_ID = new PublicKey('2TUdVCMQsefMs28hTeMKAHTjgz8cdMbo7V5oEdrYSu7G') +const COMPOSER_SEED = Buffer.from('Composer') +const LZ_TYPES_SEED = Buffer.from('LzComposeTypes') +const USDE_MINT = new PublicKey('DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT') +const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') + +const connection = new Connection('http://localhost:8899', 'confirmed') + +test('LzCompose -> swap_v2 via local validator', async () => { + const payer = Keypair.generate() + // Create a new provider with the funded payer. + const provider = new anchor.AnchorProvider(connection, new anchor.Wallet(payer), {}) + anchor.setProvider(provider) + await provider.connection.requestAirdrop(payer.publicKey, 10000000e9) + + const program = new anchor.Program(idl as anchor.Idl, COMPOSER_ID, provider) + + const raydium = await Raydium.load({ + connection: provider.connection, + owner: payer, + disableLoadToken: true, + }) + const { data: pools } = await raydium.api.fetchPoolByMints({ + mint1: USDE_MINT, + mint2: USDC_MINT, + }) + const pool = pools[0] + const poolStatePda = new PublicKey(pool.id) + const ammConfigPda = new PublicKey('4BLNHtVe942GSs4teSZqGX24xwKNkqU7bGgNn3iUiUpw') + const observationPda = getPdaObservationAccount(RAYDIUM_CLMM_ID, poolStatePda).publicKey + const tickLower = getPdaTickArrayAddress(RAYDIUM_CLMM_ID, poolStatePda, -1).publicKey + const tickCurrent = getPdaTickArrayAddress(RAYDIUM_CLMM_ID, poolStatePda, 0).publicKey + const tickUpper = getPdaTickArrayAddress(RAYDIUM_CLMM_ID, poolStatePda, 1).publicKey + console.log('raydium accounts derived') + + const [authorityPda] = PublicKey.findProgramAddressSync([poolStatePda.toBuffer()], RAYDIUM_CLMM_ID) + + const { publicKey: inputVaultPda } = getPdaPoolVaultId(RAYDIUM_CLMM_ID, poolStatePda, USDE_MINT) + const { publicKey: outputVaultPda } = getPdaPoolVaultId(RAYDIUM_CLMM_ID, poolStatePda, USDC_MINT) + console.log('vaults derived') + + const oftPda = Keypair.generate() + const endpointPda = Keypair.generate() + const [composerPda] = PublicKey.findProgramAddressSync([COMPOSER_SEED, oftPda.publicKey.toBuffer()], COMPOSER_ID) + const [lzTypesPda] = PublicKey.findProgramAddressSync([LZ_TYPES_SEED, composerPda.toBuffer()], COMPOSER_ID) + console.log('composer accounts derived') + + const composerUsdeAta = await getOrCreateAssociatedTokenAccount(connection, payer, USDE_MINT, composerPda, true) + const userUsdcAta = await getOrCreateAssociatedTokenAccount(connection, payer, USDC_MINT, composerPda, true) + await new Promise((resolve) => setTimeout(resolve, 2000)) // wait 2 seconds + console.log('ATAs created') + + const init = await program.methods + .initComposer({ + oftPda: oftPda.publicKey, + endpointPda: endpointPda.publicKey, + }) + .accounts({ + composer: composerPda, + lzComposeTypesAccounts: lzTypesPda, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + }) + .signers([payer]) + .rpc() + console.log('initComposer tx:', init) + const amountLd = new anchor.BN(250_000) + const minOut = new anchor.BN(1) + const message = Buffer.alloc(80) + amountLd.toArrayLike(Buffer, 'be', 8).copy(message, 32) + oftPda.publicKey.toBuffer().copy(message, 40) + minOut.toArrayLike(Buffer, 'be', 8).copy(message, 72) + + const swapIx = await program.methods + .lzCompose({ from: oftPda.publicKey, to: composerPda, guid: [1], index: 0, message }) + .accounts({ + composer: composerPda, + clmmProgram: RAYDIUM_CLMM_ID, + payer: payer.publicKey, + ammConfig: ammConfigPda, + poolState: poolStatePda, + inputTokenAccount: composerUsdeAta.address, + outputTokenAccount: userUsdcAta.address, + inputVault: inputVaultPda, + outputVault: outputVaultPda, + observationState: observationPda, + tokenProgram: TOKEN_PROGRAM_ID, + tokenProgram2022: TOKEN_2022_PROGRAM_ID, + memoProgram: MEMO_PROGRAM_ID, + inputVaultMint: USDE_MINT, + outputVaultMint: USDC_MINT, + authority: authorityPda, + tickArrayLower: tickLower, + tickArrayCurrent: tickCurrent, + tickArrayUpper: tickUpper, + }) + .signers([payer]) + .instruction() + + const tx = new Transaction().add(swapIx) + tx.feePayer = payer.publicKey + tx.recentBlockhash = (await provider.connection.getLatestBlockhash()).blockhash + tx.sign(payer) + + if (provider.sendAndConfirm) { + await provider.sendAndConfirm(tx, [payer]) + } else { + throw new Error('sendAndConfirm is not defined on the provider') + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 061180cf3..6483b3e62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1852,7 +1852,7 @@ importers: version: 3.0.75(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4) '@layerzerolabs/lz-solana-sdk-v2': specifier: 3.0.0 - version: 3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 @@ -2041,6 +2041,16 @@ importers: version: 5.5.3 examples/oft-solana-composer-library: + dependencies: + '@raydium-io/raydium-sdk-v2': + specifier: 0.1.120-alpha + version: 0.1.120-alpha(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/spl-memo': + specifier: ^0.2.5 + version: 0.2.5(@solana/web3.js@1.95.8) + axios: + specifier: ^1.8.4 + version: 1.8.4(debug@4.3.7) devDependencies: '@coral-xyz/anchor': specifier: ^0.29.0 @@ -2249,6 +2259,9 @@ importers: jest: specifier: ^29.7.0 version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + litesvm: + specifier: ^0.2.0 + version: 0.2.0 mocha: specifier: ^10.2.0 version: 10.2.0 @@ -3526,7 +3539,7 @@ importers: version: 1.95.8 '@sqds/multisig': specifier: ^2.1.3 - version: 2.1.3(typescript@5.5.3) + version: 2.1.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@swc/core': specifier: ^1.4.0 version: 1.4.0 @@ -3580,7 +3593,7 @@ importers: version: 3.3.0 '@ton/ton': specifier: npm:@layerzerolabs/ton@15.2.0-rc.3 - version: /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) + version: /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) p-memoize: specifier: ~4.0.4 version: 4.0.4 @@ -3660,7 +3673,7 @@ importers: version: 2.16.2 jest: specifier: ^29.6.2 - version: 29.7.0(@types/node@18.18.14) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) tsup: specifier: ~8.0.1 version: 8.0.1(@swc/core@1.4.0)(typescript@5.5.3) @@ -3763,7 +3776,7 @@ importers: version: 29.5.12 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.14) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) tslib: specifier: ~2.6.2 version: 2.6.3 @@ -4288,13 +4301,13 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.0 - version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 '@layerzerolabs/oft-v2-solana-sdk': specifier: ^3.0.38 - version: 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + version: 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/protocol-devtools': specifier: ~1.1.6 version: link:../protocol-devtools @@ -4915,13 +4928,13 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.59 - version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 '@layerzerolabs/oft-v2-solana-sdk': specifier: ^3.0.59 - version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/protocol-devtools': specifier: ~1.1.6 version: link:../protocol-devtools @@ -5026,7 +5039,7 @@ importers: version: 12.6.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.14) + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) tsup: specifier: ^8.0.1 version: 8.0.1(@swc/core@1.4.0)(typescript@5.5.3) @@ -5810,7 +5823,7 @@ packages: resolution: {integrity: sha512-kJsoy4fAPTOhzVr7Vwq8s/AUg6BQiJDa7WOqRzev4zsuIS3+JCuIZ6vUd7UBsjnxtmguJJulMRs9qWCzVBt2XA==} engines: {node: '>=15.10.0'} dependencies: - axios: 1.7.4(debug@4.3.7) + axios: 1.7.4 got: 11.8.6 transitivePeerDependencies: - debug @@ -6405,7 +6418,6 @@ packages: commander: ~12.1.0 dependencies: commander: 12.1.0 - dev: true /@coral-xyz/anchor-errors@0.30.1: resolution: {integrity: sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==} @@ -6458,7 +6470,6 @@ packages: - bufferutil - encoding - utf-8-validate - dev: true /@coral-xyz/anchor@0.30.1: resolution: {integrity: sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ==} @@ -6504,7 +6515,6 @@ packages: '@solana/web3.js': 1.95.8 bn.js: 5.2.1 buffer-layout: 1.2.2 - dev: true /@coral-xyz/borsh@0.30.1(@solana/web3.js@1.95.8): resolution: {integrity: sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==} @@ -7649,7 +7659,7 @@ packages: '@ledgerhq/hw-transport-webhid': 6.30.0 '@ledgerhq/hw-transport-webusb': 6.29.4 '@mysten/bcs': 1.2.0 - axios: 1.8.4 + axios: 1.8.4(debug@4.3.7) bech32: 2.0.0 bignumber.js: 9.1.2 bip32: 5.0.0-rc.0(typescript@5.5.3) @@ -7934,49 +7944,6 @@ packages: jest-util: 29.7.0 slash: 3.0.0 - /@jest/core@29.7.0: - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 18.18.14 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - dev: true - /@jest/core@29.7.0(ts-node@10.9.2): resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8262,7 +8229,6 @@ packages: tiny-invariant: 1.3.3 transitivePeerDependencies: - debug - dev: true /@layerzerolabs/lz-config-types@3.0.59(ethers@5.7.2): resolution: {integrity: sha512-V8hcvabSMG/fCA1pt+mkumVwcT1/uWeH4jFqIcC+Elisw4e6C+ALsWBWKRkiOVzi8yFkKaddE9fPrI3iK46vsQ==} @@ -8280,58 +8246,10 @@ packages: /@layerzerolabs/lz-core@3.0.66: resolution: {integrity: sha512-emp2bo09+cQ8ivLjNSG6vRbV9N8H88yeLRTiR1YcZ1UjLFaElm+8TcqpaHmKhKqGYQCUaz9IfVv7I6VE1pD3Tw==} + dev: true /@layerzerolabs/lz-core@3.0.81: resolution: {integrity: sha512-fY5KOwXEl7V38Nah5AmRsfExy26pTs9e1+5TOTAmvGHScTTOLpqMy+JRVTGMyf/wSbDGmtpysHJy6tGODaIdAw==} - dev: true - - /@layerzerolabs/lz-corekit-solana@3.0.0: - resolution: {integrity: sha512-vJtDiS7QM77BN/VuZJsM/xH6ZJmUY3vDlHOmRMHsjHf5sfXqMafNL4+FKCRIijPVPGR0DCJARiWrRtgZchv9+w==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@layerzerolabs/lz-core': 3.0.66 - '@noble/hashes': 1.7.1 - '@noble/secp256k1': 1.7.1 - '@solana/web3.js': 1.95.8 - bip39: 3.1.0 - ed25519-hd-key: 1.3.0 - mem: 8.1.1 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - dev: true - - /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): - resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} - dependencies: - '@layerzerolabs/lz-core': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@metaplex-foundation/umi': 0.9.2 - '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) - '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@noble/hashes': 1.7.1 - '@noble/secp256k1': 1.7.1 - '@solana/web3.js': 1.95.8 - bip39: 3.1.0 - ed25519-hd-key: 1.3.0 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - '@jest/globals' - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - bufferutil - - chai - - debug - - encoding - - supports-color - - typescript - - utf-8-validate - dev: true /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} @@ -8363,71 +8281,11 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): - resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} - dependencies: - '@layerzerolabs/lz-core': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@metaplex-foundation/umi': 0.9.2 - '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) - '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@noble/hashes': 1.7.1 - '@noble/secp256k1': 1.7.1 - '@solana/web3.js': 1.95.8 - bip39: 3.1.0 - ed25519-hd-key: 1.3.0 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - '@jest/globals' - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - bufferutil - - chai - - debug - - encoding - - supports-color - - typescript - - utf-8-validate - dev: true - - /@layerzerolabs/lz-corekit-solana@3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): - resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} - dependencies: - '@layerzerolabs/lz-core': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) - '@metaplex-foundation/umi': 0.9.2 - '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) - '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@noble/hashes': 1.7.1 - '@noble/secp256k1': 1.7.1 - '@solana/web3.js': 1.95.8 - bip39: 3.1.0 - ed25519-hd-key: 1.3.0 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - '@jest/globals' - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - bufferutil - - chai - - debug - - encoding - - supports-color - - typescript - - utf-8-validate - dev: false - - /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): resolution: {integrity: sha512-16rLQA33OyQlfpv8UX4mNnby8XJGNXJEy5okASQJQv3am+N7jLW11dvZxaoSFAlie//w9TbaNAAZe1iuwMLJ6w==} dependencies: '@layerzerolabs/lz-core': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@metaplex-foundation/umi': 0.9.2 '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) @@ -8454,11 +8312,11 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-16rLQA33OyQlfpv8UX4mNnby8XJGNXJEy5okASQJQv3am+N7jLW11dvZxaoSFAlie//w9TbaNAAZe1iuwMLJ6w==} dependencies: '@layerzerolabs/lz-core': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@metaplex-foundation/umi': 0.9.2 '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) @@ -8514,7 +8372,6 @@ packages: - supports-color - typescript - utf-8-validate - dev: true /@layerzerolabs/lz-definitions@3.0.75: resolution: {integrity: sha512-TIbFBCfuElg6WcND4HNUROTSAayBETDC0YrISVadByo3iM2baAi+rpGC1kdrOxOTRlSBetd2khTOUCd7/sZdOQ==} @@ -8968,7 +8825,7 @@ packages: resolution: {integrity: sha512-0WDrlJ+DZ3GGxKPgcConoxQdTB7icpoOYjeTcwmBtJcUnXIE7G3vxzKUAKsUccGegFdgHMEj1K4D+3NPZbNQ2A==} dependencies: '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 @@ -9033,11 +8890,11 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-foundation@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-foundation@3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} dependencies: '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 @@ -9054,17 +8911,19 @@ packages: - supports-color - typescript - utf-8-validate - dev: true + dev: false - /@layerzerolabs/lz-foundation@3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): - resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} + /@layerzerolabs/lz-foundation@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-rGbjBzm7qeu1OmdaxpSFWsh8QulZGk1DiB+Sow9iUDJPkJKXqAjm40vQ1gj5KQiETGz/hAxOjgmilEjg8MFduA==} dependencies: '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 - '@scure/base': 1.2.1 + '@scure/base': 1.2.4 + bech32: 2.0.0 + memoizee: 0.4.17 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -9077,13 +8936,13 @@ packages: - supports-color - typescript - utf-8-validate - dev: false + dev: true /@layerzerolabs/lz-foundation@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-rGbjBzm7qeu1OmdaxpSFWsh8QulZGk1DiB+Sow9iUDJPkJKXqAjm40vQ1gj5KQiETGz/hAxOjgmilEjg8MFduA==} dependencies: '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 @@ -9127,7 +8986,6 @@ packages: - supports-color - typescript - utf-8-validate - dev: true /@layerzerolabs/lz-movevm-sdk-v2@3.0.59(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-qYP5R5nlmO/siy3q68MH3nDnvb0ORzae3KSo2BD3cctNLiEpCjBei+8OcWMo9qZpJDMovNlqtTEaHt1CJmKxBQ==} @@ -9153,15 +9011,16 @@ packages: /@layerzerolabs/lz-serdes@3.0.66: resolution: {integrity: sha512-kk/0pPKuNzIXL1Fh8caH3daYA/Lv2OIPeD7z/VIBANdyCBiYgncF6QK8muoFJ1iIvoQSnpAmgwgww1DlnYa2NA==} + dev: true - /@layerzerolabs/lz-serdes@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-serdes@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): resolution: {integrity: sha512-edzKOV0sA5+jbqzHYFMX4XOPj7nhnaiZVxl1DbsLlboTBVX8lyJlCmxrEM31+iWB47cuBmyJdO3UkDpaRuQbLQ==} dependencies: '@coral-xyz/anchor': 0.29.0 '@layerzerolabs/lz-aptos-sdk-v1': 3.0.81 '@layerzerolabs/lz-core': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/tron-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/tron-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 ed25519-hd-key: 1.3.0 @@ -9183,14 +9042,14 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-serdes@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + /@layerzerolabs/lz-serdes@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-edzKOV0sA5+jbqzHYFMX4XOPj7nhnaiZVxl1DbsLlboTBVX8lyJlCmxrEM31+iWB47cuBmyJdO3UkDpaRuQbLQ==} dependencies: '@coral-xyz/anchor': 0.29.0 '@layerzerolabs/lz-aptos-sdk-v1': 3.0.81 '@layerzerolabs/lz-core': 3.0.81 - '@layerzerolabs/lz-utilities': 3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) - '@layerzerolabs/tron-utilities': 3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/tron-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 ed25519-hd-key: 1.3.0 @@ -9212,28 +9071,21 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): - resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} + /@layerzerolabs/lz-serdes@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-edzKOV0sA5+jbqzHYFMX4XOPj7nhnaiZVxl1DbsLlboTBVX8lyJlCmxrEM31+iWB47cuBmyJdO3UkDpaRuQbLQ==} dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/address': 5.7.0 - '@ethersproject/bignumber': 5.7.0 - '@ethersproject/bytes': 5.7.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/sha2': 5.7.0 - '@ethersproject/solidity': 5.7.0 - '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-v2-utilities': 3.0.81 - '@metaplex-foundation/beet': 0.7.2 - '@metaplex-foundation/beet-solana': 0.4.1 - '@metaplex-foundation/solita': 0.20.1 - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@solana/web3.js': 1.95.8 - bn.js: 5.2.1 - bs58: 5.0.0 - ethereumjs-util: 7.1.5 + '@coral-xyz/anchor': 0.29.0 + '@layerzerolabs/lz-aptos-sdk-v1': 3.0.81 + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/tron-utilities': 3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 tiny-invariant: 1.3.3 + tronweb: 5.3.3 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -9243,13 +9095,11 @@ packages: - chai - debug - encoding - - fastestsmallesttextencoderdecoder - supports-color - typescript - utf-8-validate - dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} dependencies: '@ethersproject/abi': 5.7.0 @@ -9259,13 +9109,13 @@ packages: '@ethersproject/keccak256': 5.7.0 '@ethersproject/sha2': 5.7.0 '@ethersproject/solidity': 5.7.0 - '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/solita': 0.20.1 - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@solana/web3.js': 1.95.8 bn.js: 5.2.1 bs58: 5.0.0 @@ -9286,7 +9136,7 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} dependencies: '@ethersproject/abi': 5.7.0 @@ -9296,47 +9146,17 @@ packages: '@ethersproject/keccak256': 5.7.0 '@ethersproject/sha2': 5.7.0 '@ethersproject/solidity': 5.7.0 - '@layerzerolabs/lz-corekit-solana': 3.0.0 - '@layerzerolabs/lz-definitions': 3.0.75 - '@layerzerolabs/lz-v2-utilities': 3.0.75 - '@metaplex-foundation/beet': 0.7.2 - '@metaplex-foundation/beet-solana': 0.4.1 - '@metaplex-foundation/solita': 0.20.1 - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@solana/web3.js': 1.95.8 - bn.js: 5.2.1 - bs58: 5.0.0 - ethereumjs-util: 7.1.5 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - bufferutil - - encoding - - fastestsmallesttextencoderdecoder - - supports-color - - typescript - - utf-8-validate - dev: true - - /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): - resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} - dependencies: - '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@layerzerolabs/lz-serdes': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 - '@metaplex-foundation/umi': 0.9.2 - '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) - '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/solita': 0.20.1 '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@solana/web3.js': 1.95.8 bn.js: 5.2.1 bs58: 5.0.0 + ethereumjs-util: 7.1.5 tiny-invariant: 1.3.3 transitivePeerDependencies: - '@jest/globals' @@ -9389,50 +9209,14 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): - resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} - dependencies: - '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-serdes': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.81 - '@metaplex-foundation/beet': 0.7.2 - '@metaplex-foundation/beet-solana': 0.4.1 - '@metaplex-foundation/umi': 0.9.2 - '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) - '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) - '@solana/web3.js': 1.95.8 - bn.js: 5.2.1 - bs58: 5.0.0 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - '@jest/globals' - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - bufferutil - - chai - - debug - - encoding - - fastestsmallesttextencoderdecoder - - supports-color - - typescript - - utf-8-validate - dev: true - - /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): - resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} + /@layerzerolabs/lz-solana-sdk-v2@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-xUogDY3IhPm65R+7DisfAPGofDTb38gsCW8k+V290zbOmTRSWKVFMsZlNxrVMH6+7jsKsMYQ/2bqMCqlJKkNEQ==} dependencies: - '@layerzerolabs/lz-corekit-solana': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@layerzerolabs/lz-definitions': 3.0.81 - '@layerzerolabs/lz-foundation': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) - '@layerzerolabs/lz-serdes': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-foundation': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-serdes': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -9459,16 +9243,16 @@ packages: - supports-color - typescript - utf-8-validate - dev: false + dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-xUogDY3IhPm65R+7DisfAPGofDTb38gsCW8k+V290zbOmTRSWKVFMsZlNxrVMH6+7jsKsMYQ/2bqMCqlJKkNEQ==} dependencies: - '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-foundation': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) '@layerzerolabs/lz-serdes': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -9477,7 +9261,7 @@ packages: '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@solana/web3.js': 1.95.8 bn.js: 5.2.1 bs58: 5.0.0 @@ -9531,7 +9315,6 @@ packages: - supports-color - typescript - utf-8-validate - dev: true /@layerzerolabs/lz-ton-sdk-v2@3.0.27: resolution: {integrity: sha512-AU1uOzmLjWvyHdJGTo689bXLsCS/QAmfQSjvZ4544muLfpGVLl3l6lOl8DwmN1UQuwKKK94C5rEvoElVUYf0zQ==} @@ -9636,37 +9419,7 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): - resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} - dependencies: - '@ethersproject/bytes': 5.7.0 - '@initia/initia.js': 0.2.32(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.81 - '@solana/web3.js': 1.95.8 - '@ton/core': 0.59.0(@ton/crypto@3.3.0) - '@ton/crypto': 3.3.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) - aptos: 1.21.0 - bip39: 3.1.0 - ed25519-hd-key: 1.3.0 - ethers: 5.7.2 - memoizee: 0.4.17 - winston: 3.11.0 - transitivePeerDependencies: - - '@jest/globals' - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - bufferutil - - chai - - debug - - encoding - - supports-color - - typescript - - utf-8-validate - dev: true - - /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): resolution: {integrity: sha512-QDGV2DAP8qbbAGTOcbsREr6CK8LsmPzHu9q96i7dXTXCvUq9FsibjjFVYfy3ctVMngVbUVOK9fJ+KtOnYLunCA==} dependencies: '@ethersproject/bytes': 5.7.0 @@ -9675,7 +9428,7 @@ packages: '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 dayjs: 1.11.13 @@ -9698,7 +9451,7 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-QDGV2DAP8qbbAGTOcbsREr6CK8LsmPzHu9q96i7dXTXCvUq9FsibjjFVYfy3ctVMngVbUVOK9fJ+KtOnYLunCA==} dependencies: '@ethersproject/bytes': 5.7.0 @@ -9707,7 +9460,7 @@ packages: '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 dayjs: 1.11.13 @@ -9760,7 +9513,6 @@ packages: - supports-color - typescript - utf-8-validate - dev: true /@layerzerolabs/lz-v2-utilities@3.0.75: resolution: {integrity: sha512-6172reYvXMjBnpQZYlAz+wkxjBJzpBfL5IzAkB0Swc9t27L/ZqDS9e4HLG+lIXqcVaC7jc5cprUw0exbk1002A==} @@ -9792,12 +9544,12 @@ packages: '@layerzerolabs/lz-definitions': 3.0.75 dev: true - /@layerzerolabs/oft-v2-solana-sdk@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/oft-v2-solana-sdk@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-P06/a5+ixph0u1AQkDZ0P0oFaIAdfGPl/UezMfWXUpiWLth428RT0rrMR6qI7z6X1uxqlUFNIotz2ET1fyFcpQ==} dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -9828,8 +9580,8 @@ packages: dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -9838,7 +9590,7 @@ packages: '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@solana/web3.js': 1.95.8 bn.js: 5.2.1 - dotenv: 16.4.5 + dotenv: 16.4.7 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -9885,12 +9637,12 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-ijbvj6/Gc4O4WLHfqnrBuKUtIhpaYws/ORj2apZFT1RKSgAX8CCJ9aZmn0ClamEG98i+PpXoroPPU46LMOMZyA==} dependencies: '@ethersproject/bytes': 5.7.0 - '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -9921,8 +9673,8 @@ packages: dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -9931,7 +9683,7 @@ packages: '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@solana/web3.js': 1.95.8 bn.js: 5.2.1 - dotenv: 16.4.5 + dotenv: 16.4.7 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -10025,7 +9777,7 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@jest/globals@29.7.0)(@ton/core@0.59.0)(chai@4.5.0) - axios: 1.8.4 + axios: 1.8.4(debug@4.3.7) dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -10053,7 +9805,7 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@ton/core@0.59.0)(chai@4.4.1) - axios: 1.8.4 + axios: 1.8.4(debug@4.3.7) dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -10082,7 +9834,7 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@jest/globals@29.7.0)(@ton/core@0.59.0)(chai@4.5.0) - axios: 1.8.4 + axios: 1.8.4(debug@4.3.7) dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -10098,41 +9850,33 @@ packages: - encoding - supports-color - typescript - dev: true - /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3): - resolution: {integrity: sha512-Aq7htcWn31DxSYs8l0sJcQ76uAJRPy50lg6M2G+qNLSXpO01sPcUSb8oK/TmCKo+S+rDIFWUNKFjT0B/rll1YQ==} - peerDependencies: - '@ton/core': '>=0.59.0' - '@ton/crypto': '>=3.2.0' + /@layerzerolabs/tron-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-bWv3GJJ6J/Wu79zlYEe6Cqv7UwzxC8tkA4rcHv6REnrbR/7iEaLcqLrVA/G1KWhOiUj5Rjqdws0bFpjJHdxXxQ==} dependencies: - '@ton/blueprint': 0.25.0(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) - '@ton/core': 0.59.0(@ton/crypto@3.3.0) - '@ton/crypto': 3.3.0 - '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) - '@ton/test-utils': 0.4.2(@jest/globals@29.7.0)(@ton/core@0.59.0)(chai@4.5.0) - axios: 1.7.9 - dataloader: 2.2.2 - symbol.inspect: 1.0.1 - teslabot: 1.5.0 - zod: 3.22.4 + '@ethersproject/providers': 5.7.2 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + ethers: 5.7.2 + tronweb: 5.3.3 transitivePeerDependencies: - '@jest/globals' - '@swc/core' - '@swc/wasm' - - '@ton/ton' - '@types/node' + - bufferutil - chai - debug - encoding - supports-color - typescript + - utf-8-validate + dev: true /@layerzerolabs/tron-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-bWv3GJJ6J/Wu79zlYEe6Cqv7UwzxC8tkA4rcHv6REnrbR/7iEaLcqLrVA/G1KWhOiUj5Rjqdws0bFpjJHdxXxQ==} dependencies: '@ethersproject/providers': 5.7.2 - '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) ethers: 5.7.2 tronweb: 5.3.3 transitivePeerDependencies: @@ -10168,7 +9912,6 @@ packages: - supports-color - typescript - utf-8-validate - dev: true /@ledgerhq/devices@8.4.4: resolution: {integrity: sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A==} @@ -10306,7 +10049,7 @@ packages: '@ethersproject/address': 5.7.0 '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.12) '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.12) - axios: 1.7.4(debug@4.3.7) + axios: 1.8.4(debug@4.3.7) cbor: 9.0.2 chai: 4.5.0 chalk: 4.1.2 @@ -10617,7 +10360,6 @@ packages: /@noble/hashes@1.7.0: resolution: {integrity: sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==} engines: {node: ^14.21.3 || >=16} - dev: true /@noble/hashes@1.7.1: resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} @@ -11012,7 +10754,7 @@ packages: dependencies: amazon-cognito-identity-js: 6.3.12 async-retry: 1.3.3 - axios: 1.7.4(debug@4.3.5) + axios: 1.8.4(debug@4.3.5) lodash: 4.17.21 node-fetch: 2.7.0 transitivePeerDependencies: @@ -11141,6 +10883,30 @@ packages: /@protobufjs/utf8@1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + /@raydium-io/raydium-sdk-v2@0.1.120-alpha(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-53YSohrCexzkHubjlohcP8LPdWJXD75nS5NS11iW0qWraWHMJ/RUBDzMHk02RCMyQj7k+SBIsqrlZimhniBdJw==} + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/spl-token': 0.4.12(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + axios: 1.8.4(debug@4.3.7) + big.js: 6.2.2 + bn.js: 5.2.1 + dayjs: 1.11.13 + decimal.js-light: 2.5.1 + jsonfile: 6.1.0 + lodash: 4.17.21 + toformat: 2.0.0 + tsconfig-paths: 4.2.0 + transitivePeerDependencies: + - bufferutil + - debug + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + dev: false + /@resolver-engine/core@0.3.3: resolution: {integrity: sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ==} dependencies: @@ -11309,7 +11075,7 @@ packages: '@ethersproject/solidity': 5.7.0 '@safe-global/safe-deployments': 1.33.0 ethereumjs-util: 7.1.5 - semver: 7.6.0 + semver: 7.6.3 web3: 1.10.4 web3-core: 1.10.4 web3-utils: 1.10.4 @@ -11323,6 +11089,7 @@ packages: /@safe-global/safe-core-sdk-types@2.3.0: resolution: {integrity: sha512-dU0KkDV1KJNf11ajbUjWiSi4ygdyWfhk1M50lTJWUdCn1/2Bsb/hICM8LoEk6DCoFumxaoCet02SmYakXsW2CA==} + deprecated: 'WARNING: This project has been renamed to @safe-global/types-kit. Please, migrate from @safe-global/safe-core-sdk-types@5.1.0 to @safe-global/types-kit@1.0.0.' dependencies: '@ethersproject/bignumber': 5.7.0 '@ethersproject/contracts': 5.7.0 @@ -11336,7 +11103,7 @@ packages: /@safe-global/safe-deployments@1.33.0: resolution: {integrity: sha512-G9qGMsha6idMnDuk98dE//inQL09w97hcQ5ZTdSWIHCzJ9mFdN0K4DH2afjZOwdt+Y4g8gZmY3z+kR38MPEToQ==} dependencies: - semver: 7.6.2 + semver: 7.6.3 /@scure/base@1.1.5: resolution: {integrity: sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==} @@ -11786,6 +11553,16 @@ packages: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + /@solana/spl-memo@0.2.5(@solana/web3.js@1.95.8): + resolution: {integrity: sha512-0Zx5t3gAdcHlRTt2O3RgGlni1x7vV7Xq7j4z9q8kKOMgU03PyoTbFQ/BSYCcICHzkaqD7ZxAiaJ6dlXolg01oA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.91.6 + dependencies: + '@solana/web3.js': 1.95.8 + buffer: 6.0.3 + dev: false + /@solana/spl-token-group@0.0.5(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==} engines: {node: '>=16'} @@ -11862,19 +11639,6 @@ packages: - fastestsmallesttextencoderdecoder - typescript - /@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8)(typescript@5.5.3): - resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.95.3 - dependencies: - '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@solana/web3.js': 1.95.8 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - typescript - dev: true - /@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.98.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} engines: {node: '>=16'} @@ -11905,25 +11669,6 @@ packages: - typescript - utf-8-validate - /@solana/spl-token@0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3): - resolution: {integrity: sha512-bvohO3rIMSVL24Pb+I4EYTJ6cL82eFpInEXD/I8K8upOGjpqHsKUoAempR/RnUlI1qSFNyFlWJfu6MNUgfbCQQ==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.88.0 - dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/buffer-layout-utils': 0.2.0 - '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8)(typescript@5.5.3) - '@solana/web3.js': 1.95.8 - buffer: 6.0.3 - transitivePeerDependencies: - - bufferutil - - encoding - - fastestsmallesttextencoderdecoder - - typescript - - utf-8-validate - dev: true - /@solana/spl-token@0.4.12(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-K6CxzSoO1vC+WBys25zlSDaW0w4UFZO/IvEZquEI35A/PjqXNQHeVigmDCZYEJfESvYarKwsr8tYr/29lPtvaw==} engines: {node: '>=16'} @@ -12050,14 +11795,14 @@ packages: resolution: {integrity: sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==} dev: true - /@sqds/multisig@2.1.3(typescript@5.5.3): + /@sqds/multisig@2.1.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-WOiL7La+RSiJsz7jVO85yhSiiSvNMUthiWuLPeWVOoD6IYa34BEAzanF1RdXRWGglSbRFYCTkyr+Ay1WmXmSRQ==} engines: {node: '>=14'} dependencies: '@metaplex-foundation/beet': 0.7.1 '@metaplex-foundation/beet-solana': 0.4.0 '@metaplex-foundation/cusper': 0.0.2 - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(typescript@5.5.3) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@solana/web3.js': 1.95.8 '@types/bn.js': 5.1.6 assert: 2.1.0 @@ -12283,10 +12028,10 @@ packages: '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 '@ton/tolk-js': 0.6.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@tonconnect/sdk': 2.2.0 arg: 5.0.2 - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) chalk: 4.1.2 dotenv: 16.4.7 inquirer: 8.2.6 @@ -12317,7 +12062,7 @@ packages: '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@jest/globals@29.7.0)(@layerzerolabs/ton@15.2.0-rc.3)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@tonconnect/sdk': 2.2.0 arg: 5.0.2 - axios: 1.8.4 + axios: 1.8.4(debug@4.3.7) chalk: 4.1.2 dotenv: 16.4.7 inquirer: 8.2.6 @@ -12439,7 +12184,6 @@ packages: /@tronweb3/google-protobuf@3.21.4: resolution: {integrity: sha512-joxgV4esCdyZ921AprMIG1T7HjkypquhbJ5qJti/priCBJhRE1z9GOxIEMvayxSVSRbMGIoJNE0Knrg3vpwM1w==} deprecated: This package is deprecated. Please use the official package google-protobuf instead. - dev: true /@trufflesuite/bigint-buffer@1.1.9: resolution: {integrity: sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==} @@ -13115,7 +12859,6 @@ packages: engines: {node: '>=6.5'} dependencies: event-target-shim: 5.0.1 - dev: true /abortcontroller-polyfill@1.7.5: resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} @@ -13521,7 +13264,6 @@ packages: /atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} - dev: true /auto-bind@4.0.0: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} @@ -13555,21 +13297,12 @@ packages: /axios@0.25.0: resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} dependencies: - follow-redirects: 1.15.6(debug@4.3.5) + follow-redirects: 1.15.9(debug@4.3.7) transitivePeerDependencies: - debug dev: true - /axios@1.7.4(debug@4.3.5): - resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} - dependencies: - follow-redirects: 1.15.9(debug@4.3.5) - form-data: 4.0.1 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - /axios@1.7.4(debug@4.3.7): + /axios@1.7.4: resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} dependencies: follow-redirects: 1.15.9(debug@4.3.7) @@ -13578,16 +13311,16 @@ packages: transitivePeerDependencies: - debug - /axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + /axios@1.8.4(debug@4.3.5): + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} dependencies: - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9(debug@4.3.5) form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - /axios@1.8.4: + /axios@1.8.4(debug@4.3.7): resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} dependencies: follow-redirects: 1.15.9(debug@4.3.7) @@ -13702,6 +13435,10 @@ packages: is-windows: 1.0.2 dev: true + /big.js@6.2.2: + resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} + dev: false + /bigint-buffer@1.1.5: resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} engines: {node: '>= 10.0.0'} @@ -14773,7 +14510,6 @@ packages: /dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - dev: true /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -14839,6 +14575,10 @@ packages: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} + /decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + dev: false + /decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} @@ -16083,7 +15823,7 @@ packages: optional: true dependencies: '@solidity-parser/parser': 0.14.5 - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) cli-table3: 0.5.1 colors: 1.4.0 ethereum-cryptography: 1.2.0 @@ -16286,11 +16026,9 @@ packages: /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} - dev: true /eventemitter3@3.1.2: resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} - dev: true /eventemitter3@4.0.4: resolution: {integrity: sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==} @@ -16420,7 +16158,6 @@ packages: /extended-buffer@6.1.0: resolution: {integrity: sha512-cQwfrYOZEzSqsSkav15ppifOynfJfCPwfPM1wutukoBlvqr2G8ib0uR1EcVmxCWfS4XjYPCywJDAS0s6LeICBQ==} - dev: true /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} @@ -16483,7 +16220,6 @@ packages: /fast-redact@3.5.0: resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} engines: {node: '>=6'} - dev: true /fast-stable-stringify@1.0.0: resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} @@ -16985,7 +16721,6 @@ packages: minimatch: 9.0.5 minipass: 7.1.2 path-scurry: 1.10.1 - dev: true /glob@11.0.1: resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} @@ -17702,7 +17437,6 @@ packages: /injectpromise@1.0.0: resolution: {integrity: sha512-qNq5wy4qX4uWHcVFOEU+RqZkoVG65FhvGkyDWbuBxILMjK6A1LFf5A1mgXZkD4nRx5FCorD81X/XvPKp/zVfPA==} - dev: true /ink-gradient@2.0.0(ink@3.2.0)(react@17.0.2): resolution: {integrity: sha512-d2BK/EzzBRoDL54NWkS3JGE4J8xtzwRVWxDAIkQ/eQ60XIzrFMtT5JlUqgV05Qlt32Jvk50qW51YqxGJggTuqA==} @@ -18324,7 +18058,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jackspeak@4.0.1: resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} @@ -18401,34 +18134,6 @@ packages: - babel-plugin-macros - supports-color - /jest-cli@29.7.0(@types/node@18.18.14): - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) - exit: 0.1.2 - import-local: 3.1.0 - jest-config: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - dev: true - /jest-cli@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -18772,27 +18477,6 @@ packages: merge-stream: 2.0.0 supports-color: 8.1.1 - /jest@29.7.0(@types/node@18.18.14): - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.7.0 - '@jest/types': 29.6.3 - import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@18.18.14) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - dev: true - /jest@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -19148,6 +18832,68 @@ packages: wrap-ansi: 9.0.0 dev: true + /litesvm-darwin-arm64@0.2.0: + resolution: {integrity: sha512-fqGE7Z6iT+5Xob9o1xs0N7Tf3c1meJxDERf7Z6T7Loqm0seyUsfjM0gTOvMJHiWvvnOx+qIo/Cjhtp84Kb9v8w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /litesvm-darwin-universal@0.2.0: + resolution: {integrity: sha512-CNgVrQ9tknv8JbtpmOO0SNXuOQj769A3ufWHrMFEBuFhCr9CJ6RQI9a/HxPWjMHTC7EzlcF8klMupPYpqM8KGA==} + engines: {node: '>= 10'} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /litesvm-darwin-x64@0.2.0: + resolution: {integrity: sha512-blRxD4y0FIx3E27Iv7oX0uz9x3v9KPD+++HLatBx5K5bh57qrAStUINsgT6MchYrxxOE0T2xktJaiSQSEMxWSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /litesvm-linux-x64-gnu@0.2.0: + resolution: {integrity: sha512-2PQcLvn5c8GLGPp/vLCFaladWe61wbtwwTlIMLEfPeU5f88jBPOaEryO85+0FHZS1awbT97caa55sFDpAO4CWw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /litesvm-linux-x64-musl@0.2.0: + resolution: {integrity: sha512-9vAYFIicGjZIlwoCBii6yJmfIUVS4cdF0CxoAL+DGexTBLECk+Tr+WEFEXsvsYe4s81jxP04TTiUnasj/EaIcw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /litesvm@0.2.0: + resolution: {integrity: sha512-75+ZMkSFY5ynI3S+vCMnZv1wcILg5iNEj21B+XE/G3/P2dfTPj+rbdNM1q3eLFdlnXdewhiB4BLypKsBnQTSOw==} + engines: {node: '>= 10'} + dependencies: + '@solana/web3.js': 1.95.8 + bs58: 4.0.1 + optionalDependencies: + litesvm-darwin-arm64: 0.2.0 + litesvm-darwin-universal: 0.2.0 + litesvm-darwin-x64: 0.2.0 + litesvm-linux-x64-gnu: 0.2.0 + litesvm-linux-x64-musl: 0.2.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + /load-json-file@1.1.0: resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} engines: {node: '>=0.10.0'} @@ -19285,7 +19031,6 @@ packages: /lru-cache@10.1.0: resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} engines: {node: 14 || >=16.14} - dev: true /lru-cache@11.0.0: resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==} @@ -19301,6 +19046,7 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 + dev: true /lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} @@ -19339,6 +19085,7 @@ packages: engines: {node: '>=6'} dependencies: p-defer: 1.0.0 + dev: false /markdown-table@1.1.3: resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==} @@ -19363,14 +19110,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - /mem@8.1.1: - resolution: {integrity: sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==} - engines: {node: '>=10'} - dependencies: - map-age-cleaner: 0.1.3 - mimic-fn: 3.1.0 - dev: true - /memdown@5.1.0: resolution: {integrity: sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==} engines: {node: '>=6'} @@ -19488,6 +19227,7 @@ packages: /mimic-fn@3.1.0: resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} engines: {node: '>=8'} + dev: false /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} @@ -19601,7 +19341,7 @@ packages: engines: {node: '>=4'} deprecated: This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that. dependencies: - mkdirp: 1.0.4 + mkdirp: 3.0.1 /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} @@ -19613,6 +19353,7 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + dev: true /mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} @@ -19623,7 +19364,6 @@ packages: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} hasBin: true - dev: true /mnemonist@0.38.5: resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} @@ -19995,7 +19735,6 @@ packages: /on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} - dev: true /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -20078,6 +19817,7 @@ packages: /p-defer@1.0.0: resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} engines: {node: '>=4'} + dev: false /p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} @@ -20289,7 +20029,6 @@ packages: dependencies: lru-cache: 10.1.0 minipass: 7.1.2 - dev: true /path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} @@ -20384,11 +20123,9 @@ packages: dependencies: readable-stream: 4.7.0 split2: 4.2.0 - dev: true /pino-std-serializers@6.2.2: resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} - dev: true /pino@8.21.0: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} @@ -20405,7 +20142,6 @@ packages: safe-stable-stringify: 2.4.3 sonic-boom: 3.8.1 thread-stream: 2.7.0 - dev: true /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} @@ -20523,7 +20259,6 @@ packages: /process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} - dev: true /process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} @@ -20697,7 +20432,6 @@ packages: /querystring-es3@0.2.1: resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} engines: {node: '>=0.4.x'} - dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -20705,7 +20439,6 @@ packages: /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - dev: true /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} @@ -20766,7 +20499,7 @@ packages: resolution: {integrity: sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==} dependencies: shell-quote: 1.8.1 - ws: 7.5.9 + ws: 7.5.10 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -20859,7 +20592,6 @@ packages: events: 3.3.0 process: 0.11.10 string_decoder: 1.3.0 - dev: true /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} @@ -20874,7 +20606,6 @@ packages: /real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - dev: true /reduce-flatten@2.0.0: resolution: {integrity: sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==} @@ -21290,17 +21021,11 @@ packages: lru-cache: 6.0.0 dev: true - /semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - /semver@7.6.2: resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} engines: {node: '>=10'} hasBin: true + dev: true /semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} @@ -21658,7 +21383,6 @@ packages: resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} dependencies: atomic-sleep: 1.0.0 - dev: true /sort-object-keys@1.1.3: resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} @@ -21745,7 +21469,6 @@ packages: /split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - dev: true /spok@1.5.5: resolution: {integrity: sha512-IrJIXY54sCNFASyHPOY+jEirkiJ26JDqsGiI0Dvhwcnkl0PEWi1PSsrkYql0rzDw8LFVTcA7rdUCAJdE2HE+2Q==} @@ -21961,7 +21684,6 @@ packages: /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - dev: true /strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} @@ -22243,7 +21965,6 @@ packages: resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} dependencies: real-require: 0.2.0 - dev: true /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -22319,6 +22040,10 @@ packages: dependencies: is-number: 7.0.0 + /toformat@2.0.0: + resolution: {integrity: sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ==} + dev: false + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -22397,7 +22122,7 @@ packages: '@babel/runtime': 7.25.6 '@ethersproject/abi': 5.7.0 '@tronweb3/google-protobuf': 3.21.4 - axios: 1.8.4 + axios: 1.8.4(debug@4.3.7) bignumber.js: 9.1.2 ethereum-cryptography: 2.1.3 ethers: 5.7.2 @@ -22411,7 +22136,6 @@ packages: - bufferutil - debug - utf-8-validate - dev: true /ts-api-utils@1.3.0(typescript@5.5.3): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} @@ -22529,6 +22253,15 @@ packages: strip-bom: 3.0.0 dev: true + /tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: false + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -23009,7 +22742,6 @@ packages: /validator@13.15.0: resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==} engines: {node: '>= 0.10'} - dev: true /varint@5.0.2: resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} @@ -23096,7 +22828,7 @@ packages: resolution: {integrity: sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww==} engines: {node: '>=8.0.0'} dependencies: - '@types/bn.js': 5.1.5 + '@types/bn.js': 5.1.6 '@types/node': 12.20.55 bignumber.js: 9.1.2 web3-core-helpers: 1.10.4 @@ -23542,18 +23274,6 @@ packages: utf-8-validate: optional: true - /ws@7.5.9: - resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - /ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -23608,12 +23328,14 @@ packages: /yaeti@0.0.6: resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} engines: {node: '>=0.10.32'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} From 05c6639ddf886e3ccc80cb74b9a789248fc3ae89 Mon Sep 17 00:00:00 2001 From: Krak Date: Thu, 1 May 2025 12:55:46 -0700 Subject: [PATCH 09/11] chore: add new testing for composer --- .../oft-solana-composer-library/.gitignore | 3 + .../oft-solana-composer-library/Anchor.toml | 47 ++- .../hardhat.config.ts | 6 +- .../composer/src/instructions/lz_compose.rs | 51 ++- .../src/instructions/lz_compose_types.rs | 104 ++++-- .../programs/composer/src/lib.rs | 2 +- .../programs/composer/src/state/composer.rs | 2 +- .../programs/endpoint-mock/src/lib.rs | 2 +- .../runbooks/README.md | 49 +++ .../runbooks/deployment/main.tx | 60 +++ .../runbooks/deployment/signers.devnet.tx | 8 + .../runbooks/deployment/signers.localnet.tx | 8 + .../runbooks/deployment/signers.mainnet.tx | 10 + .../tasks/evm/send.ts | 78 ++-- .../tasks/index.ts | 3 + .../tasks/solana/getKeypair.ts | 16 + .../tasks/solana/getRaydiumPools.ts | 105 ++++++ .../tasks/solana/initComposer.ts | 60 +++ .../test/anchor/lz_compose_local.test.ts | 346 ++++++++++++++---- examples/oft-solana-composer-library/txtx.yml | 19 + 20 files changed, 830 insertions(+), 149 deletions(-) create mode 100644 examples/oft-solana-composer-library/runbooks/README.md create mode 100644 examples/oft-solana-composer-library/runbooks/deployment/main.tx create mode 100644 examples/oft-solana-composer-library/runbooks/deployment/signers.devnet.tx create mode 100644 examples/oft-solana-composer-library/runbooks/deployment/signers.localnet.tx create mode 100644 examples/oft-solana-composer-library/runbooks/deployment/signers.mainnet.tx create mode 100644 examples/oft-solana-composer-library/tasks/solana/getKeypair.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts create mode 100644 examples/oft-solana-composer-library/tasks/solana/initComposer.ts create mode 100644 examples/oft-solana-composer-library/txtx.yml diff --git a/examples/oft-solana-composer-library/.gitignore b/examples/oft-solana-composer-library/.gitignore index 0f7da464c..3303873c5 100644 --- a/examples/oft-solana-composer-library/.gitignore +++ b/examples/oft-solana-composer-library/.gitignore @@ -6,6 +6,9 @@ coverage.json target typechain typechain-types +wallet.json +keypair.json +secret.txt # Hardhat files cache diff --git a/examples/oft-solana-composer-library/Anchor.toml b/examples/oft-solana-composer-library/Anchor.toml index 41c498a66..2c598bb0f 100644 --- a/examples/oft-solana-composer-library/Anchor.toml +++ b/examples/oft-solana-composer-library/Anchor.toml @@ -6,15 +6,54 @@ seeds = false skip-lint = false [programs.localnet] -composer = "2TUdVCMQsefMs28hTeMKAHTjgz8cdMbo7V5oEdrYSu7G" -oft = "BH2YoCQV4i7gagmJqhj6nBByroJJwAQHa76XKV921e9m" +composer = "4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq" +oft = "Gwdvzz5Qo8qBzG9B1NVZgLjwBVfHpympeb17QZAFQVB5" [registry] url = "https://api.apr.dev" [provider] -cluster = "http://localhost:8899" -wallet = "./krak.json" +cluster = "localnet" +wallet = "./keypair.json" [scripts] test = "npx jest test/anchor/" + +[test] +reset = true + +[test.validator] +url = "https://api.mainnet-beta.solana.com" # This is the url of the cluster that accounts are cloned from (See `test.validator.clone`). + +[[test.validator.clone]] +address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" + +[[test.validator.clone]] +address = "9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x" + +[[test.validator.clone]] +address = "D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL" + +[[test.validator.clone]] +address = "Cv3s57YQzJRp988qvXPfZN5xX7BUFcgB1ycA9jzSrh2n" + +[[test.validator.clone]] +address = "J1jEbmdbtsfA26igi1ryQjejnYVQRpxnobZh9tgSaH1h" + +[[test.validator.clone]] +address = "8AQHmKsoFh5Uh8bzYnnQ4Fx2QL2axPao9hjnt1YQBvdS" + +[[test.validator.clone]] +address = "3ZUDGLF7xw26gcXUhZK3tL4MU2VizCUkXoHdb2LQ4zDM" + +[[test.validator.clone]] +address = "5Nb7bdyi4jkMdA8Xqmt72HS1fpkGRQ573Gf8PJeGunsy" + +[[test.validator.clone]] +address = "sUfgz2jsDhN8qn1PrkWr9pVnUrLqqL1yB7WAnCj62Xz" + +[[test.validator.clone]] +address = "DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT" + +[[test.validator.clone]] +address = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" diff --git a/examples/oft-solana-composer-library/hardhat.config.ts b/examples/oft-solana-composer-library/hardhat.config.ts index e298b44c3..b7b1f6384 100644 --- a/examples/oft-solana-composer-library/hardhat.config.ts +++ b/examples/oft-solana-composer-library/hardhat.config.ts @@ -59,9 +59,9 @@ const config: HardhatUserConfig = { ], }, networks: { - 'sepolia-testnet': { - eid: EndpointId.SEPOLIA_V2_TESTNET, - url: process.env.RPC_URL_SEPOLIA || 'https://gateway.tenderly.co/public/sepolia', + 'bsc-mainnet': { + eid: EndpointId.BSC_MAINNET, + url: process.env.RPC_URL_BSC, accounts, }, hardhat: { diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs index c76d00b8e..0d66f07ff 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs @@ -2,6 +2,7 @@ use crate::*; use anchor_lang::prelude::*; use anchor_spl::token::{Token}; use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; +use anchor_spl::associated_token::AssociatedToken; use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; use raydium_clmm_cpi::cpi::swap_v2; @@ -17,8 +18,9 @@ use oapp::LzComposeParams; use spl_memo; #[derive(Accounts)] +#[instruction(params: LzComposeParams)] pub struct LzCompose<'info> { - // Use the stored “oft” field as the unique seed. + // Use the stored "oft" field as the unique seed. #[account( mut, seeds = [COMPOSER_SEED, &composer.oft.to_bytes()], @@ -28,6 +30,9 @@ pub struct LzCompose<'info> { pub clmm_program: Program<'info, RaydiumClmm>, /// The user performing the swap. + /// The end-user or intermediary paying for this swap and transaction fees. + /// Must be mutable because we fund the ATA creation below. + #[account(mut)] pub payer: Signer<'info>, /// The factory state to read protocol fees. @@ -77,9 +82,9 @@ pub struct LzCompose<'info> { pub output_vault_mint: Box>, // Extra accounts required by the CPI. - /// The authority account for the swap. - pub authority: AccountInfo<'info>, - + /// The bitmap extension account for this pool’s tick arrays + #[account(mut)] + pub tick_bitmap: AccountInfo<'info>, /// Tick arrays for the swap range. #[account(mut)] pub tick_array_lower: AccountInfo<'info>, @@ -87,6 +92,26 @@ pub struct LzCompose<'info> { pub tick_array_current: AccountInfo<'info>, #[account(mut)] pub tick_array_upper: AccountInfo<'info>, + + /// CHECK: Will be validated in `apply()` against the payload. + #[account(mut)] + pub to_address: UncheckedAccount<'info>, + + /// The recipient’s ATA for the output mint. + /// If it doesn’t exist, Anchor will create it, funded by `payer`. + #[account( + init_if_needed, + payer = payer, + associated_token::mint = output_vault_mint, + associated_token::authority = to_address, + associated_token::token_program = token_program + )] + pub to_token_account: Box>, + + /// Anchor needs these to create an ATA if missing: + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent>, } impl LzCompose<'_> { @@ -102,6 +127,12 @@ impl LzCompose<'_> { compose_from.copy_from_slice(&msg[40..72]); let inner = &msg[72..]; let min_amount_out = u64::from_be_bytes(inner[0..8].try_into().unwrap()); + let receiver = Pubkey::new_from_array(inner[8..40].try_into().unwrap()); + require_keys_eq!( + ctx.accounts.to_address.key(), + receiver, + ComposerError::InvalidTo + ); // Build CPI accounts struct. let cpi_accounts = SwapSingleV2 { @@ -109,7 +140,7 @@ impl LzCompose<'_> { amm_config: ctx.accounts.amm_config.to_account_info(), pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.input_token_account.to_account_info(), - output_token_account: ctx.accounts.output_token_account.to_account_info(), + output_token_account: ctx.accounts.to_token_account.to_account_info(), input_vault: ctx.accounts.input_vault.to_account_info(), output_vault: ctx.accounts.output_vault.to_account_info(), observation_state: ctx.accounts.observation_state.to_account_info(), @@ -122,15 +153,7 @@ impl LzCompose<'_> { let mut cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts); cpi_ctx = cpi_ctx.with_remaining_accounts(vec![ - ctx.accounts.payer.to_account_info(), - ctx.accounts.authority.to_account_info(), - ctx.accounts.amm_config.to_account_info(), - ctx.accounts.pool_state.to_account_info(), - ctx.accounts.input_token_account.to_account_info(), - ctx.accounts.output_token_account.to_account_info(), - ctx.accounts.input_vault.to_account_info(), - ctx.accounts.output_vault.to_account_info(), - ctx.accounts.observation_state.to_account_info(), + ctx.accounts.tick_bitmap.to_account_info(), ctx.accounts.tick_array_lower.to_account_info(), ctx.accounts.tick_array_current.to_account_info(), ctx.accounts.tick_array_upper.to_account_info(), diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs index f1c4004d6..4e4281e89 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs @@ -13,90 +13,134 @@ use oapp::{endpoint::ID as ENDPOINT_ID, LzComposeParams}; #[derive(Accounts)] pub struct LzComposeTypes<'info> { + // === Raydium CLMM accounts === + /// The on‐chain Raydium CLMM program itself, used for the CPI into `swap_v2`. pub clmm_program: Program<'info, RaydiumClmm>, - /// The user performing the swap. - pub payer: Signer<'info>, + + /// The SPL‐compatible token program (v1) used by Raydium for transfers. + pub token_program: Program<'info, Token>, + /// The SPL‐compatible token program (v2) used by Raydium for transfers. + pub token_program_2022: Program<'info, Token2022>, + /// The SPL Memo program for attaching arbitrary memos to the swap CPI. + #[account(address = spl_memo::id())] + pub memo_program: UncheckedAccount<'info>, + + /// Raydium AMM configuration (fees, tick spacing, etc). Must match `pool_state.load()?.amm_config`. #[account(address = pool_state.load()?.amm_config)] pub amm_config: Box>, + + /// The on‐chain pool state PDA, which holds the current price, liquidity, and fee growth. pub pool_state: AccountLoader<'info, PoolState>, - pub input_token_account: Box>, - pub output_token_account: Box>, + + /// The “vault” token account for token A (input token) belonging to the pool. + #[account(mut)] pub input_vault: Box>, + /// The “vault” token account for token B (output token) belonging to the pool. + #[account(mut)] pub output_vault: Box>, - #[account(address = pool_state.load()?.observation_key)] - pub observation_state: AccountLoader<'info, ObservationState>, - pub token_program: Program<'info, Token>, - pub token_program_2022: Program<'info, Token2022>, - #[account(address = spl_memo::id())] - pub memo_program: UncheckedAccount<'info>, + + /// The SPL‐Token Mint for the input vault (must equal `input_vault.mint`). #[account(address = input_vault.mint)] pub input_vault_mint: Box>, + /// The SPL‐Token Mint for the output vault (must equal `output_vault.mint`). #[account(address = output_vault.mint)] pub output_vault_mint: Box>, - /// The LayerZero endpoint program. - pub lz_program: AccountInfo<'info>, - /// The authority account for the swap. - pub authority: AccountInfo<'info>, - /// Tick array accounts for the swap range. + + /// The oracle observation state PDA for the pool’s price oracle. + #[account(address = pool_state.load()?.observation_key)] + pub observation_state: AccountLoader<'info, ObservationState>, + + /// The three tick‐array PDAs covering the price range for this swap. + #[account(mut)] pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] pub tick_array_current: AccountInfo<'info>, + #[account(mut)] pub tick_array_upper: AccountInfo<'info>, + + /// The bitmap extension account that tracks which tick arrays are initialized. + #[account(mut)] + pub tick_bitmap: AccountInfo<'info>, + + /// The end‐user’s token account for deposit (must be an ATA for the input mint). + #[account(mut)] + pub input_token_account: Box>, + /// The end‐user’s token account for withdrawal (must be an ATA for the output mint). + #[account(mut)] + pub output_token_account: Box>, + + /// === LayerZero composer / endpoint accounts === + /// The LayerZero endpoint program ID (used for the final `clear_compose` CPI). + /// Anchor will not enforce it, but the CPI macro checks it at runtime. + pub lz_program: AccountInfo<'info>, + + /// The signer paying for this transaction (also used as Raydium swap payer). + pub payer: Signer<'info>, } impl LzComposeTypes<'_> { - /// Generates the ordered list of LzAccounts expected by the clear_compose call. - /// Base accounts for the swap are listed first, followed by extra accounts. + /// Build the ordered `Vec` listing: + /// 1. All Raydium CPI accounts (payer, amm_config, pool_state, vaults, tick arrays, etc.) + /// 2. All extra LayerZero “clear compose” accounts (`send_compose`/`clear_compose` machinery). + /// + /// The returned accounts vector is passed into `clear_compose` so that the endpoint + /// program can verify the message (guid, index, from, to, etc.) and mark it consumed. pub fn apply( ctx: &Context, params: &LzComposeParams, ) -> Result> { let mut accounts = Vec::new(); - // Base accounts for swap operations. + + // --- Raydium base accounts for `swap_v2` CPI --- + // 1) Who pays the swap fees / funds any borrow accounts.push(LzAccount { pubkey: ctx.accounts.payer.key(), is_signer: true, is_writable: false, }); - accounts.push(LzAccount { - pubkey: ctx.accounts.authority.key(), - is_signer: false, - is_writable: false, - }); + // 2) AMM config holding fee parameters accounts.push(LzAccount { pubkey: ctx.accounts.amm_config.key(), is_signer: false, is_writable: false, }); + // 3) Pool state (price, liquidity) accounts.push(LzAccount { pubkey: ctx.accounts.pool_state.key(), is_signer: false, is_writable: true, }); + // 4) User token account for input accounts.push(LzAccount { pubkey: ctx.accounts.input_token_account.key(), is_signer: false, is_writable: true, }); + // 5) User token account for output accounts.push(LzAccount { pubkey: ctx.accounts.output_token_account.key(), is_signer: false, is_writable: true, }); + // 6) Vault for input token accounts.push(LzAccount { pubkey: ctx.accounts.input_vault.key(), is_signer: false, is_writable: true, }); + // 7) Vault for output token accounts.push(LzAccount { pubkey: ctx.accounts.output_vault.key(), is_signer: false, is_writable: true, }); + // 8) Oracle observation accounts.push(LzAccount { pubkey: ctx.accounts.observation_state.key(), is_signer: false, is_writable: true, }); + // 9) Tick arrays that cover the swap’s price range accounts.push(LzAccount { pubkey: ctx.accounts.tick_array_lower.key(), is_signer: false, @@ -113,17 +157,17 @@ impl LzComposeTypes<'_> { is_writable: true, }); - // Append additional accounts required for clear_compose. + // --- LayerZero clear_compose accounts (replay protection) --- let mut extra_accounts = get_accounts_for_clear_compose( ENDPOINT_ID, - ¶ms.from, - &ctx.accounts.lz_program.key(), - ¶ms.guid, - params.index, - ¶ms.message, + ¶ms.from, // the “sender” OFT address + &ctx.accounts.lz_program.key(), // the endpoint program ID + ¶ms.guid, // message GUID + params.index, // sequence index + ¶ms.message, // raw payload bytes ); accounts.append(&mut extra_accounts); - + Ok(accounts) } } diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index c9f95df4f..2f7d1a331 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -8,7 +8,7 @@ use oapp::{endpoint_cpi::LzAccount, LzComposeParams}; use state::*; -declare_id!("2TUdVCMQsefMs28hTeMKAHTjgz8cdMbo7V5oEdrYSu7G"); +declare_id!("4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq"); const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; const COMPOSER_SEED: &[u8] = b"Composer"; diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs index 24abfd331..2c97e79a0 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -35,10 +35,10 @@ pub struct LzComposeTypesAccounts { pub input_vault_mint: Pubkey, pub output_vault_mint: Pubkey, pub lz_program: Pubkey, - pub authority: Pubkey, pub tick_array_lower: Pubkey, pub tick_array_current: Pubkey, pub tick_array_upper: Pubkey, + pub to_address: Pubkey, } impl LzComposeTypesAccounts { diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs index d3e1d0a22..46e64bf1f 100644 --- a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use instructions::*; use state::*; -declare_id!("34Pyq27SBytfMMbK1xJaa5Bg79gxgMG6ovRdZH5Ya9gu"); +declare_id!("935nnG5dUJCXwrZG3NyaYrf9xBDAABkoJbevzxPQAcic"); pub const OAPP_SEED: &[u8] = b"OApp"; diff --git a/examples/oft-solana-composer-library/runbooks/README.md b/examples/oft-solana-composer-library/runbooks/README.md new file mode 100644 index 000000000..d35fd0ccd --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/README.md @@ -0,0 +1,49 @@ +# oft-solana-composer-library Runbooks + +[![Txtx](https://img.shields.io/badge/Operated%20with-Txtx-gree?labelColor=gray)](https://txtx.sh) + +## Runbooks available + +### deployment +Deploy programs + +## Getting Started + +This repository is using [txtx](https://txtx.sh) for handling its on-chain operations. + +`txtx` takes its inspiration from a battle tested devops best practice named `infrastructure as code`, that have transformed cloud architectures. + +`txtx` simplifies and streamlines Smart Contract Infrastructure management across blockchains, focusing on robustness, reproducibility and composability. + +### Installation + +```console +$ curl -sL https://install.txtx.sh/ | bash +``` + +### Scaffold a new runbook + +```console +$ txtx new +``` + +Access tutorials and documentation at [docs.txtx.sh](https://docs.txtx.sh) to understand the syntax and discover the powerful features of txtx. + +Additionally, the [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=txtx.txtx) will make writing runbooks easier. + +### List runbooks available in this repository +```console +$ txtx ls +Name ID Description +BNS Multisig bns-multisig Register a BNS name using a multisig signer +``` + +### Execute an existing runbook +```console +$ txtx run bns-multisig +``` + +### Update the README documentation +```console +$ txtx docs --update +``` diff --git a/examples/oft-solana-composer-library/runbooks/deployment/main.tx b/examples/oft-solana-composer-library/runbooks/deployment/main.tx new file mode 100644 index 000000000..9c08b4f12 --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/main.tx @@ -0,0 +1,60 @@ +################################################################ +# Manage oft-solana-composer-library deployment through Crypto Infrastructure as Code +################################################################ + +addon "svm" { + rpc_api_url = input.rpc_api_url + network_id = input.network_id +} +variable "composer" { + value = svm::get_program_from_anchor_project("composer") +} + +variable "initComposerTx" { + value = "c7c2de2b94f8c2ab3aad2be6167477f2fc6864f5623e3791aa2f25cbcb5207a202ff3c383bdc379f1c5eae5faa88478e8729bc45057eb95154f0f7ec4a3ec80503c62b8b7cdd97cc" +} + +action "deploy_composer" "svm::deploy_program" { + description = "Deploy composer program" + program = variable.composer + authority = signer.authority + payer = signer.payer +} + +action "program_call" "svm::process_instructions" { + description = "Invoke composer init instructions" + program = variable.composer + instruction { + program_idl = variable.composer.idl + instruction_name = "initComposer" + instruction_args = [{ + oftPda: "4x3oQtX4MhjTKGBeXDZbtTSLZ9cUWo5waN2UChAuthtS", + endpointPda: "2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3" + }] + composer { + public_key = "7XMGP9XXajZHXQpWoXZqqLawD5pUbY7tzxmiTga23mi9" // You'll need to provide the composer account public key + } + lzComposeTypesAccounts { + public_key = "2SwnA7KLCVwsQweHx6oiiW7Yo8WmLPjGgAiV6J8souDD" // You'll need to provide the lz_compose_types_accounts public key + } + payer { + public_key = signer.payer.public_key + } + systemProgram { + public_key = svm::system_program_id() + } + } + signers = [signer.payer] + depends_on = [action.deploy_composer] +} + +// action "send_transaction" "svm::sign_transaction" { +// description = "Send transaction" +// transaction_bytes = variable.initComposerTx +// signers = [signer.payer] +// depends_on = [action.deploy_composer] +// } + +// output "program_call_signature" { +// value = action.program_call.signature +// } \ No newline at end of file diff --git a/examples/oft-solana-composer-library/runbooks/deployment/signers.devnet.tx b/examples/oft-solana-composer-library/runbooks/deployment/signers.devnet.tx new file mode 100644 index 000000000..e838b87ed --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/signers.devnet.tx @@ -0,0 +1,8 @@ + +signer "payer" "svm::web_wallet" { + // expected_address = input.expected_payer_address +} + +signer "authority" "svm::web_wallet" { + // expected_address = input.expected_payer_address +} diff --git a/examples/oft-solana-composer-library/runbooks/deployment/signers.localnet.tx b/examples/oft-solana-composer-library/runbooks/deployment/signers.localnet.tx new file mode 100644 index 000000000..98c2a6b70 --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/signers.localnet.tx @@ -0,0 +1,8 @@ + +signer "payer" "svm::secret_key" { + keypair_json = input.authority_keypair_json +} + +signer "authority" "svm::secret_key" { + keypair_json = input.authority_keypair_json +} diff --git a/examples/oft-solana-composer-library/runbooks/deployment/signers.mainnet.tx b/examples/oft-solana-composer-library/runbooks/deployment/signers.mainnet.tx new file mode 100644 index 000000000..6f2212000 --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/signers.mainnet.tx @@ -0,0 +1,10 @@ + +# For mainnet deployment, use web wallets, hardware wallets, or multisig for key security. + +# signer "payer" "svm::web_wallet" { +# address = "YOUR_WEB_WALLET_PUBLIC_KEY" +# } + +# signer "authority" "svm::squads" { +# address = "YOUR_SQUAD_PUBLIC_KEY" +# } diff --git a/examples/oft-solana-composer-library/tasks/evm/send.ts b/examples/oft-solana-composer-library/tasks/evm/send.ts index 2253e6314..7a419d2c2 100644 --- a/examples/oft-solana-composer-library/tasks/evm/send.ts +++ b/examples/oft-solana-composer-library/tasks/evm/send.ts @@ -1,60 +1,84 @@ import bs58 from 'bs58' -import { BigNumber } from 'ethers' +import { BigNumber, ethers } from 'ethers' import { task, types } from 'hardhat/config' import { ActionType, HardhatRuntimeEnvironment } from 'hardhat/types' import { makeBytes32 } from '@layerzerolabs/devtools' import { EndpointId } from '@layerzerolabs/lz-definitions' +import { Options } from '@layerzerolabs/lz-v2-utilities' import { getLayerZeroScanLink } from '../solana' interface TaskArguments { dstEid: number amount: string - to: string + minAmount: string + composer: string + solanaReceiver: string contractName: string } const action: ActionType = async ( - { dstEid, amount, to, contractName }, + { dstEid, amount, minAmount, composer, solanaReceiver, contractName }, hre: HardhatRuntimeEnvironment ) => { const signer = await hre.ethers.getNamedSigner('deployer') - // @ts-ignore - const token = (await hre.ethers.getContract(contractName)).connect(signer) - // if (isSepolia(hre.network.name)) { - // // @ts-ignore - // const erc20Token = (await hre.ethers.getContractAt(IERC20, address)).connect(signer) - // const approvalTxResponse = await erc20Token.approve(token.address, amount) - // const approvalTxReceipt = await approvalTxResponse.wait() - // console.log(`approve: ${amount}: ${approvalTxReceipt.transactionHash}`) - // } + // ─── Point at the *existing* USDE IOFT on BSC ───────────────────────────── + const USDE_ADDRESS = '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34' + // we expect `contractName` to match the ABI / interface of an IOFT + const oft = await hre.ethers.getContractAt(contractName, USDE_ADDRESS, signer) + // ───────────────────────────────────────────────────────────────────────── const amountLD = BigNumber.from(amount) + // 1) turn your Solana base58 address into a 32-byte hex string + const receiverBytes = bs58.decode(solanaReceiver) // Uint8Array of length 32 + // 2) solidity-pack your two fields: + // - a 64-bit BE integer for minAmountLD + // - a full 32-byte receiver pubkey + const composeMsg = ethers.utils.solidityPack( + ['uint64', 'bytes32'], + [BigNumber.from(minAmount), makeBytes32(bs58.decode(composer))] + ) const sendParam = { dstEid, - to: makeBytes32(bs58.decode(to)), + to: makeBytes32(bs58.decode(composer)), amountLD: amountLD.toString(), - minAmountLD: amountLD.mul(9_000).div(10_000).toString(), - extraOptions: '0x', - composeMsg: '0x', + minAmountLD: amountLD.toString(), + extraOptions: Options.newOptions() + .addExecutorLzReceiveOption(200_000, 2_500_000) + .addExecutorComposeOption(0, 200_000, 2_500_000) + .toBytes(), + composeMsg: composeMsg, // you can build your 112-byte composeMsg here oftCmd: '0x', } - const [msgFee] = await token.functions.quoteSend(sendParam, false) - const txResponse = await token.functions.send(sendParam, msgFee, signer.address, { - value: msgFee.nativeFee, - gasLimit: 500_000, - }) + + // quote how much LayerZero fees you must pay + const [msgFee] = await oft.functions.quoteSend(sendParam, false) + + // now actually send + const txResponse = await oft.functions.send( + sendParam, + msgFee, + signer.address, // refund address for any dust + { + value: msgFee.nativeFee, + gasLimit: 500_000, + } + ) const txReceipt = await txResponse.wait() - console.log(`send: ${amount} to ${to}: ${txReceipt.transactionHash}`) + + console.log(`▶ send ${amount} → ${solanaReceiver}: ${txReceipt.transactionHash}`) console.log( - `Track cross-chain transfer here: ${getLayerZeroScanLink(txReceipt.transactionHash, dstEid == EndpointId.SOLANA_V2_TESTNET)}` + `🔗 Track it: ${getLayerZeroScanLink(txReceipt.transactionHash, dstEid === EndpointId.SOLANA_V2_TESTNET)}` ) } -task('send', 'Sends a transaction', action) +task('send', 'Send USDE via LayerZero') .addParam('dstEid', 'Destination endpoint ID', undefined, types.int, false) - .addParam('amount', 'Amount to send in wei', undefined, types.string, false) - .addParam('to', 'Recipient address', undefined, types.string, false) - .addOptionalParam('contractName', 'Name of the contract in deployments folder', 'MyOFT', types.string) + .addParam('amount', 'Amount to send (in LD)', undefined, types.string, false) + .addParam('solanaReceiver', 'Solana receiver (base58)', undefined, types.string, false) + .addParam('composer', 'Solana composer (base58)', undefined, types.string, false) + .addParam('minAmount', 'Minimum amount to receive (in LD)', undefined, types.string, false) + .addOptionalParam('contractName', 'Name of the IOFT contract interface', 'MyOFT', types.string) + .setAction(action) diff --git a/examples/oft-solana-composer-library/tasks/index.ts b/examples/oft-solana-composer-library/tasks/index.ts index cda96e8e6..669ed2f55 100644 --- a/examples/oft-solana-composer-library/tasks/index.ts +++ b/examples/oft-solana-composer-library/tasks/index.ts @@ -15,3 +15,6 @@ import './solana/getPrioFees' import './solana/base58' import './solana/setInboundRateLimit' import './solana/setOutboundRateLimit' +import './solana/getRaydiumPools' +import './solana/getKeypair' +import './solana/initComposer' diff --git a/examples/oft-solana-composer-library/tasks/solana/getKeypair.ts b/examples/oft-solana-composer-library/tasks/solana/getKeypair.ts new file mode 100644 index 000000000..b81af8eb7 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getKeypair.ts @@ -0,0 +1,16 @@ +import fs from 'fs' + +import bs58 from 'bs58' +import { task, types } from 'hardhat/config' +/** + * New helper task: decode a base58‐encoded Solana secret key into its raw byte array. + */ +task('decode-key', 'Decode a Base58‐encoded Solana secret key and print the raw byte array as JSON') + .addParam('secret', 'Base58-encoded secret key', undefined, types.string) + .addOptionalParam('out', 'Output file path', 'decoded-key.json', types.string) + .setAction(async ({ secret, out }: { secret: string; out: string }) => { + const bytes = bs58.decode(secret) + const arr = Array.from(bytes) + fs.writeFileSync(out, JSON.stringify(arr, null, 2)) + console.log(`Wrote ${arr.length} bytes to ${out}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts b/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts new file mode 100644 index 000000000..0d8d24f5b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts @@ -0,0 +1,105 @@ +import { + Raydium, + TICK_ARRAY_SIZE, + TickUtils, + getPdaAmmConfigId, + getPdaExBitmapAccount, + getPdaTickArrayAddress, +} from '@raydium-io/raydium-sdk-v2' +import { PublicKey } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' + +import { deriveConnection } from './index' // <— your helper + +import type { EndpointId } from '@layerzerolabs/lz-definitions' + +interface GetPoolsTaskArgs { + eid: EndpointId + mint1: string + mint2: string +} + +task('lz:raydium:get-pools', 'Fetches Raydium CLMM pool PDAs') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('mint1', 'First mint address', undefined, devtoolsTypes.string) + .addParam('mint2', 'Second mint address', undefined, devtoolsTypes.string) + .setAction(async ({ eid, mint1, mint2 }: GetPoolsTaskArgs) => { + // 1) Derive a connection + wallet (Keypair) for the given endpoint + const { connection } = await deriveConnection(eid) + + // 2) Load the Raydium SDK (defaults to mainnet for fetchPoolByMints, but uses your connection URL) + const raydium = await Raydium.load({ + connection, + disableLoadToken: true, + }) + + // 3) Find the pool matching your two mints + const { data: pools } = await raydium.api.fetchPoolByMints({ + mint1: new PublicKey(mint1), + mint2: new PublicKey(mint2), + }) + if (pools.length === 0) { + console.error('❌ No Raydium CLMM pool found for these mints') + return + } + const poolId = pools[0].id + console.log('→ Found Pool ID:', poolId) + + // 4) Fetch the on-chain state AND all the PDAs (tick arrays, vaults, etc.) in one RPC call + const { poolInfo, poolKeys } = await raydium.clmm.getPoolInfoFromRpc(poolId) + + // poolKeys.id is the Pool State PDA + const poolStatePda = new PublicKey(poolKeys.id) + + // poolInfo.programId (or poolKeys.programId in the raw on-chain output) is the CLMM program + const clmmProgramId = new PublicKey(poolInfo.programId) + + // Derive the authority PDA + const [authorityPda] = PublicKey.findProgramAddressSync([poolStatePda.toBuffer()], clmmProgramId) + + // derive the amm config PDA + const ammConfigPda = getPdaAmmConfigId(clmmProgramId, poolInfo.config.index).publicKey + + // derive the tick-array–bitmap PDA + bump + const { publicKey: tickBitmapPda, nonce: tickBitmapBump } = getPdaExBitmapAccount(clmmProgramId, poolStatePda) + + console.log('Pool Info:', JSON.stringify(poolInfo, null, 2)) + + // 2) extract tick info + const { + config: { tickSpacing }, + } = poolInfo + const tickCurrent = (poolInfo as any).tickCurrent + const centralStart = TickUtils.getTickArrayStartIndexByTick(tickCurrent, tickSpacing) + const fullWidth = tickSpacing * TICK_ARRAY_SIZE + + // 3) derive lower & upper + const lowerIndex = centralStart - fullWidth + const upperIndex = centralStart + fullWidth + const tickLowerPDA = getPdaTickArrayAddress(clmmProgramId, poolStatePda, lowerIndex).publicKey + const tickUpperPDA = getPdaTickArrayAddress(clmmProgramId, poolStatePda, upperIndex).publicKey + const tickCurrentPDA = getPdaTickArrayAddress(clmmProgramId, poolStatePda, centralStart).publicKey + + // …after you do: + // const { poolInfo, poolKeys } = await raydium.clmm.getPoolInfoFromRpc(poolId); + // 5) Print them out + console.log('\n🔑 Raydium CLMM PDAs:') + console.log(' Pool Program ID: ', poolInfo.programId) + console.log(' Pool State PDA: ', poolStatePda) + console.log(' AMM Config PDA: ', ammConfigPda) + console.log(' Observation PDA: ', poolKeys.observationId) + console.log(' Tick Arrays:') + console.log(' Tick Lower PDA: ', tickLowerPDA) + console.log(' Tick Current PDA: ', tickCurrentPDA) + console.log(' Tick Upper PDA: ', tickUpperPDA) + console.log('tick bitmap PDA:', tickBitmapPda.toBase58(), 'bump:', tickBitmapBump) + console.log('tickBitmapPda', tickBitmapPda.toString()) + // for (const [startTick, addr] of Object.entries(poolKeys.tickArrays)) { + // console.log(` @ startTick=${startTick}: ${addr.toBase58()}`); + // } + console.log(' Vault A PDA: ', poolKeys.vault.A) + console.log(' Vault B PDA: ', poolKeys.vault.B) + console.log(' Pool Authority: ', authorityPda) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/initComposer.ts b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts new file mode 100644 index 000000000..6686c519a --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts @@ -0,0 +1,60 @@ +// tasks/initComposer.ts +import fs from 'fs' + +import * as anchor from '@coral-xyz/anchor' +import { Connection, Keypair, PublicKey, SystemProgram } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import idl from '../../target/idl/composer.json' + +task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') + .addOptionalParam( + 'rpcUrl', + 'Solana JSON RPC endpoint', + 'https://mainnet.helius-rpc.com/?api-key=78552846-acd7-40df-8f1c-79439387be5a', + undefined + ) + .setAction(async ({ rpcUrl }, hre) => { + // ─── CONFIG ──────────────────────────────────────────────────────────────── + const COMPOSER_ID = new PublicKey('4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq') + const OFT_PDA = new PublicKey('4x3oQtX4MhjTKGBeXDZbtTSL9cUWo5waN2UChAuthtS') + const ENDPOINT_PDA = new PublicKey('2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3') + const COMPOSER_SEED = Buffer.from('Composer') + const LZ_TYPES_SEED = Buffer.from('LzComposeTypes') + // ──────────────────────────────────────────────────────────────────────────── + + // 1) Set up Anchor + wallet + const connection = new Connection(rpcUrl, 'confirmed') + const keypair = Keypair.fromSecretKey(new Uint8Array(JSON.parse(fs.readFileSync('./wallet.json', 'utf-8')))) + const wallet = new anchor.Wallet(keypair) + const provider = new anchor.AnchorProvider(connection, wallet, { + commitment: 'confirmed', + }) + anchor.setProvider(provider) + + // 2) Load your on-chain program + const program = new anchor.Program(idl as anchor.Idl, COMPOSER_ID, provider) + + // 3) Derive the two PDAs + const [composerPda] = PublicKey.findProgramAddressSync([COMPOSER_SEED, OFT_PDA.toBuffer()], COMPOSER_ID) + const [typesPda] = PublicKey.findProgramAddressSync([LZ_TYPES_SEED, composerPda.toBuffer()], COMPOSER_ID) + + console.log('⛓ composer PDA:', composerPda.toBase58()) + console.log('🔧 types PDA: ', typesPda.toBase58()) + + // 4) Fire the initComposer RPC + const sig = await program.methods + .initComposer({ + oftPda: OFT_PDA, + endpointPda: ENDPOINT_PDA, + }) + .accounts({ + composer: composerPda, + lzComposeTypesAccounts: typesPda, + payer: provider.wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + .rpc() + + console.log('✅ tx:', sig) + }) diff --git a/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts b/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts index 867b96296..376ba10c3 100644 --- a/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts +++ b/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts @@ -1,19 +1,19 @@ // Rewritten version of the test using local validator instead of LiteSVM import * as anchor from '@coral-xyz/anchor' -import { - Raydium, - getPdaObservationAccount, - getPdaPoolVaultId, - getPdaTickArrayAddress, -} from '@raydium-io/raydium-sdk-v2' import { MEMO_PROGRAM_ID } from '@solana/spl-memo' -import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, getOrCreateAssociatedTokenAccount } from '@solana/spl-token' -import { Connection, Keypair, PublicKey, SystemProgram, Transaction } from '@solana/web3.js' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getAccount, + getOrCreateAssociatedTokenAccount, +} from '@solana/spl-token' +import { Connection, Keypair, PublicKey, SYSVAR_RENT_PUBKEY, SystemProgram, Transaction } from '@solana/web3.js' import idl from '../../target/idl/composer.json' const RAYDIUM_CLMM_ID = new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK') -const COMPOSER_ID = new PublicKey('2TUdVCMQsefMs28hTeMKAHTjgz8cdMbo7V5oEdrYSu7G') +const COMPOSER_ID = new PublicKey('4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq') const COMPOSER_SEED = Buffer.from('Composer') const LZ_TYPES_SEED = Buffer.from('LzComposeTypes') const USDE_MINT = new PublicKey('DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT') @@ -21,73 +21,271 @@ const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') const connection = new Connection('http://localhost:8899', 'confirmed') +// Add these constants for the program owners +const BPF_LOADER_UPGRADEABLE = new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111') + +async function verifyAccount(connection: Connection, address: PublicKey, expectedOwner: PublicKey, name: string) { + const accountInfo = await connection.getAccountInfo(address) + if (!accountInfo) { + console.error(`❌ ${name} account does not exist:`, address.toString()) + return false + } + + // Special case for program accounts + if (name.toLowerCase().includes('program')) { + if (!accountInfo.owner.equals(BPF_LOADER_UPGRADEABLE)) { + console.error( + `❌ ${name} not a valid program. Expected owner ${BPF_LOADER_UPGRADEABLE.toString()}, got ${accountInfo.owner.toString()}` + ) + return false + } + } else { + // For non-program accounts, check against expected owner + if (!accountInfo.owner.equals(expectedOwner)) { + console.error( + `❌ ${name} owned by wrong program. Expected ${expectedOwner.toString()}, got ${accountInfo.owner.toString()}` + ) + return false + } + } + console.log(`✅ ${name} verified:`, address.toString()) + return true +} + test('LzCompose -> swap_v2 via local validator', async () => { const payer = Keypair.generate() // Create a new provider with the funded payer. const provider = new anchor.AnchorProvider(connection, new anchor.Wallet(payer), {}) anchor.setProvider(provider) - await provider.connection.requestAirdrop(payer.publicKey, 10000000e9) + const airdropSignature = await connection.requestAirdrop(payer.publicKey, 100e9) + // Wait 5 seconds for airdrop to be processed + await new Promise((resolve) => setTimeout(resolve, 5000)) + + // Wait for confirmation + await connection.confirmTransaction(airdropSignature) + + // Check if a mint exists + const mintInfo = await connection.getAccountInfo(USDE_MINT) + + if (mintInfo === null) { + console.log('Mint does not exist') + } else { + console.log('Mint exists') + // You can also check if it's a valid mint account + const isMint = mintInfo.owner.equals(TOKEN_PROGRAM_ID) + console.log('Is valid mint:', isMint) + } const program = new anchor.Program(idl as anchor.Idl, COMPOSER_ID, provider) - const raydium = await Raydium.load({ - connection: provider.connection, - owner: payer, - disableLoadToken: true, - }) - const { data: pools } = await raydium.api.fetchPoolByMints({ - mint1: USDE_MINT, - mint2: USDC_MINT, - }) - const pool = pools[0] - const poolStatePda = new PublicKey(pool.id) - const ammConfigPda = new PublicKey('4BLNHtVe942GSs4teSZqGX24xwKNkqU7bGgNn3iUiUpw') - const observationPda = getPdaObservationAccount(RAYDIUM_CLMM_ID, poolStatePda).publicKey - const tickLower = getPdaTickArrayAddress(RAYDIUM_CLMM_ID, poolStatePda, -1).publicKey - const tickCurrent = getPdaTickArrayAddress(RAYDIUM_CLMM_ID, poolStatePda, 0).publicKey - const tickUpper = getPdaTickArrayAddress(RAYDIUM_CLMM_ID, poolStatePda, 1).publicKey - console.log('raydium accounts derived') - - const [authorityPda] = PublicKey.findProgramAddressSync([poolStatePda.toBuffer()], RAYDIUM_CLMM_ID) - - const { publicKey: inputVaultPda } = getPdaPoolVaultId(RAYDIUM_CLMM_ID, poolStatePda, USDE_MINT) - const { publicKey: outputVaultPda } = getPdaPoolVaultId(RAYDIUM_CLMM_ID, poolStatePda, USDC_MINT) - console.log('vaults derived') - - const oftPda = Keypair.generate() - const endpointPda = Keypair.generate() - const [composerPda] = PublicKey.findProgramAddressSync([COMPOSER_SEED, oftPda.publicKey.toBuffer()], COMPOSER_ID) + const poolStatePda = new PublicKey('D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL') + const ammConfigPda = new PublicKey('9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x') + const observationPda = new PublicKey('Cv3s57YQzJRp988qvXPfZN5xX7BUFcgB1ycA9jzSrh2n') + const tickLowerPda = new PublicKey('J1jEbmdbtsfA26igi1ryQjejnYVQRpxnobZh9tgSaH1h') + const tickCurrentPda = new PublicKey('8AQHmKsoFh5Uh8bzYnnQ4Fx2QL2axPao9hjnt1YQBvdS') + const tickUpperPda = new PublicKey('3ZUDGLF7xw26gcXUhZK3tL4MU2VizCUkXoHdb2LQ4zDM') + const tickBitmapPda = new PublicKey('FHJHGbVtNoJdM5CSqPMQH8mn8D8pGfcN4JmLNqNMPBQu') + + const inputVaultPda = new PublicKey('5Nb7bdyi4jkMdA8Xqmt72HS1fpkGRQ573Gf8PJeGunsy') + const outputVaultPda = new PublicKey('sUfgz2jsDhN8qn1PrkWr9pVnUrLqqL1yB7WAnCj62Xz') + + const oftPda = new PublicKey('4x3oQtX4MhjTKGBeXDZbtTSLZ9cUWo5waN2UChAuthtS') + const endpointPda = new PublicKey('2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3') + const [composerPda] = PublicKey.findProgramAddressSync([COMPOSER_SEED, oftPda.toBuffer()], COMPOSER_ID) const [lzTypesPda] = PublicKey.findProgramAddressSync([LZ_TYPES_SEED, composerPda.toBuffer()], COMPOSER_ID) - console.log('composer accounts derived') + console.log('composerPda', composerPda.toString()) + console.log('lzTypesPda', lzTypesPda.toString()) + + // after you've defined inputVaultPda & outputVaultPda + const [inVaultInfo, outVaultInfo] = await Promise.all([ + getAccount(connection, inputVaultPda), + getAccount(connection, outputVaultPda), + ]) + console.log( + `Pool vault balances: input=${inVaultInfo.amount} (decimals ${inVaultInfo}), ` + + `output=${outVaultInfo.amount} (decimals ${outVaultInfo})` + ) - const composerUsdeAta = await getOrCreateAssociatedTokenAccount(connection, payer, USDE_MINT, composerPda, true) - const userUsdcAta = await getOrCreateAssociatedTokenAccount(connection, payer, USDC_MINT, composerPda, true) + const composerUsdeAta = await getOrCreateAssociatedTokenAccount( + connection, + payer, + USDE_MINT, + composerPda, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID + ) + const userUsdcAta = await getOrCreateAssociatedTokenAccount( + connection, + payer, + USDC_MINT, + composerPda, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID + ) await new Promise((resolve) => setTimeout(resolve, 2000)) // wait 2 seconds console.log('ATAs created') - const init = await program.methods - .initComposer({ - oftPda: oftPda.publicKey, - endpointPda: endpointPda.publicKey, - }) - .accounts({ - composer: composerPda, - lzComposeTypesAccounts: lzTypesPda, - payer: payer.publicKey, - systemProgram: SystemProgram.programId, - }) - .signers([payer]) - .rpc() - console.log('initComposer tx:', init) + try { + const init = await program.methods + .initComposer({ + oftPda: oftPda, + endpointPda: endpointPda, + }) + .accounts({ + composer: composerPda, + lzComposeTypesAccounts: lzTypesPda, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + }) + .signers([payer]) + .rpc() + console.log('initComposer tx:', init) + const initTx = await program.methods + .initComposer({ + oftPda: oftPda, + endpointPda: endpointPda, + }) + .accounts({ + composer: composerPda, + lzComposeTypesAccounts: lzTypesPda, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + }) + .transaction() + console.log('initTx', initTx.instructions[0].data.toString('hex')) + console.log('initTx', initTx) + + // Save transaction data to a file + const fs = require('fs') + const txData = { + instructions: initTx.instructions.map((ix) => ({ + programId: ix.programId.toString(), + keys: ix.keys.map((key) => ({ + pubkey: key.pubkey.toString(), + isSigner: key.isSigner, + isWritable: key.isWritable, + })), + data: ix.data.toString('hex'), + })), + recentBlockhash: initTx.recentBlockhash, + feePayer: initTx.feePayer?.toString() || null, + } + fs.writeFileSync('init_tx_data.json', JSON.stringify(txData, null, 2)) + console.log('Transaction data saved to init_tx_data.json') + } catch (error) { + console.error('Failed to initialize composer:', error) + throw error + } + + const programInfo = await connection.getAccountInfo(COMPOSER_ID) + if (!programInfo) { + console.error('Program not found at address:', COMPOSER_ID.toString()) + throw new Error('Program not deployed') + } + console.log('Program data length:', programInfo.data.length) + + if (!programInfo.executable) { + console.error('Program is not marked as executable') + throw new Error('Program not executable') + } + + // Verify all accounts exist and are owned by the correct programs + console.log('\nVerifying account ownership:') + + const accountChecks = await Promise.all([ + verifyAccount(connection, RAYDIUM_CLMM_ID, BPF_LOADER_UPGRADEABLE, 'Raydium CLMM Program'), + verifyAccount(connection, poolStatePda, RAYDIUM_CLMM_ID, 'Pool State'), + verifyAccount(connection, ammConfigPda, RAYDIUM_CLMM_ID, 'AMM Config'), + verifyAccount(connection, observationPda, RAYDIUM_CLMM_ID, 'Observation State'), + verifyAccount(connection, tickLowerPda, RAYDIUM_CLMM_ID, 'Tick Array Lower'), + verifyAccount(connection, tickCurrentPda, RAYDIUM_CLMM_ID, 'Tick Array Current'), + verifyAccount(connection, tickUpperPda, RAYDIUM_CLMM_ID, 'Tick Array Upper'), + verifyAccount(connection, inputVaultPda, TOKEN_PROGRAM_ID, 'Input Vault'), + verifyAccount(connection, outputVaultPda, TOKEN_PROGRAM_ID, 'Output Vault'), + verifyAccount(connection, USDE_MINT, TOKEN_PROGRAM_ID, 'USDE Mint'), + verifyAccount(connection, USDC_MINT, TOKEN_PROGRAM_ID, 'USDC Mint'), + ]) + + if (accountChecks.some((check) => !check)) { + console.log('\nSome accounts are not properly initialized. Initializing Raydium pool...') + + // Here we'll need to initialize the Raydium pool + // This requires several steps: + // 1. Create the AMM Config if it doesn't exist + // 2. Create the Pool + // 3. Initialize tick arrays + + // We'll need to import the Raydium SDK + // const raydiumSDK = require('@raydium-io/raydium-sdk'); + + // TODO: Add pool initialization code + // For now, let's throw an error with instructions + throw new Error(` + Please initialize the Raydium pool first. You can do this by: + 1. Using an existing pool on devnet/mainnet instead of local + 2. Or initializing a new pool using the Raydium SDK + + For local testing, you might want to use these devnet addresses instead: + - Pool: https://solscan.io/account/D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL?cluster=devnet + - AMM Config: https://solscan.io/account/9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x?cluster=devnet + + Would you like me to help you: + 1. Switch to using an existing devnet pool + 2. Or implement the pool initialization code? + `) + } + + console.log('\nAll accounts verified, proceeding with swap...') + const amountLd = new anchor.BN(250_000) + const vaultBAcc = await getAccount(connection, outputVaultPda) + const vaultBBalance = new anchor.BN(vaultBAcc.amount.toString()) + console.log(`vaultB balance=${vaultBBalance.toString()} amountLd=${amountLd.toString()}`) + if (amountLd.gt(vaultBBalance)) { + throw new Error( + `Insufficient liquidity in vaultB: requested ${amountLd.toString()}, but only ${vaultBBalance.toString()} available` + ) + } + // ---------------------------------------------------- + // 2) build the 112‐byte payload: + // [0..32) ignored by our program + // [32..40) amount_ld + // [40..72) compose_from (we can re‐use oftPda here) + // [72..80) min_amount_out + // [80..112) receiver pubkey + // ---------------------------------------------------- const minOut = new anchor.BN(1) - const message = Buffer.alloc(80) + const message = Buffer.alloc(112) + const receiver = Keypair.generate() + const receiverUsdcAta = await getOrCreateAssociatedTokenAccount( + connection, + payer, // payer funds ATA creation + USDC_MINT, // mint + receiver.publicKey, // authority + false, // allow owner off curve + undefined, + undefined, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ) amountLd.toArrayLike(Buffer, 'be', 8).copy(message, 32) - oftPda.publicKey.toBuffer().copy(message, 40) + oftPda.toBuffer().copy(message, 40) minOut.toArrayLike(Buffer, 'be', 8).copy(message, 72) + receiver.publicKey.toBuffer().copy(message, 80) const swapIx = await program.methods - .lzCompose({ from: oftPda.publicKey, to: composerPda, guid: [1], index: 0, message }) + .lzCompose({ + from: oftPda, + to: composerPda, + guid: new Array(32).fill(0), + index: 0, + message: Buffer.from(message), + extraData: Buffer.alloc(0), + }) .accounts({ composer: composerPda, clmmProgram: RAYDIUM_CLMM_ID, @@ -104,10 +302,15 @@ test('LzCompose -> swap_v2 via local validator', async () => { memoProgram: MEMO_PROGRAM_ID, inputVaultMint: USDE_MINT, outputVaultMint: USDC_MINT, - authority: authorityPda, - tickArrayLower: tickLower, - tickArrayCurrent: tickCurrent, - tickArrayUpper: tickUpper, + tickBitmap: tickBitmapPda, + tickArrayLower: tickLowerPda, + tickArrayCurrent: tickCurrentPda, + tickArrayUpper: tickUpperPda, + toAddress: receiver.publicKey, + toTokenAccount: receiverUsdcAta.address, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + systemProgram: SystemProgram.programId, + rent: SYSVAR_RENT_PUBKEY, }) .signers([payer]) .instruction() @@ -116,10 +319,17 @@ test('LzCompose -> swap_v2 via local validator', async () => { tx.feePayer = payer.publicKey tx.recentBlockhash = (await provider.connection.getLatestBlockhash()).blockhash tx.sign(payer) - - if (provider.sendAndConfirm) { - await provider.sendAndConfirm(tx, [payer]) - } else { - throw new Error('sendAndConfirm is not defined on the provider') - } -}) + // // --- INSERTED: dump every account key + owner to debug the mismatch --- + // console.log("🔍 Checking owners on every account in SwapV2…"); + // for (const { pubkey, isWritable, isSigner } of swapIx.keys) { + // const info = await connection.getAccountInfo(pubkey); + // console.log({ + // pubkey: pubkey.toString(), + // owner: info?.owner.toString() ?? "account not found", + // writable: isWritable, + // signer: isSigner, + // }); + // } + const sig = await provider.sendAndConfirm(tx, [payer]) + console.log('sig', sig) +}, 30000) diff --git a/examples/oft-solana-composer-library/txtx.yml b/examples/oft-solana-composer-library/txtx.yml new file mode 100644 index 000000000..3b1a8be5a --- /dev/null +++ b/examples/oft-solana-composer-library/txtx.yml @@ -0,0 +1,19 @@ +--- +name: oft-solana-composer-library +id: oft-solana-composer-library +runbooks: + - name: deployment + description: Deploy programs + location: runbooks/deployment +environments: + localnet: + network_id: localnet + rpc_api_url: http://127.0.0.1:8899 + payer_keypair_json: ./keypair.json + authority_keypair_json: ./keypair.json + devnet: + network_id: devnet + rpc_api_url: https://api.devnet.solana.com + payer_keypair_json: ./keypair.json + authority_keypair_json: ./keypair.json + From 67fe93708b940c02bd34fa871bf55641c8b5f1d7 Mon Sep 17 00:00:00 2001 From: Krak Date: Mon, 5 May 2025 15:33:52 -0700 Subject: [PATCH 10/11] chore: update composer program --- .../src/instructions/init_composer.rs | 67 ++++-- .../composer/src/instructions/lz_compose.rs | 220 ++++++++++-------- .../src/instructions/lz_compose_types.rs | 201 +++++++--------- .../programs/composer/src/lib.rs | 3 +- .../programs/composer/src/state/composer.rs | 73 +++--- .../programs/endpoint-mock/src/lib.rs | 2 +- .../tasks/evm/send.ts | 7 +- .../tasks/index.ts | 1 + .../tasks/solana/initComposer.ts | 95 ++++++-- .../tasks/solana/lzCompose.ts | 69 ++++++ 10 files changed, 444 insertions(+), 294 deletions(-) create mode 100644 examples/oft-solana-composer-library/tasks/solana/lzCompose.ts diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs index 7588ffdb2..bad3a4583 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs @@ -4,26 +4,15 @@ use anchor_lang::prelude::*; #[derive(Accounts)] #[instruction(params: InitComposerParams)] pub struct InitComposer<'info> { - /// The Composer configuration account, initialized as a PDA. + /// PDA holding all our static pubkeys. #[account( - init, - payer = payer, - space = Composer::SIZE, - seeds = [COMPOSER_SEED, params.oft_pda.as_ref()], + init, payer = payer, + space = Composer::SIZE, + seeds = [COMPOSER_SEED, params.oft_pda.as_ref()], bump )] pub composer: Account<'info, Composer>, - /// The LzComposeTypesAccounts account, storing extra addresses required for lz_compose_types. - #[account( - init, - payer = payer, - space = LzComposeTypesAccounts::SIZE, - seeds = [LZ_COMPOSE_TYPES_SEED, composer.key().as_ref()], - bump - )] - pub lz_compose_types_accounts: Account<'info, LzComposeTypesAccounts>, - #[account(mut)] pub payer: Signer<'info>, @@ -34,20 +23,48 @@ pub struct InitComposer<'info> { pub struct InitComposerParams { pub oft_pda: Pubkey, pub endpoint_pda: Pubkey, + pub endpoint_program: Pubkey, + pub token_program: Pubkey, + pub token_program_2022: Pubkey, + pub clmm_program: Pubkey, + pub amm_config: Pubkey, + pub pool_state: Pubkey, + pub input_vault: Pubkey, + pub output_vault: Pubkey, + pub input_vault_mint: Pubkey, + pub output_vault_mint: Pubkey, + pub observation_state: Pubkey, + pub tick_bitmap: Pubkey, + pub tick_array_lower: Pubkey, + pub tick_array_current: Pubkey, + pub tick_array_upper: Pubkey, + pub input_token_account: Pubkey, + pub output_token_account: Pubkey, } impl InitComposer<'_> { - /// Processes the init_composer instruction by writing the provided values to the Composer account. pub fn apply(ctx: &mut Context, params: &InitComposerParams) -> Result<()> { - let composer = &mut ctx.accounts.composer; - composer.oft = params.oft_pda; - composer.endpoint = params.endpoint_pda; - composer.bump = ctx.bumps.composer; - - // Initialize lz_compose_types_accounts with the composer address. - let lz_accounts = &mut ctx.accounts.lz_compose_types_accounts; - lz_accounts.composer = composer.key(); - // Other fields in LzComposeTypesAccounts can be set as needed. + let c = &mut ctx.accounts.composer; + c.oft_pda = params.oft_pda; + c.endpoint_pda = params.endpoint_pda; + c.bump = ctx.bumps.composer; + c.endpoint_program = params.endpoint_program; + c.token_program = params.token_program; + c.token_program_2022= params.token_program_2022; + c.clmm_program = params.clmm_program; + c.amm_config = params.amm_config; + c.pool_state = params.pool_state; + c.input_vault = params.input_vault; + c.output_vault = params.output_vault; + c.observation_state = params.observation_state; + c.tick_bitmap = params.tick_bitmap; + c.tick_array_lower = params.tick_array_lower; + c.tick_array_current= params.tick_array_current; + c.tick_array_upper = params.tick_array_upper; + c.input_token_account = params.input_token_account; + c.output_token_account = params.output_token_account; + c.input_vault_mint = params.input_vault_mint; + c.output_vault_mint = params.output_vault_mint; Ok(()) } } diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs index 0d66f07ff..2675828de 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs @@ -1,8 +1,12 @@ use crate::*; +use crate::state::composer::ComposerError; +use oapp::endpoint::program::Endpoint; + use anchor_lang::prelude::*; -use anchor_spl::token::{Token}; +use anchor_spl::token::Token; use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; use anchor_spl::associated_token::AssociatedToken; +use anchor_spl::associated_token::get_associated_token_address; use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; use raydium_clmm_cpi::cpi::swap_v2; @@ -20,95 +24,74 @@ use spl_memo; #[derive(Accounts)] #[instruction(params: LzComposeParams)] pub struct LzCompose<'info> { - // Use the stored "oft" field as the unique seed. + /// The user paying for swap & any ATA creation + #[account(mut)] + pub payer: Signer<'info>, + + /// The LayerZero endpoint program this composer was initialized with + #[account(address = composer.endpoint_program)] + pub endpoint_program: Program<'info, Endpoint>, + + /// SPL programs + pub token_program: Program<'info, Token>, + pub token_program_2022: Program<'info, Token2022>, + + /// Our single-PDA storing all the needed pubkeys #[account( mut, - seeds = [COMPOSER_SEED, &composer.oft.to_bytes()], - bump = composer.bump + seeds = [COMPOSER_SEED, &composer.oft_pda.to_bytes()], + bump = composer.bump )] pub composer: Account<'info, Composer>, + /// Raydium CPI program & state pub clmm_program: Program<'info, RaydiumClmm>, - /// The user performing the swap. - /// The end-user or intermediary paying for this swap and transaction fees. - /// Must be mutable because we fund the ATA creation below. - #[account(mut)] - pub payer: Signer<'info>, - - /// The factory state to read protocol fees. - #[account(address = pool_state.load()?.amm_config)] + #[account(address = composer.amm_config)] pub amm_config: Box>, - - /// The program account of the pool in which the swap will be performed. #[account(mut)] pub pool_state: AccountLoader<'info, PoolState>, - /// The user token account for input token. - #[account(mut)] - pub input_token_account: Box>, + /// User token accounts (input=A, output=B) + #[account(mut)] pub input_token_account: Box>, + #[account(mut)] pub output_token_account: Box>, - /// The user token account for output token. - #[account(mut)] - pub output_token_account: Box>, - - /// The vault token account for input token. - #[account(mut)] - pub input_vault: Box>, - - /// The vault token account for output token. - #[account(mut)] - pub output_vault: Box>, + /// Pool vaults + #[account(mut)] pub input_vault: Box>, + #[account(mut)] pub output_vault: Box>, - /// The program account for the most recent oracle observation. - #[account(mut, address = pool_state.load()?.observation_key)] + /// Price oracle & tick arrays + #[account(mut, address = composer.observation_state)] pub observation_state: AccountLoader<'info, ObservationState>, + #[account(mut)] pub tick_bitmap: AccountInfo<'info>, + #[account(mut)] pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] pub tick_array_current:AccountInfo<'info>, + #[account(mut)] pub tick_array_upper: AccountInfo<'info>, - /// SPL program for token transfers. - pub token_program: Program<'info, Token>, - - /// SPL program 2022 for token transfers. - pub token_program_2022: Program<'info, Token2022>, - - /// CHECK: Must match spl_memo::id(). + /// Memo program for the swap #[account(address = spl_memo::id())] pub memo_program: UncheckedAccount<'info>, - /// The mint of the input token vault. + /// Vault mints #[account(address = input_vault.mint)] - pub input_vault_mint: Box>, - - /// The mint of the output token vault. + pub input_vault_mint: Box>, #[account(address = output_vault.mint)] pub output_vault_mint: Box>, - // Extra accounts required by the CPI. - /// The bitmap extension account for this pool’s tick arrays - #[account(mut)] - pub tick_bitmap: AccountInfo<'info>, - /// Tick arrays for the swap range. - #[account(mut)] - pub tick_array_lower: AccountInfo<'info>, - #[account(mut)] - pub tick_array_current: AccountInfo<'info>, - #[account(mut)] - pub tick_array_upper: AccountInfo<'info>, - - /// CHECK: Will be validated in `apply()` against the payload. + /// The ultimate receiver of token B #[account(mut)] pub to_address: UncheckedAccount<'info>, - /// The recipient’s ATA for the output mint. - /// If it doesn’t exist, Anchor will create it, funded by `payer`. + /// Receiver’s ATA for token B; init_if_needed if absent #[account( init_if_needed, payer = payer, - associated_token::mint = output_vault_mint, + associated_token::mint = output_vault_mint, associated_token::authority = to_address, associated_token::token_program = token_program )] pub to_token_account: Box>, - /// Anchor needs these to create an ATA if missing: + /// Programs needed for ATA creation pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, pub rent: Sysvar<'info, Rent>, @@ -116,65 +99,104 @@ pub struct LzCompose<'info> { impl LzCompose<'_> { pub fn apply(ctx: &mut Context, params: &LzComposeParams) -> Result<()> { - // Validate that the message is coming from the expected OFT and addressed to this composer. - require!(params.from == ctx.accounts.composer.oft, ComposerError::InvalidFrom); - require!(params.to == ctx.accounts.composer.key(), ComposerError::InvalidTo); + // 1) Validate origin & destination + require!( + params.from == ctx.accounts.composer.oft_pda, + ComposerError::InvalidFrom + ); + require!( + params.to == ctx.accounts.composer.key(), + ComposerError::InvalidTo + ); + // 2) Parse payload let msg = ¶ms.message; - // Decode input amount from bytes 32..40. - let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); - let mut compose_from = [0u8; 32]; - compose_from.copy_from_slice(&msg[40..72]); - let inner = &msg[72..]; - let min_amount_out = u64::from_be_bytes(inner[0..8].try_into().unwrap()); - let receiver = Pubkey::new_from_array(inner[8..40].try_into().unwrap()); + let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); + let min_amount_out = u64::from_be_bytes(msg[72..80].try_into().unwrap()); + let receiver = Pubkey::new_from_array(msg[80..112].try_into().unwrap()); require_keys_eq!( ctx.accounts.to_address.key(), receiver, ComposerError::InvalidTo ); - // Build CPI accounts struct. + // 3) Build Raydium CPI context let cpi_accounts = SwapSingleV2 { - payer: ctx.accounts.payer.to_account_info(), - amm_config: ctx.accounts.amm_config.to_account_info(), - pool_state: ctx.accounts.pool_state.to_account_info(), + payer: ctx.accounts.payer.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.input_token_account.to_account_info(), - output_token_account: ctx.accounts.to_token_account.to_account_info(), - input_vault: ctx.accounts.input_vault.to_account_info(), - output_vault: ctx.accounts.output_vault.to_account_info(), - observation_state: ctx.accounts.observation_state.to_account_info(), - token_program: ctx.accounts.token_program.to_account_info(), - token_program_2022: ctx.accounts.token_program_2022.to_account_info(), - memo_program: ctx.accounts.memo_program.to_account_info(), - input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), - output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), + output_token_account:ctx.accounts.to_token_account.to_account_info(), + input_vault: ctx.accounts.input_vault.to_account_info(), + output_vault: ctx.accounts.output_vault.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + memo_program: ctx.accounts.memo_program.to_account_info(), + input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), + output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), }; - - let mut cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts); - cpi_ctx = cpi_ctx.with_remaining_accounts(vec![ - ctx.accounts.tick_bitmap.to_account_info(), - ctx.accounts.tick_array_lower.to_account_info(), - ctx.accounts.tick_array_current.to_account_info(), - ctx.accounts.tick_array_upper.to_account_info(), - ]); - - swap_v2(cpi_ctx, amount_ld, min_amount_out, 0u128, true)?; - - // Use the stored "oft" field as the seed. + let cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(vec![ + ctx.accounts.tick_bitmap.clone(), + ctx.accounts.tick_array_lower.clone(), + ctx.accounts.tick_array_current.clone(), + ctx.accounts.tick_array_upper.clone(), + ]); + + // 4) Attempt swap, refund on error + if let Err(_) = swap_v2(cpi_ctx, amount_ld, min_amount_out, 0u128, true) { + // Refund token A back to receiver’s ATA for mint A + // (Anchor init_if_needed below ensures the ATA exists) + let composer_ata = get_associated_token_address( + &ctx.accounts.composer.key(), + &ctx.accounts.composer.oft_pda, + ); + let receiver_ata = get_associated_token_address( + &ctx.accounts.to_address.key(), + &ctx.accounts.composer.oft_pda, + ); + + let refund_ix = anchor_spl::token::spl_token::instruction::transfer( + &ctx.accounts.token_program.key(), + &composer_ata, + &receiver_ata, + &ctx.accounts.composer.key(), + &[], + amount_ld, + )?; + + anchor_lang::solana_program::program::invoke_signed( + &refund_ix, + &[ + // composer ATA, receiver ATA, token_program + ctx.accounts.composer.to_account_info(), + ctx.accounts.to_address.to_account_info(), + ctx.accounts.token_program.to_account_info(), + ], + &[ + &[ + COMPOSER_SEED, + &ctx.accounts.composer.oft_pda.to_bytes(), + &[ctx.accounts.composer.bump], + ], + ], + )?; + } + + // 5) Clear the message on success *or* after refund let seeds: &[&[u8]] = &[ COMPOSER_SEED, - &ctx.accounts.composer.oft.to_bytes(), + &ctx.accounts.composer.oft_pda.to_bytes(), + &ctx.accounts.composer.endpoint_pda.to_bytes(), // include endpoint PDA &[ctx.accounts.composer.bump], ]; - let clear_params = ClearComposeParams { - from: params.from, - guid: params.guid, - index: params.index, + from: params.from, + guid: params.guid, + index: params.index, message: params.message.clone(), }; - clear_compose( ENDPOINT_ID, ctx.accounts.composer.key(), diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs index 4e4281e89..23609b7fb 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs @@ -1,172 +1,149 @@ +// instructions/lz_compose_types.rs +use crate::*; use anchor_lang::prelude::*; -use anchor_spl::token::{Token}; -use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; -use spl_memo; - -use raydium_clmm_cpi::{ - program::RaydiumClmm, - states::{AmmConfig, ObservationState, PoolState}, -}; - use oapp::endpoint_cpi::{get_accounts_for_clear_compose, LzAccount}; -use oapp::{endpoint::ID as ENDPOINT_ID, LzComposeParams}; +use oapp::LzComposeParams; +use spl_memo::id as memo_program_id; + +const COMPOSER_SEED: &[u8] = b"Composer"; #[derive(Accounts)] pub struct LzComposeTypes<'info> { - // === Raydium CLMM accounts === - /// The on‐chain Raydium CLMM program itself, used for the CPI into `swap_v2`. - pub clmm_program: Program<'info, RaydiumClmm>, - - /// The SPL‐compatible token program (v1) used by Raydium for transfers. - pub token_program: Program<'info, Token>, - /// The SPL‐compatible token program (v2) used by Raydium for transfers. - pub token_program_2022: Program<'info, Token2022>, - /// The SPL Memo program for attaching arbitrary memos to the swap CPI. - #[account(address = spl_memo::id())] - pub memo_program: UncheckedAccount<'info>, - - /// Raydium AMM configuration (fees, tick spacing, etc). Must match `pool_state.load()?.amm_config`. - #[account(address = pool_state.load()?.amm_config)] - pub amm_config: Box>, - - /// The on‐chain pool state PDA, which holds the current price, liquidity, and fee growth. - pub pool_state: AccountLoader<'info, PoolState>, - - /// The “vault” token account for token A (input token) belonging to the pool. - #[account(mut)] - pub input_vault: Box>, - /// The “vault” token account for token B (output token) belonging to the pool. - #[account(mut)] - pub output_vault: Box>, - - /// The SPL‐Token Mint for the input vault (must equal `input_vault.mint`). - #[account(address = input_vault.mint)] - pub input_vault_mint: Box>, - /// The SPL‐Token Mint for the output vault (must equal `output_vault.mint`). - #[account(address = output_vault.mint)] - pub output_vault_mint: Box>, - - /// The oracle observation state PDA for the pool’s price oracle. - #[account(address = pool_state.load()?.observation_key)] - pub observation_state: AccountLoader<'info, ObservationState>, - - /// The three tick‐array PDAs covering the price range for this swap. - #[account(mut)] - pub tick_array_lower: AccountInfo<'info>, - #[account(mut)] - pub tick_array_current: AccountInfo<'info>, - #[account(mut)] - pub tick_array_upper: AccountInfo<'info>, - - /// The bitmap extension account that tracks which tick arrays are initialized. - #[account(mut)] - pub tick_bitmap: AccountInfo<'info>, - - /// The end‐user’s token account for deposit (must be an ATA for the input mint). - #[account(mut)] - pub input_token_account: Box>, - /// The end‐user’s token account for withdrawal (must be an ATA for the output mint). - #[account(mut)] - pub output_token_account: Box>, - - /// === LayerZero composer / endpoint accounts === - /// The LayerZero endpoint program ID (used for the final `clear_compose` CPI). - /// Anchor will not enforce it, but the CPI macro checks it at runtime. - pub lz_program: AccountInfo<'info>, - - /// The signer paying for this transaction (also used as Raydium swap payer). - pub payer: Signer<'info>, + /// Only the Composer PDA—holds every Pubkey we need. + #[account( + seeds = [COMPOSER_SEED, &composer.oft_pda.to_bytes()], + bump = composer.bump, + )] + pub composer: Account<'info, Composer>, } -impl LzComposeTypes<'_> { - /// Build the ordered `Vec` listing: - /// 1. All Raydium CPI accounts (payer, amm_config, pool_state, vaults, tick arrays, etc.) - /// 2. All extra LayerZero “clear compose” accounts (`send_compose`/`clear_compose` machinery). - /// - /// The returned accounts vector is passed into `clear_compose` so that the endpoint - /// program can verify the message (guid, index, from, to, etc.) and mark it consumed. +impl<'info> LzComposeTypes<'info> { pub fn apply( ctx: &Context, params: &LzComposeParams, ) -> Result> { - let mut accounts = Vec::new(); - - // --- Raydium base accounts for `swap_v2` CPI --- - // 1) Who pays the swap fees / funds any borrow + let c = &ctx.accounts.composer; + let mut accounts = Vec::with_capacity(1 // payer + + 2 // SPL token programs + + 2 // Raydium config & pool + + 2 // user ATAs + + 2 // pool vaults + + 5 // observation + tick arrays + + 1 // memo + + 2 // vault mints + + 6); // clear_compose + + // 0) placeholder for the executor/payer accounts.push(LzAccount { - pubkey: ctx.accounts.payer.key(), + pubkey: Pubkey::default(), is_signer: true, + is_writable: true, + }); + + // 1) SPL token programs + accounts.push(LzAccount { + pubkey: c.token_program, + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: c.token_program_2022, + is_signer: false, is_writable: false, }); - // 2) AMM config holding fee parameters + + // 2) Raydium config + pool state accounts.push(LzAccount { - pubkey: ctx.accounts.amm_config.key(), + pubkey: c.amm_config, is_signer: false, is_writable: false, }); - // 3) Pool state (price, liquidity) accounts.push(LzAccount { - pubkey: ctx.accounts.pool_state.key(), + pubkey: c.pool_state, + is_signer: false, + is_writable: true, + }); + + // 3) User token ATAs (input A, output B) + accounts.push(LzAccount { + pubkey: c.input_token_account, is_signer: false, is_writable: true, }); - // 4) User token account for input accounts.push(LzAccount { - pubkey: ctx.accounts.input_token_account.key(), + pubkey: c.output_token_account, is_signer: false, is_writable: true, }); - // 5) User token account for output + + // 4) Pool vaults (input A, output B) accounts.push(LzAccount { - pubkey: ctx.accounts.output_token_account.key(), + pubkey: c.input_vault, is_signer: false, is_writable: true, }); - // 6) Vault for input token accounts.push(LzAccount { - pubkey: ctx.accounts.input_vault.key(), + pubkey: c.output_vault, is_signer: false, is_writable: true, }); - // 7) Vault for output token + + // 5) Observation and tick‐array accounts accounts.push(LzAccount { - pubkey: ctx.accounts.output_vault.key(), + pubkey: c.observation_state, is_signer: false, is_writable: true, }); - // 8) Oracle observation accounts.push(LzAccount { - pubkey: ctx.accounts.observation_state.key(), + pubkey: c.tick_bitmap, is_signer: false, is_writable: true, }); - // 9) Tick arrays that cover the swap’s price range accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_lower.key(), + pubkey: c.tick_array_lower, is_signer: false, is_writable: true, }); accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_current.key(), + pubkey: c.tick_array_current, is_signer: false, is_writable: true, }); accounts.push(LzAccount { - pubkey: ctx.accounts.tick_array_upper.key(), + pubkey: c.tick_array_upper, is_signer: false, is_writable: true, }); - // --- LayerZero clear_compose accounts (replay protection) --- - let mut extra_accounts = get_accounts_for_clear_compose( - ENDPOINT_ID, - ¶ms.from, // the “sender” OFT address - &ctx.accounts.lz_program.key(), // the endpoint program ID - ¶ms.guid, // message GUID - params.index, // sequence index - ¶ms.message, // raw payload bytes + // 6) SPL Memo program + accounts.push(LzAccount { + pubkey: memo_program_id(), + is_signer: false, + is_writable: false, + }); + + // 7) Vault mints + accounts.push(LzAccount { + pubkey: c.input_vault_mint, + is_signer: false, + is_writable: false, + }); + accounts.push(LzAccount { + pubkey: c.output_vault_mint, + is_signer: false, + is_writable: false, + }); + + // --- LayerZero clear_compose (replay protection) --- + let mut extra = get_accounts_for_clear_compose( + c.endpoint_program, // endpoint program ID + ¶ms.from, // OFT sender + &c.endpoint_pda, // endpoint PDA + ¶ms.guid, // message GUID + params.index, // sequence index + ¶ms.message, // raw payload ); - accounts.append(&mut extra_accounts); + accounts.append(&mut extra); Ok(accounts) } diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index 2f7d1a331..526c361dc 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -8,9 +8,8 @@ use oapp::{endpoint_cpi::LzAccount, LzComposeParams}; use state::*; -declare_id!("4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq"); +declare_id!("AJDyttBGEdzXzUiW2WV2qxb1agCTpxeGJhMMjhXio6wr"); -const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; const COMPOSER_SEED: &[u8] = b"Composer"; #[program] diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs index 2c97e79a0..ba3e41c25 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -1,54 +1,57 @@ use anchor_lang::prelude::*; -#[account] -pub struct Composer { - /// The OFT PDA (unique to this composer instance) - pub oft: Pubkey, - /// The Endpoint PDA (the authorized endpoint for LZ messages) - pub endpoint: Pubkey, - /// Bump for PDA derivation. - pub bump: u8, -} - -impl Composer { - // Discriminator (8) + 32 + 32 + 1 = 73 bytes. - pub const SIZE: usize = 8 + 32 + 32 + 1; +#[error_code] +pub enum ComposerError { + #[msg("Invalid sender address")] + InvalidFrom, + #[msg("Invalid recipient address")] + InvalidTo, + #[msg("Invalid mint authority")] + InvalidMintAuthority, + #[msg("Invalid token destination")] + InvalidTokenDest, + #[msg("Invalid sender")] + InvalidSender, + #[msg("Program is paused")] + Paused, } -/// LzComposeTypesAccounts includes the pubkeys of all accounts used in the lz_compose_types instruction. -/// (Adjust the fields as needed.) #[account] -pub struct LzComposeTypesAccounts { - pub composer: Pubkey, +pub struct Composer { + pub oft_pda: Pubkey, + pub endpoint_pda: Pubkey, + pub endpoint_program: Pubkey, + pub token_program: Pubkey, + pub token_program_2022: Pubkey, pub clmm_program: Pubkey, - pub payer: Pubkey, pub amm_config: Pubkey, pub pool_state: Pubkey, - pub input_token_account: Pubkey, - pub output_token_account: Pubkey, pub input_vault: Pubkey, pub output_vault: Pubkey, pub observation_state: Pubkey, - pub token_program: Pubkey, - pub token_program_2022: Pubkey, - pub memo_program: Pubkey, - pub input_vault_mint: Pubkey, - pub output_vault_mint: Pubkey, - pub lz_program: Pubkey, + pub tick_bitmap: Pubkey, pub tick_array_lower: Pubkey, pub tick_array_current: Pubkey, pub tick_array_upper: Pubkey, - pub to_address: Pubkey, + pub input_token_account: Pubkey, + pub output_token_account: Pubkey, + pub input_vault_mint: Pubkey, + pub output_vault_mint: Pubkey, + pub bump: u8, } -impl LzComposeTypesAccounts { - pub const SIZE: usize = 8 + std::mem::size_of::(); +impl Composer { + // 8 (discriminator) + 19*32 + 1 + pub const SIZE: usize = 8 + 19 * 32 + 1; } -#[error_code] -pub enum ComposerError { - #[msg("Invalid 'from' address. The message sender is not the expected OFT PDA.")] - InvalidFrom, - #[msg("Invalid 'to' address. The message recipient does not match the composer PDA.")] - InvalidTo, +/// LzComposeTypesAccounts includes accounts that are used in the LzComposeTypes +/// instruction. +#[account] +pub struct LzComposeTypesAccounts { + pub composer: Pubkey, +} + +impl LzComposeTypesAccounts { + pub const SIZE: usize = 8 + std::mem::size_of::(); } diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs index 46e64bf1f..cfb3919d2 100644 --- a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; use instructions::*; use state::*; -declare_id!("935nnG5dUJCXwrZG3NyaYrf9xBDAABkoJbevzxPQAcic"); +declare_id!("7zjiaGZ99nED4KSXH4hCNo84RyFQcCGjVkb73GtVuxP5"); pub const OAPP_SEED: &[u8] = b"OApp"; diff --git a/examples/oft-solana-composer-library/tasks/evm/send.ts b/examples/oft-solana-composer-library/tasks/evm/send.ts index 7a419d2c2..5e500355c 100644 --- a/examples/oft-solana-composer-library/tasks/evm/send.ts +++ b/examples/oft-solana-composer-library/tasks/evm/send.ts @@ -31,14 +31,13 @@ const action: ActionType = async ( // ───────────────────────────────────────────────────────────────────────── const amountLD = BigNumber.from(amount) - // 1) turn your Solana base58 address into a 32-byte hex string - const receiverBytes = bs58.decode(solanaReceiver) // Uint8Array of length 32 + // 1) turn your Solana base58 address into a 32-byte hex string // Uint8Array of length 32 // 2) solidity-pack your two fields: - // - a 64-bit BE integer for minAmountLD + // - a 64-bit BE integer for minAmount // - a full 32-byte receiver pubkey const composeMsg = ethers.utils.solidityPack( ['uint64', 'bytes32'], - [BigNumber.from(minAmount), makeBytes32(bs58.decode(composer))] + [BigNumber.from(minAmount), makeBytes32(bs58.decode(solanaReceiver))] ) const sendParam = { dstEid, diff --git a/examples/oft-solana-composer-library/tasks/index.ts b/examples/oft-solana-composer-library/tasks/index.ts index 669ed2f55..5a77bc367 100644 --- a/examples/oft-solana-composer-library/tasks/index.ts +++ b/examples/oft-solana-composer-library/tasks/index.ts @@ -18,3 +18,4 @@ import './solana/setOutboundRateLimit' import './solana/getRaydiumPools' import './solana/getKeypair' import './solana/initComposer' +import './solana/lzCompose' diff --git a/examples/oft-solana-composer-library/tasks/solana/initComposer.ts b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts index 6686c519a..98a7b7896 100644 --- a/examples/oft-solana-composer-library/tasks/solana/initComposer.ts +++ b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts @@ -2,7 +2,13 @@ import fs from 'fs' import * as anchor from '@coral-xyz/anchor' -import { Connection, Keypair, PublicKey, SystemProgram } from '@solana/web3.js' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getOrCreateAssociatedTokenAccount, +} from '@solana/spl-token' +import { Connection, Keypair, PublicKey, SystemProgram, TransactionSignature } from '@solana/web3.js' import { task } from 'hardhat/config' import idl from '../../target/idl/composer.json' @@ -11,16 +17,29 @@ task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') .addOptionalParam( 'rpcUrl', 'Solana JSON RPC endpoint', - 'https://mainnet.helius-rpc.com/?api-key=78552846-acd7-40df-8f1c-79439387be5a', - undefined + 'https://mainnet.helius-rpc.com/?api-key=78552846-acd7-40df-8f1c-79439387be5a' ) - .setAction(async ({ rpcUrl }, hre) => { + .setAction(async ({ rpcUrl }) => { // ─── CONFIG ──────────────────────────────────────────────────────────────── - const COMPOSER_ID = new PublicKey('4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq') + const COMPOSER_ID = new PublicKey('AJDyttBGEdzXzUiW2WV2qxb1agCTpxeGJhMMjhXio6wr') const OFT_PDA = new PublicKey('4x3oQtX4MhjTKGBeXDZbtTSL9cUWo5waN2UChAuthtS') const ENDPOINT_PDA = new PublicKey('2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3') - const COMPOSER_SEED = Buffer.from('Composer') - const LZ_TYPES_SEED = Buffer.from('LzComposeTypes') + const ENDPOINT_PROGRAM_ID = new PublicKey('76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6') + const CLMM_PROGRAM_ID = new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK') + + const AMM_CONFIG_PDA = new PublicKey('9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x') + const POOL_STATE_PDA = new PublicKey('D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL') + const OBSERVATION_PDA = new PublicKey('Cv3s57YQzJRp988qvXPfZN5xX7BUFcgB1ycA9jzSrh2n') + const TICK_LOWER_PDA = new PublicKey('J1jEbmdbtsfA26igi1ryQjejnYVQRpxnobZh9tgSaH1h') + const TICK_CURRENT_PDA = new PublicKey('8AQHmKsoFh5Uh8bzYnnQ4Fx2QL2axPao9hjnt1YQBvdS') + const TICK_UPPER_PDA = new PublicKey('3ZUDGLF7xw26gcXUhZK3tL4MU2VizCUkXoHdb2LQ4zDM') + const TICK_BITMAP_PDA = new PublicKey('FHJHGbVtNoJdM5CSqPMQH8mn8D8pGfcN4JmLNqNMPBQu') + + const INPUT_VAULT_PDA = new PublicKey('5Nb7bdyi4jkMdA8Xqmt72HS1fpkGRQ573Gf8PJeGunsy') + const OUTPUT_VAULT_PDA = new PublicKey('sUfgz2jsDhN8qn1PrkWr9pVnUrLqqL1yB7WAnCj62Xz') + + const USDE_MINT = new PublicKey('DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT') + const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') // ──────────────────────────────────────────────────────────────────────────── // 1) Set up Anchor + wallet @@ -35,26 +54,70 @@ task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') // 2) Load your on-chain program const program = new anchor.Program(idl as anchor.Idl, COMPOSER_ID, provider) - // 3) Derive the two PDAs - const [composerPda] = PublicKey.findProgramAddressSync([COMPOSER_SEED, OFT_PDA.toBuffer()], COMPOSER_ID) - const [typesPda] = PublicKey.findProgramAddressSync([LZ_TYPES_SEED, composerPda.toBuffer()], COMPOSER_ID) - + // 3) Derive the Composer PDA + const [composerPda] = PublicKey.findProgramAddressSync( + [Buffer.from('Composer'), OFT_PDA.toBuffer()], + COMPOSER_ID + ) console.log('⛓ composer PDA:', composerPda.toBase58()) - console.log('🔧 types PDA: ', typesPda.toBase58()) - // 4) Fire the initComposer RPC - const sig = await program.methods + // 4) Ensure Composer has ATAs for A & B mints + const composerUsdeAta = await getOrCreateAssociatedTokenAccount( + connection, + keypair, + USDE_MINT, + composerPda, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ) + const composerUsdcAta = await getOrCreateAssociatedTokenAccount( + connection, + keypair, + USDC_MINT, + composerPda, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ) + console.log('✅ Composer ATAs:', { + usde: composerUsdeAta.address.toBase58(), + usdc: composerUsdcAta.address.toBase58(), + }) + + // 5) Fire the initComposer RPC + const sig: TransactionSignature = await program.methods .initComposer({ oftPda: OFT_PDA, endpointPda: ENDPOINT_PDA, + endpointProgram: ENDPOINT_PROGRAM_ID, + tokenProgram: TOKEN_PROGRAM_ID, + tokenProgram2022: TOKEN_2022_PROGRAM_ID, + clmmProgram: CLMM_PROGRAM_ID, + ammConfig: AMM_CONFIG_PDA, + poolState: POOL_STATE_PDA, + inputVault: INPUT_VAULT_PDA, + outputVault: OUTPUT_VAULT_PDA, + observationState: OBSERVATION_PDA, + tickArrayLower: TICK_LOWER_PDA, + tickArrayCurrent: TICK_CURRENT_PDA, + tickArrayUpper: TICK_UPPER_PDA, + tickBitmap: TICK_BITMAP_PDA, + inputTokenAccount: composerUsdeAta.address, + outputTokenAccount: composerUsdcAta.address, + inputVaultMint: USDE_MINT, + outputVaultMint: USDC_MINT, }) .accounts({ composer: composerPda, - lzComposeTypesAccounts: typesPda, payer: provider.wallet.publicKey, systemProgram: SystemProgram.programId, }) .rpc() - console.log('✅ tx:', sig) + console.log('✅ initComposer tx:', sig) }) diff --git a/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts b/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts new file mode 100644 index 000000000..3d6034d8e --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts @@ -0,0 +1,69 @@ +import { arrayify } from '@ethersproject/bytes' +import { toWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' +import { PublicKey, TransactionMessage, VersionedTransaction } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { lzCompose } from '@layerzerolabs/lz-solana-sdk-v2' + +import { deriveConnection } from './index' + +interface Args { + srcTxHash: string +} + +task('lz:oapp:solana:clear-with-alt', 'Clear a stored payload on Solana') + .addParam('srcTxHash', 'The source transaction hash', undefined, types.string) + .setAction(async ({ srcTxHash }: Args) => { + if (!process.env.SOLANA_PRIVATE_KEY) { + throw new Error('SOLANA_PRIVATE_KEY is not defined in the environment variables.') + } + + // Fetch message metadata + const response = await fetch(`https://scan.layerzero-api.com/v1/messages/tx/${srcTxHash}`) + const data = await response.json() + const message = data.data?.[0] + + // Set up connection and wallet + const { connection, umiWalletKeyPair } = await deriveConnection(message.pathway.dstEid as EndpointId) + const signer = toWeb3JsKeypair(umiWalletKeyPair) + const guidBytes = arrayify(message.guid) + + // Build the lzReceive (compose) instruction + const lzComposeInstruction = await lzCompose( + connection, + signer.publicKey, + { + from: new PublicKey(message.pathway.receiver.address), + to: new PublicKey('9eT5hbJ82Ng9khRa2K8pEjji79j7H9ieeeYmM8cCAeQP'), + guid: Array.from(guidBytes), + index: message.pathway.nonce, + message: arrayify(message.source.tx.payload), + }, + new Uint8Array(), + 'confirmed' + ) + + // Build and sign transaction + const { blockhash } = await connection.getLatestBlockhash('confirmed') + const txMessage = new TransactionMessage({ + payerKey: signer.publicKey, + recentBlockhash: blockhash, + instructions: [lzComposeInstruction], + }).compileToV0Message() + + const tx = new VersionedTransaction(txMessage) + tx.sign([signer]) + + // Log serialized transaction + console.log(Buffer.from(tx.serialize()).toString('base64')) + + // Optional: simulate + const simulation = await connection.simulateTransaction(tx, { sigVerify: true }) + console.log('simulation', simulation) + + // Send + const sig = await connection.sendTransaction(tx) + console.log('lzReceive tx signature', sig) + }) From 3f2740bcb1be59ab85f9dc2ca8df513f81a8ba76 Mon Sep 17 00:00:00 2001 From: Krak Date: Tue, 6 May 2025 16:56:16 -0700 Subject: [PATCH 11/11] chore: continue improving composer --- .../oft-solana-composer-library/Anchor.toml | 15 +- .../composer/src/compose_msg_codec.rs | 51 ++++ .../src/instructions/init_composer.rs | 93 ++++++-- .../composer/src/instructions/lz_compose.rs | 139 ++++++----- .../src/instructions/lz_compose_types.rs | 221 +++++++++++++----- .../programs/composer/src/lib.rs | 4 +- .../programs/composer/src/state/composer.rs | 41 +++- .../tasks/solana/getRaydiumPools.ts | 1 + .../tasks/solana/initComposer.ts | 12 +- .../tasks/solana/lzCompose.ts | 119 ++++++---- 10 files changed, 511 insertions(+), 185 deletions(-) create mode 100644 examples/oft-solana-composer-library/programs/composer/src/compose_msg_codec.rs diff --git a/examples/oft-solana-composer-library/Anchor.toml b/examples/oft-solana-composer-library/Anchor.toml index 2c598bb0f..7193fabb3 100644 --- a/examples/oft-solana-composer-library/Anchor.toml +++ b/examples/oft-solana-composer-library/Anchor.toml @@ -6,24 +6,29 @@ seeds = false skip-lint = false [programs.localnet] -composer = "4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq" -oft = "Gwdvzz5Qo8qBzG9B1NVZgLjwBVfHpympeb17QZAFQVB5" +composer = "6xhpdhwyzpjxc6n2KqQS8a3W7Busn6nqGYaobVV25kN5" +oft = "HipE7QwWtLyDTQNP61dEqGN8JNjUUzCMsUxpxRQMoeYx" [registry] url = "https://api.apr.dev" [provider] -cluster = "localnet" +cluster = "Localnet" wallet = "./keypair.json" [scripts] test = "npx jest test/anchor/" [test] -reset = true +startup_wait = 5000 +shutdown_wait = 2000 +upgradeable = false [test.validator] -url = "https://api.mainnet-beta.solana.com" # This is the url of the cluster that accounts are cloned from (See `test.validator.clone`). +bind_address = "0.0.0.0" +url = "https://api.mainnet-beta.solana.com" +ledger = ".anchor/test-ledger" +rpc_port = 8899 [[test.validator.clone]] address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" diff --git a/examples/oft-solana-composer-library/programs/composer/src/compose_msg_codec.rs b/examples/oft-solana-composer-library/programs/composer/src/compose_msg_codec.rs new file mode 100644 index 000000000..7716b3732 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/compose_msg_codec.rs @@ -0,0 +1,51 @@ +const NONCE_OFFSET: usize = 0; +const SRC_EID_OFFSET: usize = 8; +const AMOUNT_LD_OFFSET: usize = 12; +const COMPOSE_FROM_OFFSET: usize = 20; +const COMPOSE_MSG_OFFSET: usize = 52; + +pub fn encode( + nonce: u64, + src_eid: u32, + amount_ld: u64, + compose_msg: &Vec, // [composeFrom][composeMsg] +) -> Vec { + let mut encoded = Vec::with_capacity(20 + compose_msg.len()); // 8 + 4 + 8 + encoded.extend_from_slice(&nonce.to_be_bytes()); + encoded.extend_from_slice(&src_eid.to_be_bytes()); + encoded.extend_from_slice(&amount_ld.to_be_bytes()); + encoded.extend_from_slice(&compose_msg); + encoded +} + +pub fn nonce(message: &[u8]) -> u64 { + let mut nonce_bytes = [0; 8]; + nonce_bytes.copy_from_slice(&message[NONCE_OFFSET..SRC_EID_OFFSET]); + u64::from_be_bytes(nonce_bytes) +} + +pub fn src_eid(message: &[u8]) -> u32 { + let mut src_eid_bytes = [0; 4]; + src_eid_bytes.copy_from_slice(&message[SRC_EID_OFFSET..AMOUNT_LD_OFFSET]); + u32::from_be_bytes(src_eid_bytes) +} + +pub fn amount_ld(message: &[u8]) -> u64 { + let mut amount_ld_bytes = [0; 8]; + amount_ld_bytes.copy_from_slice(&message[AMOUNT_LD_OFFSET..COMPOSE_FROM_OFFSET]); + u64::from_be_bytes(amount_ld_bytes) +} + +pub fn compose_from(message: &[u8]) -> [u8; 32] { + let mut compose_from = [0; 32]; + compose_from.copy_from_slice(&message[COMPOSE_FROM_OFFSET..COMPOSE_MSG_OFFSET]); + compose_from +} + +pub fn compose_msg(message: &[u8]) -> Vec { + if message.len() > COMPOSE_MSG_OFFSET { + message[COMPOSE_MSG_OFFSET..].to_vec() + } else { + Vec::new() + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs index bad3a4583..222c61d3f 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs @@ -1,18 +1,32 @@ use crate::*; use anchor_lang::prelude::*; +const COMPOSER_SEED: &[u8] = b"Composer"; +const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; + #[derive(Accounts)] #[instruction(params: InitComposerParams)] pub struct InitComposer<'info> { /// PDA holding all our static pubkeys. #[account( - init, payer = payer, + init, + payer = payer, space = Composer::SIZE, seeds = [COMPOSER_SEED, params.oft_pda.as_ref()], bump )] pub composer: Account<'info, Composer>, + /// PDA storing all fields for the `lz_compose_types` instruction. + #[account( + init, + payer = payer, + space = LzComposeTypesAccounts::SIZE, + seeds = [LZ_COMPOSE_TYPES_SEED, composer.key().as_ref()], + bump + )] + pub lz_compose_types_accounts: Account<'info, LzComposeTypesAccounts>, + #[account(mut)] pub payer: Signer<'info>, @@ -31,8 +45,6 @@ pub struct InitComposerParams { pub pool_state: Pubkey, pub input_vault: Pubkey, pub output_vault: Pubkey, - pub input_vault_mint: Pubkey, - pub output_vault_mint: Pubkey, pub observation_state: Pubkey, pub tick_bitmap: Pubkey, pub tick_array_lower: Pubkey, @@ -40,31 +52,74 @@ pub struct InitComposerParams { pub tick_array_upper: Pubkey, pub input_token_account: Pubkey, pub output_token_account: Pubkey, + pub input_vault_mint: Pubkey, + pub output_vault_mint: Pubkey, } impl InitComposer<'_> { pub fn apply(ctx: &mut Context, params: &InitComposerParams) -> Result<()> { let c = &mut ctx.accounts.composer; - c.oft_pda = params.oft_pda; - c.endpoint_pda = params.endpoint_pda; - c.bump = ctx.bumps.composer; - c.endpoint_program = params.endpoint_program; - c.token_program = params.token_program; - c.token_program_2022= params.token_program_2022; - c.clmm_program = params.clmm_program; - c.amm_config = params.amm_config; - c.pool_state = params.pool_state; - c.input_vault = params.input_vault; - c.output_vault = params.output_vault; - c.observation_state = params.observation_state; - c.tick_bitmap = params.tick_bitmap; - c.tick_array_lower = params.tick_array_lower; - c.tick_array_current= params.tick_array_current; - c.tick_array_upper = params.tick_array_upper; + c.oft_pda = params.oft_pda; + c.endpoint_pda = params.endpoint_pda; + c.bump = ctx.bumps.composer; + c.endpoint_program = params.endpoint_program; + c.token_program = params.token_program; + c.token_program_2022 = params.token_program_2022; + c.clmm_program = params.clmm_program; + c.amm_config = params.amm_config; + c.pool_state = params.pool_state; + c.input_vault = params.input_vault; + c.output_vault = params.output_vault; + c.observation_state = params.observation_state; + c.tick_bitmap = params.tick_bitmap; + c.tick_array_lower = params.tick_array_lower; + c.tick_array_current = params.tick_array_current; + c.tick_array_upper = params.tick_array_upper; c.input_token_account = params.input_token_account; c.output_token_account = params.output_token_account; c.input_vault_mint = params.input_vault_mint; c.output_vault_mint = params.output_vault_mint; + + // Seed the LzComposeTypesAccounts PDA with every field + let t = &mut ctx.accounts.lz_compose_types_accounts; + // 0) endpoint program + t.endpoint_program = params.endpoint_program; + // 1) token program + t.token_program = params.token_program; + // 2) token program 2022 + t.token_program_2022 = params.token_program_2022; + // 3) composer + t.composer = c.key(); + // 4) clmm program + t.clmm_program = params.clmm_program; + // 5) amm config + t.amm_config = params.amm_config; + // 6) pool state + t.pool_state = params.pool_state; + // 7) input token account + t.input_token_account = params.input_token_account; + // 8) output token account + t.output_token_account = params.output_token_account; + // 9) input vault + t.input_vault = params.input_vault; + // 10) output vault + t.output_vault = params.output_vault; + // 11) observation state + t.observation_state = params.observation_state; + // 12) tick bitmap + t.tick_bitmap = params.tick_bitmap; + // 13) tick array lower + t.tick_array_lower = params.tick_array_lower; + // 14) tick array current + t.tick_array_current = params.tick_array_current; + // 15) tick array upper + t.tick_array_upper = params.tick_array_upper; + // 16) memo program + t.memo_program = spl_memo::id(); + // 17) input vault mint + t.input_vault_mint = params.input_vault_mint; + // 18) output vault mint + t.output_vault_mint = params.output_vault_mint; Ok(()) } } diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs index 2675828de..d919364ec 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs @@ -1,12 +1,12 @@ -use crate::*; use crate::state::composer::ComposerError; +use crate::*; use oapp::endpoint::program::Endpoint; use anchor_lang::prelude::*; -use anchor_spl::token::Token; -use anchor_spl::token_interface::{Token2022, TokenAccount, Mint}; -use anchor_spl::associated_token::AssociatedToken; use anchor_spl::associated_token::get_associated_token_address; +use anchor_spl::associated_token::AssociatedToken; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; use raydium_clmm_cpi::cpi::swap_v2; @@ -19,24 +19,26 @@ use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; use oapp::endpoint_cpi::clear_compose; use oapp::LzComposeParams; +use crate::compose_msg_codec::{amount_ld, compose_msg}; + use spl_memo; #[derive(Accounts)] #[instruction(params: LzComposeParams)] pub struct LzCompose<'info> { - /// The user paying for swap & any ATA creation + /// 0) The user paying for swap & any ATA creation (will be the signer) #[account(mut)] pub payer: Signer<'info>, - /// The LayerZero endpoint program this composer was initialized with + /// 1) The LayerZero endpoint program this composer was initialized with #[account(address = composer.endpoint_program)] pub endpoint_program: Program<'info, Endpoint>, - /// SPL programs + /// 2,3) SPL Token programs pub token_program: Program<'info, Token>, pub token_program_2022: Program<'info, Token2022>, - /// Our single-PDA storing all the needed pubkeys + /// 4) Our single-PDA storing all the needed pubkeys #[account( mut, seeds = [COMPOSER_SEED, &composer.oft_pda.to_bytes()], @@ -44,44 +46,52 @@ pub struct LzCompose<'info> { )] pub composer: Account<'info, Composer>, - /// Raydium CPI program & state + /// 5–7) Raydium CPI program & state pub clmm_program: Program<'info, RaydiumClmm>, #[account(address = composer.amm_config)] pub amm_config: Box>, #[account(mut)] pub pool_state: AccountLoader<'info, PoolState>, - /// User token accounts (input=A, output=B) - #[account(mut)] pub input_token_account: Box>, - #[account(mut)] pub output_token_account: Box>, + /// 8,9) User token ATAs (input=A, output=B) + #[account(mut)] + pub input_token_account: Box>, + #[account(mut)] + pub output_token_account: Box>, - /// Pool vaults - #[account(mut)] pub input_vault: Box>, - #[account(mut)] pub output_vault: Box>, + /// 10,11) Pool vaults + #[account(mut)] + pub input_vault: Box>, + #[account(mut)] + pub output_vault: Box>, - /// Price oracle & tick arrays + /// 12–16) Price oracle & tick arrays #[account(mut, address = composer.observation_state)] pub observation_state: AccountLoader<'info, ObservationState>, - #[account(mut)] pub tick_bitmap: AccountInfo<'info>, - #[account(mut)] pub tick_array_lower: AccountInfo<'info>, - #[account(mut)] pub tick_array_current:AccountInfo<'info>, - #[account(mut)] pub tick_array_upper: AccountInfo<'info>, + #[account(mut)] + pub tick_bitmap: AccountInfo<'info>, + #[account(mut)] + pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] + pub tick_array_current: AccountInfo<'info>, + #[account(mut)] + pub tick_array_upper: AccountInfo<'info>, - /// Memo program for the swap + /// 17) SPL Memo program (for the swap) #[account(address = spl_memo::id())] pub memo_program: UncheckedAccount<'info>, - /// Vault mints + /// 18,19) Vault mints #[account(address = input_vault.mint)] - pub input_vault_mint: Box>, + pub input_vault_mint: Box>, #[account(address = output_vault.mint)] pub output_vault_mint: Box>, - /// The ultimate receiver of token B + /// 20) The ultimate receiver of token B #[account(mut)] pub to_address: UncheckedAccount<'info>, - /// Receiver’s ATA for token B; init_if_needed if absent + /// 21) Receiver’s ATA for token B; init_if_needed if absent #[account( init_if_needed, payer = payer, @@ -91,8 +101,10 @@ pub struct LzCompose<'info> { )] pub to_token_account: Box>, - /// Programs needed for ATA creation + /// 22) **Associated Token program** — **must** be exactly here pub associated_token_program: Program<'info, AssociatedToken>, + + /// 23–24) System & Rent pub system_program: Program<'info, System>, pub rent: Sysvar<'info, Rent>, } @@ -109,11 +121,26 @@ impl LzCompose<'_> { ComposerError::InvalidTo ); - // 2) Parse payload - let msg = ¶ms.message; - let amount_ld = u64::from_be_bytes(msg[32..40].try_into().unwrap()); - let min_amount_out = u64::from_be_bytes(msg[72..80].try_into().unwrap()); - let receiver = Pubkey::new_from_array(msg[80..112].try_into().unwrap()); + // 2) Turn your Vec into a slice, then pull‐off both parts of the payload: + let msg = params.message.as_slice(); + + // a) the “amount_ld” header (bytes 12..20 of the original message) + let amount_ld_val = amount_ld(msg); + + // b) the post-header “compose_msg” blob (everything from byte 52 onward) + // which is exactly the solidityPacked [uint64,bytes32] + let payload = compose_msg(msg); + + // sanity check: that blob *must* be exactly 8+32 = 40 bytes + require!(payload.len() == 40, ComposerError::Paused); + + // c) parse your two JS-packed fields: + // • bytes 0..8 = min_amount_out (u64 BE) + // • bytes 8..40 = receiver pubkey + let min_amount_out = u64::from_be_bytes(payload[0..8].try_into().unwrap()); + let receiver = Pubkey::new_from_array(payload[8..40].try_into().unwrap()); + + // check: the client must have passed this same receiver in `to_address` require_keys_eq!( ctx.accounts.to_address.key(), receiver, @@ -122,19 +149,19 @@ impl LzCompose<'_> { // 3) Build Raydium CPI context let cpi_accounts = SwapSingleV2 { - payer: ctx.accounts.payer.to_account_info(), - amm_config: ctx.accounts.amm_config.to_account_info(), - pool_state: ctx.accounts.pool_state.to_account_info(), + payer: ctx.accounts.payer.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.input_token_account.to_account_info(), - output_token_account:ctx.accounts.to_token_account.to_account_info(), - input_vault: ctx.accounts.input_vault.to_account_info(), - output_vault: ctx.accounts.output_vault.to_account_info(), - observation_state: ctx.accounts.observation_state.to_account_info(), - token_program: ctx.accounts.token_program.to_account_info(), - token_program_2022: ctx.accounts.token_program_2022.to_account_info(), - memo_program: ctx.accounts.memo_program.to_account_info(), - input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), - output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), + output_token_account: ctx.accounts.to_token_account.to_account_info(), + input_vault: ctx.accounts.input_vault.to_account_info(), + output_vault: ctx.accounts.output_vault.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + memo_program: ctx.accounts.memo_program.to_account_info(), + input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), + output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), }; let cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) .with_remaining_accounts(vec![ @@ -145,7 +172,7 @@ impl LzCompose<'_> { ]); // 4) Attempt swap, refund on error - if let Err(_) = swap_v2(cpi_ctx, amount_ld, min_amount_out, 0u128, true) { + if let Err(_) = swap_v2(cpi_ctx, amount_ld_val, min_amount_out, 0u128, true) { // Refund token A back to receiver’s ATA for mint A // (Anchor init_if_needed below ensures the ATA exists) let composer_ata = get_associated_token_address( @@ -156,16 +183,16 @@ impl LzCompose<'_> { &ctx.accounts.to_address.key(), &ctx.accounts.composer.oft_pda, ); - + let refund_ix = anchor_spl::token::spl_token::instruction::transfer( &ctx.accounts.token_program.key(), &composer_ata, &receiver_ata, &ctx.accounts.composer.key(), &[], - amount_ld, + amount_ld_val, )?; - + anchor_lang::solana_program::program::invoke_signed( &refund_ix, &[ @@ -174,13 +201,11 @@ impl LzCompose<'_> { ctx.accounts.to_address.to_account_info(), ctx.accounts.token_program.to_account_info(), ], - &[ - &[ - COMPOSER_SEED, - &ctx.accounts.composer.oft_pda.to_bytes(), - &[ctx.accounts.composer.bump], - ], - ], + &[&[ + COMPOSER_SEED, + &ctx.accounts.composer.oft_pda.to_bytes(), + &[ctx.accounts.composer.bump], + ]], )?; } @@ -192,9 +217,9 @@ impl LzCompose<'_> { &[ctx.accounts.composer.bump], ]; let clear_params = ClearComposeParams { - from: params.from, - guid: params.guid, - index: params.index, + from: params.from, + guid: params.guid, + index: params.index, message: params.message.clone(), }; clear_compose( diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs index 23609b7fb..008e94b1a 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs @@ -1,20 +1,82 @@ -// instructions/lz_compose_types.rs use crate::*; use anchor_lang::prelude::*; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; + use oapp::endpoint_cpi::{get_accounts_for_clear_compose, LzAccount}; use oapp::LzComposeParams; -use spl_memo::id as memo_program_id; - -const COMPOSER_SEED: &[u8] = b"Composer"; +use raydium_clmm_cpi::{ + program::RaydiumClmm, + states::{AmmConfig, ObservationState, PoolState}, +}; +/// This struct must list **in the exact same order** the 18 pubkeys your PDA was seeded with, +/// because the @layerzerolabs/lz-solana-sdk-v2 will simulate by shoving *exactly* those into the `keys` array. +/// **MUST** match the 18‐pubkey seed order **exactly**! #[derive(Accounts)] pub struct LzComposeTypes<'info> { - /// Only the Composer PDA—holds every Pubkey we need. + /// 1) LayerZero endpoint program + pub endpoint_program: Program<'info, oapp::endpoint::program::Endpoint>, + + /// 2) SPL Token program v1 + pub token_program: Program<'info, Token>, + + /// 3) SPL Token program v2 (2022) + pub token_program_2022: Program<'info, Token2022>, + + /// 4) Composer PDA #[account( - seeds = [COMPOSER_SEED, &composer.oft_pda.to_bytes()], + seeds = [COMPOSER_SEED, &composer.oft_pda.to_bytes()], bump = composer.bump, )] pub composer: Account<'info, Composer>, + + /// 5) Raydium CLMM CPI program + pub clmm_program: Program<'info, RaydiumClmm>, + + /// 6) Raydium AMM config + #[account(address = composer.amm_config)] + pub amm_config: Box>, + + /// 7) Raydium PoolState (writable) + pub pool_state: AccountLoader<'info, PoolState>, + + /// 8) User’s ATA for input token (writable) + pub input_token_account: Box>, + + /// 9) User’s ATA for output token (writable) + pub output_token_account: Box>, + + /// 10) Pool vault A (writable) + pub input_vault: Box>, + + /// 11) Pool vault B (writable) + pub output_vault: Box>, + + /// 12) Raydium ObservationState (writable) + pub observation_state: AccountLoader<'info, ObservationState>, + + /// 13) Raydium TickBitmap (writable) + pub tick_bitmap: AccountInfo<'info>, + + /// 14) Raydium TickArrayLower (writable) + pub tick_array_lower: AccountInfo<'info>, + + /// 15) Raydium TickArrayCurrent (writable) + pub tick_array_current: AccountInfo<'info>, + + /// 16) Raydium TickArrayUpper (writable) + pub tick_array_upper: AccountInfo<'info>, + + /// 17) SPL Memo program (for the swap) + #[account(address = spl_memo::id())] + pub memo_program: UncheckedAccount<'info>, + + /// 18) Vault A mint + pub input_vault_mint: Box>, + + /// 19) Vault B mint + pub output_vault_mint: Box>, } impl<'info> LzComposeTypes<'info> { @@ -22,126 +84,179 @@ impl<'info> LzComposeTypes<'info> { ctx: &Context, params: &LzComposeParams, ) -> Result> { - let c = &ctx.accounts.composer; - let mut accounts = Vec::with_capacity(1 // payer - + 2 // SPL token programs - + 2 // Raydium config & pool - + 2 // user ATAs - + 2 // pool vaults - + 5 // observation + tick arrays - + 1 // memo - + 2 // vault mints - + 6); // clear_compose - - // 0) placeholder for the executor/payer + let a = &ctx.accounts; + + // reserve for 18 static + 6 clear_compose + let mut accounts = Vec::with_capacity(18 + 6); + + // ─── Static Pubkeys (slots 0–19) ────────────────────────────── + // 0) payer placeholder accounts.push(LzAccount { pubkey: Pubkey::default(), is_signer: true, is_writable: true, }); - - // 1) SPL token programs + // 1) endpoint program accounts.push(LzAccount { - pubkey: c.token_program, + pubkey: a.endpoint_program.key(), is_signer: false, is_writable: false, }); + // 2) token_program v1 accounts.push(LzAccount { - pubkey: c.token_program_2022, + pubkey: a.token_program.key(), is_signer: false, is_writable: false, }); - - // 2) Raydium config + pool state + // 3) token_program v2 (2022) accounts.push(LzAccount { - pubkey: c.amm_config, + pubkey: a.token_program_2022.key(), is_signer: false, is_writable: false, }); + // 4) composer PDA accounts.push(LzAccount { - pubkey: c.pool_state, + pubkey: a.composer.key(), is_signer: false, is_writable: true, }); - - // 3) User token ATAs (input A, output B) + // 5) CLMM program + accounts.push(LzAccount { + pubkey: a.clmm_program.key(), + is_signer: false, + is_writable: false, + }); + // 6) AMM config + accounts.push(LzAccount { + pubkey: a.amm_config.key(), + is_signer: false, + is_writable: false, + }); + // 7) PoolState accounts.push(LzAccount { - pubkey: c.input_token_account, + pubkey: a.pool_state.key(), is_signer: false, is_writable: true, }); + // 8) user ATA A accounts.push(LzAccount { - pubkey: c.output_token_account, + pubkey: a.input_token_account.key(), is_signer: false, is_writable: true, }); - - // 4) Pool vaults (input A, output B) + // 9) user ATA B accounts.push(LzAccount { - pubkey: c.input_vault, + pubkey: a.output_token_account.key(), is_signer: false, is_writable: true, }); + // 10) input_vault accounts.push(LzAccount { - pubkey: c.output_vault, + pubkey: a.input_vault.key(), is_signer: false, is_writable: true, }); - - // 5) Observation and tick‐array accounts + // 11) output_vault + accounts.push(LzAccount { + pubkey: a.output_vault.key(), + is_signer: false, + is_writable: true, + }); + // 12) observation_state accounts.push(LzAccount { - pubkey: c.observation_state, + pubkey: a.observation_state.key(), is_signer: false, is_writable: true, }); + // 13) tick_bitmap accounts.push(LzAccount { - pubkey: c.tick_bitmap, + pubkey: a.tick_bitmap.key(), is_signer: false, is_writable: true, }); + // 14) tick_array_lower accounts.push(LzAccount { - pubkey: c.tick_array_lower, + pubkey: a.tick_array_lower.key(), is_signer: false, is_writable: true, }); + // 15) tick_array_current accounts.push(LzAccount { - pubkey: c.tick_array_current, + pubkey: a.tick_array_current.key(), is_signer: false, is_writable: true, }); + // 16) tick_array_upper accounts.push(LzAccount { - pubkey: c.tick_array_upper, + pubkey: a.tick_array_upper.key(), + is_signer: false, + is_writable: true, + }); + // 17) SPL Memo + accounts.push(LzAccount { + pubkey: spl_memo::id(), + is_signer: false, + is_writable: false, + }); + // 18) vault A mint + accounts.push(LzAccount { + pubkey: a.input_vault_mint.key(), + is_signer: false, + is_writable: false, + }); + // 19) vault B mint + accounts.push(LzAccount { + pubkey: a.output_vault_mint.key(), + is_signer: false, + is_writable: false, + }); + // 20) to_address (the ultimate receiver, from params.to) + accounts.push(LzAccount { + pubkey: params.to, is_signer: false, is_writable: true, }); - // 6) SPL Memo program + // 21) to_token_account (their ATA for token B) + let to_ata = anchor_spl::associated_token::get_associated_token_address( + ¶ms.to, + &a.output_vault_mint.key(), + ); accounts.push(LzAccount { - pubkey: memo_program_id(), + pubkey: to_ata, + is_signer: false, + is_writable: true, + }); + + // 22) Associated Token program + accounts.push(LzAccount { + pubkey: anchor_spl::associated_token::ID, is_signer: false, is_writable: false, }); - // 7) Vault mints + // 23) System program accounts.push(LzAccount { - pubkey: c.input_vault_mint, + pubkey: anchor_lang::solana_program::system_program::ID, is_signer: false, is_writable: false, }); + + // 24) Rent sysvar accounts.push(LzAccount { - pubkey: c.output_vault_mint, + pubkey: anchor_lang::solana_program::sysvar::rent::ID, is_signer: false, is_writable: false, }); - // --- LayerZero clear_compose (replay protection) --- + // ─── LayerZero clear_compose (slots 20–25) ──────────────────── let mut extra = get_accounts_for_clear_compose( - c.endpoint_program, // endpoint program ID - ¶ms.from, // OFT sender - &c.endpoint_pda, // endpoint PDA - ¶ms.guid, // message GUID - params.index, // sequence index - ¶ms.message, // raw payload + ctx.accounts.endpoint_program.key(), + ¶ms.from, + &ctx.accounts.composer.key(), + ¶ms.guid, + params.index, + ¶ms.message, ); accounts.append(&mut extra); diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs index 526c361dc..53cedd19d 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/lib.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -8,10 +8,12 @@ use oapp::{endpoint_cpi::LzAccount, LzComposeParams}; use state::*; -declare_id!("AJDyttBGEdzXzUiW2WV2qxb1agCTpxeGJhMMjhXio6wr"); +declare_id!("6xhpdhwyzpjxc6n2KqQS8a3W7Busn6nqGYaobVV25kN5"); const COMPOSER_SEED: &[u8] = b"Composer"; +pub mod compose_msg_codec; + #[program] pub mod composer { use super::*; diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs index ba3e41c25..c4cb9a7cb 100644 --- a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -45,13 +45,50 @@ impl Composer { pub const SIZE: usize = 8 + 19 * 32 + 1; } -/// LzComposeTypesAccounts includes accounts that are used in the LzComposeTypes -/// instruction. +/// This PDA holds all of the static pubkeys that `lz_compose_types` will consume. #[account] pub struct LzComposeTypesAccounts { + // 0) endpoint program + pub endpoint_program: Pubkey, + // 1) token program + pub token_program: Pubkey, + // 2) token program 2022 + pub token_program_2022: Pubkey, + // 3) composer pub composer: Pubkey, + // 4) clmm program + pub clmm_program: Pubkey, + // 5) amm config + pub amm_config: Pubkey, + // 6) pool state + pub pool_state: Pubkey, + // 7) input token account + pub input_token_account: Pubkey, + // 8) output token account + pub output_token_account: Pubkey, + // 9) input vault + pub input_vault: Pubkey, + // 10) output vault + pub output_vault: Pubkey, + // 11) observation state + pub observation_state: Pubkey, + // 12) tick bitmap + pub tick_bitmap: Pubkey, + // 13) tick array lower + pub tick_array_lower: Pubkey, + // 14) tick array current + pub tick_array_current: Pubkey, + // 15) tick array upper + pub tick_array_upper: Pubkey, + // 16) memo program + pub memo_program: Pubkey, + // 17) input vault mint + pub input_vault_mint: Pubkey, + // 18) output vault mint + pub output_vault_mint: Pubkey, } impl LzComposeTypesAccounts { + // Discriminator (8) + size_of::() pub const SIZE: usize = 8 + std::mem::size_of::(); } diff --git a/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts b/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts index 0d8d24f5b..8434704dd 100644 --- a/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts +++ b/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts @@ -87,6 +87,7 @@ task('lz:raydium:get-pools', 'Fetches Raydium CLMM pool PDAs') // 5) Print them out console.log('\n🔑 Raydium CLMM PDAs:') console.log(' Pool Program ID: ', poolInfo.programId) + console.log(' Pool ID: ', poolId) console.log(' Pool State PDA: ', poolStatePda) console.log(' AMM Config PDA: ', ammConfigPda) console.log(' Observation PDA: ', poolKeys.observationId) diff --git a/examples/oft-solana-composer-library/tasks/solana/initComposer.ts b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts index 98a7b7896..660d85a9c 100644 --- a/examples/oft-solana-composer-library/tasks/solana/initComposer.ts +++ b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts @@ -21,11 +21,12 @@ task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') ) .setAction(async ({ rpcUrl }) => { // ─── CONFIG ──────────────────────────────────────────────────────────────── - const COMPOSER_ID = new PublicKey('AJDyttBGEdzXzUiW2WV2qxb1agCTpxeGJhMMjhXio6wr') + const COMPOSER_ID = new PublicKey('6xhpdhwyzpjxc6n2KqQS8a3W7Busn6nqGYaobVV25kN5') const OFT_PDA = new PublicKey('4x3oQtX4MhjTKGBeXDZbtTSL9cUWo5waN2UChAuthtS') const ENDPOINT_PDA = new PublicKey('2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3') const ENDPOINT_PROGRAM_ID = new PublicKey('76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6') const CLMM_PROGRAM_ID = new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK') + const RECEIVER_PDA = new PublicKey('6tzUZqC33igPgP7YyDnUxQg6eupMmZGRGKdVAksgRzvk') const AMM_CONFIG_PDA = new PublicKey('9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x') const POOL_STATE_PDA = new PublicKey('D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL') @@ -61,6 +62,12 @@ task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') ) console.log('⛓ composer PDA:', composerPda.toBase58()) + const [typesPda] = PublicKey.findProgramAddressSync( + [Buffer.from('LzComposeTypes'), composerPda.toBuffer()], + COMPOSER_ID + ) + console.log('⛓ types PDA:', typesPda.toBase58()) + // 4) Ensure Composer has ATAs for A & B mints const composerUsdeAta = await getOrCreateAssociatedTokenAccount( connection, @@ -77,7 +84,7 @@ task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') connection, keypair, USDC_MINT, - composerPda, + RECEIVER_PDA, true, undefined, undefined, @@ -114,6 +121,7 @@ task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') }) .accounts({ composer: composerPda, + lzComposeTypesAccounts: typesPda, payer: provider.wallet.publicKey, systemProgram: SystemProgram.programId, }) diff --git a/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts b/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts index 3d6034d8e..500d41c4b 100644 --- a/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts +++ b/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts @@ -1,69 +1,96 @@ -import { arrayify } from '@ethersproject/bytes' -import { toWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' -import { PublicKey, TransactionMessage, VersionedTransaction } from '@solana/web3.js' +// tasks/solana/clearWithAlt.ts +import { createSignerFromKeypair } from '@metaplex-foundation/umi' +import { toWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Transaction } from '@solana/web3.js' import { task } from 'hardhat/config' -import { types } from '@layerzerolabs/devtools-evm-hardhat' import { EndpointId } from '@layerzerolabs/lz-definitions' -import { lzCompose } from '@layerzerolabs/lz-solana-sdk-v2' +import { EndpointProgram, extractComposeSentEventByTxHash, lzCompose } from '@layerzerolabs/lz-solana-sdk-v2' -import { deriveConnection } from './index' +import { deriveConnection } from '.' interface Args { srcTxHash: string + mnemonic?: string } -task('lz:oapp:solana:clear-with-alt', 'Clear a stored payload on Solana') - .addParam('srcTxHash', 'The source transaction hash', undefined, types.string) - .setAction(async ({ srcTxHash }: Args) => { - if (!process.env.SOLANA_PRIVATE_KEY) { - throw new Error('SOLANA_PRIVATE_KEY is not defined in the environment variables.') - } - +task( + 'lz:oapp:solana:clear-with-alt', + 'Fetch a ComposeSent event by source-tx and clear it on Solana in one ALT-packed tx' +) + .addParam('srcTxHash', 'The source transaction hash') + .addOptionalParam('mnemonic', 'Your wallet mnemonic (or set MNEMONIC env var)', process.env.MNEMONIC) + .setAction(async ({ srcTxHash, mnemonic }: Args, hre) => { + // 1) Build UMI + RPC connection + // Set up connection and wallet // Fetch message metadata const response = await fetch(`https://scan.layerzero-api.com/v1/messages/tx/${srcTxHash}`) const data = await response.json() const message = data.data?.[0] + const dstTxHash = message.destination.tx.txHash + console.log('message', message) // Set up connection and wallet - const { connection, umiWalletKeyPair } = await deriveConnection(message.pathway.dstEid as EndpointId) - const signer = toWeb3JsKeypair(umiWalletKeyPair) - const guidBytes = arrayify(message.guid) + const { umi, umiWalletSigner, connection } = await deriveConnection(message.pathway.dstEid as EndpointId) + const signer = createSignerFromKeypair(umi, umiWalletSigner) + const web3Signer = toWeb3JsKeypair(umiWalletSigner) + + // 2) Look up the ComposeSent event from your source tx + const events = await extractComposeSentEventByTxHash(connection, EndpointProgram.PROGRAM_ID, dstTxHash) + if (!events || events.length === 0) { + throw new Error(`No ComposeSent event found for ${dstTxHash}`) + } + const event = events[0] + + // 4) Build the lzCompose instruction + const ix = await lzCompose(connection, toWeb3JsPublicKey(signer.publicKey), event) - // Build the lzReceive (compose) instruction - const lzComposeInstruction = await lzCompose( - connection, - signer.publicKey, - { - from: new PublicKey(message.pathway.receiver.address), - to: new PublicKey('9eT5hbJ82Ng9khRa2K8pEjji79j7H9ieeeYmM8cCAeQP'), - guid: Array.from(guidBytes), - index: message.pathway.nonce, - message: arrayify(message.source.tx.payload), - }, - new Uint8Array(), - 'confirmed' - ) + // 1) Build your Instruction exactly as before + const tx = new Transaction() + tx.add(ix) + tx.feePayer = toWeb3JsPublicKey(signer.publicKey) - // Build and sign transaction + // 2) Fetch a recent blockhash const { blockhash } = await connection.getLatestBlockhash('confirmed') - const txMessage = new TransactionMessage({ - payerKey: signer.publicKey, - recentBlockhash: blockhash, - instructions: [lzComposeInstruction], - }).compileToV0Message() + tx.recentBlockhash = blockhash + + // 3) Sign the transaction (`payer` must sign; adjust if you have more signers) + tx.sign(web3Signer) + const signedTx = tx + + // 4) Send *without* preflight + const signature = await connection.sendRawTransaction(signedTx.serialize(), { + skipPreflight: true, + preflightCommitment: 'confirmed', + }) - const tx = new VersionedTransaction(txMessage) - tx.sign([signer]) + // 5) Wait for confirmation + await connection.confirmTransaction(signature, 'confirmed') - // Log serialized transaction - console.log(Buffer.from(tx.serialize()).toString('base64')) + // 6) Fetch the *on-chain* logs + const txInfo = await connection.getTransaction(signature, { commitment: 'confirmed' }) + console.log('On-chain logs:\n', txInfo?.meta?.logMessages?.join('\n')) - // Optional: simulate - const simulation = await connection.simulateTransaction(tx, { sigVerify: true }) - console.log('simulation', simulation) + // const umiInstruction = { + // programId: publicKey(ix.programId.toBase58()), + // keys: ix.keys.map((key) => ({ + // pubkey: publicKey(key.pubkey.toBase58()), + // isSigner: key.isSigner, + // isWritable: key.isWritable, + // })), + // data: ix.data, + // } + // let txBuilder = transactionBuilder().add({ + // instruction: umiInstruction, + // signers: [umiWalletSigner], // Include all required signers here + // bytesCreatedOnChain: 0, + // }) - // Send - const sig = await connection.sendTransaction(tx) - console.log('lzReceive tx signature', sig) + // const { signature } = await txBuilder.sendAndConfirm(umi, { + // skipPreflight: true, + // preflightCommitment: 'confirmed', + // }) + // console.log( + // `lzCompose: ${getExplorerTxLink(bs58.encode(signature), false)}` + // ) })