diff --git a/.changeset/tall-boats-travel.md b/.changeset/tall-boats-travel.md new file mode 100644 index 000000000..84d1bd54c --- /dev/null +++ b/.changeset/tall-boats-travel.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/omnicounter-solana-example": major +--- + +Added Solana OmniCounter example diff --git a/examples/oft-solana/package.json b/examples/oft-solana/package.json index be28cac79..2db2265dc 100644 --- a/examples/oft-solana/package.json +++ b/examples/oft-solana/package.json @@ -30,20 +30,20 @@ "@layerzerolabs/devtools-solana": "~0.1.7", "@layerzerolabs/eslint-config-next": "~2.3.39", "@layerzerolabs/io-devtools": "~0.1.11", - "@layerzerolabs/lz-definitions": "^2.3.39", - "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.39", - "@layerzerolabs/lz-evm-protocol-v2": "^2.3.39", - "@layerzerolabs/lz-evm-v1-0.7": "^2.3.39", - "@layerzerolabs/lz-solana-sdk-v2": "^2.3.39", - "@layerzerolabs/lz-v2-utilities": "^2.3.39", - "@layerzerolabs/oapp-evm": "^0.0.4", - "@layerzerolabs/oft-evm": "^0.0.11", - "@layerzerolabs/prettier-config-next": "^2.3.39", - "@layerzerolabs/protocol-devtools": "^0.4.3", - "@layerzerolabs/protocol-devtools-solana": "^1.0.6", - "@layerzerolabs/solhint-config": "^2.3.39", - "@layerzerolabs/test-devtools-evm-foundry": "~0.2.12", - "@layerzerolabs/test-devtools-evm-hardhat": "0.2.7", + "@layerzerolabs/lz-definitions": "^2.3.34", + "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.34", + "@layerzerolabs/lz-evm-protocol-v2": "^2.3.34", + "@layerzerolabs/lz-evm-v1-0.7": "^2.3.34", + "@layerzerolabs/lz-solana-sdk-v2": "^2.3.36", + "@layerzerolabs/lz-v2-utilities": "^2.3.34", + "@layerzerolabs/oapp-evm": "^0.0.3", + "@layerzerolabs/oft-evm": "^0.0.10", + "@layerzerolabs/prettier-config-next": "^2.3.34", + "@layerzerolabs/protocol-devtools": "^0.4.2", + "@layerzerolabs/protocol-devtools-solana": "^1.0.5", + "@layerzerolabs/solhint-config": "^2.3.34", + "@layerzerolabs/test-devtools-evm-foundry": "~0.2.11", + "@layerzerolabs/test-devtools-evm-hardhat": "0.2.6", "@layerzerolabs/toolbox-foundry": "~0.1.9", "@layerzerolabs/toolbox-hardhat": "~0.3.7", "@layerzerolabs/ua-devtools": "~1.0.5", diff --git a/examples/omnicounter-solana/.env.example b/examples/omnicounter-solana/.env.example new file mode 100644 index 000000000..1cffe2d99 --- /dev/null +++ b/examples/omnicounter-solana/.env.example @@ -0,0 +1,21 @@ +# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.- +# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ +# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' +# +# 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= +SOLANA_PRIVATE_KEY= +RPC_URL_SOLANA= +RPC_URL_SOLANA_TESTNET= \ No newline at end of file diff --git a/examples/omnicounter-solana/.eslintignore b/examples/omnicounter-solana/.eslintignore new file mode 100644 index 000000000..50166580a --- /dev/null +++ b/examples/omnicounter-solana/.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/omnicounter-solana/.eslintrc.js b/examples/omnicounter-solana/.eslintrc.js new file mode 100644 index 000000000..69fd2e8d2 --- /dev/null +++ b/examples/omnicounter-solana/.eslintrc.js @@ -0,0 +1,11 @@ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + extends: ['@layerzerolabs/eslint-config-next/recommended'], + rules: { + // @layerzerolabs/eslint-config-next defines rules for turborepo-based projects + // that are not relevant for this particular project + 'turbo/no-undeclared-env-vars': 'off', + 'import/no-unresolved': 'warn', + }, +}; diff --git a/examples/omnicounter-solana/.gitignore b/examples/omnicounter-solana/.gitignore new file mode 100644 index 000000000..0f7da464c --- /dev/null +++ b/examples/omnicounter-solana/.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/omnicounter-solana/.nvmrc b/examples/omnicounter-solana/.nvmrc new file mode 100644 index 000000000..b714151ef --- /dev/null +++ b/examples/omnicounter-solana/.nvmrc @@ -0,0 +1 @@ +v18.18.0 \ No newline at end of file diff --git a/examples/omnicounter-solana/.prettierignore b/examples/omnicounter-solana/.prettierignore new file mode 100644 index 000000000..fbb1e9c85 --- /dev/null +++ b/examples/omnicounter-solana/.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/omnicounter-solana/.prettierrc.js b/examples/omnicounter-solana/.prettierrc.js new file mode 100644 index 000000000..6f55b4019 --- /dev/null +++ b/examples/omnicounter-solana/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('@layerzerolabs/prettier-config-next'), +}; diff --git a/examples/omnicounter-solana/.solhintrc.js b/examples/omnicounter-solana/.solhintrc.js new file mode 100644 index 000000000..102eae347 --- /dev/null +++ b/examples/omnicounter-solana/.solhintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['solhint:recommended', require.resolve('@layerzerolabs/solhint-config')], +}; diff --git a/examples/omnicounter-solana/Anchor.toml b/examples/omnicounter-solana/Anchor.toml new file mode 100644 index 000000000..f29d291f5 --- /dev/null +++ b/examples/omnicounter-solana/Anchor.toml @@ -0,0 +1,19 @@ +[toolchain] +anchor_version = "0.29.0" + +[features] +seeds = false +skip-lint = false + +[programs.localnet] +omnicounter = '2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu' + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "Localnet" +wallet = "./junk-id.json" + +[scripts] +test = "npx jest test/anchor" diff --git a/examples/omnicounter-solana/CHANGELOG.md b/examples/omnicounter-solana/CHANGELOG.md new file mode 100644 index 000000000..f34735685 --- /dev/null +++ b/examples/omnicounter-solana/CHANGELOG.md @@ -0,0 +1,27 @@ +# @layerzerolabs/oft-solana-example + +## 0.0.5 + +### Patch Changes + +- 51ab953: fix Solana Devnet Hardhat tasks + +## 0.0.4 + +### Patch Changes + +- 5442565: Fix @solana-developers/helpers version to 2.4.0 + +## 0.0.3 + +### Patch Changes + +- 07b6f31: Remove unnecessary await (hygeine) + +## 0.0.2 + +### Patch Changes + +- 3b41b73: Add explicit bs58 dependency +- 0ccd6cd: Fix sk decode +- 9a70c7f: Fix imports for bs58 diff --git a/examples/omnicounter-solana/Cargo.lock b/examples/omnicounter-solana/Cargo.lock new file mode 100644 index 000000000..d5d41e163 --- /dev/null +++ b/examples/omnicounter-solana/Cargo.lock @@ -0,0 +1,1791 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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", +] + +[[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.3", + "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.3", + "bytemuck", + "getrandom 0.2.15", + "solana-program", + "thiserror", +] + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +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.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +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 = [ + "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 = "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", + "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", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal 0.10.3", + "borsh-schema-derive-internal 0.10.3", + "proc-macro-crate", + "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +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.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[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.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "cpi-helper" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?branch=main#0e3186dae2ab157d4b84c6c03571bae6986075d0" +dependencies = [ + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[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 = "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 = "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 = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "endpoint" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?branch=main#0e3186dae2ab157d4b84c6c03571bae6986075d0" +dependencies = [ + "anchor-lang", + "cpi-helper", + "messagelib-interface", + "solana-helper", + "solana-program", + "utils", +] + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[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 = "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 = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[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-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", +] + +[[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 = "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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[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 = "messagelib-interface" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?branch=main#0e3186dae2ab157d4b84c6c03571bae6986075d0" +dependencies = [ + "anchor-lang", +] + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +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-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 = "oapp" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?branch=main#0e3186dae2ab157d4b84c6c03571bae6986075d0" +dependencies = [ + "anchor-lang", + "endpoint", +] + +[[package]] +name = "omnicounter" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "oapp", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[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 = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[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-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[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 = "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[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.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[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.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[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.66", +] + +[[package]] +name = "solana-helper" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?branch=main#0e3186dae2ab157d4b84c6c03571bae6986075d0" +dependencies = [ + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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.3", + "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", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[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.66", +] + +[[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.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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 = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "utils" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?branch=main#0e3186dae2ab157d4b84c6c03571bae6986075d0" +dependencies = [ + "anchor-lang", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[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 = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[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.66", +] diff --git a/examples/omnicounter-solana/Cargo.toml b/examples/omnicounter-solana/Cargo.toml new file mode 100644 index 000000000..ae8c8729c --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/README.md b/examples/omnicounter-solana/README.md new file mode 100644 index 000000000..9e2aaadb0 --- /dev/null +++ b/examples/omnicounter-solana/README.md @@ -0,0 +1,730 @@ +

+ + LayerZero + +

+ +

+ Homepage | Docs | Developers +

+ +

Omnichain Fungible Token (OFT) Solana Example

+ +

+ Quickstart | Configuration | Message Execution Options | Endpoint, MessageLib, & Executor Addresses | DVN Addresses +

+ +

A boilerplate for streamlining the process of building, testing, deploying, and configuring Omnichain Fungible Tokens (OFTs) on Solana

+ +> [!WARNING] +> The OFT Solana Example is currently an experimental build that is subject to changes. +> +> You can enable this build by running: +> LZ_ENABLE_EXPERIMENTAL_SOLANA_OFT_EXAMPLE=1 npx create-lz-oapp@latest + +

+ +[Audit Reports](https://github.com/LayerZero-Labs/Audits) + +## Installation + +#### Installing dependencies + +We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice): + +```bash +pnpm install +``` + +#### Compiling your program + +```bash +pnpm compile +``` + +#### Running tests + +```bash +pnpm test +``` + +### Preparing OFT Program ID + +Create `programId` keypair files by running: + +```bash +solana-keygen new -o target/deploy/endpoint-keypair.json +solana-keygen new -o target/deploy/oft-keypair.json + +anchor keys sync +``` + +:warning: You will want to use the `--force` flag to generate your own keypair if these keys already exist. + +### Deploying OFT Program + +#### Using `anchor` + +```bash +anchor build -v +solana program deploy --program-id target/deploy/oft-keypair.json target/verifiable/oft.so -u mainnet-beta +``` + +#### Using `solana-verify` + +```bash +solana-verify build +solana program deploy --program-id target/deploy/oft-keypair.json target/deploy/oft.so -u mainnet-beta +``` + +please visit [Solana Verify CLI](https://github.com/Ellipsis-Labs/solana-verifiable-build) and [Deploy a Solana Program with the CLI](https://docs.solanalabs.com/cli/examples/deploy-a-program) for more detail. + +#### Notice + +If you encounter issues during compilation and testing, it might be due to the versions of Solana and Anchor. You can switch to Solana version `1.17.31` and Anchor version `0.29.0`, as these are the versions we have tested and verified to be working. + +## LayerZero Hardhat Helper Tasks + +LayerZero Devtools provides several helper hardhat tasks to easily deploy, verify, configure, connect, and send OFTs cross-chain. + +

+ npx hardhat lz:deploy + +
+ +Deploys your contract to any of the available networks in your [`hardhat.config.ts`](./hardhat.config.ts) when given a deploy tag (by default contract name) and returns a list of available networks to select for the deployment. For specifics around all deployment options, please refer to the [Deploying Contracts](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying) section of the documentation. LayerZero's `lz:deploy` utilizes `hardhat-deploy`. + +```yml +'arbitrum-sepolia': { + eid: EndpointId.ARBSEP_V2_TESTNET, + url: process.env.RPC_URL_ARBSEP_TESTNET, + accounts, +}, +'base-sepolia': { + eid: EndpointId.BASESEP_V2_TESTNET, + url: process.env.RPC_URL_BASE_TESTNET, + accounts, +}, +``` + +To add an existing deployment, refer to the `hardhat-deploy` instructions [here](https://github.com/wighawag/hardhat-deploy/blob/master/README.md#migrating-existing-deployment-to-hardhat-deploy). + +
+ +
+ npx hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME + +
+ +Initializes a `layerzero.config.ts` file for all available pathways between your hardhat networks with the current LayerZero default placeholder settings. This task can be incredibly useful for correctly formatting your config file. + +You can run this task by providing the `contract-name` you want to set for the config and `file-name` you want to generate: + +```bash +npx hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME +``` + +This will create a `layerzero.config.ts` in your working directory populated with your contract name and connections for every pathway possible between your hardhat networks: + +```yml +import { EndpointId } from '@layerzerolabs/lz-definitions' + +const arbsepContract = { + eid: EndpointId.ARBSEP_V2_TESTNET, + contractName: 'MyOFT', +} +const sepoliaContract = { + eid: EndpointId.SEPOLIA_V2_TESTNET, + contractName: 'MyOFT', +} + +export default { + contracts: [{ contract: arbsepContract }, { contract: sepoliaContract }], + connections: [ + { + from: arbsepContract, + to: sepoliaContract, + config: { + sendLibrary: '0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E', + receiveLibraryConfig: { receiveLibrary: '0x75Db67CDab2824970131D5aa9CECfC9F69c69636', gracePeriod: 0 }, + sendConfig: { + executorConfig: { maxMessageSize: 10000, executor: '0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897' }, + ulnConfig: { + confirmations: 1, + requiredDVNs: ['0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8'], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + // receiveConfig: { + // ulnConfig: { + // confirmations: 2, + // requiredDVNs: ['0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8'], + // optionalDVNs: [], + // optionalDVNThreshold: 0, + // }, + // }, + }, + }, + { + from: sepoliaContract, + to: arbsepContract, + config: { + sendLibrary: '0xcc1ae8Cf5D3904Cef3360A9532B477529b177cCE', + receiveLibraryConfig: { receiveLibrary: '0xdAf00F5eE2158dD58E0d3857851c432E34A3A851', gracePeriod: 0 }, + // sendConfig: { + // executorConfig: { maxMessageSize: 10000, executor: '0x718B92b5CB0a5552039B593faF724D182A881eDA' }, + // ulnConfig: { + // confirmations: 2, + // requiredDVNs: ['0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193'], + // optionalDVNs: [], + // optionalDVNThreshold: 0, + // }, + // }, + receiveConfig: { + ulnConfig: { + confirmations: 1, + requiredDVNs: ['0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193'], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + }, + }, + ], +} +``` + +
+ +
+ npx hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG + +
+ +Calls the configuration functions between your deployed OApp contracts on every chain based on the provided `layerzero.config.ts`. + +Running `lz:oapp:wire` will make the following function calls per pathway connection for a fully defined config file using your specified settings and your environment variables (Private Keys and RPCs): + +- function setPeer(uint32 \_eid, bytes32 \_peer) public virtual onlyOwner {} + +- function setConfig(address \_oapp, address \_lib, SetConfigParam[] calldata \_params) external onlyRegistered(\_lib) {} + +- function setEnforcedOptions(EnforcedOptionParam[] calldata \_enforcedOptions) public virtual onlyOwner {} + +- function setSendLibrary(address \_oapp, uint32 \_eid, address \_newLib) external onlyRegisteredOrDefault(\_newLib) isSendLib(\_newLib) onlySupportedEid(\_newLib, \_eid) {} + +- function setReceiveLibrary(address \_oapp, uint32 \_eid, address \_newLib, uint256 \_gracePeriod) external onlyRegisteredOrDefault(\_newLib) isReceiveLib(\_newLib) onlySupportedEid(\_newLib, \_eid) {} + +To use this task, run: + +```bash +npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE +``` + +Whenever you make changes to the configuration, run `lz:oapp:wire` again. The task will check your current configuration, and only apply NEW changes. + +To use a Gnosis Safe multisig as the signer for these transactions, add the following to each network in your `hardhat.config.ts` and add the `--safe` flag to `lz:oapp:wire --safe`: + +```yml +// hardhat.config.ts + +networks: { + // Include configurations for other networks as needed + fuji: { + /* ... */ + // Network-specific settings + safeConfig: { + safeUrl: 'http://something', // URL of the Safe API, not the Safe itself + safeAddress: 'address' + } + } +} +``` + +
+
+ npx hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG + +
+ +Returns your current OApp's configuration for each chain and pathway in 3 columns: + +- **Custom Configuration**: the changes that your `layerzero.config.ts` currently has set + +- **Default Configuration**: the default placeholder configuration that LayerZero provides + +- **Active Configuration**: the active configuration that applies to the message pathway (Defaults + Custom Values) + +If you do NOT explicitly set each configuration parameter, your OApp will fallback to the placeholder parameters in the default config. + +```bash +┌────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐ +│ │ Custom OApp Config │ Default OApp Config │ Active OApp Config │ +├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ +│ localNetworkName │ arbsep │ arbsep │ arbsep │ +├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ +│ remoteNetworkName │ sepolia │ sepolia │ sepolia │ +├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ +│ sendLibrary │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E │ +├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ +│ receiveLibrary │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 │ +├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ +│ sendUlnConfig │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ +│ │ │ confirmations │ 1 │ │ │ confirmations │ 1 │ │ │ confirmations │ 1 │ │ +│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ +│ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ +│ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ +│ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ +│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ +│ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │ +│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ +│ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │ +│ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ +│ │ │ │ │ +├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ +│ sendExecutorConfig │ ┌────────────────┬────────────────────────────────────────────┐ │ ┌────────────────┬────────────────────────────────────────────┐ │ ┌────────────────┬────────────────────────────────────────────┐ │ +│ │ │ executor │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │ │ │ executor │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │ │ │ executor │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │ │ +│ │ ├────────────────┼────────────────────────────────────────────┤ │ ├────────────────┼────────────────────────────────────────────┤ │ ├────────────────┼────────────────────────────────────────────┤ │ +│ │ │ maxMessageSize │ 10000 │ │ │ maxMessageSize │ 10000 │ │ │ maxMessageSize │ 10000 │ │ +│ │ └────────────────┴────────────────────────────────────────────┘ │ └────────────────┴────────────────────────────────────────────┘ │ └────────────────┴────────────────────────────────────────────┘ │ +│ │ │ │ │ +├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ +│ receiveUlnConfig │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ +│ │ │ confirmations │ 2 │ │ │ confirmations │ 2 │ │ │ confirmations │ 2 │ │ +│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ +│ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ +│ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ +│ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ │ +│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ +│ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │ +│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ +│ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │ +│ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ +│ │ │ │ │ +└────────────────────┴───────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┘ +``` + +
+
+ npx hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG + +
+ +Returns the LayerZero Executor config for each network in your `hardhat.config.ts`. You can use this method to see the max destination gas in wei (`nativeCap`) you can request in your [`execution options`](https://docs.layerzero.network/v2/developers/evm/gas-settings/options). + +```bash +┌───────────────────┬────────────────────────────────────────────┐ +│ localNetworkName │ mantle │ +├───────────────────┼────────────────────────────────────────────┤ +│ remoteNetworkName │ polygon │ +├───────────────────┼────────────────────────────────────────────┤ +│ executorDstConfig │ ┌────────────────┬───────────────────────┐ │ +│ │ │ baseGas │ 85000 │ │ +│ │ ├────────────────┼───────────────────────┤ │ +│ │ │ multiplierBps │ 12000 │ │ +│ │ ├────────────────┼───────────────────────┤ │ +│ │ │ floorMarginUSD │ 5000000000000000000 │ │ +│ │ ├────────────────┼───────────────────────┤ │ +│ │ │ nativeCap │ 681000000000000000000 │ │ +│ │ └────────────────┴───────────────────────┘ │ +│ │ │ +└───────────────────┴────────────────────────────────────────────┘ +``` + +
+ +## LayerZero Solana Helper Tasks + +This repo also comes with several Solana helpers in the form of Hardhat tasks to easily deploy, verify, configure, connect, and send OFTs on Solana. + +These helpers use the `@metaplex-foundation/umi` TypeScript framework to interact with the Solana blockchain. For more information, refer to the [Umi documentation](https://developers.metaplex.com/umi). + +
+ npx hardhat lz:oft:solana:create --program-id YOUR_OFT_PROGRAM_ID --eid YOUR_SOLANA_ENDPOINT_ID + +
+ +In addition to deploying your **OFT Program**, you will need to mint a new **SPL Token** and **OFT Config Account** to create your deployment. The `lz:oft:solana:create` task will create a new SPL Token Mint, and optionally mint an amount of tokens if given an amount. + +```typescript +task('lz:oft:solana:create', 'Mints new SPL Token and creates new OFT Config account') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet or testnet', undefined, types.eid) + .addOptionalParam('amount', 'The initial supply to mint on solana', undefined, types.int) + .setAction(async (taskArgs: Args) => { + // ... task continues + } +``` + +A successful mint will display the following output in your terminal: + +```bash +✅ Token Mint Complete! View the transaction here: https://explorer.solana.com/tx/5EeWgwuH52T1YMSs1vNJZdpGRd1o8K8HYn3T45vv17Z9rXwgYq2LWfAweJYCTXDxpjJTzmJXC3P3oRKLinhsBtZn +Your token account is: 2w9iFgRJzdSmuoNH4WiLiD1jtEr7t2YtgiGmWNwNgrgo +OFT Config: PublicKey [PublicKey(AuRJrMHgGwVVeby68cA7nu4twpwYedvJJfVJqCjt4J3y)] { + _bn: +} +✅ You created an OFT, view the transaction here: https://explorer.solana.com/tx/5nikt8nzmfQTtCWCA93wpBDSiaGJJpzKzetGgq2RPYGyF1Hekup88CXKTzBCEhPsSpUFW7kuT8VD5G3sgRNjrY4J +Accounts have been saved to ./deployments/solana-mainnet/OFT.json +``` + +
+ +
+ npx hardhat lz:oapp:init:solana --oapp-config YOUR_OAPP_CONFIG --solana-secret-key YOUR_SOLANA_SECRET_KEY + +
+ +After creating your Solana Config Account, you will need to initialize each account for every pathways you will be connecting. The `lz:oapp:init:solana` task will make these calls and initialize per the pathways defined in your `layerzero.config.ts`. + +```typescript +// ./layerzero.config.ts +const arbitrumContract: OmniPointHardhat = { + eid: EndpointId.ARBITRUM_V2_MAINNET, + contractName: "MyOFT", +}; + +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_MAINNET, + address: "YOUR_OFT_CONFIG_ACCOUNT", +}; + +// ... rest of LayerZero config +``` + +
+ +
+ npx hardhat lz:oapp:wire --oapp-config YOUR_OAPP_CONFIG --solana-program-id YOUR_OFT_PROGRAM_ID --solana-secret-key YOUR_SOLANA_SECRET_KEY + +
+ +After initializing your accounts, run the standard `lz:oapp:wire` with the new flags (`--solana-program-id` & `--solana-secret-key`). + +```bash +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts --solana-program-id YOUR_OFT_PROGRAM_ID --solana-secret-key YOUR_SOLANA_SECRET_KEY + + + + + + + ********** + ************** + ****************** + ******************** + ********* ********* + ********* ********* **** *********** + ********* ********* **** *********** + ****** ********* **** ************* **** ****** ******* **** ****** *** ** ****** + ******** ********* **** *********** **** **** ********** ******* **** *********** ****** ********** + ********* ******** **** **** **** ******** *** ***** **** **** **** ***** **** **** **** + ********* ****** **** **** **** ******* ******** **** ***** ********* *** **** **** + ********* ********* ********* *********** ****** ********** **** ********************** *** ********** **** + ********* ********* ********* ********** **** ******** **** *********** ******** *** ******* **** + ********* ********* ***** + ******************** **** + ****************** + **************** + ********** + + + + + + + +info: [OApp] Checking OApp configuration +info: [OApp] Checking OApp delegates configuration +info: [OApp] ✓ Checked OApp delegates +info: [OApp] Checking OApp peers configuration +info: [OApp] ✓ Checked OApp peers configuration +info: [OApp] Checking send libraries configuration +info: [OApp] ✓ Checked send libraries configuration +info: [OApp] Checking receive libraries configuration +info: [OApp] ✓ Checked receive libraries configuration +info: [OApp] Checking receive library timeout configuration +info: [OApp] ✓ Checked receive library timeout configuration +info: [OApp] Checking send configuration +info: [OApp] ✓ Checked send configuration +info: [OApp] Checking receive configuration +info: [OApp] ✓ Checked receive configuration +info: [OApp] Checking enforced options +info: [OApp] ✓ Checked enforced options +info: [OApp] Checking OApp callerBpsCap configuration +info: [OApp] ✓ Checked OApp callerBpsCap configuration +info: [OApp] ✓ Checked OApp configuration +info: The OApp is wired, no action is necessary +``` + +
+ +## Developing Contracts + +#### Installing dependencies + +We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice): + +```bash +pnpm install +``` + +#### Compiling your contracts + +This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: + +```bash +pnpm compile +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm compile:forge +pnpm compile:hardhat +``` + +Or adjust the `package.json` to for example remove `forge` build: + +```diff +- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat", +- "compile:forge": "forge build", +- "compile:hardhat": "hardhat compile", ++ "compile": "hardhat compile" +``` + +#### Running tests + +Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: + +```bash +pnpm test +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm test:forge +pnpm test:hardhat +pnpm test:anchor +``` + +Or adjust the `package.json` to for example remove `hardhat` tests: + +```diff +- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat", +- "test:forge": "forge test", +- "test:hardhat": "$npm_execpath hardhat test" ++ "test": "forge test" +``` + +## Deploying Contracts + +Set up deployer wallet/account: + +- Rename `.env.example` -> `.env` +- Choose your preferred means of setting up your deployer wallet/account: + +``` +MNEMONIC="test test test test test test test test test test test junk" +or... +PRIVATE_KEY="0xabc...def" +``` + +- Fund this address with the corresponding chain's native tokens you want to deploy to. + +To deploy your contracts to your desired blockchains, run the following command in your project's folder: + +```bash +npx hardhat lz:deploy +``` + +More information about available CLI arguments can be found using the `--help` flag: + +```bash +npx hardhat lz:deploy --help +``` + +By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication. + +

+ +## Connecting Contracts + +### Ethereum Configurations + +Fill out your `layerzero.config.ts` with the contracts you want to connect. You can generate the default config file for your declared hardhat networks by running: + +```bash +npx hardhat lz:oapp:config:init --contract-name [YOUR_CONTRACT_NAME] --oapp-config [CONFIG_NAME] +``` + +> [!NOTE] +> You may need to change the contract name if you're deploying multiple OApp contracts on different chains (e.g., OFT and OFT Adapter). + +
+ +```typescript +const ethereumContract: OmniPointHardhat = { + eid: EndpointId.ETHEREUM_V2_MAINNET, + contractName: "MyOFTAdapter", +}; + +const arbitrumContract: OmniPointHardhat = { + eid: EndpointId.ARBITRUM_V2_MAINNET, + contractName: "MyOFT", +}; +``` + +Then define the pathway you want to create from and to each contract: + +```typescript +connections: [ + // ETH <--> ARB PATHWAY: START + { + from: ethereumContract, + to: arbitrumContract, + }, + { + from: arbitrumContract, + to: ethereumContract, + }, + // ETH <--> ARB PATHWAY: END +]; +``` + +Finally, define the config settings for each direction of the pathway: + +```typescript +connections: [ + // ETH <--> ARB PATHWAY: START + { + from: ethereumContract, + to: arbitrumContract, + config: { + sendLibrary: contractsConfig.ethereum.sendLib302, + receiveLibraryConfig: { + receiveLibrary: contractsConfig.ethereum.receiveLib302, + gracePeriod: BigInt(0), + }, + // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid + receiveLibraryTimeoutConfig: { + lib: "0x0000000000000000000000000000000000000000", + expiry: BigInt(0), + }, + // Optional Send Configuration + // @dev Controls how the `from` chain sends messages to the `to` chain. + sendConfig: { + executorConfig: { + maxMessageSize: 10000, + // The configured Executor address + executor: contractsConfig.ethereum.executor, + }, + ulnConfig: { + // The number of block confirmations to wait on BSC before emitting the message from the source chain. + confirmations: BigInt(15), + // The address of the DVNs you will pay to verify a sent message on the source chain ). + // The destination tx will wait until ALL `requiredDVNs` verify the message. + requiredDVNs: [ + contractsConfig.ethereum.horizenDVN, // Horizen + contractsConfig.ethereum.polyhedraDVN, // Polyhedra + contractsConfig.ethereum.animocaBlockdaemonDVN, // Animoca-Blockdaemon (only available on ETH <-> Arbitrum One) + contractsConfig.ethereum.lzDVN, // LayerZero Labs + ], + // The address of the DVNs you will pay to verify a sent message on the source chain ). + // The destination tx will wait until the configured threshold of `optionalDVNs` verify a message. + optionalDVNs: [], + // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. + optionalDVNThreshold: 0, + }, + }, + // Optional Receive Configuration + // @dev Controls how the `from` chain receives messages from the `to` chain. + receiveConfig: { + ulnConfig: { + // The number of block confirmations to expect from the `to` chain. + confirmations: BigInt(20), + // The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain ). + // The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message. + requiredDVNs: [ + contractsConfig.ethereum.lzDVN, // LayerZero Labs DVN + contractsConfig.ethereum.animocaBlockdaemonDVN, // Blockdaemon-Animoca + contractsConfig.ethereum.horizenDVN, // Horizen Labs + contractsConfig.ethereum.polyhedraDVN, // Polyhedra + ], + // The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain ). + // The destination tx will wait until the configured threshold of `optionalDVNs` verify the message. + optionalDVNs: [], + // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. + optionalDVNThreshold: 0, + }, + }, + // Optional Enforced Options Configuration + // @dev Controls how much gas to use on the `to` chain, which the user pays for on the source `from` chain. + enforcedOptions: [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 65000, + value: 0, + }, + { + msgType: 2, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 65000, + value: 0, + }, + { + msgType: 2, + optionType: ExecutorOptionType.COMPOSE, + index: 0, + gas: 50000, + value: 0, + }, + ], + }, + }, + { + from: arbitrumContract, + to: ethereumContract, + }, + // ETH <--> ARB PATHWAY: END +]; +``` + +To set these config settings, run: + +```bash +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +

+ Join our community on Discord | Follow us on Twitter +

diff --git a/examples/omnicounter-solana/contracts/OmniCounter.sol b/examples/omnicounter-solana/contracts/OmniCounter.sol new file mode 100644 index 000000000..096954b6b --- /dev/null +++ b/examples/omnicounter-solana/contracts/OmniCounter.sol @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { ILayerZeroEndpointV2, MessagingFee, MessagingReceipt, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol"; + +import { OApp } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; +import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; +import { OAppPreCrimeSimulator } from "@layerzerolabs/oapp-evm/contracts/precrime/OAppPreCrimeSimulator.sol"; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +library MsgCodec { + uint8 internal constant VANILLA_TYPE = 1; + uint8 internal constant COMPOSED_TYPE = 2; + uint8 internal constant ABA_TYPE = 3; + uint8 internal constant COMPOSED_ABA_TYPE = 4; + + uint8 internal constant MSG_TYPE_OFFSET = 0; + uint8 internal constant SRC_EID_OFFSET = 1; + uint8 internal constant VALUE_OFFSET = 5; + + function encode(uint8 _type, uint32 _srcEid) internal pure returns (bytes memory) { + return abi.encodePacked(_type, _srcEid); + } + + function encode(uint8 _type, uint32 _srcEid, uint256 _value) internal pure returns (bytes memory) { + return abi.encodePacked(_type, _srcEid, _value); + } + + function msgType(bytes calldata _message) internal pure returns (uint8) { + return uint8(bytes1(_message[MSG_TYPE_OFFSET:SRC_EID_OFFSET])); + } + + function srcEid(bytes calldata _message) internal pure returns (uint32) { + return uint32(bytes4(_message[SRC_EID_OFFSET:VALUE_OFFSET])); + } + + function value(bytes calldata _message) internal pure returns (uint256) { + return uint256(bytes32(_message[VALUE_OFFSET:])); + } +} + +contract OmniCounter is ILayerZeroComposer, OApp, OAppPreCrimeSimulator { + using MsgCodec for bytes; + using OptionsBuilder for bytes; + + uint256 public count; + uint256 public composedCount; + + address public admin; + uint32 public eid; + + mapping(uint32 srcEid => mapping(bytes32 sender => uint64 nonce)) private maxReceivedNonce; + bool private orderedNonce; + + // for global assertions + mapping(uint32 srcEid => uint256 count) public inboundCount; + mapping(uint32 dstEid => uint256 count) public outboundCount; + + constructor(address _endpoint, address _delegate) OApp(_endpoint, _delegate) Ownable(_delegate) { + admin = msg.sender; + eid = ILayerZeroEndpointV2(_endpoint).eid(); + } + + modifier onlyAdmin() { + require(msg.sender == admin, "only admin"); + _; + } + + // ------------------------------- + // Only Admin + function setAdmin(address _admin) external onlyAdmin { + admin = _admin; + } + + function withdraw(address payable _to, uint256 _amount) external onlyAdmin { + (bool success, ) = _to.call{ value: _amount }(""); + require(success, "OmniCounter: withdraw failed"); + } + + // ------------------------------- + // Send + function increment(uint32 _eid, uint8 _type, bytes calldata _options) external payable { + // bytes memory options = combineOptions(_eid, _type, _options); + _lzSend(_eid, MsgCodec.encode(_type, eid), _options, MessagingFee(msg.value, 0), payable(msg.sender)); + _incrementOutbound(_eid); + } + + // this is a broken function to skip incrementing outbound count + // so that preCrime will fail + function brokenIncrement(uint32 _eid, uint8 _type, bytes calldata _options) external payable onlyAdmin { + // bytes memory options = combineOptions(_eid, _type, _options); + _lzSend(_eid, MsgCodec.encode(_type, eid), _options, MessagingFee(msg.value, 0), payable(msg.sender)); + } + + function batchIncrement( + uint32[] calldata _eids, + uint8[] calldata _types, + bytes[] calldata _options + ) external payable { + require(_eids.length == _options.length && _eids.length == _types.length, "OmniCounter: length mismatch"); + + MessagingReceipt memory receipt; + uint256 providedFee = msg.value; + for (uint256 i = 0; i < _eids.length; i++) { + address refundAddress = i == _eids.length - 1 ? msg.sender : address(this); + uint32 dstEid = _eids[i]; + uint8 msgType = _types[i]; + // bytes memory options = combineOptions(dstEid, msgType, _options[i]); + receipt = _lzSend( + dstEid, + MsgCodec.encode(msgType, eid), + _options[i], + MessagingFee(providedFee, 0), + payable(refundAddress) + ); + _incrementOutbound(dstEid); + providedFee -= receipt.fee.nativeFee; + } + } + + // ------------------------------- + // View + function quote( + uint32 _eid, + uint8 _type, + bytes calldata _options + ) public view returns (uint256 nativeFee, uint256 lzTokenFee) { + // bytes memory options = combineOptions(_eid, _type, _options); + MessagingFee memory fee = _quote(_eid, MsgCodec.encode(_type, eid), _options, false); + return (fee.nativeFee, fee.lzTokenFee); + } + + // @dev enables preCrime simulator + // @dev routes the call down from the OAppPreCrimeSimulator, and up to the OApp + function _lzReceiveSimulate( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata _extraData + ) internal virtual override { + _lzReceive(_origin, _guid, _message, _executor, _extraData); + } + + // ------------------------------- + function _lzReceive( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address /*_executor*/, + bytes calldata /*_extraData*/ + ) internal override { + _acceptNonce(_origin.srcEid, _origin.sender, _origin.nonce); + uint8 messageType = _message.msgType(); + + if (messageType == MsgCodec.VANILLA_TYPE) { + count++; + + //////////////////////////////// IMPORTANT ////////////////////////////////// + /// if you request for msg.value in the options, you should also encode it + /// into your message and check the value received at destination (example below). + /// if not, the executor could potentially provide less msg.value than you requested + /// leading to unintended behavior. Another option is to assert the executor to be + /// one that you trust. + ///////////////////////////////////////////////////////////////////////////// + require(msg.value >= _message.value(), "OmniCounter: insufficient value"); + + _incrementInbound(_origin.srcEid); + } else if (messageType == MsgCodec.COMPOSED_TYPE || messageType == MsgCodec.COMPOSED_ABA_TYPE) { + count++; + _incrementInbound(_origin.srcEid); + endpoint.sendCompose(address(this), _guid, 0, _message); + } else if (messageType == MsgCodec.ABA_TYPE) { + count++; + _incrementInbound(_origin.srcEid); + + // send back to the sender + _incrementOutbound(_origin.srcEid); + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 10); + _lzSend( + _origin.srcEid, + MsgCodec.encode(MsgCodec.VANILLA_TYPE, eid, 10), + options, + MessagingFee(msg.value, 0), + payable(address(this)) + ); + } else { + revert("invalid message type"); + } + } + + function _incrementInbound(uint32 _srcEid) internal { + inboundCount[_srcEid]++; + } + + function _incrementOutbound(uint32 _dstEid) internal { + outboundCount[_dstEid]++; + } + + function lzCompose( + address _oApp, + bytes32 /*_guid*/, + bytes calldata _message, + address, + bytes calldata + ) external payable override { + require(_oApp == address(this), "!oApp"); + require(msg.sender == address(endpoint), "!endpoint"); + + uint8 msgType = _message.msgType(); + if (msgType == MsgCodec.COMPOSED_TYPE) { + composedCount += 1; + } else if (msgType == MsgCodec.COMPOSED_ABA_TYPE) { + composedCount += 1; + + uint32 srcEid = _message.srcEid(); + _incrementOutbound(srcEid); + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + _lzSend( + srcEid, + MsgCodec.encode(MsgCodec.VANILLA_TYPE, eid), + options, + MessagingFee(msg.value, 0), + payable(address(this)) + ); + } else { + revert("invalid message type"); + } + } + + // ------------------------------- + // Ordered OApp + // this demonstrates how to build an app that requires execution nonce ordering + // normally an app should decide ordered or not on contract construction + // this is just a demo + function setOrderedNonce(bool _orderedNonce) external onlyOwner { + orderedNonce = _orderedNonce; + } + + function _acceptNonce(uint32 _srcEid, bytes32 _sender, uint64 _nonce) internal virtual { + uint64 currentNonce = maxReceivedNonce[_srcEid][_sender]; + if (orderedNonce) { + require(_nonce == currentNonce + 1, "OApp: invalid nonce"); + } + // update the max nonce anyway. once the ordered mode is turned on, missing early nonces will be rejected + if (_nonce > currentNonce) { + maxReceivedNonce[_srcEid][_sender] = _nonce; + } + } + + function nextNonce(uint32 _srcEid, bytes32 _sender) public view virtual override returns (uint64) { + if (orderedNonce) { + return maxReceivedNonce[_srcEid][_sender] + 1; + } else { + return 0; // path nonce starts from 1. if 0 it means that there is no specific nonce enforcement + } + } + + // TODO should override oApp version with added ordered nonce increment + // a governance function to skip nonce + function skipInboundNonce(uint32 _srcEid, bytes32 _sender, uint64 _nonce) public virtual onlyOwner { + endpoint.skip(address(this), _srcEid, _sender, _nonce); + if (orderedNonce) { + maxReceivedNonce[_srcEid][_sender]++; + } + } + + function isPeer(uint32 _eid, bytes32 _peer) public view override returns (bool) { + return peers[_eid] == _peer; + } + + // @dev Batch send requires overriding this function from OAppSender because the msg.value contains multiple fees + function _payNative(uint256 _nativeFee) internal virtual override returns (uint256 nativeFee) { + if (msg.value < _nativeFee) revert NotEnoughNative(msg.value); + return _nativeFee; + } + + // be able to receive ether + receive() external payable virtual {} + + fallback() external payable {} +} diff --git a/examples/omnicounter-solana/contracts/OmniCounterPreCrime.sol b/examples/omnicounter-solana/contracts/OmniCounterPreCrime.sol new file mode 100644 index 000000000..89844e458 --- /dev/null +++ b/examples/omnicounter-solana/contracts/OmniCounterPreCrime.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { PreCrime, PreCrimePeer } from "@layerzerolabs/oapp-evm/contracts/precrime/PreCrime.sol"; +import { InboundPacket } from "@layerzerolabs/oapp-evm/contracts/precrime/libs/Packet.sol"; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { OmniCounter } from "./OmniCounter.sol"; + +contract OmniCounterPreCrime is PreCrime { + struct ChainCount { + uint32 remoteEid; + uint256 inboundCount; + uint256 outboundCount; + } + + constructor(address _endpoint, address _counter) PreCrime(_endpoint, _counter) Ownable(msg.sender) {} + + function buildSimulationResult() external view override returns (bytes memory) { + address payable payableSimulator = payable(simulator); + OmniCounter counter = OmniCounter(payableSimulator); + ChainCount[] memory chainCounts = new ChainCount[](preCrimePeers.length); + for (uint256 i = 0; i < preCrimePeers.length; i++) { + uint32 remoteEid = preCrimePeers[i].eid; + chainCounts[i] = ChainCount(remoteEid, counter.inboundCount(remoteEid), counter.outboundCount(remoteEid)); + } + return abi.encode(chainCounts); + } + + function _preCrime( + InboundPacket[] memory /** _packets */, + uint32[] memory _eids, + bytes[] memory _simulations + ) internal view override { + uint32 localEid = _getLocalEid(); + ChainCount[] memory localChainCounts; + + // find local chain counts + for (uint256 i = 0; i < _eids.length; i++) { + if (_eids[i] == localEid) { + localChainCounts = abi.decode(_simulations[i], (ChainCount[])); + break; + } + } + + // local against remote + for (uint256 i = 0; i < _eids.length; i++) { + uint32 remoteEid = _eids[i]; + ChainCount[] memory remoteChainCounts = abi.decode(_simulations[i], (ChainCount[])); + (uint256 _inboundCount, ) = _findChainCounts(localChainCounts, remoteEid); + (, uint256 _outboundCount) = _findChainCounts(remoteChainCounts, localEid); + if (_inboundCount > _outboundCount) { + revert CrimeFound("inboundCount > outboundCount"); + } + } + } + + function _findChainCounts( + ChainCount[] memory _chainCounts, + uint32 _remoteEid + ) internal pure returns (uint256, uint256) { + for (uint256 i = 0; i < _chainCounts.length; i++) { + if (_chainCounts[i].remoteEid == _remoteEid) { + return (_chainCounts[i].inboundCount, _chainCounts[i].outboundCount); + } + } + return (0, 0); + } + + function _getPreCrimePeers( + InboundPacket[] memory _packets + ) internal view override returns (PreCrimePeer[] memory peers) { + PreCrimePeer[] memory allPeers = preCrimePeers; + PreCrimePeer[] memory peersTmp = new PreCrimePeer[](_packets.length); + + int256 cursor = -1; + for (uint256 i = 0; i < _packets.length; i++) { + uint32 srcEid = _packets[i].origin.srcEid; + + // push src eid & peer + int256 index = _indexOf(allPeers, srcEid); + if (index >= 0 && _indexOf(peersTmp, srcEid) < 0) { + cursor++; + peersTmp[uint256(cursor)] = allPeers[uint256(index)]; + } + } + // copy to return + if (cursor >= 0) { + uint256 len = uint256(cursor) + 1; + peers = new PreCrimePeer[](len); + for (uint256 i = 0; i < len; i++) { + peers[i] = peersTmp[i]; + } + } + } + + function _indexOf(PreCrimePeer[] memory _peers, uint32 _eid) internal pure returns (int256) { + for (uint256 i = 0; i < _peers.length; i++) { + if (_peers[i].eid == _eid) return int256(i); + } + return -1; + } +} diff --git a/examples/omnicounter-solana/deploy/OmniCounter.ts b/examples/omnicounter-solana/deploy/OmniCounter.ts new file mode 100644 index 000000000..b348eb3a9 --- /dev/null +++ b/examples/omnicounter-solana/deploy/OmniCounter.ts @@ -0,0 +1,51 @@ +import assert from 'assert' + +import { type DeployFunction } from 'hardhat-deploy/types' + +const contractName = 'OmniCounter' + +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: [ + 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/omnicounter-solana/foundry.toml b/examples/omnicounter-solana/foundry.toml new file mode 100644 index 000000000..37c3d3533 --- /dev/null +++ b/examples/omnicounter-solana/foundry.toml @@ -0,0 +1,27 @@ +[profile.default] +solc-version = '0.8.22' +src = 'contracts' +out = 'out' +test = 'test/foundry' +cache_path = 'cache/foundry' +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 ou 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/omnicounter-solana/hardhat.config.ts b/examples/omnicounter-solana/hardhat.config.ts new file mode 100644 index 000000000..d46df8e2d --- /dev/null +++ b/examples/omnicounter-solana/hardhat.config.ts @@ -0,0 +1,82 @@ +// 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 '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://rpc.sepolia.org/', + accounts, + }, + 'avalanche-testnet': { + eid: EndpointId.AVALANCHE_V2_TESTNET, + url: process.env.RPC_URL_FUJI || 'https://rpc.ankr.com/avalanche_fuji', + accounts, + }, + 'amoy-testnet': { + eid: EndpointId.AMOY_V2_TESTNET, + url: process.env.RPC_URL_AMOY || 'https://polygon-amoy-bor-rpc.publicnode.com', + accounts, + }, + }, + namedAccounts: { + deployer: { + default: 0, // wallet address of index[0], of the mnemonic in .env + }, + }, +} + +export default config diff --git a/examples/omnicounter-solana/idl/omnicounter.json b/examples/omnicounter-solana/idl/omnicounter.json new file mode 100644 index 000000000..286ade81c --- /dev/null +++ b/examples/omnicounter-solana/idl/omnicounter.json @@ -0,0 +1,546 @@ +{ + "version": "0.1.0", + "name": "omnicounter", + "instructions": [ + { + "name": "initCount", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "count", + "isMut": true, + "isSigner": false + }, + { + "name": "lzReceiveTypesAccounts", + "isMut": true, + "isSigner": false + }, + { + "name": "lzComposeTypesAccounts", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "InitCountParams" + } + } + ] + }, + { + "name": "setRemote", + "accounts": [ + { + "name": "admin", + "isMut": true, + "isSigner": true + }, + { + "name": "remote", + "isMut": true, + "isSigner": false + }, + { + "name": "count", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "SetRemoteParams" + } + } + ] + }, + { + "name": "quote", + "accounts": [ + { + "name": "count", + "isMut": false, + "isSigner": false + }, + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "QuoteParams" + } + } + ], + "returns": { + "defined": "MessagingFee" + } + }, + { + "name": "increment", + "accounts": [ + { + "name": "remote", + "isMut": false, + "isSigner": false + }, + { + "name": "count", + "isMut": false, + "isSigner": false + }, + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "IncrementParams" + } + } + ] + }, + { + "name": "lzReceive", + "accounts": [ + { + "name": "count", + "isMut": true, + "isSigner": false + }, + { + "name": "remote", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "LzReceiveParams" + } + } + ] + }, + { + "name": "lzReceiveTypes", + "accounts": [ + { + "name": "count", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "LzReceiveParams" + } + } + ], + "returns": { + "vec": { + "defined": "LzAccount" + } + } + }, + { + "name": "lzCompose", + "accounts": [ + { + "name": "count", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "LzComposeParams" + } + } + ] + }, + { + "name": "lzComposeTypes", + "accounts": [ + { + "name": "count", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "LzComposeParams" + } + } + ], + "returns": { + "vec": { + "defined": "LzAccount" + } + } + } + ], + "accounts": [ + { + "name": "EndpointSettings", + "type": { + "kind": "struct", + "fields": [ + { + "name": "eid", + "type": "u32" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "admin", + "type": "publicKey" + }, + { + "name": "lzTokenMint", + "type": { + "option": "publicKey" + } + } + ] + } + }, + { + "name": "Count", + "type": { + "kind": "struct", + "fields": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "admin", + "type": "publicKey" + }, + { + "name": "count", + "type": "u64" + }, + { + "name": "composedCount", + "type": "u64" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "endpointProgram", + "type": "publicKey" + } + ] + } + }, + { + "name": "LzComposeTypesAccounts", + "docs": [ + "LzComposeTypesAccounts includes accounts that are used in the LzComposeTypes", + "instruction." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "publicKey" + } + ] + } + }, + { + "name": "LzReceiveTypesAccounts", + "docs": [ + "LzReceiveTypesAccounts includes accounts that are used in the LzReceiveTypes", + "instruction." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "publicKey" + } + ] + } + }, + { + "name": "Remote", + "type": { + "kind": "struct", + "fields": [ + { + "name": "address", + "type": { + "array": ["u8", 32] + } + }, + { + "name": "bump", + "type": "u8" + } + ] + } + } + ], + "types": [ + { + "name": "MessagingFee", + "type": { + "kind": "struct", + "fields": [ + { + "name": "nativeFee", + "type": "u64" + }, + { + "name": "lzTokenFee", + "type": "u64" + } + ] + } + }, + { + "name": "LzComposeParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "from", + "type": "publicKey" + }, + { + "name": "to", + "type": "publicKey" + }, + { + "name": "guid", + "type": { + "array": ["u8", 32] + } + }, + { + "name": "index", + "type": "u16" + }, + { + "name": "message", + "type": "bytes" + }, + { + "name": "extraData", + "type": "bytes" + } + ] + } + }, + { + "name": "LzReceiveParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "srcEid", + "type": "u32" + }, + { + "name": "sender", + "type": { + "array": ["u8", 32] + } + }, + { + "name": "nonce", + "type": "u64" + }, + { + "name": "guid", + "type": { + "array": ["u8", 32] + } + }, + { + "name": "message", + "type": "bytes" + }, + { + "name": "extraData", + "type": "bytes" + } + ] + } + }, + { + "name": "LzAccount", + "docs": ["same to anchor_lang::prelude::AccountMeta"], + "type": { + "kind": "struct", + "fields": [ + { + "name": "pubkey", + "type": "publicKey" + }, + { + "name": "isSigner", + "type": "bool" + }, + { + "name": "isWritable", + "type": "bool" + } + ] + } + }, + { + "name": "IncrementParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dstEid", + "type": "u32" + }, + { + "name": "msgType", + "type": "u8" + }, + { + "name": "options", + "type": "bytes" + }, + { + "name": "nativeFee", + "type": "u64" + }, + { + "name": "lzTokenFee", + "type": "u64" + } + ] + } + }, + { + "name": "InitCountParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "admin", + "type": "publicKey" + }, + { + "name": "endpoint", + "type": "publicKey" + } + ] + } + }, + { + "name": "QuoteParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dstEid", + "type": "u32" + }, + { + "name": "receiver", + "type": { + "array": ["u8", 32] + } + }, + { + "name": "msgType", + "type": "u8" + }, + { + "name": "options", + "type": "bytes" + }, + { + "name": "payInLzToken", + "type": "bool" + } + ] + } + }, + { + "name": "SetRemoteParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "dstEid", + "type": "u32" + }, + { + "name": "remote", + "type": { + "array": ["u8", 32] + } + } + ] + } + } + ], + "errors": [ + { + "code": 6000, + "name": "InvalidMessageType" + } + ], + "metadata": { + "address": "2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu" + } +} diff --git a/examples/omnicounter-solana/jest.config.ts b/examples/omnicounter-solana/jest.config.ts new file mode 100644 index 000000000..a5634529d --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/junk-id.json b/examples/omnicounter-solana/junk-id.json new file mode 100644 index 000000000..8ccf579ce --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/layerzero.config.ts b/examples/omnicounter-solana/layerzero.config.ts new file mode 100644 index 000000000..d6143d728 --- /dev/null +++ b/examples/omnicounter-solana/layerzero.config.ts @@ -0,0 +1,68 @@ +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import type { OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' + +const sepoliaContract: OmniPointHardhat = { + eid: EndpointId.SEPOLIA_V2_TESTNET, + contractName: 'MyOFT', +} + +const fujiContract: OmniPointHardhat = { + eid: EndpointId.AVALANCHE_V2_TESTNET, + contractName: 'MyOFT', +} + +const amoyContract: OmniPointHardhat = { + eid: EndpointId.AMOY_V2_TESTNET, + contractName: 'MyOFT', +} + +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET, + address: '', +} + +const config: OAppOmniGraphHardhat = { + contracts: [ + { + contract: fujiContract, + }, + { + contract: sepoliaContract, + }, + { + contract: amoyContract, + }, + { + contract: solanaContract, + }, + ], + connections: [ + { + from: fujiContract, + to: sepoliaContract, + }, + { + from: fujiContract, + to: amoyContract, + }, + { + from: sepoliaContract, + to: fujiContract, + }, + { + from: sepoliaContract, + to: amoyContract, + }, + { + from: amoyContract, + to: sepoliaContract, + }, + { + from: amoyContract, + to: fujiContract, + }, + ], +} + +export default config diff --git a/examples/omnicounter-solana/oft-solana.png b/examples/omnicounter-solana/oft-solana.png new file mode 100644 index 000000000..56639969d Binary files /dev/null and b/examples/omnicounter-solana/oft-solana.png differ diff --git a/examples/omnicounter-solana/oft.jpg b/examples/omnicounter-solana/oft.jpg new file mode 100644 index 000000000..e0bcdbec5 Binary files /dev/null and b/examples/omnicounter-solana/oft.jpg differ diff --git a/examples/omnicounter-solana/package.json b/examples/omnicounter-solana/package.json new file mode 100644 index 000000000..71100f161 --- /dev/null +++ b/examples/omnicounter-solana/package.json @@ -0,0 +1,122 @@ +{ + "name": "@layerzerolabs/omnicounter-solana-example", + "version": "0.0.5", + "private": true, + "scripts": { + "clean": "rm -rf target artifacts cache out .anchor", + "compile": "$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 && $npm_execpath run test:anchor", + "test:anchor": "anchor test", + "test:forge": "forge test", + "test:hardhat": "hardhat test" + }, + "resolutions": { + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + }, + "devDependencies": { + "@babel/core": "^7.23.9", + "@coral-xyz/anchor": "^0.29.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@layerzerolabs/devtools": "~0.3.23", + "@layerzerolabs/devtools-evm": "~0.4.0", + "@layerzerolabs/devtools-evm-hardhat": "^1.1.0", + "@layerzerolabs/devtools-solana": "~0.1.6", + "@layerzerolabs/eslint-config-next": "~2.3.34", + "@layerzerolabs/io-devtools": "~0.1.11", + "@layerzerolabs/lz-definitions": "^2.3.34", + "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.34", + "@layerzerolabs/lz-evm-protocol-v2": "^2.3.34", + "@layerzerolabs/lz-evm-v1-0.7": "^2.3.34", + "@layerzerolabs/lz-solana-sdk-v2": "^2.3.31", + "@layerzerolabs/lz-v2-utilities": "^2.3.34", + "@layerzerolabs/oapp-evm": "^0.0.3", + "@layerzerolabs/oft-evm": "^0.0.10", + "@layerzerolabs/prettier-config-next": "^2.3.34", + "@layerzerolabs/protocol-devtools": "^0.4.2", + "@layerzerolabs/protocol-devtools-solana": "^1.0.5", + "@layerzerolabs/solhint-config": "^2.3.34", + "@layerzerolabs/test-devtools-evm-foundry": "~0.2.11", + "@layerzerolabs/test-devtools-evm-hardhat": "0.2.6", + "@layerzerolabs/toolbox-foundry": "~0.1.9", + "@layerzerolabs/toolbox-hardhat": "~0.3.6", + "@layerzerolabs/tsup-config-next": "^2.3.31", + "@layerzerolabs/typescript-config-next": "^2.3.31", + "@layerzerolabs/ua-devtools": "~1.0.4", + "@layerzerolabs/ua-devtools-evm": "~3.0.0", + "@layerzerolabs/ua-devtools-evm-hardhat": "~3.0.0", + "@layerzerolabs/ua-devtools-solana": "~1.0.4", + "@metaplex-foundation/beet": "^0.7.1", + "@metaplex-foundation/beet-solana": "^0.4.0", + "@metaplex-foundation/mpl-token-metadata": "^3.2.1", + "@metaplex-foundation/mpl-toolbox": "^0.9.4", + "@metaplex-foundation/solita": "^0.20.1", + "@metaplex-foundation/umi": "^0.9.2", + "@metaplex-foundation/umi-bundle-defaults": "^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", + "@openzeppelin/contracts": "^5.0.2", + "@openzeppelin/contracts-upgradeable": "^5.0.2", + "@rushstack/eslint-patch": "^1.7.0", + "@solana-developers/helpers": "~2.4.0", + "@solana/spl-token": "^0.4.8", + "@solana/web3.js": "~1.95.0", + "@sqds/sdk": "^2.0.4", + "@swc/core": "^1.4.0", + "@swc/jest": "^0.2.36", + "@types/bn.js": "^5.1.5", + "@types/chai": "^4.3.11", + "@types/jest": "^29.5.12", + "@types/mocha": "^10.0.6", + "@types/node": "~18.18.14", + "bip39": "^3.1.0", + "bn.js": "^5.2.1", + "bs58": "^6.0.0", + "chai": "^4.4.1", + "dotenv": "^16.4.5", + "ed25519-hd-key": "^1.3.0", + "eslint": "^8.55.0", + "eslint-plugin-jest-extended": "~2.0.0", + "ethereumjs-util": "^7.1.5", + "ethers": "^5.7.2", + "fp-ts": "^2.16.2", + "hardhat": "^2.22.3", + "hardhat-contract-sizer": "^2.10.0", + "hardhat-deploy": "^0.12.1", + "jest": "^29.7.0", + "mocha": "^10.2.0", + "prettier": "^3.2.5", + "rimraf": "^5.0.5", + "solhint": "^4.1.1", + "solidity-bytes-utils": "^0.8.2", + "tiny-invariant": "^1.3.1", + "ts-mocha": "^10.0.0", + "ts-node": "^10.9.2", + "tsup": "^8.0.1", + "typescript": "^5.4.4", + "zx": "^8.1.3" + }, + "engines": { + "node": ">=18.16.0" + }, + "pnpm": { + "overrides": { + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + } + }, + "overrides": { + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + } +} diff --git a/examples/omnicounter-solana/programs/counter/Cargo.toml b/examples/omnicounter-solana/programs/counter/Cargo.toml new file mode 100644 index 000000000..be45554dd --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "omnicounter" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "omnicounter" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["oapp/idl-build"] + +[dependencies] +anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } +oapp = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", branch = "main" } diff --git a/examples/omnicounter-solana/programs/counter/Xargo.toml b/examples/omnicounter-solana/programs/counter/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/omnicounter-solana/programs/counter/readme.md b/examples/omnicounter-solana/programs/counter/readme.md new file mode 100644 index 000000000..a4c57371f --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/readme.md @@ -0,0 +1 @@ +### solana counter program diff --git a/examples/omnicounter-solana/programs/counter/src/errors.rs b/examples/omnicounter-solana/programs/counter/src/errors.rs new file mode 100644 index 000000000..8e1dcada4 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/errors.rs @@ -0,0 +1,6 @@ +use anchor_lang::prelude::error_code; + +#[error_code] +pub enum CounterError { + InvalidMessageType, +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/increment.rs b/examples/omnicounter-solana/programs/counter/src/instructions/increment.rs new file mode 100644 index 000000000..9bf13e7d2 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/increment.rs @@ -0,0 +1,56 @@ +use crate::*; +use anchor_lang::prelude::*; +use oapp::endpoint::{ + instructions::SendParams, state::EndpointSettings, ENDPOINT_SEED, ID as ENDPOINT_ID, +}; + +#[derive(Accounts)] +#[instruction(params: IncrementParams)] +pub struct Increment<'info> { + #[account( + seeds = [ + REMOTE_SEED, + &count.key().to_bytes(), + ¶ms.dst_eid.to_be_bytes() + ], + bump = remote.bump + )] + pub remote: Account<'info, Remote>, + #[account(seeds = [COUNT_SEED, &count.id.to_be_bytes()], bump = count.bump)] + pub count: Account<'info, Count>, + #[account(seeds = [ENDPOINT_SEED], bump = endpoint.bump, seeds::program = ENDPOINT_ID)] + pub endpoint: Account<'info, EndpointSettings>, +} +impl<'info> Increment<'info> { + pub fn apply(ctx: &mut Context, params: &IncrementParams) -> Result<()> { + let message = msg_codec::encode(params.msg_type, ctx.accounts.endpoint.eid); + let seeds: &[&[u8]] = &[COUNT_SEED, &[ctx.accounts.count.id], &[ctx.accounts.count.bump]]; + + // calling endpoint cpi + let send_params = SendParams { + dst_eid: params.dst_eid, + receiver: ctx.accounts.remote.address, + message, + options: params.options.clone(), + native_fee: params.native_fee, + lz_token_fee: params.lz_token_fee, + }; + oapp::endpoint_cpi::send( + ENDPOINT_ID, + ctx.accounts.count.key(), + ctx.remaining_accounts, + seeds, + send_params, + )?; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct IncrementParams { + pub dst_eid: u32, + pub msg_type: u8, + pub options: Vec, + pub native_fee: u64, + pub lz_token_fee: u64, +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/init_count.rs b/examples/omnicounter-solana/programs/counter/src/instructions/init_count.rs new file mode 100644 index 000000000..e95f021ce --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/init_count.rs @@ -0,0 +1,66 @@ +use crate::*; +use oapp::endpoint::{instructions::RegisterOAppParams, ID as ENDPOINT_ID}; + +#[derive(Accounts)] +#[instruction(params: InitCountParams)] +pub struct InitCount<'info> { + #[account(mut)] + pub payer: Signer<'info>, + #[account( + init, + payer = payer, + space = Count::SIZE, + seeds = [COUNT_SEED, ¶ms.id.to_be_bytes()], + bump + )] + pub count: Account<'info, Count>, + #[account( + init, + payer = payer, + space = LzReceiveTypesAccounts::SIZE, + seeds = [LZ_RECEIVE_TYPES_SEED, &count.key().to_bytes()], + bump + )] + pub lz_receive_types_accounts: Account<'info, LzReceiveTypesAccounts>, + #[account( + init, + payer = payer, + space = LzComposeTypesAccounts::SIZE, + seeds = [LZ_COMPOSE_TYPES_SEED, &count.key().to_bytes()], + bump + )] + pub lz_compose_types_accounts: Account<'info, LzComposeTypesAccounts>, + pub system_program: Program<'info, System>, +} + +impl InitCount<'_> { + pub fn apply(ctx: &mut Context, params: &InitCountParams) -> Result<()> { + ctx.accounts.count.id = params.id; + ctx.accounts.count.admin = params.admin; + ctx.accounts.count.bump = ctx.bumps.count; + ctx.accounts.count.endpoint_program = params.endpoint; + + ctx.accounts.lz_receive_types_accounts.count = ctx.accounts.count.key(); + ctx.accounts.lz_compose_types_accounts.count = ctx.accounts.count.key(); + + // calling endpoint cpi + let register_params = RegisterOAppParams { delegate: ctx.accounts.count.admin }; + let seeds: &[&[u8]] = &[COUNT_SEED, &[ctx.accounts.count.id], &[ctx.accounts.count.bump]]; + oapp::endpoint_cpi::register_oapp( + ENDPOINT_ID, + ctx.accounts.count.key(), + ctx.remaining_accounts, + seeds, + register_params, + )?; + + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct InitCountParams { + pub id: u8, + pub admin: Pubkey, + pub endpoint: Pubkey, +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/lz_compose.rs b/examples/omnicounter-solana/programs/counter/src/instructions/lz_compose.rs new file mode 100644 index 000000000..9369acd9c --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/lz_compose.rs @@ -0,0 +1,34 @@ +use crate::*; +use anchor_lang::prelude::*; +use oapp::{ + endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}, + LzComposeParams, +}; + +#[derive(Accounts)] +pub struct LzCompose<'info> { + #[account(mut, seeds = [COUNT_SEED, &count.id.to_be_bytes()], bump = count.bump)] + pub count: Account<'info, Count>, +} + +impl LzCompose<'_> { + pub fn apply(ctx: &mut Context, params: &LzComposeParams) -> Result<()> { + ctx.accounts.count.composed_count += 1; + + let seeds: &[&[u8]] = + &[COUNT_SEED, &ctx.accounts.count.id.to_be_bytes(), &[ctx.accounts.count.bump]]; + let params = ClearComposeParams { + from: params.from, + guid: params.guid, + index: params.index, + message: params.message.clone(), + }; + oapp::endpoint_cpi::clear_compose( + ENDPOINT_ID, + ctx.accounts.count.key(), + &ctx.remaining_accounts, + seeds, + params, + ) + } +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/lz_compose_types.rs b/examples/omnicounter-solana/programs/counter/src/instructions/lz_compose_types.rs new file mode 100644 index 000000000..189fbd32f --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/lz_compose_types.rs @@ -0,0 +1,43 @@ +use crate::*; +use oapp::endpoint_cpi::{get_accounts_for_clear_compose, LzAccount}; +use oapp::{endpoint::ID as ENDPOINT_ID, LzComposeParams}; + +/// LzComposeTypes instruction provides a list of accounts that are used in the LzCompose +/// instruction. The list of accounts required by this LzComposeTypes instruction can be found +/// from the specific PDA account that is generated by the LZ_COMPOSE_TYPES_SEED. +#[derive(Accounts)] +pub struct LzComposeTypes<'info> { + #[account(seeds = [COUNT_SEED, &count.id.to_be_bytes()], bump = count.bump)] + pub count: Account<'info, Count>, +} + +impl LzComposeTypes<'_> { + /// 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> { + let mut accounts = vec![ + // count + LzAccount { pubkey: ctx.accounts.count.key(), is_signer: false, is_writable: true }, + ]; + + let accounts_for_composing = get_accounts_for_clear_compose( + ENDPOINT_ID, + &ctx.accounts.count.key(), + &ctx.accounts.count.key(), // self + ¶ms.guid, + params.index, + ¶ms.message, + ); + + accounts.extend(accounts_for_composing); + + Ok(accounts) + } +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/lz_receive.rs b/examples/omnicounter-solana/programs/counter/src/instructions/lz_receive.rs new file mode 100644 index 000000000..2676d8ff9 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/lz_receive.rs @@ -0,0 +1,71 @@ +use crate::*; +use anchor_lang::prelude::*; +use oapp::{ + endpoint::{ + cpi::accounts::Clear, + instructions::{ClearParams, SendComposeParams}, + ConstructCPIContext, ID as ENDPOINT_ID, + }, + LzReceiveParams, +}; + +#[derive(Accounts)] +#[instruction(params: LzReceiveParams)] +pub struct LzReceive<'info> { + #[account(mut, seeds = [COUNT_SEED, &count.id.to_be_bytes()], bump = count.bump)] + pub count: Account<'info, Count>, + #[account( + seeds = [REMOTE_SEED, &count.key().to_bytes(), ¶ms.src_eid.to_be_bytes()], + bump = remote.bump, + constraint = params.sender == remote.address + )] + pub remote: Account<'info, Remote>, +} + +impl LzReceive<'_> { + pub fn apply(ctx: &mut Context, params: &LzReceiveParams) -> Result<()> { + let seeds: &[&[u8]] = + &[COUNT_SEED, &ctx.accounts.count.id.to_be_bytes(), &[ctx.accounts.count.bump]]; + + // the first 9 accounts are for clear() + let accounts_for_clear = &ctx.remaining_accounts[0..Clear::MIN_ACCOUNTS_LEN]; + let _ = oapp::endpoint_cpi::clear( + ENDPOINT_ID, + ctx.accounts.count.key(), + accounts_for_clear, + seeds, + ClearParams { + receiver: ctx.accounts.count.key(), + src_eid: params.src_eid, + sender: params.sender, + nonce: params.nonce, + guid: params.guid, + message: params.message.clone(), + }, + )?; + + let msg_type = msg_codec::msg_type(¶ms.message); + match msg_type { + msg_codec::VANILLA_TYPE => ctx.accounts.count.count += 1, + msg_codec::COMPOSED_TYPE => { + ctx.accounts.count.count += 1; + + oapp::endpoint_cpi::send_compose( + ENDPOINT_ID, + ctx.accounts.count.key(), + &ctx.remaining_accounts[Clear::MIN_ACCOUNTS_LEN..], + seeds, + SendComposeParams { + to: ctx.accounts.count.key(), // self + guid: params.guid, + index: 0, + message: params.message.clone(), + }, + )?; + }, + // ABA_TYPE & COMPOSED_ABA_TYPE are not supported + _ => return Err(CounterError::InvalidMessageType.into()), + } + Ok(()) + } +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/lz_receive_types.rs b/examples/omnicounter-solana/programs/counter/src/instructions/lz_receive_types.rs new file mode 100644 index 000000000..44c88c87d --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/lz_receive_types.rs @@ -0,0 +1,67 @@ +use crate::*; +use oapp::endpoint_cpi::{get_accounts_for_clear, get_accounts_for_send_compose, LzAccount}; +use oapp::{endpoint::ID as ENDPOINT_ID, LzReceiveParams}; + +/// LzReceiveTypes instruction provides a list of accounts that are used in the LzReceive +/// instruction. The list of accounts required by this LzReceiveTypes instruction can be found +/// from the specific PDA account that is generated by the LZ_RECEIVE_TYPES_SEED. +#[derive(Accounts)] +pub struct LzReceiveTypes<'info> { + #[account(seeds = [COUNT_SEED, &count.id.to_be_bytes()], bump = count.bump)] + pub count: Account<'info, Count>, +} + +impl LzReceiveTypes<'_> { + /// The list of accounts should follow the rules below: + /// 1. Include all the accounts that are used in the LzReceive instruction, including the + /// accounts that are used by the Endpoint program. + /// 2. Set the account is a signer with ZERO address if the LzReceive instruction needs a payer + /// to pay fee, like rent. + /// 3. Set the account is writable if the LzReceive instruction needs to modify the account. + pub fn apply( + ctx: &Context, + params: &LzReceiveParams, + ) -> Result> { + // There are two accounts that are used in the LzReceive instruction, + // except those accounts for endpoint program. + // The first account is the count account, that is the fixed one. + let count = ctx.accounts.count.key(); + + // The second account is the remote account, we find it by the params.src_eid. + let seeds = [REMOTE_SEED, &count.to_bytes(), ¶ms.src_eid.to_be_bytes()]; + let (remote, _) = Pubkey::find_program_address(&seeds, ctx.program_id); + + let mut accounts = vec![ + // count + LzAccount { pubkey: count, is_signer: false, is_writable: true }, + // remote + LzAccount { pubkey: remote, is_signer: false, is_writable: false }, + ]; + + // append the accounts for the clear ix + let accounts_for_clear = get_accounts_for_clear( + ENDPOINT_ID, + &count, + params.src_eid, + ¶ms.sender, + params.nonce, + ); + accounts.extend(accounts_for_clear); + + // if the message type is composed, we need to append the accounts for the composing ix + let is_composed = msg_codec::msg_type(¶ms.message) == msg_codec::COMPOSED_TYPE; + if is_composed { + let accounts_for_composing = get_accounts_for_send_compose( + ENDPOINT_ID, + &count, + &count, // self + ¶ms.guid, + 0, + ¶ms.message, + ); + accounts.extend(accounts_for_composing); + } + + Ok(accounts) + } +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/mod.rs b/examples/omnicounter-solana/programs/counter/src/instructions/mod.rs new file mode 100644 index 000000000..db5e73da2 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/mod.rs @@ -0,0 +1,17 @@ +pub mod increment; +pub mod init_count; +pub mod lz_compose; +pub mod lz_compose_types; +pub mod lz_receive; +pub mod lz_receive_types; +pub mod quote; +pub mod set_remote; + +pub use increment::*; +pub use init_count::*; +pub use lz_compose::*; +pub use lz_compose_types::*; +pub use lz_receive::*; +pub use lz_receive_types::*; +pub use quote::*; +pub use set_remote::*; diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/quote.rs b/examples/omnicounter-solana/programs/counter/src/instructions/quote.rs new file mode 100644 index 000000000..9d9af2af8 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/quote.rs @@ -0,0 +1,40 @@ +use crate::*; +use anchor_lang::prelude::*; +use oapp::endpoint::{ + instructions::QuoteParams as EndpointQuoteParams, state::EndpointSettings, ENDPOINT_SEED, + ID as ENDPOINT_ID, +}; + +#[derive(Accounts)] +#[instruction(params: QuoteParams)] +pub struct Quote<'info> { + #[account(seeds = [COUNT_SEED, &count.id.to_be_bytes()], bump = count.bump)] + pub count: Account<'info, Count>, + #[account(seeds = [ENDPOINT_SEED], bump = endpoint.bump, seeds::program = ENDPOINT_ID)] + pub endpoint: Account<'info, EndpointSettings>, +} +impl<'info> Quote<'info> { + pub fn apply(ctx: &Context, params: &QuoteParams) -> Result { + let message = msg_codec::encode(params.msg_type, ctx.accounts.endpoint.eid); + + // calling endpoint cpi + let quote_params = EndpointQuoteParams { + sender: ctx.accounts.count.key(), + dst_eid: params.dst_eid, + receiver: params.receiver, + message, + pay_in_lz_token: params.pay_in_lz_token, + options: params.options.clone(), + }; + oapp::endpoint_cpi::quote(ENDPOINT_ID, ctx.remaining_accounts, quote_params) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct QuoteParams { + pub dst_eid: u32, + pub receiver: [u8; 32], + pub msg_type: u8, + pub options: Vec, + pub pay_in_lz_token: bool, +} diff --git a/examples/omnicounter-solana/programs/counter/src/instructions/set_remote.rs b/examples/omnicounter-solana/programs/counter/src/instructions/set_remote.rs new file mode 100644 index 000000000..ee71f7831 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/instructions/set_remote.rs @@ -0,0 +1,35 @@ +use crate::*; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +#[instruction(params: SetRemoteParams)] +pub struct SetRemote<'info> { + #[account(mut, address = count.admin)] + pub admin: Signer<'info>, + #[account( + init_if_needed, + payer = admin, + space = Remote::SIZE, + seeds = [REMOTE_SEED, &count.key().to_bytes(), ¶ms.dst_eid.to_be_bytes()], + bump + )] + pub remote: Account<'info, Remote>, + #[account(seeds = [COUNT_SEED, &count.id.to_be_bytes()], bump = count.bump)] + pub count: Account<'info, Count>, + pub system_program: Program<'info, System>, +} + +impl SetRemote<'_> { + pub fn apply(ctx: &mut Context, params: &SetRemoteParams) -> Result<()> { + ctx.accounts.remote.address = params.remote; + ctx.accounts.remote.bump = ctx.bumps.remote; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct SetRemoteParams { + pub id: u8, + pub dst_eid: u32, + pub remote: [u8; 32], +} diff --git a/examples/omnicounter-solana/programs/counter/src/lib.rs b/examples/omnicounter-solana/programs/counter/src/lib.rs new file mode 100644 index 000000000..5b102626d --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/lib.rs @@ -0,0 +1,60 @@ +mod errors; +mod instructions; +mod msg_codec; +mod state; + +use anchor_lang::prelude::*; +use errors::*; +use instructions::*; +use oapp::{endpoint::MessagingFee, endpoint_cpi::LzAccount, LzComposeParams, LzReceiveParams}; +use state::*; + +declare_id!("2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu"); + +const LZ_RECEIVE_TYPES_SEED: &[u8] = b"LzReceiveTypes"; +const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; +const COUNT_SEED: &[u8] = b"Count"; +const REMOTE_SEED: &[u8] = b"Remote"; + +#[program] +pub mod omnicounter { + use super::*; + + pub fn init_count(mut ctx: Context, params: InitCountParams) -> Result<()> { + InitCount::apply(&mut ctx, ¶ms) + } + + pub fn set_remote(mut ctx: Context, params: SetRemoteParams) -> Result<()> { + SetRemote::apply(&mut ctx, ¶ms) + } + + pub fn quote(ctx: Context, params: QuoteParams) -> Result { + Quote::apply(&ctx, ¶ms) + } + + pub fn increment(mut ctx: Context, params: IncrementParams) -> Result<()> { + Increment::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) + } + + 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> { + LzComposeTypes::apply(&ctx, ¶ms) + } +} diff --git a/examples/omnicounter-solana/programs/counter/src/msg_codec.rs b/examples/omnicounter-solana/programs/counter/src/msg_codec.rs new file mode 100644 index 000000000..388eff2a0 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/msg_codec.rs @@ -0,0 +1,25 @@ +pub const VANILLA_TYPE: u8 = 1; +pub const COMPOSED_TYPE: u8 = 2; +// ABA_TYPE & COMPOSED_ABA_TYPE are not supported +// pub const ABA_TYPE: u8 = 3; +// pub const COMPOSED_ABA_TYPE: u8 = 4; + +pub const MSG_TYPE_OFFSET: usize = 0; +pub const SRC_EID_OFFSET: usize = 1; + +pub fn encode(msg_type: u8, src_eid: u32) -> Vec { + let mut encoded = Vec::new(); + encoded.push(msg_type); + encoded.extend_from_slice(&src_eid.to_be_bytes()); + encoded +} + +pub fn msg_type(message: &[u8]) -> u8 { + message[MSG_TYPE_OFFSET] +} + +pub fn src_eid(message: &[u8]) -> u32 { + let mut eid_bytes = [0; 4]; + eid_bytes.copy_from_slice(&message[SRC_EID_OFFSET..]); + u32::from_be_bytes(eid_bytes) +} diff --git a/examples/omnicounter-solana/programs/counter/src/state/count.rs b/examples/omnicounter-solana/programs/counter/src/state/count.rs new file mode 100644 index 000000000..1ae08f24e --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/state/count.rs @@ -0,0 +1,37 @@ +use crate::*; + +#[account] +pub struct Count { + pub id: u8, + pub admin: Pubkey, + pub count: u64, + pub composed_count: u64, + pub bump: u8, + pub endpoint_program: Pubkey, +} + +impl Count { + pub const SIZE: usize = 8 + std::mem::size_of::(); +} + +/// LzReceiveTypesAccounts includes accounts that are used in the LzReceiveTypes +/// instruction. +#[account] +pub struct LzReceiveTypesAccounts { + pub count: Pubkey, +} + +impl LzReceiveTypesAccounts { + 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/omnicounter-solana/programs/counter/src/state/mod.rs b/examples/omnicounter-solana/programs/counter/src/state/mod.rs new file mode 100644 index 000000000..465e0d870 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/state/mod.rs @@ -0,0 +1,5 @@ +pub mod count; +mod remote; + +pub use count::*; +pub use remote::*; diff --git a/examples/omnicounter-solana/programs/counter/src/state/remote.rs b/examples/omnicounter-solana/programs/counter/src/state/remote.rs new file mode 100644 index 000000000..3b1bc3723 --- /dev/null +++ b/examples/omnicounter-solana/programs/counter/src/state/remote.rs @@ -0,0 +1,11 @@ +use crate::*; + +#[account] +pub struct Remote { + pub address: [u8; 32], + pub bump: u8, +} + +impl Remote { + pub const SIZE: usize = 8 + std::mem::size_of::(); +} diff --git a/examples/omnicounter-solana/rust-toolchain.toml b/examples/omnicounter-solana/rust-toolchain.toml new file mode 100644 index 000000000..114aa9750 --- /dev/null +++ b/examples/omnicounter-solana/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.75.0" +# rustup install nightly +# channel = "nightly-2024-02-04" # if you want to expand the macro, you need to use nightly version +components = ["rustfmt", "clippy"] diff --git a/examples/omnicounter-solana/rustfmt.toml b/examples/omnicounter-solana/rustfmt.toml new file mode 100644 index 000000000..e9f667f2e --- /dev/null +++ b/examples/omnicounter-solana/rustfmt.toml @@ -0,0 +1,23 @@ +# Basic +hard_tabs = false +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Preserve" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Format comments +comment_width = 100 +wrap_comments = true +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Front" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = true +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = true +use_field_init_shorthand = true \ No newline at end of file diff --git a/examples/omnicounter-solana/scripts/config.ts b/examples/omnicounter-solana/scripts/config.ts new file mode 100644 index 000000000..4c81f9313 --- /dev/null +++ b/examples/omnicounter-solana/scripts/config.ts @@ -0,0 +1,206 @@ +import { arrayify, hexZeroPad } from '@ethersproject/bytes' +import { Connection, Keypair, PublicKey, Signer, TransactionInstruction } from '@solana/web3.js' + +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { + EndpointProgram, + ExecutorPDADeriver, + SetConfigType, + UlnProgram, + buildVersionedTransaction, +} from '@layerzerolabs/lz-solana-sdk-v2' + +import { OmniCounterProgram } from '../src' + +const endpointProgram = new EndpointProgram.Endpoint(new PublicKey('76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6')) // endpoint program id, mainnet and testnet are the same +const ulnProgram = new UlnProgram.Uln(new PublicKey('7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH')) // uln program id, mainnet and testnet are the same +const executorProgram = new PublicKey('6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn') // executor program id, mainnet and testnet are the same + +const counterProgram = new OmniCounterProgram.OmniCounter(new PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu')) + +const connection = new Connection('https://api.mainnet-beta.solana.com') +const signer = Keypair.fromSecretKey(new Uint8Array([])) +const remotePeers: { [key in EndpointId]?: string } = { + [EndpointId.ARBSEP_V2_TESTNET]: '0x7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH', // EVM counter addr +} + +;(async () => { + await initCounter(connection, signer, signer) + for (const [remoteStr, remotePeer] of Object.entries(remotePeers)) { + const remotePeerBytes = arrayify(hexZeroPad(remotePeer, 32)) + const remote = parseInt(remoteStr) as EndpointId + await setPeers(connection, signer, remote, remotePeerBytes) + await initSendLibrary(connection, signer, remote) + await initReceiveLibrary(connection, signer, remote) + await initOappNonce(connection, signer, remote, remotePeerBytes) + await setSendLibrary(connection, signer, remote) + await setReceiveLibrary(connection, signer, remote) + await initUlnConfig(connection, signer, signer, remote) + await setOappExecutor(connection, signer, remote) + } +})() + +async function initCounter(connection: Connection, payer: Keypair, admin: Keypair): Promise { + const [count] = counterProgram.idPDA() + let current = false + try { + await OmniCounterProgram.accounts.Count.fromAccountAddress(connection, count, { + commitment: 'confirmed', + }) + current = true + } catch (e) { + /*count not init*/ + } + const ix = await counterProgram.initCount( + connection, + payer.publicKey, + admin.publicKey, // admin/delegate double check it, is the same public key + endpointProgram + ) + if (ix == null) { + // already initialized + return Promise.resolve() + } + sendAndConfirm(connection, [admin], [ix]) +} + +async function setPeers( + connection: Connection, + admin: Keypair, + remote: EndpointId, + remotePeer: Uint8Array +): Promise { + const ix = counterProgram.setRemote(admin.publicKey, remotePeer, remote) + const [remotePDA] = counterProgram.omniCounterDeriver.remote(remote) + let current = '' + try { + const info = await OmniCounterProgram.accounts.Remote.fromAccountAddress(connection, remotePDA, { + commitment: 'confirmed', + }) + current = Buffer.from(info.address).toString('hex') + } catch (e) { + /*remote not init*/ + } + if (current == Buffer.from(remotePeer).toString('hex')) { + return Promise.resolve() + } + sendAndConfirm(connection, [admin], [ix]) +} + +async function initSendLibrary(connection: Connection, admin: Keypair, remote: EndpointId): Promise { + const [id] = counterProgram.idPDA() + const ix = await endpointProgram.initSendLibrary(connection, admin.publicKey, id, remote) + if (ix == null) { + return Promise.resolve() + } + sendAndConfirm(connection, [admin], [ix]) +} + +async function initReceiveLibrary(connection: Connection, admin: Keypair, remote: EndpointId): Promise { + const [id] = counterProgram.idPDA() + const ix = await endpointProgram.initReceiveLibrary(connection, admin.publicKey, id, remote) + if (ix == null) { + return Promise.resolve() + } + sendAndConfirm(connection, [admin], [ix]) +} + +async function initOappNonce( + connection: Connection, + admin: Keypair, + remote: EndpointId, + remotePeer: Uint8Array +): Promise { + const [id] = counterProgram.idPDA() + const ix = await endpointProgram.initOAppNonce(connection, admin.publicKey, remote, id, remotePeer) + if (ix === null) return Promise.resolve() + const current = false + try { + const nonce = await endpointProgram.getNonce(connection, id, remote, remotePeer) + if (nonce) { + console.log('nonce already set') + return Promise.resolve() + } + } catch (e) { + /*nonce not init*/ + } + sendAndConfirm(connection, [admin], [ix]) +} + +async function setSendLibrary(connection: Connection, admin: Keypair, remote: EndpointId): Promise { + const [id] = counterProgram.idPDA() + const sendLib = await endpointProgram.getSendLibrary(connection, id, remote) + const current = sendLib ? sendLib.msgLib.toBase58() : '' + const [expectedSendLib] = ulnProgram.deriver.messageLib() + const expected = expectedSendLib.toBase58() + if (current === expected) { + return Promise.resolve() + } + const ix = await endpointProgram.setSendLibrary(admin.publicKey, id, ulnProgram.program, remote) + sendAndConfirm(connection, [admin], [ix]) +} + +async function setReceiveLibrary(connection: Connection, admin: Keypair, remote: EndpointId): Promise { + const [id] = counterProgram.idPDA() + const receiveLib = await endpointProgram.getReceiveLibrary(connection, id, remote) + const current = receiveLib ? receiveLib.msgLib.toBase58() : '' + const [expectedMessageLib] = ulnProgram.deriver.messageLib() + const expected = expectedMessageLib.toBase58() + if (current === expected) { + return Promise.resolve() + } + const ix = await endpointProgram.setReceiveLibrary(admin.publicKey, id, ulnProgram.program, remote) + sendAndConfirm(connection, [admin], [ix]) +} + +async function initUlnConfig( + connection: Connection, + payer: Keypair, + admin: Keypair, + remote: EndpointId +): Promise { + const [id] = counterProgram.idPDA() + + const current = await ulnProgram.getSendConfigState(connection, id, remote) + if (current) { + return Promise.resolve() + } + const ix = await endpointProgram.initOappConfig(admin.publicKey, ulnProgram, payer.publicKey, id, remote) + sendAndConfirm(connection, [admin], [ix]) +} + +async function setOappExecutor(connection: Connection, admin: Keypair, remote: EndpointId): Promise { + const [id] = counterProgram.idPDA() + const defaultOutboundMaxMessageSize = 10000 + + const [executorPda] = new ExecutorPDADeriver(executorProgram).config() + const expected: UlnProgram.types.ExecutorConfig = { + maxMessageSize: defaultOutboundMaxMessageSize, + executor: executorPda, + } + + const current = (await ulnProgram.getSendConfigState(connection, id, remote))?.executor + const ix = await endpointProgram.setOappConfig(connection, admin.publicKey, id, ulnProgram.program, remote, { + configType: SetConfigType.EXECUTOR, + value: expected, + }) + if ( + current && + current.executor.toBase58() === expected.executor.toBase58() && + current.maxMessageSize === expected.maxMessageSize + ) { + return Promise.resolve() + } + await sendAndConfirm(connection, [admin], [ix]) +} + +async function sendAndConfirm( + connection: Connection, + signers: Signer[], + instructions: TransactionInstruction[] +): Promise { + const tx = await buildVersionedTransaction(connection, signers[0].publicKey, instructions, 'confirmed') + tx.sign(signers) + const hash = await connection.sendTransaction(tx, { skipPreflight: true }) + await connection.confirmTransaction(hash, 'confirmed') +} diff --git a/examples/omnicounter-solana/scripts/generate.ts b/examples/omnicounter-solana/scripts/generate.ts new file mode 100644 index 000000000..6e3d31c13 --- /dev/null +++ b/examples/omnicounter-solana/scripts/generate.ts @@ -0,0 +1,21 @@ +import { writeFile } from 'fs/promises' +import * as path from 'path' + +import { Solita } from '@metaplex-foundation/solita' + +async function generateTypeScriptSDK() { + const generatedIdlDir = path.join(__dirname, '..', 'idl') + const address = '2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu' + const generatedSDKDir = path.join(__dirname, '..', 'src', 'generated', 'omnicounter') + const idl = require('../idl/omnicounter.json') + if (idl.metadata?.address == null) { + idl.metadata = { ...idl.metadata, address } + await writeFile(generatedIdlDir + '/omnicounter.json', JSON.stringify(idl, null, 2)) + } + const gen = new Solita(idl, { formatCode: true }) + await gen.renderAndWriteTo(generatedSDKDir) +} + +;(async () => { + await generateTypeScriptSDK() +})() diff --git a/examples/omnicounter-solana/solhint.config.js b/examples/omnicounter-solana/solhint.config.js new file mode 100644 index 000000000..52efe629c --- /dev/null +++ b/examples/omnicounter-solana/solhint.config.js @@ -0,0 +1 @@ +module.exports = require('@layerzerolabs/solhint-config'); diff --git a/examples/omnicounter-solana/src/generated/omnicounter/accounts/Count.ts b/examples/omnicounter-solana/src/generated/omnicounter/accounts/Count.ts new file mode 100644 index 000000000..e36629860 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/accounts/Count.ts @@ -0,0 +1,192 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beet from '@metaplex-foundation/beet' +import * as beetSolana from '@metaplex-foundation/beet-solana' + +/** + * Arguments used to create {@link Count} + * @category Accounts + * @category generated + */ +export type CountArgs = { + id: number + admin: web3.PublicKey + count: beet.bignum + composedCount: beet.bignum + bump: number + endpointProgram: web3.PublicKey +} + +export const countDiscriminator = [116, 199, 152, 236, 90, 156, 182, 0] +/** + * Holds the data for the {@link Count} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class Count implements CountArgs { + private constructor( + readonly id: number, + readonly admin: web3.PublicKey, + readonly count: beet.bignum, + readonly composedCount: beet.bignum, + readonly bump: number, + readonly endpointProgram: web3.PublicKey + ) {} + + /** + * Creates a {@link Count} instance from the provided args. + */ + static fromArgs(args: CountArgs) { + return new Count(args.id, args.admin, args.count, args.composedCount, args.bump, args.endpointProgram) + } + + /** + * Deserializes the {@link Count} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo(accountInfo: web3.AccountInfo, offset = 0): [Count, number] { + return Count.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link Count} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo(address, commitmentOrConfig) + if (accountInfo == null) { + throw new Error(`Unable to find Count account at ${address}`) + } + return Count.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder(programId: web3.PublicKey = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu')) { + return beetSolana.GpaBuilder.fromStruct(programId, countBeet) + } + + /** + * Deserializes the {@link Count} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [Count, number] { + return countBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link Count} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return countBeet.serialize({ + accountDiscriminator: countDiscriminator, + ...this, + }) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link Count} + */ + static get byteSize() { + return countBeet.byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link Count} data from rent + * + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption(Count.byteSize, commitment) + } + + /** + * Determines if the provided {@link Buffer} has the correct byte size to + * hold {@link Count} data. + */ + static hasCorrectByteSize(buf: Buffer, offset = 0) { + return buf.byteLength - offset === Count.byteSize + } + + /** + * Returns a readable version of {@link Count} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + id: this.id, + admin: this.admin.toBase58(), + count: (() => { + const x = <{ toNumber: () => number }>this.count + if (typeof x.toNumber === 'function') { + try { + return x.toNumber() + } catch (_) { + return x + } + } + return x + })(), + composedCount: (() => { + const x = <{ toNumber: () => number }>this.composedCount + if (typeof x.toNumber === 'function') { + try { + return x.toNumber() + } catch (_) { + return x + } + } + return x + })(), + bump: this.bump, + endpointProgram: this.endpointProgram.toBase58(), + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const countBeet = new beet.BeetStruct< + Count, + CountArgs & { + accountDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['accountDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['id', beet.u8], + ['admin', beetSolana.publicKey], + ['count', beet.u64], + ['composedCount', beet.u64], + ['bump', beet.u8], + ['endpointProgram', beetSolana.publicKey], + ], + Count.fromArgs, + 'Count' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/accounts/EndpointSettings.ts b/examples/omnicounter-solana/src/generated/omnicounter/accounts/EndpointSettings.ts new file mode 100644 index 000000000..84dc997bf --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/accounts/EndpointSettings.ts @@ -0,0 +1,166 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beet from '@metaplex-foundation/beet' +import * as beetSolana from '@metaplex-foundation/beet-solana' + +/** + * Arguments used to create {@link EndpointSettings} + * @category Accounts + * @category generated + */ +export type EndpointSettingsArgs = { + eid: number + bump: number + admin: web3.PublicKey + lzTokenMint: beet.COption +} + +export const endpointSettingsDiscriminator = [221, 232, 73, 56, 10, 66, 72, 14] +/** + * Holds the data for the {@link EndpointSettings} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class EndpointSettings implements EndpointSettingsArgs { + private constructor( + readonly eid: number, + readonly bump: number, + readonly admin: web3.PublicKey, + readonly lzTokenMint: beet.COption + ) {} + + /** + * Creates a {@link EndpointSettings} instance from the provided args. + */ + static fromArgs(args: EndpointSettingsArgs) { + return new EndpointSettings(args.eid, args.bump, args.admin, args.lzTokenMint) + } + + /** + * Deserializes the {@link EndpointSettings} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo(accountInfo: web3.AccountInfo, offset = 0): [EndpointSettings, number] { + return EndpointSettings.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link EndpointSettings} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo(address, commitmentOrConfig) + if (accountInfo == null) { + throw new Error(`Unable to find EndpointSettings account at ${address}`) + } + return EndpointSettings.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder(programId: web3.PublicKey = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu')) { + return beetSolana.GpaBuilder.fromStruct(programId, endpointSettingsBeet) + } + + /** + * Deserializes the {@link EndpointSettings} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [EndpointSettings, number] { + return endpointSettingsBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link EndpointSettings} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return endpointSettingsBeet.serialize({ + accountDiscriminator: endpointSettingsDiscriminator, + ...this, + }) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link EndpointSettings} for the provided args. + * + * @param args need to be provided since the byte size for this account + * depends on them + */ + static byteSize(args: EndpointSettingsArgs) { + const instance = EndpointSettings.fromArgs(args) + return endpointSettingsBeet.toFixedFromValue({ + accountDiscriminator: endpointSettingsDiscriminator, + ...instance, + }).byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link EndpointSettings} data from rent + * + * @param args need to be provided since the byte size for this account + * depends on them + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + args: EndpointSettingsArgs, + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption(EndpointSettings.byteSize(args), commitment) + } + + /** + * Returns a readable version of {@link EndpointSettings} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + eid: this.eid, + bump: this.bump, + admin: this.admin.toBase58(), + lzTokenMint: this.lzTokenMint, + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const endpointSettingsBeet = new beet.FixableBeetStruct< + EndpointSettings, + EndpointSettingsArgs & { + accountDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['accountDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['eid', beet.u32], + ['bump', beet.u8], + ['admin', beetSolana.publicKey], + ['lzTokenMint', beet.coption(beetSolana.publicKey)], + ], + EndpointSettings.fromArgs, + 'EndpointSettings' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/accounts/LzComposeTypesAccounts.ts b/examples/omnicounter-solana/src/generated/omnicounter/accounts/LzComposeTypesAccounts.ts new file mode 100644 index 000000000..12017dcbb --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/accounts/LzComposeTypesAccounts.ts @@ -0,0 +1,150 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beetSolana from '@metaplex-foundation/beet-solana' +import * as beet from '@metaplex-foundation/beet' + +/** + * Arguments used to create {@link LzComposeTypesAccounts} + * @category Accounts + * @category generated + */ +export type LzComposeTypesAccountsArgs = { + count: web3.PublicKey +} + +export const lzComposeTypesAccountsDiscriminator = [157, 200, 178, 122, 8, 235, 148, 103] +/** + * Holds the data for the {@link LzComposeTypesAccounts} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class LzComposeTypesAccounts implements LzComposeTypesAccountsArgs { + private constructor(readonly count: web3.PublicKey) {} + + /** + * Creates a {@link LzComposeTypesAccounts} instance from the provided args. + */ + static fromArgs(args: LzComposeTypesAccountsArgs) { + return new LzComposeTypesAccounts(args.count) + } + + /** + * Deserializes the {@link LzComposeTypesAccounts} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo(accountInfo: web3.AccountInfo, offset = 0): [LzComposeTypesAccounts, number] { + return LzComposeTypesAccounts.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link LzComposeTypesAccounts} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo(address, commitmentOrConfig) + if (accountInfo == null) { + throw new Error(`Unable to find LzComposeTypesAccounts account at ${address}`) + } + return LzComposeTypesAccounts.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder(programId: web3.PublicKey = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu')) { + return beetSolana.GpaBuilder.fromStruct(programId, lzComposeTypesAccountsBeet) + } + + /** + * Deserializes the {@link LzComposeTypesAccounts} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [LzComposeTypesAccounts, number] { + return lzComposeTypesAccountsBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link LzComposeTypesAccounts} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return lzComposeTypesAccountsBeet.serialize({ + accountDiscriminator: lzComposeTypesAccountsDiscriminator, + ...this, + }) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link LzComposeTypesAccounts} + */ + static get byteSize() { + return lzComposeTypesAccountsBeet.byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link LzComposeTypesAccounts} data from rent + * + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption(LzComposeTypesAccounts.byteSize, commitment) + } + + /** + * Determines if the provided {@link Buffer} has the correct byte size to + * hold {@link LzComposeTypesAccounts} data. + */ + static hasCorrectByteSize(buf: Buffer, offset = 0) { + return buf.byteLength - offset === LzComposeTypesAccounts.byteSize + } + + /** + * Returns a readable version of {@link LzComposeTypesAccounts} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + count: this.count.toBase58(), + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const lzComposeTypesAccountsBeet = new beet.BeetStruct< + LzComposeTypesAccounts, + LzComposeTypesAccountsArgs & { + accountDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['accountDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['count', beetSolana.publicKey], + ], + LzComposeTypesAccounts.fromArgs, + 'LzComposeTypesAccounts' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/accounts/LzReceiveTypesAccounts.ts b/examples/omnicounter-solana/src/generated/omnicounter/accounts/LzReceiveTypesAccounts.ts new file mode 100644 index 000000000..2b073febb --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/accounts/LzReceiveTypesAccounts.ts @@ -0,0 +1,150 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beetSolana from '@metaplex-foundation/beet-solana' +import * as beet from '@metaplex-foundation/beet' + +/** + * Arguments used to create {@link LzReceiveTypesAccounts} + * @category Accounts + * @category generated + */ +export type LzReceiveTypesAccountsArgs = { + count: web3.PublicKey +} + +export const lzReceiveTypesAccountsDiscriminator = [248, 87, 167, 117, 5, 251, 21, 126] +/** + * Holds the data for the {@link LzReceiveTypesAccounts} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class LzReceiveTypesAccounts implements LzReceiveTypesAccountsArgs { + private constructor(readonly count: web3.PublicKey) {} + + /** + * Creates a {@link LzReceiveTypesAccounts} instance from the provided args. + */ + static fromArgs(args: LzReceiveTypesAccountsArgs) { + return new LzReceiveTypesAccounts(args.count) + } + + /** + * Deserializes the {@link LzReceiveTypesAccounts} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo(accountInfo: web3.AccountInfo, offset = 0): [LzReceiveTypesAccounts, number] { + return LzReceiveTypesAccounts.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link LzReceiveTypesAccounts} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo(address, commitmentOrConfig) + if (accountInfo == null) { + throw new Error(`Unable to find LzReceiveTypesAccounts account at ${address}`) + } + return LzReceiveTypesAccounts.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder(programId: web3.PublicKey = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu')) { + return beetSolana.GpaBuilder.fromStruct(programId, lzReceiveTypesAccountsBeet) + } + + /** + * Deserializes the {@link LzReceiveTypesAccounts} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [LzReceiveTypesAccounts, number] { + return lzReceiveTypesAccountsBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link LzReceiveTypesAccounts} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return lzReceiveTypesAccountsBeet.serialize({ + accountDiscriminator: lzReceiveTypesAccountsDiscriminator, + ...this, + }) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link LzReceiveTypesAccounts} + */ + static get byteSize() { + return lzReceiveTypesAccountsBeet.byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link LzReceiveTypesAccounts} data from rent + * + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption(LzReceiveTypesAccounts.byteSize, commitment) + } + + /** + * Determines if the provided {@link Buffer} has the correct byte size to + * hold {@link LzReceiveTypesAccounts} data. + */ + static hasCorrectByteSize(buf: Buffer, offset = 0) { + return buf.byteLength - offset === LzReceiveTypesAccounts.byteSize + } + + /** + * Returns a readable version of {@link LzReceiveTypesAccounts} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + count: this.count.toBase58(), + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const lzReceiveTypesAccountsBeet = new beet.BeetStruct< + LzReceiveTypesAccounts, + LzReceiveTypesAccountsArgs & { + accountDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['accountDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['count', beetSolana.publicKey], + ], + LzReceiveTypesAccounts.fromArgs, + 'LzReceiveTypesAccounts' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/accounts/Remote.ts b/examples/omnicounter-solana/src/generated/omnicounter/accounts/Remote.ts new file mode 100644 index 000000000..3b4d2979c --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/accounts/Remote.ts @@ -0,0 +1,156 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import * as beetSolana from '@metaplex-foundation/beet-solana' + +/** + * Arguments used to create {@link Remote} + * @category Accounts + * @category generated + */ +export type RemoteArgs = { + address: number[] /* size: 32 */ + bump: number +} + +export const remoteDiscriminator = [142, 141, 61, 250, 205, 173, 146, 34] +/** + * Holds the data for the {@link Remote} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class Remote implements RemoteArgs { + private constructor( + readonly address: number[] /* size: 32 */, + readonly bump: number + ) {} + + /** + * Creates a {@link Remote} instance from the provided args. + */ + static fromArgs(args: RemoteArgs) { + return new Remote(args.address, args.bump) + } + + /** + * Deserializes the {@link Remote} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo(accountInfo: web3.AccountInfo, offset = 0): [Remote, number] { + return Remote.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link Remote} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo(address, commitmentOrConfig) + if (accountInfo == null) { + throw new Error(`Unable to find Remote account at ${address}`) + } + return Remote.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder(programId: web3.PublicKey = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu')) { + return beetSolana.GpaBuilder.fromStruct(programId, remoteBeet) + } + + /** + * Deserializes the {@link Remote} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [Remote, number] { + return remoteBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link Remote} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return remoteBeet.serialize({ + accountDiscriminator: remoteDiscriminator, + ...this, + }) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link Remote} + */ + static get byteSize() { + return remoteBeet.byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link Remote} data from rent + * + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption(Remote.byteSize, commitment) + } + + /** + * Determines if the provided {@link Buffer} has the correct byte size to + * hold {@link Remote} data. + */ + static hasCorrectByteSize(buf: Buffer, offset = 0) { + return buf.byteLength - offset === Remote.byteSize + } + + /** + * Returns a readable version of {@link Remote} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + address: this.address, + bump: this.bump, + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const remoteBeet = new beet.BeetStruct< + Remote, + RemoteArgs & { + accountDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['accountDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['address', beet.uniformFixedSizeArray(beet.u8, 32)], + ['bump', beet.u8], + ], + Remote.fromArgs, + 'Remote' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/accounts/index.ts b/examples/omnicounter-solana/src/generated/omnicounter/accounts/index.ts new file mode 100644 index 000000000..5fdba5e34 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/accounts/index.ts @@ -0,0 +1,19 @@ +export * from './Count' +export * from './EndpointSettings' +export * from './LzComposeTypesAccounts' +export * from './LzReceiveTypesAccounts' +export * from './Remote' + +import { EndpointSettings } from './EndpointSettings' +import { Count } from './Count' +import { LzComposeTypesAccounts } from './LzComposeTypesAccounts' +import { LzReceiveTypesAccounts } from './LzReceiveTypesAccounts' +import { Remote } from './Remote' + +export const accountProviders = { + EndpointSettings, + Count, + LzComposeTypesAccounts, + LzReceiveTypesAccounts, + Remote, +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/errors/index.ts b/examples/omnicounter-solana/src/generated/omnicounter/errors/index.ts new file mode 100644 index 000000000..f881dbf24 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/errors/index.ts @@ -0,0 +1,52 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +type ErrorWithCode = Error & { code: number } +type MaybeErrorWithCode = ErrorWithCode | null | undefined + +const createErrorFromCodeLookup: Map ErrorWithCode> = new Map() +const createErrorFromNameLookup: Map ErrorWithCode> = new Map() + +/** + * InvalidMessageType: '' + * + * @category Errors + * @category generated + */ +export class InvalidMessageTypeError extends Error { + readonly code: number = 0x1770 + readonly name: string = 'InvalidMessageType' + constructor() { + super('') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidMessageTypeError) + } + } +} + +createErrorFromCodeLookup.set(0x1770, () => new InvalidMessageTypeError()) +createErrorFromNameLookup.set('InvalidMessageType', () => new InvalidMessageTypeError()) + +/** + * Attempts to resolve a custom program error from the provided error code. + * @category Errors + * @category generated + */ +export function errorFromCode(code: number): MaybeErrorWithCode { + const createError = createErrorFromCodeLookup.get(code) + return createError != null ? createError() : null +} + +/** + * Attempts to resolve a custom program error from the provided error name, i.e. 'Unauthorized'. + * @category Errors + * @category generated + */ +export function errorFromName(name: string): MaybeErrorWithCode { + const createError = createErrorFromNameLookup.get(name) + return createError != null ? createError() : null +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/index.ts b/examples/omnicounter-solana/src/generated/omnicounter/index.ts new file mode 100644 index 000000000..af9339855 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/index.ts @@ -0,0 +1,21 @@ +import { PublicKey } from '@solana/web3.js' +export * from './accounts' +export * from './errors' +export * from './instructions' +export * from './types' + +/** + * Program address + * + * @category constants + * @category generated + */ +export const PROGRAM_ADDRESS = '2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu' + +/** + * Program public key + * + * @category constants + * @category generated + */ +export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/increment.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/increment.ts new file mode 100644 index 000000000..69a6b4a33 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/increment.ts @@ -0,0 +1,104 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { IncrementParams, incrementParamsBeet } from '../types/IncrementParams' + +/** + * @category Instructions + * @category Increment + * @category generated + */ +export type IncrementInstructionArgs = { + params: IncrementParams +} +/** + * @category Instructions + * @category Increment + * @category generated + */ +export const incrementStruct = new beet.FixableBeetArgsStruct< + IncrementInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', incrementParamsBeet], + ], + 'IncrementInstructionArgs' +) +/** + * Accounts required by the _increment_ instruction + * + * @property [] remote + * @property [] count + * @property [] endpoint + * @category Instructions + * @category Increment + * @category generated + */ +export type IncrementInstructionAccounts = { + remote: web3.PublicKey + count: web3.PublicKey + endpoint: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const incrementInstructionDiscriminator = [11, 18, 104, 9, 104, 174, 59, 33] + +/** + * Creates a _Increment_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category Increment + * @category generated + */ +export function createIncrementInstruction( + accounts: IncrementInstructionAccounts, + args: IncrementInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = incrementStruct.serialize({ + instructionDiscriminator: incrementInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.remote, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.count, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.endpoint, + isWritable: false, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/index.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/index.ts new file mode 100644 index 000000000..2a3236780 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/index.ts @@ -0,0 +1,8 @@ +export * from './increment' +export * from './initCount' +export * from './lzCompose' +export * from './lzComposeTypes' +export * from './lzReceive' +export * from './lzReceiveTypes' +export * from './quote' +export * from './setRemote' diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/initCount.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/initCount.ts new file mode 100644 index 000000000..b1c630756 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/initCount.ts @@ -0,0 +1,117 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { InitCountParams, initCountParamsBeet } from '../types/InitCountParams' + +/** + * @category Instructions + * @category InitCount + * @category generated + */ +export type InitCountInstructionArgs = { + params: InitCountParams +} +/** + * @category Instructions + * @category InitCount + * @category generated + */ +export const initCountStruct = new beet.BeetArgsStruct< + InitCountInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', initCountParamsBeet], + ], + 'InitCountInstructionArgs' +) +/** + * Accounts required by the _initCount_ instruction + * + * @property [_writable_, **signer**] payer + * @property [_writable_] count + * @property [_writable_] lzReceiveTypesAccounts + * @property [_writable_] lzComposeTypesAccounts + * @category Instructions + * @category InitCount + * @category generated + */ +export type InitCountInstructionAccounts = { + payer: web3.PublicKey + count: web3.PublicKey + lzReceiveTypesAccounts: web3.PublicKey + lzComposeTypesAccounts: web3.PublicKey + systemProgram?: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const initCountInstructionDiscriminator = [230, 121, 12, 3, 79, 21, 202, 1] + +/** + * Creates a _InitCount_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category InitCount + * @category generated + */ +export function createInitCountInstruction( + accounts: InitCountInstructionAccounts, + args: InitCountInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = initCountStruct.serialize({ + instructionDiscriminator: initCountInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.count, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.lzReceiveTypesAccounts, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.lzComposeTypesAccounts, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzCompose.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzCompose.ts new file mode 100644 index 000000000..a6e1c913a --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzCompose.ts @@ -0,0 +1,90 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { LzComposeParams, lzComposeParamsBeet } from '../types/LzComposeParams' + +/** + * @category Instructions + * @category LzCompose + * @category generated + */ +export type LzComposeInstructionArgs = { + params: LzComposeParams +} +/** + * @category Instructions + * @category LzCompose + * @category generated + */ +export const lzComposeStruct = new beet.FixableBeetArgsStruct< + LzComposeInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', lzComposeParamsBeet], + ], + 'LzComposeInstructionArgs' +) +/** + * Accounts required by the _lzCompose_ instruction + * + * @property [_writable_] count + * @category Instructions + * @category LzCompose + * @category generated + */ +export type LzComposeInstructionAccounts = { + count: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const lzComposeInstructionDiscriminator = [143, 252, 164, 222, 203, 105, 240, 7] + +/** + * Creates a _LzCompose_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category LzCompose + * @category generated + */ +export function createLzComposeInstruction( + accounts: LzComposeInstructionAccounts, + args: LzComposeInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = lzComposeStruct.serialize({ + instructionDiscriminator: lzComposeInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.count, + isWritable: true, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzComposeTypes.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzComposeTypes.ts new file mode 100644 index 000000000..b97c79fc2 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzComposeTypes.ts @@ -0,0 +1,90 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { LzComposeParams, lzComposeParamsBeet } from '../types/LzComposeParams' + +/** + * @category Instructions + * @category LzComposeTypes + * @category generated + */ +export type LzComposeTypesInstructionArgs = { + params: LzComposeParams +} +/** + * @category Instructions + * @category LzComposeTypes + * @category generated + */ +export const lzComposeTypesStruct = new beet.FixableBeetArgsStruct< + LzComposeTypesInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', lzComposeParamsBeet], + ], + 'LzComposeTypesInstructionArgs' +) +/** + * Accounts required by the _lzComposeTypes_ instruction + * + * @property [] count + * @category Instructions + * @category LzComposeTypes + * @category generated + */ +export type LzComposeTypesInstructionAccounts = { + count: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const lzComposeTypesInstructionDiscriminator = [112, 121, 229, 66, 151, 84, 64, 50] + +/** + * Creates a _LzComposeTypes_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category LzComposeTypes + * @category generated + */ +export function createLzComposeTypesInstruction( + accounts: LzComposeTypesInstructionAccounts, + args: LzComposeTypesInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = lzComposeTypesStruct.serialize({ + instructionDiscriminator: lzComposeTypesInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.count, + isWritable: false, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzReceive.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzReceive.ts new file mode 100644 index 000000000..d3687cf62 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzReceive.ts @@ -0,0 +1,97 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { LzReceiveParams, lzReceiveParamsBeet } from '../types/LzReceiveParams' + +/** + * @category Instructions + * @category LzReceive + * @category generated + */ +export type LzReceiveInstructionArgs = { + params: LzReceiveParams +} +/** + * @category Instructions + * @category LzReceive + * @category generated + */ +export const lzReceiveStruct = new beet.FixableBeetArgsStruct< + LzReceiveInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', lzReceiveParamsBeet], + ], + 'LzReceiveInstructionArgs' +) +/** + * Accounts required by the _lzReceive_ instruction + * + * @property [_writable_] count + * @property [] remote + * @category Instructions + * @category LzReceive + * @category generated + */ +export type LzReceiveInstructionAccounts = { + count: web3.PublicKey + remote: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const lzReceiveInstructionDiscriminator = [8, 179, 120, 109, 33, 118, 189, 80] + +/** + * Creates a _LzReceive_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category LzReceive + * @category generated + */ +export function createLzReceiveInstruction( + accounts: LzReceiveInstructionAccounts, + args: LzReceiveInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = lzReceiveStruct.serialize({ + instructionDiscriminator: lzReceiveInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.count, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.remote, + isWritable: false, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzReceiveTypes.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzReceiveTypes.ts new file mode 100644 index 000000000..b2f673d72 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/lzReceiveTypes.ts @@ -0,0 +1,90 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { LzReceiveParams, lzReceiveParamsBeet } from '../types/LzReceiveParams' + +/** + * @category Instructions + * @category LzReceiveTypes + * @category generated + */ +export type LzReceiveTypesInstructionArgs = { + params: LzReceiveParams +} +/** + * @category Instructions + * @category LzReceiveTypes + * @category generated + */ +export const lzReceiveTypesStruct = new beet.FixableBeetArgsStruct< + LzReceiveTypesInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', lzReceiveParamsBeet], + ], + 'LzReceiveTypesInstructionArgs' +) +/** + * Accounts required by the _lzReceiveTypes_ instruction + * + * @property [] count + * @category Instructions + * @category LzReceiveTypes + * @category generated + */ +export type LzReceiveTypesInstructionAccounts = { + count: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const lzReceiveTypesInstructionDiscriminator = [221, 17, 246, 159, 248, 128, 31, 96] + +/** + * Creates a _LzReceiveTypes_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category LzReceiveTypes + * @category generated + */ +export function createLzReceiveTypesInstruction( + accounts: LzReceiveTypesInstructionAccounts, + args: LzReceiveTypesInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = lzReceiveTypesStruct.serialize({ + instructionDiscriminator: lzReceiveTypesInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.count, + isWritable: false, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/quote.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/quote.ts new file mode 100644 index 000000000..e02486182 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/quote.ts @@ -0,0 +1,97 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { QuoteParams, quoteParamsBeet } from '../types/QuoteParams' + +/** + * @category Instructions + * @category Quote + * @category generated + */ +export type QuoteInstructionArgs = { + params: QuoteParams +} +/** + * @category Instructions + * @category Quote + * @category generated + */ +export const quoteStruct = new beet.FixableBeetArgsStruct< + QuoteInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', quoteParamsBeet], + ], + 'QuoteInstructionArgs' +) +/** + * Accounts required by the _quote_ instruction + * + * @property [] count + * @property [] endpoint + * @category Instructions + * @category Quote + * @category generated + */ +export type QuoteInstructionAccounts = { + count: web3.PublicKey + endpoint: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const quoteInstructionDiscriminator = [149, 42, 109, 247, 134, 146, 213, 123] + +/** + * Creates a _Quote_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category Quote + * @category generated + */ +export function createQuoteInstruction( + accounts: QuoteInstructionAccounts, + args: QuoteInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = quoteStruct.serialize({ + instructionDiscriminator: quoteInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.count, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.endpoint, + isWritable: false, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/instructions/setRemote.ts b/examples/omnicounter-solana/src/generated/omnicounter/instructions/setRemote.ts new file mode 100644 index 000000000..06d76ed11 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/instructions/setRemote.ts @@ -0,0 +1,110 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import { SetRemoteParams, setRemoteParamsBeet } from '../types/SetRemoteParams' + +/** + * @category Instructions + * @category SetRemote + * @category generated + */ +export type SetRemoteInstructionArgs = { + params: SetRemoteParams +} +/** + * @category Instructions + * @category SetRemote + * @category generated + */ +export const setRemoteStruct = new beet.BeetArgsStruct< + SetRemoteInstructionArgs & { + instructionDiscriminator: number[] /* size: 8 */ + } +>( + [ + ['instructionDiscriminator', beet.uniformFixedSizeArray(beet.u8, 8)], + ['params', setRemoteParamsBeet], + ], + 'SetRemoteInstructionArgs' +) +/** + * Accounts required by the _setRemote_ instruction + * + * @property [_writable_, **signer**] admin + * @property [_writable_] remote + * @property [] count + * @category Instructions + * @category SetRemote + * @category generated + */ +export type SetRemoteInstructionAccounts = { + admin: web3.PublicKey + remote: web3.PublicKey + count: web3.PublicKey + systemProgram?: web3.PublicKey + anchorRemainingAccounts?: web3.AccountMeta[] +} + +export const setRemoteInstructionDiscriminator = [41, 111, 192, 109, 136, 163, 89, 81] + +/** + * Creates a _SetRemote_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category SetRemote + * @category generated + */ +export function createSetRemoteInstruction( + accounts: SetRemoteInstructionAccounts, + args: SetRemoteInstructionArgs, + programId = new web3.PublicKey('2tLJfE12h5RY7vJqK6i41taeg8ejzigoFXduBanDV4Cu') +) { + const [data] = setRemoteStruct.serialize({ + instructionDiscriminator: setRemoteInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.admin, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.remote, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.count, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + ] + + if (accounts.anchorRemainingAccounts != null) { + for (const acc of accounts.anchorRemainingAccounts) { + keys.push(acc) + } + } + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/IncrementParams.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/IncrementParams.ts new file mode 100644 index 000000000..cbc03dae7 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/IncrementParams.ts @@ -0,0 +1,30 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +export type IncrementParams = { + dstEid: number + msgType: number + options: Uint8Array + nativeFee: beet.bignum + lzTokenFee: beet.bignum +} + +/** + * @category userTypes + * @category generated + */ +export const incrementParamsBeet = new beet.FixableBeetArgsStruct( + [ + ['dstEid', beet.u32], + ['msgType', beet.u8], + ['options', beet.bytes], + ['nativeFee', beet.u64], + ['lzTokenFee', beet.u64], + ], + 'IncrementParams' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/InitCountParams.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/InitCountParams.ts new file mode 100644 index 000000000..79ac5b7b1 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/InitCountParams.ts @@ -0,0 +1,28 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beet from '@metaplex-foundation/beet' +import * as beetSolana from '@metaplex-foundation/beet-solana' +export type InitCountParams = { + id: number + admin: web3.PublicKey + endpoint: web3.PublicKey +} + +/** + * @category userTypes + * @category generated + */ +export const initCountParamsBeet = new beet.BeetArgsStruct( + [ + ['id', beet.u8], + ['admin', beetSolana.publicKey], + ['endpoint', beetSolana.publicKey], + ], + 'InitCountParams' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/LzAccount.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/LzAccount.ts new file mode 100644 index 000000000..58d90c025 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/LzAccount.ts @@ -0,0 +1,28 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beetSolana from '@metaplex-foundation/beet-solana' +import * as beet from '@metaplex-foundation/beet' +export type LzAccount = { + pubkey: web3.PublicKey + isSigner: boolean + isWritable: boolean +} + +/** + * @category userTypes + * @category generated + */ +export const lzAccountBeet = new beet.BeetArgsStruct( + [ + ['pubkey', beetSolana.publicKey], + ['isSigner', beet.bool], + ['isWritable', beet.bool], + ], + 'LzAccount' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/LzComposeParams.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/LzComposeParams.ts new file mode 100644 index 000000000..87ee764e7 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/LzComposeParams.ts @@ -0,0 +1,34 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beetSolana from '@metaplex-foundation/beet-solana' +import * as beet from '@metaplex-foundation/beet' +export type LzComposeParams = { + from: web3.PublicKey + to: web3.PublicKey + guid: number[] /* size: 32 */ + index: number + message: Uint8Array + extraData: Uint8Array +} + +/** + * @category userTypes + * @category generated + */ +export const lzComposeParamsBeet = new beet.FixableBeetArgsStruct( + [ + ['from', beetSolana.publicKey], + ['to', beetSolana.publicKey], + ['guid', beet.uniformFixedSizeArray(beet.u8, 32)], + ['index', beet.u16], + ['message', beet.bytes], + ['extraData', beet.bytes], + ], + 'LzComposeParams' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/LzReceiveParams.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/LzReceiveParams.ts new file mode 100644 index 000000000..61c45e0e7 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/LzReceiveParams.ts @@ -0,0 +1,32 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +export type LzReceiveParams = { + srcEid: number + sender: number[] /* size: 32 */ + nonce: beet.bignum + guid: number[] /* size: 32 */ + message: Uint8Array + extraData: Uint8Array +} + +/** + * @category userTypes + * @category generated + */ +export const lzReceiveParamsBeet = new beet.FixableBeetArgsStruct( + [ + ['srcEid', beet.u32], + ['sender', beet.uniformFixedSizeArray(beet.u8, 32)], + ['nonce', beet.u64], + ['guid', beet.uniformFixedSizeArray(beet.u8, 32)], + ['message', beet.bytes], + ['extraData', beet.bytes], + ], + 'LzReceiveParams' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/MessagingFee.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/MessagingFee.ts new file mode 100644 index 000000000..9a569d7bf --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/MessagingFee.ts @@ -0,0 +1,24 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +export type MessagingFee = { + nativeFee: beet.bignum + lzTokenFee: beet.bignum +} + +/** + * @category userTypes + * @category generated + */ +export const messagingFeeBeet = new beet.BeetArgsStruct( + [ + ['nativeFee', beet.u64], + ['lzTokenFee', beet.u64], + ], + 'MessagingFee' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/QuoteParams.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/QuoteParams.ts new file mode 100644 index 000000000..2a824ea47 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/QuoteParams.ts @@ -0,0 +1,30 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +export type QuoteParams = { + dstEid: number + receiver: number[] /* size: 32 */ + msgType: number + options: Uint8Array + payInLzToken: boolean +} + +/** + * @category userTypes + * @category generated + */ +export const quoteParamsBeet = new beet.FixableBeetArgsStruct( + [ + ['dstEid', beet.u32], + ['receiver', beet.uniformFixedSizeArray(beet.u8, 32)], + ['msgType', beet.u8], + ['options', beet.bytes], + ['payInLzToken', beet.bool], + ], + 'QuoteParams' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/SetRemoteParams.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/SetRemoteParams.ts new file mode 100644 index 000000000..705f4f156 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/SetRemoteParams.ts @@ -0,0 +1,26 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +export type SetRemoteParams = { + id: number + dstEid: number + remote: number[] /* size: 32 */ +} + +/** + * @category userTypes + * @category generated + */ +export const setRemoteParamsBeet = new beet.BeetArgsStruct( + [ + ['id', beet.u8], + ['dstEid', beet.u32], + ['remote', beet.uniformFixedSizeArray(beet.u8, 32)], + ], + 'SetRemoteParams' +) diff --git a/examples/omnicounter-solana/src/generated/omnicounter/types/index.ts b/examples/omnicounter-solana/src/generated/omnicounter/types/index.ts new file mode 100644 index 000000000..2d4085776 --- /dev/null +++ b/examples/omnicounter-solana/src/generated/omnicounter/types/index.ts @@ -0,0 +1,8 @@ +export * from './IncrementParams' +export * from './InitCountParams' +export * from './LzAccount' +export * from './LzComposeParams' +export * from './LzReceiveParams' +export * from './MessagingFee' +export * from './QuoteParams' +export * from './SetRemoteParams' diff --git a/examples/omnicounter-solana/src/index.ts b/examples/omnicounter-solana/src/index.ts new file mode 100644 index 000000000..c23113806 --- /dev/null +++ b/examples/omnicounter-solana/src/index.ts @@ -0,0 +1,2 @@ +export * from './pda-deriver' +export * as OmniCounterProgram from './omnicounter' diff --git a/examples/omnicounter-solana/src/omnicounter.ts b/examples/omnicounter-solana/src/omnicounter.ts new file mode 100644 index 000000000..0c2299381 --- /dev/null +++ b/examples/omnicounter-solana/src/omnicounter.ts @@ -0,0 +1,309 @@ +import { hexlify } from '@ethersproject/bytes' +import { + AccountMeta, + Commitment, + ComputeBudgetProgram, + Connection, + GetAccountInfoConfig, + PublicKey, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from '@solana/web3.js' + +import { EndpointProgram, EventPDADeriver, SimpleMessageLibProgram, UlnProgram } from '@layerzerolabs/lz-solana-sdk-v2' +import { PacketPath } from '@layerzerolabs/lz-v2-utilities' + +import * as accounts from './generated/omnicounter/accounts' +import * as errors from './generated/omnicounter/errors' +import * as instructions from './generated/omnicounter/instructions' +import * as types from './generated/omnicounter/types' +import { OmniCounterPDADeriver } from './pda-deriver' + +export { accounts, errors, instructions, types } + +export enum MessageType { + VANILLA = 1, + COMPOSED_TYPE = 2, +} + +export class OmniCounter { + omniCounterDeriver: OmniCounterPDADeriver + endpoint: EndpointProgram.Endpoint | undefined + + constructor( + public readonly program: PublicKey, + public counterId = 0 + ) { + this.omniCounterDeriver = new OmniCounterPDADeriver(program, counterId) + } + + idPDA(): [PublicKey, number] { + return this.omniCounterDeriver.count() + } + + async initCount( + connection: Connection, + payer: PublicKey, + admin: PublicKey, + endpoint: EndpointProgram.Endpoint, + commitmentOrConfig: Commitment | GetAccountInfoConfig = 'confirmed' + ): Promise { + const [id] = this.idPDA() + const [oAppRegistry] = endpoint.deriver.oappRegistry(id) + const info = await connection.getAccountInfo(id, commitmentOrConfig) + if (info) { + return null + } + const [eventAuthority] = new EventPDADeriver(endpoint.program).eventAuthority() + const ixAccounts = EndpointProgram.instructions.createRegisterOappInstructionAccounts( + { + payer: payer, + oapp: this.idPDA()[0], + oappRegistry: oAppRegistry, + eventAuthority, + program: endpoint.program, + }, + endpoint.program + ) + // these accounts are used for the CPI, so we need to set them to false + const registerOAppAccounts = [ + { + pubkey: endpoint.program, + isSigner: false, + isWritable: false, + }, + ...ixAccounts, + ] + // the first two accounts are both signers, so we need to set them to false, solana will set them to signer internally + registerOAppAccounts[1].isSigner = false + registerOAppAccounts[2].isSigner = false + return instructions.createInitCountInstruction( + { + payer, + count: id, + lzReceiveTypesAccounts: this.omniCounterDeriver.lzReceiveTypesAccounts()[0], + lzComposeTypesAccounts: this.omniCounterDeriver.lzComposeTypesAccounts()[0], + anchorRemainingAccounts: registerOAppAccounts, + } satisfies instructions.InitCountInstructionAccounts, + { + params: { + endpoint: endpoint.program, + id: this.counterId, + admin, + } satisfies types.InitCountParams, + } satisfies instructions.InitCountInstructionArgs, + this.program + ) + } + + async getRemote( + connection: Connection, + dstEid: number, + commitmentOrConfig?: Commitment | GetAccountInfoConfig + ): Promise { + const [remotePDA] = this.omniCounterDeriver.remote(dstEid) + const info = await connection.getAccountInfo(remotePDA, commitmentOrConfig) + if (info) { + const remote = await accounts.Remote.fromAccountAddress(connection, remotePDA, commitmentOrConfig) + return Uint8Array.from(remote.address) + } + return null + } + + setRemote(admin: PublicKey, dstAddress: Uint8Array, dstEid: number): TransactionInstruction { + const [remotePDA] = this.omniCounterDeriver.remote(dstEid) + return instructions.createSetRemoteInstruction( + { + admin, + count: this.idPDA()[0], + remote: remotePDA, + } satisfies instructions.SetRemoteInstructionAccounts, + { + params: { + id: this.counterId, + dstEid, + remote: Array.from(dstAddress), + } satisfies types.SetRemoteParams, + }, + this.program + ) + } + + async getCount( + connection: Connection, + commitmentOrConfig: Commitment | GetAccountInfoConfig = 'confirmed' + ): Promise { + const [countPDA] = this.idPDA() + const info = await connection.getAccountInfo(countPDA, commitmentOrConfig) + if (info) { + const [count] = accounts.Count.fromAccountInfo(info, 0) + return count + } + return null + } + + async increment( + connection: Connection, + payer: PublicKey, + fee: EndpointProgram.types.MessagingFee, + mint: PublicKey | null, // Token mint account + dstEid: number, + msgType: MessageType | number, + options: Uint8Array, + remainingAccounts?: AccountMeta[], + commitmentOrConfig: Commitment | GetAccountInfoConfig = 'confirmed' + ): Promise { + const endpoint = await this.getEndpoint(connection) + const msgLibProgram = await this.getSendLibraryProgram(connection, payer, dstEid, endpoint) + const [countPDA] = this.omniCounterDeriver.count() + const [remotePDA] = this.omniCounterDeriver.remote(dstEid) + const [endpointSettingPDA] = endpoint.deriver.setting() + const receiverInfo = await accounts.Remote.fromAccountAddress(connection, remotePDA, commitmentOrConfig) + const packetPath: PacketPath = { + srcEid: 0, + dstEid, + sender: hexlify(countPDA.toBytes()), + receiver: hexlify(receiverInfo.address), + } + return instructions.createIncrementInstruction( + { + remote: remotePDA, + count: countPDA, + endpoint: endpointSettingPDA, + // Get remaining accounts from msgLib(simple_msgLib or uln) + anchorRemainingAccounts: + remainingAccounts ?? + (await endpoint.getSendIXAccountMetaForCPI( + connection, + payer, + packetPath, + msgLibProgram, + commitmentOrConfig + )), + } satisfies instructions.IncrementInstructionAccounts, + { + params: { + dstEid: dstEid, + msgType: msgType, + nativeFee: fee.nativeFee, + lzTokenFee: fee.lzTokenFee, + options, + } satisfies types.IncrementParams, + } satisfies instructions.IncrementInstructionArgs, + this.program + ) + } + + async quote( + connection: Connection, + payer: PublicKey, + dstEid: number, + msgType: MessageType | number, + options: Uint8Array, + payInLzToken: boolean, + remainingAccounts?: AccountMeta[], + commitmentOrConfig: Commitment | GetAccountInfoConfig = 'confirmed' + ): Promise { + const endpoint = await this.getEndpoint(connection) + const [id] = this.idPDA() + const msgLibProgram = await this.getSendLibraryProgram(connection, payer, dstEid, endpoint) + const [remotePDA] = this.omniCounterDeriver.remote(dstEid) + const [endpointSettingPDA] = endpoint.deriver.setting() + const receiverInfo = await accounts.Remote.fromAccountAddress(connection, remotePDA, commitmentOrConfig) + const packetPath: PacketPath = { + srcEid: 0, + dstEid, + sender: hexlify(id.toBytes()), + receiver: hexlify(receiverInfo.address), + } + const ix = instructions.createQuoteInstruction( + { + count: id, + endpoint: endpointSettingPDA, + // Get remaining accounts from msgLib(simple_msgLib or uln) + anchorRemainingAccounts: + remainingAccounts ?? + (await endpoint.getQuoteIXAccountMetaForCPI(connection, payer, packetPath, msgLibProgram)), + } satisfies instructions.QuoteInstructionAccounts, + { + params: { + dstEid: dstEid, + msgType: msgType, + options, + receiver: receiverInfo.address, + payInLzToken, + } satisfies types.QuoteParams, + } satisfies instructions.QuoteInstructionArgs, + this.program + ) + + //TODO: get compute units: const units = await getSimulationComputeUnits(this.connection, [ix], walletPk, []) + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 1000000, + }) + + const tx = new VersionedTransaction( + new TransactionMessage({ + instructions: [modifyComputeUnits, ix], + payerKey: payer, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + }).compileToV0Message() + ) + const simulateResp = await connection.simulateTransaction(tx, { + sigVerify: false, + commitment: 'confirmed', + }) + const returnPrefix = `Program return: ${this.program.toBase58()} ` + const returnLog = simulateResp.value.logs?.find((l) => l.startsWith(returnPrefix)) + if (returnLog === undefined) { + console.error('error logs', simulateResp.value.logs) + throw new Error('View expected return log') + } else { + const buffer = Buffer.from(returnLog.slice(returnPrefix.length), 'base64') + const fee = EndpointProgram.types.messagingFeeBeet.read(buffer, 0) + return fee + } + } + + async getEndpoint(connection: Connection): Promise { + if (this.endpoint) { + return this.endpoint + } + const [id] = this.omniCounterDeriver.count() + const info = await accounts.Count.fromAccountAddress(connection, id) + const programAddr = info.endpointProgram + const endpoint = new EndpointProgram.Endpoint(programAddr) + this.endpoint = endpoint + return endpoint + } + + async getSendLibraryProgram( + connection: Connection, + payer: PublicKey, + dstEid: number, + endpoint?: EndpointProgram.Endpoint + ): Promise { + if (!endpoint) { + endpoint = await this.getEndpoint(connection) + } + const [id] = this.idPDA() + const sendLibInfo = await endpoint.getSendLibrary(connection, id, dstEid) + if (!sendLibInfo?.programId) { + throw new Error('Send library not initialized or blocked message library') + } + const { programId: msgLibProgram } = sendLibInfo + const msgLibVersion = await endpoint.getMessageLibVersion(connection, payer, msgLibProgram) + if (msgLibVersion?.major.toString() === '0' && msgLibVersion.minor == 0 && msgLibVersion.endpointVersion == 2) { + return new SimpleMessageLibProgram.SimpleMessageLib(msgLibProgram) + } else if ( + msgLibVersion?.major.toString() === '3' && + msgLibVersion.minor == 0 && + msgLibVersion.endpointVersion == 2 + ) { + return new UlnProgram.Uln(msgLibProgram) + } + + throw new Error(`Unsupported message library version: ${JSON.stringify(msgLibVersion, null, 2)}`) + } +} diff --git a/examples/omnicounter-solana/src/pda-deriver.ts b/examples/omnicounter-solana/src/pda-deriver.ts new file mode 100644 index 000000000..6be4f1dfc --- /dev/null +++ b/examples/omnicounter-solana/src/pda-deriver.ts @@ -0,0 +1,41 @@ +import { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' + +import { oappIDPDA } from '@layerzerolabs/lz-solana-sdk-v2' + +export const COUNT_SEED = 'Count' +export const REMOTE_SEED = 'Remote' +export const LZ_RECEIVE_TYPES_SEED = 'LzReceiveTypes' +export const LZ_COMPOSE_TYPES_SEED = 'LzComposeTypes' + +export class OmniCounterPDADeriver { + constructor( + public readonly program: PublicKey, + public counterId = 0 + ) {} + + count(): [PublicKey, number] { + return oappIDPDA(this.program, COUNT_SEED, this.counterId) + } + + remote(dstChainId: number): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from(REMOTE_SEED), this.count()[0].toBytes(), new BN(dstChainId).toArrayLike(Buffer, 'be', 4)], + this.program + ) + } + + lzReceiveTypesAccounts(): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from(LZ_RECEIVE_TYPES_SEED, 'utf8'), this.count()[0].toBytes()], + this.program + ) + } + + lzComposeTypesAccounts(): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from(LZ_COMPOSE_TYPES_SEED, 'utf8'), this.count()[0].toBytes()], + this.program + ) + } +} diff --git a/examples/omnicounter-solana/tasks/common/types.ts b/examples/omnicounter-solana/tasks/common/types.ts new file mode 100644 index 000000000..bf0b4bc87 --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/tasks/common/utils.ts b/examples/omnicounter-solana/tasks/common/utils.ts new file mode 100644 index 000000000..a92f2fd2b --- /dev/null +++ b/examples/omnicounter-solana/tasks/common/utils.ts @@ -0,0 +1,76 @@ +import assert from 'assert' + +import { 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, createConnectionFactory, createRpcUrlFactory } from '@layerzerolabs/devtools-solana' +import { ChainType, EndpointId, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +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 paramter 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()) => { + return async (eid: EndpointId): Promise>> => { + assert( + endpointIdToChainType(eid) === ChainType.SOLANA, + `Solana signer factory can only create signers for Solana networks. Received ${formatEid(eid)}` + ) + + return new OmniSignerSolana(eid, await connectionFactory(eid), wallet) + } +} diff --git a/examples/omnicounter-solana/tasks/common/wire.ts b/examples/omnicounter-solana/tasks/common/wire.ts new file mode 100644 index 000000000..377d7979a --- /dev/null +++ b/examples/omnicounter-solana/tasks/common/wire.ts @@ -0,0 +1,181 @@ +import { Keypair, PublicKey } from '@solana/web3.js' +import { subtask, task } from 'hardhat/config' + +import { firstFactory } from '@layerzerolabs/devtools' +import { SUBTASK_LZ_SIGN_AND_SEND, inheritTask, types } from '@layerzerolabs/devtools-evm-hardhat' +import { setTransactionSizeBuffer } from '@layerzerolabs/devtools-solana' +import { type LogLevel, createLogger } from '@layerzerolabs/io-devtools' +import { OftProgram } from '@layerzerolabs/lz-solana-sdk-v2' +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 { initOFTAccounts } from '@layerzerolabs/ua-devtools-solana' + +import { keyPair, publicKey } from './types' +import { createSdkFactory, createSolanaConnectionFactory, createSolanaSignerFactory } from './utils' + +import type { SignAndSendTaskArgs } from '@layerzerolabs/devtools-evm-hardhat/tasks' + +/** + * Additional CLI arguments for our custom wire task + */ +interface Args { + logLevel: LogLevel + solanaProgramId: PublicKey + solanaSecretKey?: Keypair + internalConfigurator?: OAppConfigurator +} + +/** + * We extend the default wiring task to add functionality required by Solana + */ +task(TASK_LZ_OAPP_WIRE) + // The first thing we add is the solana secret key, used to create a signer + // + // This secret key will also be used as the user account, required to use the OFT SDK + .addParam( + 'solanaSecretKey', + 'Secret key of the user account that will be used to send transactions', + undefined, + keyPair, + true + ) + // The next (optional) parameter is the OFT program ID + // + // Only pass this if you deployed a new OFT program, if you are using the default + // LayerZero OFT program you can omit this + .addParam('solanaProgramId', 'The OFT program ID to use', OftProgram.OFT_DEFAULT_PROGRAM_ID, publicKey, 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, types.fn, 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 + // + // + + if (args.solanaSecretKey == null) { + logger.warn( + `Missing --solana-secret-key CLI argument. A random keypair will be generated and interaction with solana programs will not be possible` + ) + } + + // The first step is to create the user Keypair from the secret passed in + const wallet = args.solanaSecretKey ?? Keypair.generate() + const userAccount = wallet.publicKey + + // Then we grab the programId from the args + const programId = args.solanaProgramId + + 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(wallet, connectionFactory) + + // + // + // 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 Swell OFT', + (args: SubtaskConfigureTaskArgs, hre, runSuper) => + runSuper({ + ...args, + configurator: configurator ?? args.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 Swell 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) + // The first thing we add is the solana secret key, used to create a signer + // + // This secret key will also be used as the user account, required to use the OFT SDK + .addParam( + 'solanaSecretKey', + 'Secret key of the user account that will be used to send transactions', + undefined, + keyPair, + true + ) + // The next (optional) parameter is the OFT program ID + // + // Only pass this if you deployed a new OFT program, if you are using the default + // LayerZero OFT program you can omit this + .addParam('solanaProgramId', 'The OFT program ID to use', OftProgram.OFT_DEFAULT_PROGRAM_ID, publicKey, true) + .setAction(async (args: Args, hre) => { + return hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: configureOwnable }) + }) + +// We'll create clones of the wire task and only override the configurator argument +const wireLikeTask = inheritTask(TASK_LZ_OAPP_WIRE) + +// This task will use the `initOFTAccounts` configurator that initializes the Solana accounts +wireLikeTask('lz:oapp:init:solana') + .setDescription('Initialize OFT accounts for Solana') + .setAction(async (args: Args, hre) => + hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: initOFTAccounts }) + ) diff --git a/examples/omnicounter-solana/tasks/index.ts b/examples/omnicounter-solana/tasks/index.ts new file mode 100644 index 000000000..27c1b64fb --- /dev/null +++ b/examples/omnicounter-solana/tasks/index.ts @@ -0,0 +1,9 @@ +import './common/wire' +import './solana/createOFT' +import './solana/createOFTAdapter' +import './solana/mintTo' +import './solana/sendOFT' +import './solana/setFreezeAuthority' +import './solana/setOFTMintAuthority' +import './solana/setMetadataAuthority' +import './solana/setRateLimit' diff --git a/examples/omnicounter-solana/tasks/solana/createOFT.ts b/examples/omnicounter-solana/tasks/solana/createOFT.ts new file mode 100644 index 000000000..fcc81ed2c --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/createOFT.ts @@ -0,0 +1,216 @@ +// Import necessary functions and classes from Solana SDKs +import assert from 'assert' +import fs from 'fs' + +import { TokenStandard, createAndMint } from '@metaplex-foundation/mpl-token-metadata' +import { + AuthorityType, + findAssociatedTokenPda, + mplToolbox, + setAuthority, + setComputeUnitPrice, +} from '@metaplex-foundation/mpl-toolbox' +import { + TransactionBuilder, + createSignerFromKeypair, + generateSigner, + percentAmount, + signerIdentity, +} from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsInstruction, fromWeb3JsPublicKey, toWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { getExplorerLink } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { OFT_SEED, OftTools } from '@layerzerolabs/lz-solana-sdk-v2' + +import { createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +interface Args { + amount: number + eid: EndpointId + programId: string +} + +task('lz:oft:solana:create', 'Mints new SPL Token and creates new OFT Config account') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet or testnet', undefined, types.eid) + .addOptionalParam('amount', 'The initial supply to mint on solana', undefined, types.int) + .setAction(async (taskArgs: Args) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + // 1. Setup UMI environment using environment variables (private key and Solana RPC) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + // Initialize UMI with the Solana RPC URL and necessary tools + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + + // Generate a wallet keypair from the private key stored in the environment + const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(bs58.decode(privateKey)) + + // Convert the UMI keypair to a format compatible with web3.js + // This is necessary as the @layerzerolabs/lz-solana-sdk-v2 library uses web3.js keypairs + const web3WalletKeyPair = toWeb3JsKeypair(umiWalletKeyPair) + + // Create a signer object for UMI to use in transactions + const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair) + + // Set the UMI environment to use the signer identity + umi.use(signerIdentity(umiWalletSigner)) + + // Define the OFT Program ID based on the task arguments + const OFT_PROGRAM_ID = new PublicKey(taskArgs.programId) + + // Number of decimals for the token (recommended value for SHARED_DECIMALS is 6) + const LOCAL_DECIMALS = 9 + + // Define the number of shared decimals for the token + // The OFT Standard handles differences in decimal precision before every cross-chain + // by "cleaning" the dust off of the amount to send by dividing by a `decimalConversionRate`. + // For example, when you send a value of 123456789012345678 (18 decimals): + // + // decimalConversionRate = 10^(localDecimals − sharedDecimals) = 10^(18−6) = 10^12 + // 123456789012345678 / 10^12 = 123456.789012345678 = 123456 + // amount = 123456 * 10^12 = 12345600000000000 + // + // For more information, see the OFT Standard documentation: + // https://docs.layerzero.network/v2/developers/solana/oft/native#token-transfer-precision + const SHARED_DECIMALS = 6 + + // Interface to hold account details for saving later + interface AccountDetails { + name: string + publicKey: string + } + + // Interface for the JSON structure that will store account information + interface AccountsJson { + timestamp: string + accounts: AccountDetails[] + } + + // 2. Generate the accounts we want to create (SPL Token) + + // Generate a new keypair for the SPL token mint account + const token = generateSigner(umi) + + // Convert the UMI keypair to web3.js compatible keypair + const web3TokenKeyPair = toWeb3JsKeypair(token) + + // Get the average compute unit price + // getFee() uses connection.getRecentPrioritizationFees() to get recent fees and averages them + const { averageFeeExcludingZeros } = await getFee(connection) + const computeUnitPrice = BigInt(Math.round(averageFeeExcludingZeros)) + + // Create and mint the SPL token using UMI + const createTokenTx = await createAndMint(umi, { + mint: token, // New token account + name: 'Mock', // Token name + symbol: 'Mock', // Token symbol + isMutable: true, // Allow token metadata to be mutable + decimals: LOCAL_DECIMALS, // Number of decimals for the token + uri: '', // URI for token metadata + sellerFeeBasisPoints: percentAmount(0), // Fee percentage + authority: umiWalletSigner, // Authority for the token mint + amount: taskArgs.amount, // Initial amount to mint + tokenOwner: umiWalletSigner.publicKey, // Owner of the token + tokenStandard: TokenStandard.Fungible, // Token type (Fungible) + }) + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice * BigInt(2) })) + .sendAndConfirm(umi) + + // Log the transaction and token mint details + const createTokenTransactionSignature = bs58.encode(createTokenTx.signature) + const createTokenLink = getExplorerLink('tx', createTokenTransactionSignature.toString(), 'mainnet-beta') + console.log(`✅ Token Mint Complete! View the transaction here: ${createTokenLink}`) + + // Find the associated token account using the generated token mint + const tokenAccount = findAssociatedTokenPda(umi, { + mint: token.publicKey, + owner: umiWalletSigner.publicKey, + }) + + console.log(`Your token account is: ${tokenAccount[0]}`) + + // 3. Derive OFT Config from those accounts and program ID + + // Derive the OFT Config public key from the token mint keypair and OFT Program ID + const [oftConfig] = PublicKey.findProgramAddressSync( + [Buffer.from(OFT_SEED), web3TokenKeyPair.publicKey.toBuffer()], + OFT_PROGRAM_ID + ) + + console.log(`OFT Config:`, oftConfig) + + // 4. Create new account (OFT Config) + + // Create a new transaction to transfer mint authority to the OFT Config account and initialize a new native OFT + const setAuthorityTx = setAuthority(umi, { + owned: token.publicKey, // SPL Token mint account + owner: umiWalletKeyPair.publicKey, // Current authority of the token mint + authorityType: AuthorityType.MintTokens, // Authority type to transfer + newAuthority: fromWeb3JsPublicKey(oftConfig), // New authority (OFT Config) + }) + + // Initialize the OFT using the OFT Config and the token mint + const oftConfigMintIx = await OftTools.createInitNativeOftIx( + web3WalletKeyPair.publicKey, // Payer + web3WalletKeyPair.publicKey, // Admin + web3TokenKeyPair.publicKey, // Mint account + web3WalletKeyPair.publicKey, // OFT Mint Authority + SHARED_DECIMALS, // OFT Shared Decimals + TOKEN_PROGRAM_ID, // Token Program ID + OFT_PROGRAM_ID // OFT Program ID + ) + + // Convert the instruction to UMI format + const convertedInstruction = fromWeb3JsInstruction(oftConfigMintIx) + + // Build the transaction with the OFT Config initialization instruction + const configBuilder = new TransactionBuilder([ + { + instruction: convertedInstruction, + signers: [umiWalletSigner], + bytesCreatedOnChain: 0, + }, + ]) + + // Set the fee payer and send the transaction + const oftConfigTransaction = await setAuthorityTx + .add(configBuilder) + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice * BigInt(4) })) + .sendAndConfirm(umi) + + // Log the transaction details + const oftConfigSignature = bs58.encode(oftConfigTransaction.signature) + const oftConfigLink = getExplorerLink('tx', oftConfigSignature.toString(), 'mainnet-beta') + console.log(`✅ You created an OFT, view the transaction here: ${oftConfigLink}`) + + // Save the account details to a JSON file + const accountsJson: AccountsJson = { + timestamp: new Date().toISOString(), + accounts: [ + { name: 'SPL Token Mint Account', publicKey: web3TokenKeyPair.publicKey.toString() }, + { name: 'OFT Config Account', publicKey: oftConfig.toString() }, + { name: 'OFT Program ID', publicKey: taskArgs.programId }, + ], + } + + const outputDir = `./deployments/${endpointIdToNetwork(taskArgs.eid)}` + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }) + } + + // Write the JSON file to the specified directory + fs.writeFileSync(`${outputDir}/OFT.json`, JSON.stringify(accountsJson, null, 2)) + console.log(`Accounts have been saved to ${outputDir}/OFT.json`) + }) diff --git a/examples/omnicounter-solana/tasks/solana/createOFTAdapter.ts b/examples/omnicounter-solana/tasks/solana/createOFTAdapter.ts new file mode 100644 index 000000000..08703ac76 --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/createOFTAdapter.ts @@ -0,0 +1,201 @@ +// Import necessary functions and classes from Solana SDKs +import assert from 'assert' +import fs from 'fs' + +import { TokenStandard, createAndMint } from '@metaplex-foundation/mpl-token-metadata' +import { findAssociatedTokenPda, mplToolbox, setComputeUnitPrice } from '@metaplex-foundation/mpl-toolbox' +import { + TransactionBuilder, + createSignerFromKeypair, + generateSigner, + percentAmount, + signerIdentity, +} from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsInstruction, toWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { getExplorerLink } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { OFT_SEED, OftTools } from '@layerzerolabs/lz-solana-sdk-v2' + +import { createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +interface Args { + amount: number + eid: EndpointId + programId: string +} + +task('lz:oft-adapter:solana:create', 'Mints new SPL Token, Lockbox, and new OFT Adapter Config account') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet or testnet', undefined, types.eid) + .addOptionalParam('amount', 'The initial supply to mint on solana', undefined, types.int) + .setAction(async (taskArgs: Args) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + // 1. Setup UMI environment using environment variables (private key and Solana RPC) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + // Initialize UMI with the Solana RPC URL and necessary tools + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + + // Generate a wallet keypair from the private key stored in the environment + const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(bs58.decode(privateKey)) + + // Convert the UMI keypair to a format compatible with web3.js + // This is necessary as the @layerzerolabs/lz-solana-sdk-v2 library uses web3.js keypairs + const web3WalletKeyPair = toWeb3JsKeypair(umiWalletKeyPair) + + // Create a signer object for UMI to use in transactions + const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair) + + // Set the UMI environment to use the signer identity + umi.use(signerIdentity(umiWalletSigner)) + + // Define the OFT Program ID based on the task arguments + const OFT_PROGRAM_ID = new PublicKey(taskArgs.programId) + + // Define the number of decimals for the token + const LOCAL_DECIMALS = 9 + + // Define the number of shared decimals for the token + // The OFT Standard handles differences in decimal precision before every cross-chain + // by "cleaning" the dust off of the amount to send by dividing by a `decimalConversionRate`. + // For example, when you send a value of 123456789012345678 (18 decimals): + // + // decimalConversionRate = 10^(localDecimals − sharedDecimals) = 10^(18−6) = 10^12 + // 123456789012345678 / 10^12 = 123456.789012345678 = 123456 + // amount = 123456 * 10^12 = 12345600000000000 + // + // For more information, see the OFT Standard documentation: + // https://docs.layerzero.network/v2/developers/solana/oft/native#token-transfer-precision + const SHARED_DECIMALS = 6 + + // Interface to hold account details for saving later + interface AccountDetails { + name: string + publicKey: string + } + + // Interface for the JSON structure that will store account information + interface AccountsJson { + timestamp: string + accounts: AccountDetails[] + } + + // 2. Generate the accounts we want to create (SPL Token / Lockbox) + + // Generate a new keypair for the SPL token mint account + const token = generateSigner(umi) + + // Generate a new keypair for the Lockbox account + const lockbox = generateSigner(umi) + + // Convert the UMI keypairs to web3.js compatible keypairs + const web3TokenKeyPair = toWeb3JsKeypair(token) + const web3LockboxKeypair = toWeb3JsKeypair(lockbox) + + // Get the average compute unit price + // getFee() uses connection.getRecentPrioritizationFees() to get recent fees and averages them + // This is necessary as Solana's default compute unit price is not always sufficient to land the tx + const { averageFeeExcludingZeros } = await getFee(connection) + const computeUnitPrice = BigInt(Math.round(averageFeeExcludingZeros)) + + // Create and mint the SPL token using UMI + const createTokenTx = await createAndMint(umi, { + mint: token, // New token account + name: 'Mock', // Token name + symbol: 'Mock', // Token symbol + isMutable: true, // Allow token metadata to be mutable + decimals: LOCAL_DECIMALS, // Number of decimals for the token + uri: '', // URI for token metadata + sellerFeeBasisPoints: percentAmount(0), // Fee percentage + authority: umiWalletSigner, // Authority for the token mint + amount: taskArgs.amount, // Initial amount to mint + tokenOwner: umiWalletSigner.publicKey, // Owner of the token + tokenStandard: TokenStandard.Fungible, // Token type (Fungible) + }) + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice * BigInt(2) })) + .sendAndConfirm(umi) + + // Log the transaction and token mint details + console.log( + `✅ Token Mint Complete! View the transaction here: ${getExplorerLink('tx', bs58.encode(createTokenTx.signature), 'mainnet-beta')}` + ) + + // Find the associated token account using the generated token mint + const tokenAccount = findAssociatedTokenPda(umi, { + mint: token.publicKey, + owner: umiWalletSigner.publicKey, + }) + + console.log(`Your token account is: ${tokenAccount[0]}`) + + // 3. Derive OFT Config from those accounts and program ID + + // Derive the OFT Config public key from the Lockbox keypair and OFT Program ID + const [oftConfig] = PublicKey.findProgramAddressSync( + [Buffer.from(OFT_SEED), web3LockboxKeypair.publicKey.toBuffer()], + OFT_PROGRAM_ID + ) + + console.log(`OFT Config:`, oftConfig) + + // 4. Create new account (OFT Adapter Config) + + // Create the OFT Adapter Config initialization instruction + const adapterIx = await OftTools.createInitAdapterOftIx( + web3WalletKeyPair.publicKey, // Payer + web3WalletKeyPair.publicKey, // Admin + web3TokenKeyPair.publicKey, // SPL Token Mint Account + web3LockboxKeypair.publicKey, // Lockbox account + SHARED_DECIMALS, // Number of shared decimals + TOKEN_PROGRAM_ID, // Token program ID + OFT_PROGRAM_ID // OFT Program ID + ) + + // Build and send the transaction with the create OFT Adapter instruction + const oftConfigTransaction = await new TransactionBuilder([ + { + instruction: fromWeb3JsInstruction(adapterIx), + signers: [umiWalletSigner, lockbox], + bytesCreatedOnChain: 0, + }, + ]) + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice * BigInt(4) })) + .sendAndConfirm(umi) + + // Log the transaction details + console.log( + `✅ You created an OFT Adapter, view the transaction here: ${getExplorerLink('tx', bs58.encode(oftConfigTransaction.signature), 'mainnet-beta')}` + ) + + // Save the account details to a JSON file + const accountsJson: AccountsJson = { + timestamp: new Date().toISOString(), + accounts: [ + { name: 'SPL Token Mint Account', publicKey: web3TokenKeyPair.publicKey.toString() }, + { name: 'OFT Config Account', publicKey: oftConfig.toString() }, + { name: 'OFT Program ID', publicKey: taskArgs.programId }, + { name: 'OFT Lockbox', publicKey: web3LockboxKeypair.publicKey.toString() }, + ], + } + + const outputDir = `./deployments/solana-${endpointIdToNetwork(taskArgs.eid)}` + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }) + } + + // Write the JSON file to the specified directory + fs.writeFileSync(`${outputDir}/OFTAdapter.json`, JSON.stringify(accountsJson, null, 2)) + console.log(`Accounts have been saved to ${outputDir}/OFTAdapter.json`) + }) diff --git a/examples/omnicounter-solana/tasks/solana/mintTo.ts b/examples/omnicounter-solana/tasks/solana/mintTo.ts new file mode 100644 index 000000000..185d76945 --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/mintTo.ts @@ -0,0 +1,106 @@ +// Import necessary modules and classes from Solana SDKs and other libraries +import assert from 'assert' + +import { fetchDigitalAsset } from '@metaplex-foundation/mpl-token-metadata' +import { mplToolbox, setComputeUnitPrice } from '@metaplex-foundation/mpl-toolbox' +import { TransactionBuilder, createSignerFromKeypair, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsInstruction, fromWeb3JsKeypair, fromWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID, getOrCreateAssociatedTokenAccount } from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' +import { getExplorerLink } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftTools } from '@layerzerolabs/lz-solana-sdk-v2' + +import { createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +interface Args { + amount: number + mint: PublicKey + to: PublicKey + eid: EndpointId + programId: string +} + +// Define a Hardhat task for minting tokens on Solana using the OFT pass-through +task('lz:oft:solana:mint', 'Mint tokens on Solana using OFT pass-through') + .addParam('amount', 'The amount of tokens to send') + .addParam('mint', 'The OFT token mint public key') + .addParam('to', 'The recipient address on Solana') + .addParam('eid', 'The source endpoint ID') + .addParam('programId', 'The OFT program ID') + .setAction(async (taskArgs: Args) => { + 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) + + // Initialize Solana connection and UMI framework + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) + umi.use(signerIdentity(umiWalletSigner)) + + // Define OFT program and token mint public keys + const oftProgramId = new PublicKey(taskArgs.programId) + const mintPublicKey = new PublicKey(taskArgs.mint) + const toPublicKey = new PublicKey(taskArgs.to) + const umiMintPublicKey = fromWeb3JsPublicKey(mintPublicKey) + + const web3TokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + keypair, + mintPublicKey, + toPublicKey, + undefined, + 'finalized' + ) + + // Fetch token metadata + const mintInfo = (await fetchDigitalAsset(umi, umiMintPublicKey)).mint + const amount = taskArgs.amount * 10 ** mintInfo.decimals + + const oftMintIx = await OftTools.createMintToIx( + keypair.publicKey, + mintPublicKey, + web3TokenAccount.address, // which account to mint to? + BigInt(amount), + TOKEN_PROGRAM_ID, + oftProgramId + ) + + // Convert the instruction and create the transaction builder + const convertedInstruction = fromWeb3JsInstruction(oftMintIx) + const transactionBuilder = new TransactionBuilder([ + { + instruction: convertedInstruction, + signers: [umiWalletSigner], + bytesCreatedOnChain: 0, + }, + ]) + + // Fetch simulation compute units and set compute unit price + const { averageFeeExcludingZeros } = await getFee(connection) + const priorityFee = Math.round(averageFeeExcludingZeros) + const computeUnitPrice = BigInt(priorityFee) + console.log(`Compute unit price: ${computeUnitPrice}`) + + // Build and send the transaction + const transactionSignature = await transactionBuilder + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice * BigInt(4) })) + .sendAndConfirm(umi) + + // Encode the transaction signature and generate explorer links + const transactionSignatureBase58 = bs58.encode(transactionSignature.signature) + const solanaTxLink = getExplorerLink('tx', transactionSignatureBase58.toString(), 'mainnet-beta') + + console.log(`✅ Minted ${taskArgs.amount} token(s) to: ${web3TokenAccount.address}!`) + console.log(`View Solana transaction here: ${solanaTxLink}`) + }) diff --git a/examples/omnicounter-solana/tasks/solana/sendOFT.ts b/examples/omnicounter-solana/tasks/solana/sendOFT.ts new file mode 100644 index 000000000..fc079dc76 --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/sendOFT.ts @@ -0,0 +1,199 @@ +import assert from 'assert' + +import { fetchDigitalAsset } from '@metaplex-foundation/mpl-token-metadata' +import { + fetchAddressLookupTable, + findAssociatedTokenPda, + mplToolbox, + setComputeUnitLimit, + setComputeUnitPrice, +} from '@metaplex-foundation/mpl-toolbox' +import { + AddressLookupTableInput, + TransactionBuilder, + createSignerFromKeypair, + signerIdentity, +} from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { + fromWeb3JsInstruction, + fromWeb3JsKeypair, + fromWeb3JsPublicKey, + toWeb3JsPublicKey, +} from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' +import { getExplorerLink, getSimulationComputeUnits } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { hexlify } from 'ethers/lib/utils' +import { task } from 'hardhat/config' + +import { formatEid } from '@layerzerolabs/devtools' +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OFT_SEED, OftPDADeriver, OftProgram, OftTools, SendHelper } from '@layerzerolabs/lz-solana-sdk-v2' +import { Options, addressToBytes32 } from '@layerzerolabs/lz-v2-utilities' + +import { createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +interface Args { + amount: number + to: string + fromEid: EndpointId + toEid: EndpointId + programId: string + mint: string +} + +const LOOKUP_TABLE_ADDRESS: Partial> = { + [EndpointId.SOLANA_V2_MAINNET]: new PublicKey('AokBxha6VMLLgf97B5VYHEtqztamWmYERBmmFvjuTzJB'), + [EndpointId.SOLANA_V2_TESTNET]: new PublicKey('9thqPdbR27A1yLWw2spwJLySemiGMXxPnEvfmXVk4KuK'), +} + +// 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, types.int) + .addParam('fromEid', 'The source endpoint ID', undefined, types.eid) + .addParam('to', 'The recipient address on the destination chain') + .addParam('toEid', 'The destination endpoint ID', undefined, types.eid) + .addParam('mint', 'The OFT token mint public key', undefined, types.string) + .addParam('programId', 'The OFT program ID', undefined, types.string) + .setAction(async (taskArgs: Args) => { + 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 lookupTableAddress = LOOKUP_TABLE_ADDRESS[taskArgs.fromEid] + assert(lookupTableAddress != null, `No lookup table found for ${formatEid(taskArgs.fromEid)}`) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.fromEid) + + // Initialize Solana connection and UMI framework + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) + umi.use(signerIdentity(umiWalletSigner)) + + // Define OFT program and token mint public keys + const oftProgramId = new PublicKey(taskArgs.programId) + const mintPublicKey = new PublicKey(taskArgs.mint) + const umiMintPublicKey = fromWeb3JsPublicKey(mintPublicKey) + + // Find the associated token account + const tokenAccount = findAssociatedTokenPda(umi, { + mint: umiMintPublicKey, + owner: umiWalletSigner.publicKey, + }) + + // Derive the OFT configuration PDA + const [oftConfigPda] = PublicKey.findProgramAddressSync( + [Buffer.from(OFT_SEED), mintPublicKey.toBuffer()], + oftProgramId + ) + + // Fetch token metadata + const mintInfo = (await fetchDigitalAsset(umi, umiMintPublicKey)).mint + const destinationEid: EndpointId = taskArgs.toEid + const amount = taskArgs.amount * 10 ** mintInfo.decimals + + // Derive peer address and fetch peer information + const deriver = new OftPDADeriver(oftProgramId) + const [peerAddress] = deriver.peer(oftConfigPda, destinationEid) + const peerInfo = await OftProgram.accounts.Peer.fromAccountAddress(connection, peerAddress) + + // Set up send helper and convert recipient address to bytes32 + const sendHelper = new SendHelper() + const recipientAddressBytes32 = addressToBytes32(taskArgs.to) + + // Quote the fee for the cross-chain transfer + const feeQuote = await OftTools.quoteWithUln( + connection, + keypair.publicKey, + mintPublicKey, + destinationEid, + BigInt(amount), + (BigInt(amount) * BigInt(9)) / BigInt(10), + Options.newOptions().addExecutorLzReceiveOption(0, 0).toBytes(), + Array.from(recipientAddressBytes32), + false, // payInZRO + undefined, // tokenEscrow + undefined, // composeMsg + peerInfo.address, + await sendHelper.getQuoteAccounts( + connection, + keypair.publicKey, + oftConfigPda, + destinationEid, + hexlify(peerInfo.address) + ), + TOKEN_PROGRAM_ID, // SPL Token Program + oftProgramId // OFT Program + ) + + console.log(feeQuote) + + // Create the instruction for sending tokens + const sendInstruction = await OftTools.sendWithUln( + connection, + keypair.publicKey, // payer + mintPublicKey, // tokenMint + toWeb3JsPublicKey(tokenAccount[0]), // tokenSource + destinationEid, + BigInt(amount), + (BigInt(amount) * BigInt(9)) / BigInt(10), + Options.newOptions().addExecutorLzReceiveOption(0, 0).toBytes(), + Array.from(recipientAddressBytes32), + feeQuote.nativeFee, + undefined, // payInZRO + undefined, + undefined, + peerInfo.address, + undefined, + TOKEN_PROGRAM_ID, // SPL Token Program + oftProgramId // OFT Program + ) + + // Convert the instruction and create the transaction builder + const convertedInstruction = fromWeb3JsInstruction(sendInstruction) + const transactionBuilder = new TransactionBuilder([ + { + instruction: convertedInstruction, + signers: [umiWalletSigner], + bytesCreatedOnChain: 0, + }, + ]) + + // Fetch simulation compute units and set compute unit price + const { averageFeeExcludingZeros } = await getFee(connection) + const priorityFee = Math.round(averageFeeExcludingZeros) + const computeUnitPrice = BigInt(priorityFee) + console.log(`Compute unit price: ${computeUnitPrice}`) + + const addressLookupTableInput: AddressLookupTableInput = await fetchAddressLookupTable( + umi, + fromWeb3JsPublicKey(lookupTableAddress) + ) + const { value: lookupTableAccount } = await connection.getAddressLookupTable(new PublicKey(lookupTableAddress)) + const computeUnits = await getSimulationComputeUnits(connection, [sendInstruction], keypair.publicKey, [ + lookupTableAccount!, + ]) + + // Build and send the transaction + const transactionSignature = await transactionBuilder + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice * BigInt(4) })) + .add(setComputeUnitLimit(umi, { units: computeUnits! * 1.1 })) + .setAddressLookupTables([addressLookupTableInput]) + .sendAndConfirm(umi) + + // Encode the transaction signature and generate explorer links + const transactionSignatureBase58 = bs58.encode(transactionSignature.signature) + const solanaTxLink = getExplorerLink('tx', transactionSignatureBase58.toString(), 'mainnet-beta') + const layerZeroTxLink = `https://layerzeroscan.com/tx/${transactionSignatureBase58}` + + console.log(`✅ Sent ${taskArgs.amount} token(s) to destination EID: ${destinationEid}!`) + console.log(`View Solana transaction here: ${solanaTxLink}`) + console.log(`Track cross-chain transfer here: ${layerZeroTxLink}`) + }) diff --git a/examples/omnicounter-solana/tasks/solana/setFreezeAuthority.ts b/examples/omnicounter-solana/tasks/solana/setFreezeAuthority.ts new file mode 100644 index 000000000..7cb1034c1 --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/setFreezeAuthority.ts @@ -0,0 +1,74 @@ +// Import necessary modules and classes from Solana SDKs and other libraries +import assert from 'assert' + +import { AuthorityType, mplToolbox, setAuthority, setComputeUnitPrice } from '@metaplex-foundation/mpl-toolbox' +import { createSignerFromKeypair, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { PublicKey } from '@solana/web3.js' +import { getExplorerLink } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +interface Args { + newAuthority: PublicKey + mint: PublicKey + eid: EndpointId +} + +// Define a Hardhat task for setting the freeze authority on a Solana mint +task('lz:oft:solana:set-freeze-authority', 'Transfer Solana mint authority to a new account') + .addParam('newAuthority', 'The Solana address to transfer authority to') + .addParam('mint', 'The OFT token mint public key') + .addParam('eid', 'The Solana endpoint ID') + .setAction(async (taskArgs: Args) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + // 1. Setup UMI environment using environment variables (private key and Solana RPC) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + // Initialize UMI framework with the Solana connection + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + + // Generate a wallet keypair from the private key stored in the environment + const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(bs58.decode(privateKey)) + const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair) + umi.use(signerIdentity(umiWalletSigner)) + + // Define the mint and new authority public keys + const mintPublicKey = new PublicKey(taskArgs.mint) + const newAuthorityPublicKey = new PublicKey(taskArgs.newAuthority) + const token = fromWeb3JsPublicKey(mintPublicKey) + + // Create the transaction to set the new freeze authority + const setAuthorityTx = setAuthority(umi, { + owned: token, + owner: umiWalletKeyPair.publicKey, + authorityType: AuthorityType.FreezeAccount, + newAuthority: fromWeb3JsPublicKey(newAuthorityPublicKey), + }) + + // Fetch simulation compute unit price + const { averageFeeExcludingZeros } = await getFee(connection) + const avgComputeUnitPrice = Math.round(averageFeeExcludingZeros) + const computeUnitPrice = BigInt(avgComputeUnitPrice * 2) + + // Build and send the transaction + const transaction = await setAuthorityTx + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice })) + .sendAndConfirm(umi) + + const transactionSignature = bs58.encode(transaction.signature) + + // Provide transaction details + const metadataUpdateLink = getExplorerLink('tx', transactionSignature.toString(), 'mainnet-beta') + console.log(`✅ Freeze authority updated! View the transaction here: ${metadataUpdateLink}`) + }) diff --git a/examples/omnicounter-solana/tasks/solana/setMetadataAuthority.ts b/examples/omnicounter-solana/tasks/solana/setMetadataAuthority.ts new file mode 100644 index 000000000..3d5ed5f40 --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/setMetadataAuthority.ts @@ -0,0 +1,86 @@ +// Import necessary modules and classes +import assert from 'assert' + +import { + fetchMetadataFromSeeds, + mplTokenMetadata, + updateAsUpdateAuthorityV2, +} from '@metaplex-foundation/mpl-token-metadata' +import { TransactionBuilder, createSignerFromKeypair, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { PublicKey } from '@solana/web3.js' +import { getExplorerLink } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { createSolanaConnectionFactory } from '../common/utils' + +interface Args { + newAuthority: PublicKey + mint: PublicKey + eid: EndpointId +} + +// Define a Hardhat task to set the metadata update authority for a token mint +task('lz:oft:solana:set-metadata-authority', 'Transfers the account metadata authority for the provided mint') + .addParam('mint', 'The token mint public key to update') + .addParam('newAuthority', 'The new update authority public key') + .addParam('eid', 'The Solana endpoint ID') + .setAction(async (taskArgs: Args) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + // 1. Setup UMI environment using environment variables (private key and Solana RPC) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + // Initialize UMI framework with the Solana connection + const umi = createUmi(connection.rpcEndpoint).use(mplTokenMetadata()) + + // Generate a wallet keypair from the private key stored in the environment + const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(bs58.decode(privateKey)) + const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair) + umi.use(signerIdentity(umiWalletSigner)) + + // Convert public keys to UMI format + const mintPublicKey = new PublicKey(taskArgs.mint) + const newAuthorityPublicKey = new PublicKey(taskArgs.newAuthority) + const umiMintPublicKey = fromWeb3JsPublicKey(mintPublicKey) + const umiNewAuthorityPublicKey = fromWeb3JsPublicKey(newAuthorityPublicKey) + + try { + // Fetch initial metadata for the mint + const initialMetadata = await fetchMetadataFromSeeds(umi, { mint: umiMintPublicKey }) + console.log('Initial metadata:', initialMetadata) + + // Create update instruction to set the new authority + const updateInstruction = updateAsUpdateAuthorityV2(umi, { + mint: umiMintPublicKey, + authority: umiWalletSigner, + data: { + ...initialMetadata, // Keep existing metadata + }, + newUpdateAuthority: umiNewAuthorityPublicKey, + isMutable: true, + }) + + // Build and send the transaction + const builder = new TransactionBuilder().add(updateInstruction) + const transactionResult = await builder.sendAndConfirm(umi) + const transactionSignature = bs58.encode(transactionResult.signature) + + // Provide transaction details + const metadataUpdateLink = getExplorerLink('tx', transactionSignature.toString(), 'mainnet-beta') + console.log(`✅ Metadata authority updated! View the transaction here: ${metadataUpdateLink}`) + + // Fetch and display new metadata to confirm update + const newMetadata = await fetchMetadataFromSeeds(umi, { mint: umiMintPublicKey }) + console.log('New metadata:', newMetadata) + } catch (error) { + console.error('Error updating metadata authority:', error) + } + }) diff --git a/examples/omnicounter-solana/tasks/solana/setOFTMintAuthority.ts b/examples/omnicounter-solana/tasks/solana/setOFTMintAuthority.ts new file mode 100644 index 000000000..2b4de2e35 --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/setOFTMintAuthority.ts @@ -0,0 +1,91 @@ +import assert from 'assert' + +import { mplToolbox, setComputeUnitPrice } from '@metaplex-foundation/mpl-toolbox' +import { TransactionBuilder, createSignerFromKeypair, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsInstruction, toWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' +import { PublicKey } from '@solana/web3.js' +import { getExplorerLink } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OFT_SEED, OftTools } from '@layerzerolabs/lz-solana-sdk-v2' + +import { createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +interface Args { + newAuthority: PublicKey + mint: PublicKey + programId: string + eid: EndpointId +} + +task('lz:oft:solana:set-mint-authority', 'Sets solana mint authority to new account') + .addParam('newAuthority', 'The solana address to transfer authority to') + .addParam('mint', 'The OFT token mint public key') + .addParam('programId', 'The OFT program ID', undefined, types.string) + .addParam('eid', 'The Solana endpoint ID') + .setAction(async (taskArgs: Args) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + // 1. Setup UMI environment using environment variables (private key and Solana RPC) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + // Initialize UMI framework with the Solana connection + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + + // Generate a wallet keypair from the private key stored in the environment + const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(bs58.decode(privateKey)) + const web3WalletKeyPair = toWeb3JsKeypair(umiWalletKeyPair) + const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair) + umi.use(signerIdentity(umiWalletSigner)) + + const mintPublicKey = new PublicKey(taskArgs.mint) + const newAuthorityPublicKey = new PublicKey(taskArgs.newAuthority) + const oftProgramId = new PublicKey(taskArgs.programId) + + // Derive the oftConfig + const [oftConfig] = PublicKey.findProgramAddressSync( + [Buffer.from(OFT_SEED), mintPublicKey.toBuffer()], + oftProgramId + ) + + // Create the instruction using OftTools + const setMintAuthorityIx = await OftTools.createSetMintAuthorityIx( + web3WalletKeyPair.publicKey, + oftConfig, + newAuthorityPublicKey, + oftProgramId + ) + + // Convert the instruction and create the transaction builder + const convertedInstruction = fromWeb3JsInstruction(setMintAuthorityIx) + const transactionBuilder = new TransactionBuilder([ + { + instruction: convertedInstruction, + signers: [umiWalletSigner], + bytesCreatedOnChain: 0, + }, + ]) + + // Fetch simulation compute unit price + const { averageFeeExcludingZeros } = await getFee(connection) + const avgComputeUnitPrice = Math.round(averageFeeExcludingZeros) + const computeUnitPrice = BigInt(avgComputeUnitPrice * 2) + + // Send and confirm the transaction + const transactionSignature = await transactionBuilder + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice })) + .sendAndConfirm(umi) + const setOFTAuthoritySignature = bs58.encode(transactionSignature.signature) + const setOFTAuthorityLink = getExplorerLink('tx', setOFTAuthoritySignature.toString(), 'mainnet-beta') + console.log( + `✅ You set ${newAuthorityPublicKey} as the mint authority of your OFT Config Account: ${oftConfig}, see transaction here: ${setOFTAuthorityLink}` + ) + }) diff --git a/examples/omnicounter-solana/tasks/solana/setRateLimit.ts b/examples/omnicounter-solana/tasks/solana/setRateLimit.ts new file mode 100644 index 000000000..a8a8f330e --- /dev/null +++ b/examples/omnicounter-solana/tasks/solana/setRateLimit.ts @@ -0,0 +1,113 @@ +import assert from 'assert' + +import { mplToolbox, setComputeUnitPrice } from '@metaplex-foundation/mpl-toolbox' +import { TransactionBuilder, createSignerFromKeypair, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsInstruction, fromWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' +import { Keypair, PublicKey } from '@solana/web3.js' +import { getExplorerLink } from '@solana-developers/helpers' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { formatOmniVector } from '@layerzerolabs/devtools' +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OFT_SEED, OftTools } from '@layerzerolabs/lz-solana-sdk-v2' +import { OAppOmniGraph } from '@layerzerolabs/ua-devtools' +import { OAppOmniGraphHardhatSchema, SUBTASK_LZ_OAPP_CONFIG_LOAD } from '@layerzerolabs/ua-devtools-evm-hardhat' + +import { createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +interface Args { + mint: string + eid: EndpointId + programId: string + oappConfig: string +} + +task('lz:oft:solana: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 or testnet', undefined, types.eid) + .addParam('oappConfig', 'The LayerZero Solana config') + .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 graph: OAppOmniGraph = await hre.run(SUBTASK_LZ_OAPP_CONFIG_LOAD, { + configPath: taskArgs.oappConfig, + schema: OAppOmniGraphHardhatSchema, + task: 'lz:oft:solana:rate-limit', + }) + + const solanaRateLimits = { + rateLimitConfig: { + rateLimitCapacity: BigInt('10000000000000000'), + rateLimitRefillRatePerSecond: BigInt('2777777777778'), + }, + } + + let solanaEid: EndpointId + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + // Initialize UMI framework with the Solana connection + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) + umi.use(signerIdentity(umiWalletSigner)) + + const mintPublicKey = new PublicKey(taskArgs.mint) + const OFT_PROGRAM_ID = new PublicKey(taskArgs.programId) + + // Derive the OFT Config's PDA + const [oftConfig] = PublicKey.findProgramAddressSync( + [Buffer.from(OFT_SEED), mintPublicKey.toBuffer()], + OFT_PROGRAM_ID + ) + + for (const peer of graph.connections.filter((connection) => connection.vector.from.eid === solanaEid)) { + try { + const setRateLimitIx = await OftTools.createSetRateLimitIx( + keypair.publicKey, + oftConfig, + peer.vector.to.eid, + solanaRateLimits.rateLimitConfig.rateLimitCapacity, + solanaRateLimits.rateLimitConfig.rateLimitRefillRatePerSecond, + true, + OFT_PROGRAM_ID + ) + + // Convert the instruction and create the transaction builder + const convertedInstruction = fromWeb3JsInstruction(setRateLimitIx) + const transactionBuilder = new TransactionBuilder([ + { + instruction: convertedInstruction, + signers: [umiWalletSigner], + bytesCreatedOnChain: 0, + }, + ]) + + // Fetch simulation compute unit price + const { averageFeeExcludingZeros } = await getFee(connection) + const avgComputeUnitPrice = Math.round(averageFeeExcludingZeros) + const computeUnitPrice = BigInt(avgComputeUnitPrice * 1.1) + + // Send and confirm the transaction + const transactionSignature = await transactionBuilder + .add(setComputeUnitPrice(umi, { microLamports: computeUnitPrice })) + .sendAndConfirm(umi) + const setRateLimitSignature = bs58.encode(transactionSignature.signature) + const setRateLimitLink = getExplorerLink('tx', setRateLimitSignature.toString(), 'mainnet-beta') + console.log( + `✅ You set ${solanaRateLimits.rateLimitConfig.rateLimitCapacity} with a refill of ${solanaRateLimits.rateLimitConfig.rateLimitRefillRatePerSecond} per second for ${formatOmniVector(peer.vector)}! View the transaction here: ${setRateLimitLink}` + ) + } catch (error) { + console.error(`Error processing LayerZero peer with from EID ${formatOmniVector(peer.vector)}:`, error) + } + } + }) diff --git a/examples/omnicounter-solana/tasks/utils/getFee.ts b/examples/omnicounter-solana/tasks/utils/getFee.ts new file mode 100644 index 000000000..08582a423 --- /dev/null +++ b/examples/omnicounter-solana/tasks/utils/getFee.ts @@ -0,0 +1,74 @@ +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 +): Promise<{ + averageFeeIncludingZeros: number + averageFeeExcludingZeros: number + medianFee: number +}> => { + try { + const publicKey = new PublicKey('JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4') + + 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/omnicounter-solana/test/anchor/omnicounter.test.ts b/examples/omnicounter-solana/test/anchor/omnicounter.test.ts new file mode 100644 index 000000000..4c47ac18b --- /dev/null +++ b/examples/omnicounter-solana/test/anchor/omnicounter.test.ts @@ -0,0 +1,6 @@ +describe('OmniCounter', () => { + it('should initialize an OmniCounter', async () => { + console.log('OmniCounter not implemented') + // No other logic or checks here + }) +}) diff --git a/examples/omnicounter-solana/test/foundry/OmniCounter.t.sol b/examples/omnicounter-solana/test/foundry/OmniCounter.t.sol new file mode 100644 index 000000000..df5cbc15e --- /dev/null +++ b/examples/omnicounter-solana/test/foundry/OmniCounter.t.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.15; + +import { Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol"; +import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol"; +import { Errors } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Errors.sol"; + +import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; +import { PreCrimePeer } from "@layerzerolabs/oapp-evm/contracts/precrime/interfaces/IPreCrime.sol"; + +import { TestHelperOz5 as TestHelper } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; + +import { OmniCounter, MsgCodec } from "../../contracts/OmniCounter.sol"; +import { OmniCounterPreCrime } from "../../contracts/OmniCounterPreCrime.sol"; + +import "forge-std/console.sol"; + +contract OmniCounterTest is TestHelper { + using OptionsBuilder for bytes; + + uint32 aEid = 1; + uint32 bEid = 2; + + // omnicounter with precrime + OmniCounter aCounter; + OmniCounterPreCrime aPreCrime; + OmniCounter bCounter; + OmniCounterPreCrime bPreCrime; + + address offchain = address(0xDEAD); + + error CrimeFound(bytes crime); + + function setUp() public virtual override { + super.setUp(); + + setUpEndpoints(2, LibraryType.UltraLightNode); + + address[] memory uas = setupOApps(type(OmniCounter).creationCode, 1, 2); + aCounter = OmniCounter(payable(uas[0])); + bCounter = OmniCounter(payable(uas[1])); + + setUpPreCrime(); + } + + function setUpPreCrime() public { + // set up precrime for aCounter + aPreCrime = new OmniCounterPreCrime(address(aCounter.endpoint()), address(aCounter)); + aPreCrime.setMaxBatchSize(10); + + PreCrimePeer[] memory aCounterPreCrimePeers = new PreCrimePeer[](1); + aCounterPreCrimePeers[0] = PreCrimePeer( + bEid, + addressToBytes32(address(bPreCrime)), + addressToBytes32(address(bCounter)) + ); + aPreCrime.setPreCrimePeers(aCounterPreCrimePeers); + + aCounter.setPreCrime(address(aPreCrime)); + + // set up precrime for bCounter + bPreCrime = new OmniCounterPreCrime(address(bCounter.endpoint()), address(bCounter)); + bPreCrime.setMaxBatchSize(10); + + PreCrimePeer[] memory bCounterPreCrimePeers = new PreCrimePeer[](1); + bCounterPreCrimePeers[0] = PreCrimePeer( + aEid, + addressToBytes32(address(aPreCrime)), + addressToBytes32(address(aCounter)) + ); + bPreCrime.setPreCrimePeers(bCounterPreCrimePeers); + + bCounter.setPreCrime(address(bPreCrime)); + } + + // classic message passing A -> B + function test_increment() public { + uint256 counterBefore = bCounter.count(); + + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.VANILLA_TYPE, options); + aCounter.increment{ value: nativeFee }(bEid, MsgCodec.VANILLA_TYPE, options); + + assertEq(bCounter.count(), counterBefore, "shouldn't be increased until packet is verified"); + + // verify packet to bCounter manually + verifyPackets(bEid, addressToBytes32(address(bCounter))); + + assertEq(bCounter.count(), counterBefore + 1, "increment assertion failure"); + } + + function test_batchIncrement() public { + uint256 counterBefore = bCounter.count(); + + uint256 batchSize = 5; + uint32[] memory eids = new uint32[](batchSize); + uint8[] memory types = new uint8[](batchSize); + bytes[] memory options = new bytes[](batchSize); + bytes memory option = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + uint256 fee; + for (uint256 i = 0; i < batchSize; i++) { + eids[i] = bEid; + types[i] = MsgCodec.VANILLA_TYPE; + options[i] = option; + (uint256 nativeFee, ) = aCounter.quote(eids[i], types[i], options[i]); + fee += nativeFee; + } + + vm.expectRevert(); // Errors.InvalidAmount + aCounter.batchIncrement{ value: fee - 1 }(eids, types, options); + + aCounter.batchIncrement{ value: fee }(eids, types, options); + verifyPackets(bEid, addressToBytes32(address(bCounter))); + + assertEq(bCounter.count(), counterBefore + batchSize, "batchIncrement assertion failure"); + } + + function test_nativeDrop_increment() public { + uint256 balanceBefore = address(bCounter).balance; + + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(200000, 0) + .addExecutorNativeDropOption(1 gwei, addressToBytes32(address(bCounter))); + (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.VANILLA_TYPE, options); + aCounter.increment{ value: nativeFee }(bEid, MsgCodec.VANILLA_TYPE, options); + + // verify packet to bCounter manually + verifyPackets(bEid, addressToBytes32(address(bCounter))); + + assertEq(address(bCounter).balance, balanceBefore + 1 gwei, "nativeDrop assertion failure"); + + // transfer funds out + address payable receiver = payable(address(0xABCD)); + + // withdraw with non admin + vm.startPrank(receiver); + vm.expectRevert(); + bCounter.withdraw(receiver, 1 gwei); + vm.stopPrank(); + + // withdraw with admin + bCounter.withdraw(receiver, 1 gwei); + assertEq(address(bCounter).balance, 0, "withdraw assertion failure"); + assertEq(receiver.balance, 1 gwei, "withdraw assertion failure"); + } + + // classic message passing A -> B1 -> B2 + function test_lzCompose_increment() public { + uint256 countBefore = bCounter.count(); + uint256 composedCountBefore = bCounter.composedCount(); + + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(200000, 0) + .addExecutorLzComposeOption(0, 200000, 0); + (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.COMPOSED_TYPE, options); + aCounter.increment{ value: nativeFee }(bEid, MsgCodec.COMPOSED_TYPE, options); + + verifyPackets(bEid, addressToBytes32(address(bCounter)), 0, address(bCounter)); + + assertEq(bCounter.count(), countBefore + 1, "increment B1 assertion failure"); + assertEq(bCounter.composedCount(), composedCountBefore + 1, "increment B2 assertion failure"); + } + + // A -> B -> A + function test_ABA_increment() public { + uint256 countABefore = aCounter.count(); + uint256 countBBefore = bCounter.count(); + + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(10000000, 10000000); + (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.ABA_TYPE, options); + aCounter.increment{ value: nativeFee }(bEid, MsgCodec.ABA_TYPE, options); + + verifyPackets(bEid, addressToBytes32(address(bCounter))); + assertEq(aCounter.count(), countABefore, "increment A assertion failure"); + assertEq(bCounter.count(), countBBefore + 1, "increment B assertion failure"); + + verifyPackets(aEid, addressToBytes32(address(aCounter))); + assertEq(aCounter.count(), countABefore + 1, "increment A assertion failure"); + } + + // A -> B1 -> B2 -> A + function test_lzCompose_ABA_increment() public { + uint256 countABefore = aCounter.count(); + uint256 countBBefore = bCounter.count(); + uint256 composedCountBBefore = bCounter.composedCount(); + + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(200000, 0) + .addExecutorLzComposeOption(0, 10000000, 10000000); + (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.COMPOSED_ABA_TYPE, options); + aCounter.increment{ value: nativeFee }(bEid, MsgCodec.COMPOSED_ABA_TYPE, options); + + verifyPackets(bEid, addressToBytes32(address(bCounter)), 0, address(bCounter)); + assertEq(bCounter.count(), countBBefore + 1, "increment B1 assertion failure"); + assertEq(bCounter.composedCount(), composedCountBBefore + 1, "increment B2 assertion failure"); + + verifyPackets(aEid, addressToBytes32(address(aCounter))); + assertEq(aCounter.count(), countABefore + 1, "increment A assertion failure"); + } + + function test_omniCounterPreCrime() public { + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + (uint256 nativeFee, ) = aCounter.quote(bEid, MsgCodec.VANILLA_TYPE, options); + + aCounter.increment{ value: nativeFee }(bEid, MsgCodec.VANILLA_TYPE, options); + aCounter.increment{ value: nativeFee }(bEid, MsgCodec.VANILLA_TYPE, options); + assertEq(aCounter.outboundCount(bEid), 2, "outboundCount assertion failure"); + + // precrime should pass + bytes[] memory packets = new bytes[](2); + uint256[] memory packetMsgValues = new uint256[](2); + bytes memory message = MsgCodec.encode(MsgCodec.VANILLA_TYPE, aEid); + packets[0] = PacketV1Codec.encode( + Packet(1, aEid, address(aCounter), bEid, addressToBytes32(address(bCounter)), bytes32(0), message) + ); + packets[1] = PacketV1Codec.encode( + Packet(2, aEid, address(aCounter), bEid, addressToBytes32(address(bCounter)), bytes32(0), message) + ); + + vm.startPrank(offchain); + + bytes[] memory simulations = new bytes[](2); + simulations[0] = aPreCrime.simulate(new bytes[](0), new uint256[](0)); + simulations[1] = bPreCrime.simulate(packets, packetMsgValues); + + bPreCrime.preCrime(packets, packetMsgValues, simulations); + + verifyPackets(bEid, addressToBytes32(address(bCounter))); + assertEq(bCounter.inboundCount(aEid), 2, "inboundCount assertion failure"); + + vm.startPrank(address(this)); + + // precrime a broken increment + aCounter.brokenIncrement{ value: nativeFee }(bEid, MsgCodec.VANILLA_TYPE, options); + assertEq(aCounter.outboundCount(bEid), 2, "outboundCount assertion failure"); // broken outbound increment + + packets = new bytes[](1); + packetMsgValues = new uint256[](1); + packets[0] = PacketV1Codec.encode( + Packet(3, aEid, address(aCounter), bEid, addressToBytes32(address(bCounter)), bytes32(0), message) + ); + + vm.startPrank(offchain); + + simulations[0] = aPreCrime.simulate(new bytes[](0), new uint256[](0)); + simulations[1] = bPreCrime.simulate(packets, packetMsgValues); + + bytes memory expectedError = abi.encodeWithSelector(CrimeFound.selector, "inboundCount > outboundCount"); + vm.expectRevert(expectedError); + + bPreCrime.preCrime(packets, packetMsgValues, simulations); + + verifyPackets(bEid, addressToBytes32(address(bCounter))); + assertEq(bCounter.inboundCount(aEid), 3, "inboundCount assertion failure"); // state broken + } +} diff --git a/examples/omnicounter-solana/test/mocks/ERC20Mock.sol b/examples/omnicounter-solana/test/mocks/ERC20Mock.sol new file mode 100644 index 000000000..6ce17e418 --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/test/mocks/OFTComposerMock.sol b/examples/omnicounter-solana/test/mocks/OFTComposerMock.sol new file mode 100644 index 000000000..fdd5c2426 --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/test/mocks/OFTMock.sol b/examples/omnicounter-solana/test/mocks/OFTMock.sol new file mode 100644 index 000000000..cef8770f5 --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/tsconfig.json b/examples/omnicounter-solana/tsconfig.json new file mode 100644 index 000000000..5fcf450fd --- /dev/null +++ b/examples/omnicounter-solana/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/omnicounter-solana/turbo.json b/examples/omnicounter-solana/turbo.json new file mode 100644 index 000000000..b38dc23a0 --- /dev/null +++ b/examples/omnicounter-solana/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "pipeline": { + "compile": { + "outputs": ["target/**"] + } + } +} diff --git a/packages/devtools-evm-hardhat/test/simulation/docker-compose.yaml b/packages/devtools-evm-hardhat/test/simulation/docker-compose.yaml new file mode 100644 index 000000000..eaaad4969 --- /dev/null +++ b/packages/devtools-evm-hardhat/test/simulation/docker-compose.yaml @@ -0,0 +1,36 @@ +services: + amoy: + build: + dockerfile: Dockerfile + target: node-evm + command: + - anvil + - '--host' + - 0.7.143.228 + - '--port' + - '46991' + - '--count' + - '-1842012111' + - '--block-time' + - '531644837' + someNetwork: + build: + dockerfile: Dockerfile + target: node-evm + command: + - anvil + - '--mnemonic' + - >- + chalk sick discover husband wink long math satisfy curious rough make + alcohol + rpc: + build: + dockerfile: Dockerfile + target: proxy-evm + ports: + - '16:8545' + depends_on: + amoy: + condition: service_healthy + someNetwork: + condition: service_healthy diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94e062649..b934cae57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -482,6 +482,12 @@ importers: '@layerzerolabs/toolbox-hardhat': specifier: ~0.3.7 version: link:../../packages/toolbox-hardhat + '@layerzerolabs/tsup-config-next': + specifier: ^2.3.31 + version: 2.3.38(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.5.4) + '@layerzerolabs/typescript-config-next': + specifier: ^2.3.31 + version: 2.3.38 '@layerzerolabs/ua-devtools': specifier: ~1.0.5 version: link:../../packages/ua-devtools @@ -494,12 +500,21 @@ importers: '@layerzerolabs/ua-devtools-solana': specifier: ~1.0.5 version: link:../../packages/ua-devtools-solana + '@metaplex-foundation/beet': + specifier: ^0.7.1 + version: 0.7.2 + '@metaplex-foundation/beet-solana': + specifier: ^0.4.0 + version: 0.4.1 '@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/solita': + specifier: ^0.20.1 + version: 0.20.1 '@metaplex-foundation/umi': specifier: ^0.9.2 version: 0.9.2 @@ -545,6 +560,9 @@ importers: '@swc/jest': specifier: ^0.2.36 version: 0.2.36(@swc/core@1.4.0) + '@types/bn.js': + specifier: ^5.1.5 + version: 5.1.5 '@types/chai': specifier: ^4.3.11 version: 4.3.11 @@ -557,6 +575,12 @@ importers: '@types/node': specifier: ~18.18.14 version: 18.18.14 + bip39: + specifier: ^3.1.0 + version: 3.1.0 + bn.js: + specifier: ^5.2.1 + version: 5.2.1 bs58: specifier: ^6.0.0 version: 6.0.0 @@ -566,12 +590,18 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.5 + ed25519-hd-key: + specifier: ^1.3.0 + version: 1.3.0 eslint: specifier: ^8.55.0 version: 8.57.0 eslint-plugin-jest-extended: specifier: ~2.0.0 version: 2.0.0(eslint@8.57.0)(typescript@5.5.4) + ethereumjs-util: + specifier: ^7.1.5 + version: 7.1.5 ethers: specifier: ^5.7.2 version: 5.7.2 @@ -596,18 +626,50 @@ importers: prettier: specifier: ^3.2.5 version: 3.2.5 + rimraf: + specifier: ^5.0.5 + version: 5.0.9 solhint: specifier: ^4.1.1 version: 4.1.1(typescript@5.5.4) solidity-bytes-utils: specifier: ^0.8.2 version: 0.8.2 + tiny-invariant: + specifier: ^1.3.1 + version: 1.3.3 + ts-mocha: + specifier: ^10.0.0 + version: 10.0.0(mocha@10.2.0) ts-node: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.4) + tsup: + specifier: ^8.0.1 + version: 8.0.1(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.5.4) typescript: specifier: ^5.4.4 version: 5.5.4 + zx: + specifier: ^8.1.3 + version: 8.1.4 + + examples/omnicounter-solana: + devDependencies: + '@babel/core': + specifier: ^7.23.9 + version: 7.23.9 + '@coral-xyz/anchor': + specifier: ^0.29.0 + version: 0.29.0 + '@ethersproject/bytes': + specifier: ^5.7.0 + version: 5.7.0 + '@ethersproject/keccak256': + specifier: ^5.7.0 + version: 5.7.0 + '@layerzerolabs/devtools': + specifier: ~0.3.23 examples/onft721: devDependencies: @@ -3521,7 +3583,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.9 '@babel/types': 7.23.9 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3568,7 +3630,7 @@ packages: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.6.2 + semver: 7.6.3 dev: true /@changesets/assemble-release-plan@6.0.0: @@ -3579,7 +3641,7 @@ packages: '@changesets/get-dependents-graph': 2.0.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - semver: 7.6.2 + semver: 7.6.3 dev: true /@changesets/changelog-git@0.2.0: @@ -3651,7 +3713,7 @@ packages: '@manypkg/get-packages': 1.1.3 chalk: 2.4.2 fs-extra: 7.0.1 - semver: 7.6.2 + semver: 7.6.3 dev: true /@changesets/get-release-plan@4.0.0: @@ -3736,6 +3798,42 @@ packages: prettier: 2.8.8 dev: true + /@chialab/cjs-to-esm@0.17.11: + resolution: {integrity: sha512-yzIKx1J7ykDoKm+CQXw/gkI0B3YbGDUAkkz70ohoPVI9stSeNwdvXMNcNwMQMndQleJkTS8aOi4j3JRrBbVAng==} + engines: {node: '>=13'} + dependencies: + '@chialab/estransform': 0.17.5 + dev: true + + /@chialab/esbuild-plugin-commonjs@0.17.2: + resolution: {integrity: sha512-C30UShSb89PJiO+mJTwAS1ei6aKVxMcxZQrl0g1JAJVd4d1AE6OjcbYlCs4847PcbmMNqYZrTcWpgIg+zRQL3Q==} + engines: {node: '>=13'} + dependencies: + '@chialab/cjs-to-esm': 0.17.11 + '@chialab/esbuild-rna': 0.17.8 + '@chialab/node-resolve': 0.17.1 + dev: true + + /@chialab/esbuild-rna@0.17.8: + resolution: {integrity: sha512-hovU4W5zlyMnbJjexdczpQ9mcUfFsJuv9FWUhzpXiQwPprlp5lul+frTed9s8AyVDTDq2yq3Gx2Ac411QsXYGA==} + engines: {node: '>=13'} + dependencies: + '@chialab/estransform': 0.17.5 + '@chialab/node-resolve': 0.17.1 + dev: true + + /@chialab/estransform@0.17.5: + resolution: {integrity: sha512-maJUFkwk0ie0L4VvDO74NDYyRvaTQAI0qmSmrms8bZxUkZ+zQZd1ByWKDCYTRwtR6AOzTvgEOl2ZvEG+OUKv/A==} + engines: {node: '>=13'} + dependencies: + '@parcel/source-map': 2.1.1 + dev: true + + /@chialab/node-resolve@0.17.1: + resolution: {integrity: sha512-YWaK0MKKeB0FILI6j7qiAlGoSC9MqJZDFXzlAgfOaMCbn8Feqh6njxv7mI3oVkdi7QwV6zPRaTN6hKig/NriMA==} + engines: {node: '>=13'} + dev: true + /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -5018,12 +5116,30 @@ packages: - encoding - utf-8-validate dev: true + '@noble/hashes': 1.4.0 + '@noble/secp256k1': 1.7.1 + '@solana/web3.js': 1.95.2 + 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-definitions@2.3.39: resolution: {integrity: sha512-PcRrn7/ZNEcNoa23gkfJ0GNLVFYl/OVj5h9ZOTLNCTXzI2qPAcA2n2VQI9t4qMHX+3c+V7pZhvQMmD9qt4fY4g==} dependencies: tiny-invariant: 1.3.3 + /@layerzerolabs/lz-definitions@2.3.39: + resolution: {integrity: sha512-PcRrn7/ZNEcNoa23gkfJ0GNLVFYl/OVj5h9ZOTLNCTXzI2qPAcA2n2VQI9t4qMHX+3c+V7pZhvQMmD9qt4fY4g==} + dependencies: + tiny-invariant: 1.3.3 + dev: true + /@layerzerolabs/lz-evm-messagelib-v2@2.3.39(@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@2.3.39)(@layerzerolabs/lz-evm-v1-0.7@2.3.39)(@openzeppelin/contracts-upgradeable@4.9.5)(@openzeppelin/contracts@4.9.5)(hardhat-deploy@0.12.1)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-DlWvoy8xjDygZVsBtLC87lLtbdNINdvTHG1RP6UTFyRuHv81MUjg7VQbdcSdFTds5VWM48I64ay7q1lSoviyHg==} peerDependencies: @@ -5324,6 +5440,19 @@ packages: bs58: 5.0.0 tiny-invariant: 1.3.3 + /@layerzerolabs/lz-v2-utilities@2.3.39: + resolution: {integrity: sha512-sue9hz030+alJu29gPSjQXTDKJF5z8KjeJ9JWNgUqIFosgCs44C1ok4sVse+7uaNP//fIokuRcV4hvUw5mRSZg==} + 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/solidity': 5.7.0 + bs58: 5.0.0 + tiny-invariant: 1.3.3 + dev: true + /@layerzerolabs/prettier-config-next@2.3.39: resolution: {integrity: sha512-IrOi1kjxIZEYYtbeS0zIUe816MbornatPKl6cRIdS/JD5dz/sc+SqbZxl66/iNhkfe/PeFVWSxmNRiu/xofxUA==} dependencies: @@ -5340,6 +5469,26 @@ packages: - typescript dev: true + /@layerzerolabs/tsup-config-next@2.3.38(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.5.4): + resolution: {integrity: sha512-YpkaVp/g8tQ88+MFmWBu2JX997WLZPr+mOMtPX1+RmmVqvKU8aAl0TKW/cGPVw9gZ8rOrJiZD99M597v9K+IKA==} + dependencies: + '@chialab/esbuild-plugin-commonjs': 0.17.2 + esbuild: 0.19.11 + esbuild-node-externals: 1.14.0(esbuild@0.19.11) + tsup: 8.0.1(@swc/core@1.4.0)(ts-node@10.9.2)(typescript@5.5.4) + transitivePeerDependencies: + - '@microsoft/api-extractor' + - '@swc/core' + - postcss + - supports-color + - ts-node + - typescript + dev: true + + /@layerzerolabs/typescript-config-next@2.3.38: + resolution: {integrity: sha512-kcbOo/+b1c8w0W6s18yDq7lRUYRkVY4lhiUk2rsmDXiZ1jJobTeAmIzXub17IE9WxXLFkfrh6VTUW3NCIqhT8Q==} + dev: true + /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: @@ -5918,6 +6067,13 @@ packages: resolution: {integrity: sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==} dev: true + /@parcel/source-map@2.1.1: + resolution: {integrity: sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==} + engines: {node: ^12.18.3 || >=14} + dependencies: + detect-libc: 1.0.3 + dev: true + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -6770,6 +6926,15 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true + /@types/fs-extra@11.0.4: + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + requiresBuild: true + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 18.18.14 + dev: true + optional: true + /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: @@ -6816,6 +6981,14 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/jsonfile@6.1.4: + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + requiresBuild: true + dependencies: + '@types/node': 18.18.14 + dev: true + optional: true + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -6841,6 +7014,14 @@ packages: dependencies: undici-types: 5.26.5 + /@types/node@22.5.0: + resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==} + requiresBuild: true + dependencies: + undici-types: 6.19.8 + dev: true + optional: true + /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true @@ -7850,7 +8031,7 @@ packages: /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: - semver: 7.6.2 + semver: 7.6.3 dev: true /bundle-require@4.0.2(esbuild@0.19.11): @@ -8665,6 +8846,12 @@ packages: engines: {node: '>=12.20'} dev: true + /detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -8680,6 +8867,11 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /diff@3.5.0: + resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} + engines: {node: '>=0.3.1'} + dev: true + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -8969,6 +9161,17 @@ packages: d: 1.0.2 ext: 1.7.0 + /esbuild-node-externals@1.14.0(esbuild@0.19.11): + resolution: {integrity: sha512-jMWnTlCII3cLEjR5+u0JRSTJuP+MgbjEHKfwSIAI41NgLQ0ZjfzjchlbEn0r7v2u5gCBMSEYvYlkO7GDG8gG3A==} + engines: {node: '>=12'} + peerDependencies: + esbuild: 0.12 - 0.23 + dependencies: + esbuild: 0.19.11 + find-up: 5.0.0 + tslib: 2.6.3 + dev: true + /esbuild-plugin-copy@2.1.1(esbuild@0.23.0): resolution: {integrity: sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw==} peerDependencies: @@ -10320,7 +10523,7 @@ packages: foreground-child: 3.1.1 jackspeak: 2.3.6 minimatch: 9.0.4 - minipass: 7.0.4 + minipass: 7.1.2 path-scurry: 1.10.1 dev: true @@ -10349,6 +10552,7 @@ packages: /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -10405,7 +10609,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.0 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 dev: true @@ -10827,7 +11031,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5 transitivePeerDependencies: - supports-color dev: true @@ -12536,7 +12740,6 @@ packages: hasBin: true dependencies: minimist: 1.2.8 - dev: false /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} @@ -13218,7 +13421,7 @@ packages: dependencies: '@solidity-parser/parser': 0.17.0 prettier: 3.2.5 - semver: 7.6.2 + semver: 7.6.3 solidity-comments-extractor: 0.0.8 dev: true @@ -13662,6 +13865,7 @@ packages: /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true dependencies: glob: 7.2.3 @@ -13815,12 +14019,6 @@ packages: 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==} engines: {node: '>=10'} @@ -14692,6 +14890,19 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-mocha@10.0.0(mocha@10.2.0): + resolution: {integrity: sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==} + engines: {node: '>= 6.X.X'} + hasBin: true + peerDependencies: + mocha: ^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X + dependencies: + mocha: 10.2.0 + ts-node: 7.0.1 + optionalDependencies: + tsconfig-paths: 3.15.0 + dev: true + /ts-node@10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.4): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -14723,6 +14934,21 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + /ts-node@7.0.1: + resolution: {integrity: sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==} + engines: {node: '>=4.2.0'} + hasBin: true + dependencies: + arrify: 1.0.1 + buffer-from: 1.1.2 + diff: 3.5.0 + make-error: 1.3.6 + minimist: 1.2.8 + mkdirp: 0.5.6 + source-map-support: 0.5.21 + yn: 2.0.0 + dev: true + /tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: @@ -15058,6 +15284,12 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + requiresBuild: true + dev: true + optional: true + /undici@5.28.2: resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==} engines: {node: '>=14.0'} @@ -15856,6 +16088,11 @@ packages: yargs-parser: 21.1.1 dev: true + /yn@2.0.0: + resolution: {integrity: sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==} + engines: {node: '>=4'} + dev: true + /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -15908,6 +16145,15 @@ packages: /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + /zx@8.1.4: + resolution: {integrity: sha512-QFDYYpnzdpRiJ3dL2102Cw26FpXpWshW4QLTGxiYfIcwdAqg084jRCkK/kuP/NOSkxOjydRwNFG81qzA5r1a6w==} + engines: {node: '>= 12.17.0'} + hasBin: true + optionalDependencies: + '@types/fs-extra': 11.0.4 + '@types/node': 22.5.0 + dev: true + github.com/LayerZero-Labs/es5-ext/7e360296a7e27176e240bc063b32f5ada55f9aa6: resolution: {tarball: https://codeload.github.com/LayerZero-Labs/es5-ext/tar.gz/7e360296a7e27176e240bc063b32f5ada55f9aa6} name: es5-ext