diff --git a/examples/oft-solana-composer-library/.env.example b/examples/oft-solana-composer-library/.env.example new file mode 100644 index 000000000..a2ee9642c --- /dev/null +++ b/examples/oft-solana-composer-library/.env.example @@ -0,0 +1,22 @@ +# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.- +# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ +# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' +# +# Example environment configuration +# +# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.- +# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ +# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' + +# By default, the examples support both mnemonic-based and private key-based authentication +# +# You don't need to set both of these values, just pick the one that you prefer and set that one +# +# By default, the Solana example will use the default cluster RPC URL if no other value is provided +# For SOLANA_PRIVATE_KEY use base58 encoding +MNEMONIC= +PRIVATE_KEY= # Private key for EVM contract owner/delegate +SOLANA_PRIVATE_KEY= +SOLANA_KEYPAIR_PATH= +RPC_URL_SOLANA= +RPC_URL_SOLANA_TESTNET= \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.eslintignore b/examples/oft-solana-composer-library/.eslintignore new file mode 100644 index 000000000..50166580a --- /dev/null +++ b/examples/oft-solana-composer-library/.eslintignore @@ -0,0 +1,13 @@ +.anchor +.turbo +node_modules +target +artifacts +cache +dist +out +*.log +*.sol +*.yaml +*.lock +package-lock.json \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.eslintrc.js b/examples/oft-solana-composer-library/.eslintrc.js new file mode 100644 index 000000000..bd7363593 --- /dev/null +++ b/examples/oft-solana-composer-library/.eslintrc.js @@ -0,0 +1,12 @@ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + root: true, + extends: ['@layerzerolabs/eslint-config-next/recommended'], + rules: { + // @layerzerolabs/eslint-config-next defines rules for turborepo-based projects + // that are not relevant for this particular project + 'turbo/no-undeclared-env-vars': 'off', + 'import/no-unresolved': 'warn', + }, +}; diff --git a/examples/oft-solana-composer-library/.gitignore b/examples/oft-solana-composer-library/.gitignore new file mode 100644 index 000000000..3303873c5 --- /dev/null +++ b/examples/oft-solana-composer-library/.gitignore @@ -0,0 +1,29 @@ +.anchor +node_modules +.env +coverage +coverage.json +target +typechain +typechain-types +wallet.json +keypair.json +secret.txt + +# Hardhat files +cache +artifacts + + +# LayerZero specific files +.layerzero + +# foundry test compilation files +out + +# pnpm +pnpm-error.log + +# Editor and OS files +.DS_Store +.idea diff --git a/examples/oft-solana-composer-library/.nvmrc b/examples/oft-solana-composer-library/.nvmrc new file mode 100644 index 000000000..3462e8c26 --- /dev/null +++ b/examples/oft-solana-composer-library/.nvmrc @@ -0,0 +1 @@ +v18.19.0 \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.prettierignore b/examples/oft-solana-composer-library/.prettierignore new file mode 100644 index 000000000..fbb1e9c85 --- /dev/null +++ b/examples/oft-solana-composer-library/.prettierignore @@ -0,0 +1,13 @@ +.anchor +.turbo +node_modules/ +target +artifacts/ +cache/ +dist/ +out/ +*.log +*ignore +*.yaml +*.lock +package-lock.json \ No newline at end of file diff --git a/examples/oft-solana-composer-library/.prettierrc.js b/examples/oft-solana-composer-library/.prettierrc.js new file mode 100644 index 000000000..6f55b4019 --- /dev/null +++ b/examples/oft-solana-composer-library/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('@layerzerolabs/prettier-config-next'), +}; diff --git a/examples/oft-solana-composer-library/.solhintrc.js b/examples/oft-solana-composer-library/.solhintrc.js new file mode 100644 index 000000000..102eae347 --- /dev/null +++ b/examples/oft-solana-composer-library/.solhintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['solhint:recommended', require.resolve('@layerzerolabs/solhint-config')], +}; diff --git a/examples/oft-solana-composer-library/Anchor.toml b/examples/oft-solana-composer-library/Anchor.toml new file mode 100644 index 000000000..7193fabb3 --- /dev/null +++ b/examples/oft-solana-composer-library/Anchor.toml @@ -0,0 +1,64 @@ +[toolchain] +anchor_version = "0.29.0" + +[features] +seeds = false +skip-lint = false + +[programs.localnet] +composer = "6xhpdhwyzpjxc6n2KqQS8a3W7Busn6nqGYaobVV25kN5" +oft = "HipE7QwWtLyDTQNP61dEqGN8JNjUUzCMsUxpxRQMoeYx" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "Localnet" +wallet = "./keypair.json" + +[scripts] +test = "npx jest test/anchor/" + +[test] +startup_wait = 5000 +shutdown_wait = 2000 +upgradeable = false + +[test.validator] +bind_address = "0.0.0.0" +url = "https://api.mainnet-beta.solana.com" +ledger = ".anchor/test-ledger" +rpc_port = 8899 + +[[test.validator.clone]] +address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" + +[[test.validator.clone]] +address = "9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x" + +[[test.validator.clone]] +address = "D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL" + +[[test.validator.clone]] +address = "Cv3s57YQzJRp988qvXPfZN5xX7BUFcgB1ycA9jzSrh2n" + +[[test.validator.clone]] +address = "J1jEbmdbtsfA26igi1ryQjejnYVQRpxnobZh9tgSaH1h" + +[[test.validator.clone]] +address = "8AQHmKsoFh5Uh8bzYnnQ4Fx2QL2axPao9hjnt1YQBvdS" + +[[test.validator.clone]] +address = "3ZUDGLF7xw26gcXUhZK3tL4MU2VizCUkXoHdb2LQ4zDM" + +[[test.validator.clone]] +address = "5Nb7bdyi4jkMdA8Xqmt72HS1fpkGRQ573Gf8PJeGunsy" + +[[test.validator.clone]] +address = "sUfgz2jsDhN8qn1PrkWr9pVnUrLqqL1yB7WAnCj62Xz" + +[[test.validator.clone]] +address = "DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT" + +[[test.validator.clone]] +address = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" diff --git a/examples/oft-solana-composer-library/Cargo.lock b/examples/oft-solana-composer-library/Cargo.lock new file mode 100644 index 000000000..fe600d563 --- /dev/null +++ b/examples/oft-solana-composer-library/Cargo.lock @@ -0,0 +1,2757 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anchor-attribute-access-control" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" +dependencies = [ + "anchor-syn", + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" +dependencies = [ + "anchor-syn", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +dependencies = [ + "anchor-syn", + "borsh-derive-internal 0.10.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-space" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-program", + "anchor-derive-accounts", + "anchor-derive-serde", + "anchor-derive-space", + "anchor-syn", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.10.4", + "bytemuck", + "getrandom 0.2.15", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-spl" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a" +dependencies = [ + "anchor-lang", + "mpl-token-metadata", + "solana-program", + "spl-associated-token-account", + "spl-token", + "spl-token-2022", +] + +[[package]] +name = "anchor-syn" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" +dependencies = [ + "anyhow", + "bs58 0.5.1", + "heck", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.8", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake3" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" +dependencies = [ + "borsh-derive 0.10.4", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831213f80d9423998dd696e2c5345aba6be7a0bd8cd19e31c5243e13df1cef89" +dependencies = [ + "borsh-derive-internal 0.10.4", + "borsh-schema-derive-internal 0.10.4", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65d6ba50644c98714aa2a70d13d7df3cd75cd2b523a2b452bf010443800976b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276691d96f063427be83e6692b86148e488ebba9f48f77788724ca027ba3b6d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "composer" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "oapp", + "raydium-clmm-cpi", + "solana-helper", + "spl-memo", + "utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpi-helper" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.100", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.8", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "endpoint" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", + "cpi-helper", + "messagelib-interface", + "solana-helper", + "solana-program", + "utils", +] + +[[package]] +name = "endpoint-mock" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "cpi-helper", + "solana-program", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.5", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.2", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint", + "thiserror", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "messagelib-interface" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", +] + +[[package]] +name = "mpl-token-metadata" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8ee05284d79b367ae8966d558e1a305a781fc80c9df51f37775169117ba64f" +dependencies = [ + "borsh 0.10.4", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", +] + +[[package]] +name = "num_enum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +dependencies = [ + "num_enum_derive 0.7.0", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "oapp" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", + "endpoint", +] + +[[package]] +name = "oft" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "oapp", + "solana-helper", + "utils", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.24", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "raydium-clmm-cpi" +version = "0.1.0" +source = "git+https://github.com/St0rmBr3w/raydium-cpi/?branch=anchor-0.29.0#586486b3ee307c157ac12bec5a779717bbdac489" +dependencies = [ + "ahash 0.8.5", + "anchor-lang", + "anchor-spl", + "num_enum 0.7.0", + "solana-program", + "spl-memo", + "spl-token", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "solana-frozen-abi" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96734b05823c8b515f8e3cc02641a27aee2c9760b1a43c74cb20f2a1ab0ab76c" +dependencies = [ + "ahash 0.8.5", + "blake3", + "block-buffer 0.10.4", + "bs58 0.4.0", + "bv", + "byteorder", + "cc", + "either", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "solana-frozen-abi-macro", + "subtle", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a0f1291a464fd046135d019d57a81be165ee3d23aa7df880b47dac683a0582a" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.100", +] + +[[package]] +name = "solana-helper" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c6deff8c48efb84b5828db064ad9873ef3445f129f888b4b6a664bd5220e35" +dependencies = [ + "bs58 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "solana-logger" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5977c8f24b83cf50e7139ffdb25d70bad6a177f18ccc79ca2293d6a987fa81c" +dependencies = [ + "env_logger", + "lazy_static", + "log", +] + +[[package]] +name = "solana-program" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6412447793f8a3ef7526655906728325093b472e481791ac5c584e8d272166dc" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", + "bincode", + "bitflags", + "blake3", + "borsh 0.10.4", + "borsh 0.9.3", + "bs58 0.4.0", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.15", + "itertools", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1", + "light-poseidon", + "log", + "memoffset", + "num-bigint", + "num-derive 0.3.3", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3 0.10.8", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-sdk" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de1ce8848de4198f9bc7e4574252be02b1ed86ecbc2fff506780d5f8d6e4c4a8" +dependencies = [ + "assert_matches", + "base64 0.21.7", + "bincode", + "bitflags", + "borsh 0.10.4", + "bs58 0.4.0", + "bytemuck", + "byteorder", + "chrono", + "derivation-path", + "digest 0.10.7", + "ed25519-dalek", + "ed25519-dalek-bip32", + "generic-array", + "hmac 0.12.1", + "itertools", + "js-sys", + "lazy_static", + "libsecp256k1", + "log", + "memmap2", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.6.1", + "pbkdf2 0.11.0", + "qstring", + "qualifier_attr", + "rand 0.7.3", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "serde_with", + "sha2 0.10.8", + "sha3 0.10.8", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-program", + "solana-sdk-macro", + "thiserror", + "uriparse", + "wasm-bindgen", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cc46bbda0a5472d8d0a4c846b22941436ac45c31456d3e885a387a5f264f7" +dependencies = [ + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "1.17.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597dddc8ab46852dea7fc3d22e031fa4ffdb1b2291ac24d960605424a510a5f5" +dependencies = [ + "aes-gcm-siv", + "base64 0.21.7", + "bincode", + "bytemuck", + "byteorder", + "curve25519-dalek", + "getrandom 0.1.16", + "itertools", + "lazy_static", + "merlin", + "num-derive 0.3.3", + "num-traits", + "rand 0.7.3", + "serde", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "spl-associated-token-account" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385e31c29981488f2820b2022d8e731aae3b02e6e18e2fd854e4c9a94dc44fc3" +dependencies = [ + "assert_matches", + "borsh 0.10.4", + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-token", + "spl-token-2022", + "thiserror", +] + +[[package]] +name = "spl-discriminator" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" +dependencies = [ + "quote", + "spl-discriminator-syn", + "syn 2.0.100", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.100", + "thiserror", +] + +[[package]] +name = "spl-memo" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" +dependencies = [ + "solana-program", +] + +[[package]] +name = "spl-pod" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +dependencies = [ + "borsh 0.10.4", + "bytemuck", + "solana-program", + "solana-zk-token-sdk", + "spl-program-error", +] + +[[package]] +name = "spl-program-error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "solana-program", + "spl-program-error-derive", + "thiserror", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.8", + "syn 2.0.100", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-token" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.6.1", + "solana-program", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.0", + "solana-program", + "solana-zk-token-sdk", + "spl-memo", + "spl-pod", + "spl-token", + "spl-token-metadata-interface", + "spl-transfer-hook-interface", + "spl-type-length-value", + "thiserror", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c16ce3ba6979645fb7627aa1e435576172dd63088dc7848cb09aa331fa1fe4f" +dependencies = [ + "borsh 0.10.4", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b" +dependencies = [ + "arrayref", + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution", + "spl-type-length-value", +] + +[[package]] +name = "spl-type-length-value" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +dependencies = [ + "bytemuck", + "solana-program", + "spl-discriminator", + "spl-pod", + "spl-program-error", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + +[[package]] +name = "utils" +version = "0.1.0" +source = "git+https://github.com/LayerZero-Labs/LayerZero-v2.git?rev=34321ac15e47e0dafd25d66659e2f3d1b9b6db8f#34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" +dependencies = [ + "anchor-lang", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] diff --git a/examples/oft-solana-composer-library/Cargo.toml b/examples/oft-solana-composer-library/Cargo.toml new file mode 100644 index 000000000..ae8c8729c --- /dev/null +++ b/examples/oft-solana-composer-library/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +members = ["programs/*"] +resolver = "2" + +# [features] +# idl-build = ["anchor-lang/idl-build"] + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 + +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/examples/oft-solana-composer-library/README.md b/examples/oft-solana-composer-library/README.md new file mode 100644 index 000000000..e1ab56126 --- /dev/null +++ b/examples/oft-solana-composer-library/README.md @@ -0,0 +1,376 @@ +

+ + LayerZero + +

+ +

+ Homepage | Docs | Developers +

+ +

Omnichain Fungible Token (OFT) Solana Example

+ +## Requirements + +- Rust `v1.75.0` +- Anchor `v0.29` +- Solana CLI `v1.17.31` +- Docker +- Node.js + +## Setup + +We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice). + +[Docker](https://docs.docker.com/get-started/get-docker/) is required to build using anchor. We highly recommend that you use the most up-to-date Docker version to avoid any issues with anchor +builds. + +:warning: You need anchor version `0.29` and solana version `1.17.31` specifically to compile the build artifacts. Using higher Anchor and Solana versions can introduce unexpected issues during compilation. See the following issues in Anchor's repo: [1](https://github.com/coral-xyz/anchor/issues/3089), [2](https://github.com/coral-xyz/anchor/issues/2835). After compiling the correct build artifacts, you can change the Solana version to higher versions. + +### Install Rust + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +``` + +### Install Solana + +```bash +sh -c "$(curl -sSfL https://release.solana.com/v1.17.31/install)" +``` + +### Install Anchor + +Install and use the correct version + +```bash +cargo install --git https://github.com/coral-xyz/anchor --tag v0.29.0 anchor-cli --locked +``` + +### Get the code + +```bash +LZ_ENABLE_SOLANA_OFT_EXAMPLE=1 npx create-lz-oapp@latest +``` + +### Installing Dependencies + +```bash +pnpm install +``` + +### Running tests + +```bash +pnpm test +``` + +### Get Devnet SOL + +```bash +solana airdrop 5 -u devnet +``` + +We recommend that you request 5 devnet SOL, which should be sufficient for this walkthrough. For the example here, we will be using Solana Devnet. If you hit rate limits, you can also use the [official Solana faucet](https://faucet.solana.com/). + +### Prepare `.env` + +```bash +cp .env.example .env +``` + +#### Solana Keypair + +By default, the scripts will use the keypair at the default location `~/.config/solana/id.json`. If you want to use this keypair, there is no need to set any environment variable. There will, however, be a prompt when running certain commands to confirm that you want to use the default keypair. + +If you wish to use a different keypair, then you can set either of the following in the `.env`: + +1. `SOLANA_PRIVATE_KEY` - this can be either in base58 string format (i.e. when imported from a wallet) or the Uint8 Array in string format (all in one line, e.g. `[1,1,...1]`). + +2. `SOLANA_KEYPAIR_PATH` - the location to the keypair file that you want to use. + +#### Solana RPC + +Also set the `RPC_URL_SOLANA_TESTNET` value. Note that while the naming used here is `TESTNET`, it refers to the [Solana Devnet](https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts#solana-testnet). We use `TESTNET` to keep it consistent with the existing EVM testnets. + +## Deploy + +### Prepare the OFT Program ID + +Create `programId` keypair files by running: + +```bash +solana-keygen new -o target/deploy/endpoint-keypair.json --force +solana-keygen new -o target/deploy/oft-keypair.json --force + +anchor keys sync +``` + +:warning: `--force` flag overwrites the existing keys with the ones you generate. + +Run + +``` +anchor keys list +``` + +to view the generated programIds (public keys). The output should look something like this: + +``` +endpoint: +oft: +``` + +Copy the OFT's program ID, which you will use in the build step. + +### Building and Deploying the Solana OFT Program + +Ensure you have Docker running before running the build command. + +#### Build the Solana OFT program + +```bash +anchor build -v -e OFT_ID= +``` + +Where `` is replaced with your OFT Program ID copied from the previous step. + + + +#### Preview Rent Costs for the Solana OFT + +:information_source: The majority of the SOL required to deploy your program will be for [**rent**](https://solana.com/docs/core/fees#rent) (specifically, for the minimum balance of SOL required for [rent-exemption](https://solana.com/docs/core/fees#rent-exempt)), which is calculated based on the amount of bytes the program or account uses. Programs typically require more rent than PDAs as more bytes are required to store the program's executable code. + +In our case, the OFT Program's rent accounts for roughly 99% of the SOL needed during deployment, while the other accounts' rent, OFT Store, Mint, Mint Authority Multisig and Escrow make up for only a fraction of the SOL needed. + +You can preview how much SOL would be needed for the program account. Note that the total SOL required would to be slightly higher than just this, to account for the other accounts that need to be created. + +```bash +solana rent $(wc -c < target/verifiable/oft.so) +``` + +You should see an output such as + +```bash +Rent-exempt minimum: 3.87415872 SOL +``` + +:information_source: LayerZero's default deployment path for Solana OFTs require you to deploy your own OFT program as this means you own the Upgrade Authority and don't rely on LayerZero to manage that authority for you. Read [this](https://neodyme.io/en/blog/solana_upgrade_authority/) to understand more no why this is important. + +#### Deploy the Solana OFT + +While for building, we must use Solana `v1.17.31`, for deploying, we will be using `v1.18.26` as it provides an improved program deployment experience (i.e. ability to attach priority fees and also exact-sized on-chain program length which prevents needing to provide 2x the rent as in `v1.17.31`). + +##### Temporarily switch to Solana `v1.18.26` + +First, we switch to Solana `v1.18.26` (remember to switch back to `v1.17.31` later) + +```bash +sh -c "$(curl -sSfL https://release.solana.com/v1.18.26/install)" +``` + +##### (Recommended) Deploying with a priority fee + +This section applies if you are unable to land your deployment transaction due to network congestion. + +:information_source: [Priority Fees](https://solana.com/developers/guides/advanced/how-to-use-priority-fees) are Solana's mechanism to allow transactions to be prioritized during periods of network congestion. When the network is busy, transactions without priority fees might never be processed. It is then necessary to include priority fees, or wait until the network is less congested. Priority fees are calculated as follows: `priorityFee = compute budget * compute unit price`. We can make use of priority fees by attaching the `--with-compute-unit-price` flag to our `solana program deploy` command. Note that the flag takes in a value in micro lamports, where 1 micro lamport = 0.000001 lamport. + +You can run refer QuickNode's [Solana Priority Fee Tracker](https://www.quicknode.com/gas-tracker/solana) to know what value you'd need to pass into the `--with-compute-unit-price` flag. + +##### Run the deploy command + +```bash +solana program deploy --program-id target/deploy/oft-keypair.json target/verifiable/oft.so -u devnet --with-compute-unit-price +``` + +:information_source: the `-u` flag specifies the RPC URL that should be used. The options are `mainnet-beta, devnet, testnet, localhost`, which also have their respective shorthands: `-um, -ud, -ut, -ul` + +:warning: If the deployment is slow, it could be that the network is congested and you might need to increase the priority fee. + +##### Switch back to Solana `1.17.31` + +:warning: After deploying, make sure to switch back to v1.17.31 after deploying. If you need to rebuild artifacts, you must use Solana CLI version `1.17.31` and Anchor version `0.29.0` + +```bash +sh -c "$(curl -sSfL https://release.solana.com/v1.17.31/install)" +``` + +### Create the Solana OFT + +:information_source: For **OFT** and **OFT Mint-and-Burn Adapter**, the SPL token's Mint Authority is set to the **Mint Authority Multisig**, which always has the **OFT Store** as a signer. The multisig is fixed to needing 1 of N signatures. + +:information_source: For **OFT** and **OFT Mint-And-Burn Adapter**, you have the option to specify additional signers through the `--additional-minters` flag. If you choose not to, you must pass in `--only-oft-store true`, which means only the **OFT Store** will be a signer for the \_Mint Authority Multisig\*. + +:warning: If you choose to go with `--only-oft-store`, you will not be able to add in other signers/minters or update the Mint Authority, and the Freeze Authority will be immediately renounced. The token Mint Authority will be fixed Mint Authority Multisig address while the Freeze Authority will be set to None. + +#### For OFT: + +```bash +pnpm hardhat lz:oft:solana:create --eid 40168 --program-id +``` + +:warning: Use `--additional-minters` flag to add a CSV of additional minter addresses to the Mint Authority Multisig. If you do not want to, you must specify `--only-oft-store true`. + +:information_source: You can also specify `--amount ` to have the OFT minted to your deployer address upon token creation. + +#### For OFTAdapter: + +```bash +pnpm hardhat lz:oft-adapter:solana:create --eid 40168 --program-id --mint --token-program +``` + +:information_source: You can use OFT Adapter if you want to use an existing token on Solana. For OFT Adapter, tokens will be locked when sending to other chains and unlocked when receiving from other chains. + +#### For OFT Mint-And-Burn Adapter (MABA): + +```bash +pnpm hardhat lz:oft:solana:create --eid 40168 --program-id --mint --token-program +``` + +:information_source: You can use OFT Mint-And-Burn Adapter if you want to use an existing token on Solana. For OFT Mint-And-Burn Adapter, tokens will be burned when sending to other chains and minted when receiving from other chains. + +:warning: You cannot use this option if your token's Mint Authority has been renounced. + +:warning: Note that for MABA mode, before attempting any cross-chain transfers, **you must transfer the Mint Authority** for `lz_receive` to work, as that is not handled in the script (since you are using an existing token). If you opted for `--additional-minters`, then you must transfer the Mint Authority to the newly created multisig (this is the `mintAuthority` value in the `/deployments/solana-/OFT.json`). If not, then it should be set to the OFT Store address, which is `oftStore` in the same file. + +### Note on the LZ Config file, [layerzero.config.ts](./layerzero.config.ts) + +In [layerzero.config.ts](./layerzero.config.ts), the `solanaContract.address` is auto-populated with the `oftStore` address from the deployment file, which has the default path of `deployments/solana-`. + +```typescript +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET, + address: getOftStoreAddress(EndpointId.SOLANA_V2_TESTNET), +}; +``` + +:warning: Ensure that you `address` is specified only for the solana contract object. Do not specify addresses for the EVM chain contract objects. Under the hood, we use `hardhat-deploy` to retrieve the contract addresses of the deployed EVM chain contracts. You will run into an error if you specify `address` for an EVM chain contract object. + +### Deploy a sepolia OFT peer + +```bash +pnpm hardhat lz:deploy # follow the prompts +``` + +Note: If you are on testnet, consider using `MyOFTMock` to allow test token minting. If you do use `MyOFTMock`, make sure to update the `sepoliaContract.contractName` in [layerzero.config.ts](./layerzero.config.ts) to `MyOFTMock`. + +#### Initialize the OFT Program's SendConfig and ReceiveConfig Accounts + +:warning: Do this only when initializing the OFT for the first time. The only exception is if a new pathway is added later. If so, run this again to properly initialize the pathway. + +Run the following command to init the pathway config. This step is unique to pathways that involve Solana. + +```bash +npx hardhat lz:oft:solana:init-config --oapp-config layerzero.config.ts +``` + +### Wire + +Run the following to wire the pathways specified in your `layerzero.config.ts` + +```bash +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +With a squads multisig, you can simply append the `--multisig-key` flag to the end of the above command. + +### Mint OFT on Solana + +This is only relevant for **OFT**. If you opted to include the `--amount` flag in the create step, that means you already have minted some Solana OFT and you can skip this section. + +:information_source: This is only possible if you specified your deployer address as part of the `--additional-minters` flag when creating the Solana OFT. If you had chosen `--only-oft-store true`, you will not be able to mint your OFT on Solana. + +First, you need to create the Associated Token Account for your address. + +```bash +spl-token create-account +``` + +Then, you can mint. Remember this is in local decimals, so with local decimals of 9, you would need to pass in `--amount 1000000000` to mint 1 OFT. + +```bash +spl-token mint --multisig-signer ~/.config/solana/id.json --owner +``` + +:information_source: `~/.config/solana/id.json` assumes that you will use the keypair in the default location. To verify if this path applies to you, run `solana config get` and not the keypair path value. + +:information_source: You can get the `` address from [deployments/solana-testnet/OFT.json](deployments/solana-testnet/OFT.json). + +### Set Message Execution Options + +Refer to [Generating Execution Options](https://docs.layerzero.network/v2/developers/solana/gas-settings/options#generating-options) to learn how to build the options param for send transactions. + +Note that you will need to either enable `enforcedOptions` in [./layerzero.config.ts](./layerzero.config.ts) or pass in a value for `_options` when calling `send()`. Having neither will cause a revert when calling send(). + +For this example, we have already included `enforcedOptions` by default in the `layerzero.config.ts`, which will take effect in the wiring step. + +#### (Optional) If specifying the `_options` value when calling `send()` + +It's only necessary to specify `_options` if you do not have `enforcedOptions`. + +For Sepolia -> Solana, you should pass in the options value into the script at [tasks/evm/send.ts](./tasks/evm/send.ts) as the value for `sendParam.extraOptions`. + +For Solana -> Sepolia, you should pass in the options value into the script at [tasks/solana/sendOFT.ts](./tasks/solana/sendOFT.ts) as the value for `options` for both in `quote` and `send`. + +### Send + +#### Send SOL -> Sepolia + +```bash +npx hardhat lz:oft:solana:send --amount --from-eid 40168 --to --to-eid 40161 +``` + +#### Send Sepolia -> SOL + +```bash +npx hardhat --network sepolia-testnet send --dst-eid 40168 --amount --to +``` + +:information_source: If you encounter an error such as `No Contract deployed with name`, ensure that the `tokenName` in the task defined in `tasks/evm/send.ts` matches the deployed contract name. + +### Set a new Mint Authority Multisig + +If you are not happy with the deployer being a mint authority, you can create and set a new mint authority by running: + +```bash +pnpm hardhat lz:oft:solana:setauthority --eid --mint --program-id --escrow --additional-minters +``` + +The `OFTStore` is automatically added as a mint authority to the newly created mint authority, and does not need to be +included in the `--additional-minters` list. + +## Appendix + +### Solana Program Verification + +Refer to [Verify the OFT Program](https://docs.layerzero.network/v2/developers/solana/oft/program#optional-verify-the-oft-program). + +### Transferring ownership + +Ownership of OFTs can be transferred via running the wire command after the appropriate changes are made to the LZ Config file (`layerzero.config.ts`). You need to first set the `delegate` value, and then only the `owner` value. + +**First, set the `delegate`.** + +How to set delegate: https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/configuring-pathways#adding-delegate + +Now run + +``` +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +and execute the transactions. + +**Then, set the `owner`.** + +How to set owner: https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/configuring-pathways#adding-owner + +Now, run + +``` +npx hardhat lz:ownable:transfer-ownership --oapp-config layerzero.config.ts +``` + +### Troubleshooting + +Refer to the [Solana Troubleshooting page on the LayerZero Docs](https://docs.layerzero.network/v2/developers/solana/troubleshooting/common-errors) to see how to solve common error when deploying Solana OFTs. diff --git a/examples/oft-solana-composer-library/contracts/MyOFT.sol b/examples/oft-solana-composer-library/contracts/MyOFT.sol new file mode 100644 index 000000000..f8bc7b47f --- /dev/null +++ b/examples/oft-solana-composer-library/contracts/MyOFT.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; + +contract MyOFT is OFT { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} +} diff --git a/examples/oft-solana-composer-library/contracts/mocks/MyOFTMock.sol b/examples/oft-solana-composer-library/contracts/mocks/MyOFTMock.sol new file mode 100644 index 000000000..3ebb888d4 --- /dev/null +++ b/examples/oft-solana-composer-library/contracts/mocks/MyOFTMock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { MyOFT } from "../MyOFT.sol"; + +// @dev WARNING: This is for testing purposes only +contract MyOFTMock is MyOFT { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) MyOFT(_name, _symbol, _lzEndpoint, _delegate) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} diff --git a/examples/oft-solana-composer-library/deploy/MyOFT.ts b/examples/oft-solana-composer-library/deploy/MyOFT.ts new file mode 100644 index 000000000..bcbdeb19b --- /dev/null +++ b/examples/oft-solana-composer-library/deploy/MyOFT.ts @@ -0,0 +1,53 @@ +import assert from 'assert' + +import { type DeployFunction } from 'hardhat-deploy/types' + +const contractName = 'MyOFT' + +const deploy: DeployFunction = async (hre) => { + const { getNamedAccounts, deployments } = hre + + const { deploy } = deployments + const { deployer } = await getNamedAccounts() + + assert(deployer, 'Missing named deployer account') + + console.log(`Network: ${hre.network.name}`) + console.log(`Deployer: ${deployer}`) + + // This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2 + // + // @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments + // from @layerzerolabs packages based on the configuration in your hardhat config + // + // For this to work correctly, your network config must define an eid property + // set to `EndpointId` as defined in @layerzerolabs/lz-definitions + // + // For example: + // + // networks: { + // fuji: { + // ... + // eid: EndpointId.AVALANCHE_V2_TESTNET + // } + // } + const endpointV2Deployment = await hre.deployments.get('EndpointV2') + + const { address } = await deploy(contractName, { + from: deployer, + args: [ + 'MyOFT', // name + 'MOFT', // symbol + endpointV2Deployment.address, // LayerZero's EndpointV2 address + deployer, // owner + ], + log: true, + skipIfAlreadyDeployed: false, + }) + + console.log(`Deployed contract: ${contractName}, network: ${hre.network.name}, address: ${address}`) +} + +deploy.tags = [contractName] + +export default deploy diff --git a/examples/oft-solana-composer-library/foundry.toml b/examples/oft-solana-composer-library/foundry.toml new file mode 100644 index 000000000..9819f073e --- /dev/null +++ b/examples/oft-solana-composer-library/foundry.toml @@ -0,0 +1,30 @@ +[profile.default] +solc-version = '0.8.22' +src = 'contracts' +out = 'out' +test = 'test/foundry' +cache_path = 'cache/foundry' +optimizer = true +optimizer_runs = 20_000 + +libs = [ + # We provide a set of useful contract utilities + # in the lib directory of @layerzerolabs/toolbox-foundry: + # + # - forge-std + # - ds-test + # - solidity-bytes-utils + 'node_modules/@layerzerolabs/toolbox-foundry/lib', + 'node_modules', +] + +remappings = [ + # Due to a misconfiguration of solidity-bytes-utils, an outdated version + # of forge-std is being dragged in + # + # To remedy this, we'll remap the ds-test and forge-std imports to our own versions + 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test', + 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std', + '@layerzerolabs/=node_modules/@layerzerolabs/', + '@openzeppelin/=node_modules/@openzeppelin/', +] diff --git a/examples/oft-solana-composer-library/hardhat.config.ts b/examples/oft-solana-composer-library/hardhat.config.ts new file mode 100644 index 000000000..b7b1f6384 --- /dev/null +++ b/examples/oft-solana-composer-library/hardhat.config.ts @@ -0,0 +1,79 @@ +// Get the environment configuration from .env file +// +// To make use of automatic environment setup: +// - Duplicate .env.example file and name it .env +// - Fill in the environment variables +import 'dotenv/config' + +import 'hardhat-deploy' +import '@nomicfoundation/hardhat-ethers' +import '@nomiclabs/hardhat-waffle' +import 'hardhat-deploy-ethers' +import 'hardhat-contract-sizer' +import '@nomiclabs/hardhat-ethers' +import '@layerzerolabs/toolbox-hardhat' + +import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types' + +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import './tasks/index' + +// Set your preferred authentication method +// +// If you prefer using a mnemonic, set a MNEMONIC environment variable +// to a valid mnemonic +const MNEMONIC = process.env.MNEMONIC + +// If you prefer to be authenticated using a private key, set a PRIVATE_KEY environment variable +const PRIVATE_KEY = process.env.PRIVATE_KEY + +const accounts: HttpNetworkAccountsUserConfig | undefined = MNEMONIC + ? { mnemonic: MNEMONIC } + : PRIVATE_KEY + ? [PRIVATE_KEY] + : undefined + +if (accounts == null) { + console.warn( + 'Could not find MNEMONIC or PRIVATE_KEY environment variables. It will not be possible to execute transactions in your example.' + ) +} + +const config: HardhatUserConfig = { + paths: { + cache: 'cache/hardhat', + tests: 'test/hardhat', + }, + solidity: { + compilers: [ + { + version: '0.8.22', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], + }, + networks: { + 'bsc-mainnet': { + eid: EndpointId.BSC_MAINNET, + url: process.env.RPC_URL_BSC, + accounts, + }, + hardhat: { + // Need this for testing because TestHelperOz5.sol is exceeding the compiled contract size limit + allowUnlimitedContractSize: true, + }, + }, + namedAccounts: { + deployer: { + default: 0, // wallet address of index[0], of the mnemonic in .env + }, + }, +} + +export default config diff --git a/examples/oft-solana-composer-library/jest.config.ts b/examples/oft-solana-composer-library/jest.config.ts new file mode 100644 index 000000000..a5634529d --- /dev/null +++ b/examples/oft-solana-composer-library/jest.config.ts @@ -0,0 +1,12 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + reporters: [['github-actions', { silent: false }], 'default'], + testEnvironment: 'node', + testTimeout: 15000, + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + transform: { + '^.+\\.(t|j)sx?$': '@swc/jest', + }, +} diff --git a/examples/oft-solana-composer-library/junk-id.json b/examples/oft-solana-composer-library/junk-id.json new file mode 100644 index 000000000..8ccf579ce --- /dev/null +++ b/examples/oft-solana-composer-library/junk-id.json @@ -0,0 +1,6 @@ +[ + 101, 96, 5, 237, 143, 245, 198, 118, 241, 242, 185, 196, 246, 72, 152, 231, + 30, 170, 168, 48, 19, 92, 179, 54, 175, 98, 167, 177, 62, 91, 162, 83, 255, + 175, 71, 42, 217, 187, 228, 197, 222, 137, 131, 197, 89, 69, 190, 209, 113, + 186, 78, 149, 158, 115, 255, 26, 162, 25, 122, 247, 1, 33, 92, 96 +] diff --git a/examples/oft-solana-composer-library/layerzero.config.ts b/examples/oft-solana-composer-library/layerzero.config.ts new file mode 100644 index 000000000..7e4b46ed8 --- /dev/null +++ b/examples/oft-solana-composer-library/layerzero.config.ts @@ -0,0 +1,57 @@ +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { ExecutorOptionType } from '@layerzerolabs/lz-v2-utilities' +import { generateConnectionsConfig } from '@layerzerolabs/metadata-tools' +import { OAppEnforcedOption, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' + +import { getOftStoreAddress } from './tasks/solana' + +// Note: Do not use address for EVM OmniPointHardhat contracts. Contracts are loaded using hardhat-deploy. +// If you do use an address, ensure artifacts exists. +const sepoliaContract: OmniPointHardhat = { + eid: EndpointId.SEPOLIA_V2_TESTNET, + contractName: 'MyOFT', +} + +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET, + address: getOftStoreAddress(EndpointId.SOLANA_V2_TESTNET), +} + +const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 80000, + value: 0, + }, +] + +const SOLANA_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 200000, + value: 2500000, + }, +] + +// Learn about Message Execution Options: https://docs.layerzero.network/v2/developers/solana/oft/account#message-execution-options +// Learn more about the Simple Config Generator - https://docs.layerzero.network/v2/developers/evm/technical-reference/simple-config +export default async function () { + // note: pathways declared here are automatically bidirectional + // if you declare A,B there's no need to declare B,A + const connections = await generateConnectionsConfig([ + [ + sepoliaContract, // Chain A contract + solanaContract, // Chain B contract + [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] + [15, 32], // [A to B confirmations, B to A confirmations] + [SOLANA_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain B enforcedOptions, Chain A enforcedOptions + ], + ]) + + return { + contracts: [{ contract: sepoliaContract }, { contract: solanaContract }], + connections, + } +} diff --git a/examples/oft-solana-composer-library/package.json b/examples/oft-solana-composer-library/package.json new file mode 100644 index 000000000..45a5fd3e4 --- /dev/null +++ b/examples/oft-solana-composer-library/package.json @@ -0,0 +1,123 @@ +{ + "name": "@layerzerolabs/oft-solana-composer-library-example", + "version": "0.0.1", + "private": true, + "scripts": { + "clean": "rm -rf target artifacts cache out .anchor", + "compile": "concurrently -c auto --names forge,hardhat,anchor '$npm_execpath run compile:forge' '$npm_execpath run compile:hardhat' '$npm_execpath run compile:anchor'", + "compile:anchor": "anchor build", + "compile:forge": "forge build", + "compile:hardhat": "hardhat compile", + "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", + "lint:sol": "solhint 'contracts/**/*.sol'", + "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", + "test:anchor": "anchor test", + "test:forge": "forge test", + "test:hardhat": "hardhat test" + }, + "resolutions": { + "@solana/web3.js": "^1.98.0", + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + }, + "dependencies": { + "@raydium-io/raydium-sdk-v2": "0.1.120-alpha", + "@solana/spl-memo": "^0.2.5", + "axios": "^1.8.4" + }, + "devDependencies": { + "@coral-xyz/anchor": "^0.29.0", + "@ethersproject/bytes": "^5.7.0", + "@layerzerolabs/devtools": "~0.4.8", + "@layerzerolabs/devtools-evm": "~1.0.6", + "@layerzerolabs/devtools-evm-hardhat": "^2.0.9", + "@layerzerolabs/devtools-solana": "~1.0.8", + "@layerzerolabs/eslint-config-next": "~2.3.39", + "@layerzerolabs/io-devtools": "~0.1.16", + "@layerzerolabs/lz-definitions": "^3.0.75", + "@layerzerolabs/lz-evm-messagelib-v2": "^3.0.75", + "@layerzerolabs/lz-evm-protocol-v2": "^3.0.75", + "@layerzerolabs/lz-evm-v1-0.7": "^3.0.75", + "@layerzerolabs/lz-solana-sdk-v2": "3.0.0", + "@layerzerolabs/lz-v2-utilities": "^3.0.75", + "@layerzerolabs/metadata-tools": "^0.4.1", + "@layerzerolabs/oapp-evm": "^0.3.2", + "@layerzerolabs/oft-evm": "^3.1.3", + "@layerzerolabs/oft-v2-solana-sdk": "^3.0.59", + "@layerzerolabs/prettier-config-next": "^2.3.39", + "@layerzerolabs/protocol-devtools": "^1.1.6", + "@layerzerolabs/protocol-devtools-evm": "~3.0.7", + "@layerzerolabs/protocol-devtools-solana": "^4.0.9", + "@layerzerolabs/solhint-config": "^3.0.12", + "@layerzerolabs/test-devtools-evm-foundry": "~6.0.3", + "@layerzerolabs/test-devtools-evm-hardhat": "~0.5.2", + "@layerzerolabs/toolbox-foundry": "~0.1.12", + "@layerzerolabs/toolbox-hardhat": "~0.6.9", + "@layerzerolabs/ua-devtools": "~3.0.6", + "@layerzerolabs/ua-devtools-evm": "~5.0.7", + "@layerzerolabs/ua-devtools-evm-hardhat": "~6.0.11", + "@layerzerolabs/ua-devtools-solana": "~4.1.2", + "@metaplex-foundation/mpl-token-metadata": "^3.2.1", + "@metaplex-foundation/mpl-toolbox": "^0.9.4", + "@metaplex-foundation/umi": "^0.9.2", + "@metaplex-foundation/umi-bundle-defaults": "^0.9.2", + "@metaplex-foundation/umi-eddsa-web3js": "^0.9.2", + "@metaplex-foundation/umi-public-keys": "^0.8.9", + "@metaplex-foundation/umi-web3js-adapters": "^0.9.2", + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@nomiclabs/hardhat-ethers": "^2.2.3", + "@nomiclabs/hardhat-waffle": "^2.0.6", + "@openzeppelin/contracts": "^5.0.2", + "@openzeppelin/contracts-upgradeable": "^5.0.2", + "@rushstack/eslint-patch": "^1.7.0", + "@solana-developers/helpers": "~2.8.1", + "@solana/spl-token": "^0.4.8", + "@solana/web3.js": "~1.95.8", + "@sqds/sdk": "^2.0.4", + "@swc/core": "^1.4.0", + "@swc/jest": "^0.2.36", + "@types/chai": "^4.3.11", + "@types/jest": "^29.5.12", + "@types/mocha": "^10.0.6", + "@types/node": "~18.18.14", + "bs58": "^6.0.0", + "chai": "^4.4.1", + "concurrently": "~9.1.0", + "dotenv": "^16.4.5", + "eslint": "^8.55.0", + "eslint-plugin-jest-extended": "~2.0.0", + "ethereumjs-util": "^7.1.5", + "ethers": "^5.7.2", + "exponential-backoff": "~3.1.1", + "fp-ts": "^2.16.2", + "hardhat": "^2.22.10", + "hardhat-contract-sizer": "^2.10.0", + "hardhat-deploy": "^0.12.1", + "hardhat-deploy-ethers": "^0.4.2", + "jest": "^29.7.0", + "litesvm": "^0.2.0", + "mocha": "^10.2.0", + "prettier": "^3.2.5", + "solhint": "^4.1.1", + "solidity-bytes-utils": "^0.8.2", + "ts-node": "^10.9.2", + "typescript": "^5.4.4" + }, + "engines": { + "node": ">=18.16.0" + }, + "pnpm": { + "overrides": { + "@solana/web3.js": "^1.98.0", + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + } + }, + "overrides": { + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1", + "@solana/web3.js": "^1.98.0" + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/Cargo.toml b/examples/oft-solana-composer-library/programs/composer/Cargo.toml new file mode 100644 index 000000000..0dd445b2d --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "composer" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "composer" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build", "oapp/idl-build"] + +[dependencies] +anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } +anchor-spl = "0.29.0" +raydium-clmm-cpi = { git = "https://github.com/St0rmBr3w/raydium-cpi/", package = "raydium-clmm-cpi", branch = "anchor-0.29.0" } +oapp = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +utils = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +spl-memo = "=4.0.0" +solana-helper = "0.1.0" \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/composer/Xargo.toml b/examples/oft-solana-composer-library/programs/composer/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/oft-solana-composer-library/programs/composer/build.rs b/examples/oft-solana-composer-library/programs/composer/build.rs new file mode 100644 index 000000000..8859933ee --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-env-changed=OFT_ID"); +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/compose_msg_codec.rs b/examples/oft-solana-composer-library/programs/composer/src/compose_msg_codec.rs new file mode 100644 index 000000000..7716b3732 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/compose_msg_codec.rs @@ -0,0 +1,51 @@ +const NONCE_OFFSET: usize = 0; +const SRC_EID_OFFSET: usize = 8; +const AMOUNT_LD_OFFSET: usize = 12; +const COMPOSE_FROM_OFFSET: usize = 20; +const COMPOSE_MSG_OFFSET: usize = 52; + +pub fn encode( + nonce: u64, + src_eid: u32, + amount_ld: u64, + compose_msg: &Vec, // [composeFrom][composeMsg] +) -> Vec { + let mut encoded = Vec::with_capacity(20 + compose_msg.len()); // 8 + 4 + 8 + encoded.extend_from_slice(&nonce.to_be_bytes()); + encoded.extend_from_slice(&src_eid.to_be_bytes()); + encoded.extend_from_slice(&amount_ld.to_be_bytes()); + encoded.extend_from_slice(&compose_msg); + encoded +} + +pub fn nonce(message: &[u8]) -> u64 { + let mut nonce_bytes = [0; 8]; + nonce_bytes.copy_from_slice(&message[NONCE_OFFSET..SRC_EID_OFFSET]); + u64::from_be_bytes(nonce_bytes) +} + +pub fn src_eid(message: &[u8]) -> u32 { + let mut src_eid_bytes = [0; 4]; + src_eid_bytes.copy_from_slice(&message[SRC_EID_OFFSET..AMOUNT_LD_OFFSET]); + u32::from_be_bytes(src_eid_bytes) +} + +pub fn amount_ld(message: &[u8]) -> u64 { + let mut amount_ld_bytes = [0; 8]; + amount_ld_bytes.copy_from_slice(&message[AMOUNT_LD_OFFSET..COMPOSE_FROM_OFFSET]); + u64::from_be_bytes(amount_ld_bytes) +} + +pub fn compose_from(message: &[u8]) -> [u8; 32] { + let mut compose_from = [0; 32]; + compose_from.copy_from_slice(&message[COMPOSE_FROM_OFFSET..COMPOSE_MSG_OFFSET]); + compose_from +} + +pub fn compose_msg(message: &[u8]) -> Vec { + if message.len() > COMPOSE_MSG_OFFSET { + message[COMPOSE_MSG_OFFSET..].to_vec() + } else { + Vec::new() + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs new file mode 100644 index 000000000..222c61d3f --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/init_composer.rs @@ -0,0 +1,125 @@ +use crate::*; +use anchor_lang::prelude::*; + +const COMPOSER_SEED: &[u8] = b"Composer"; +const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes"; + +#[derive(Accounts)] +#[instruction(params: InitComposerParams)] +pub struct InitComposer<'info> { + /// PDA holding all our static pubkeys. + #[account( + init, + payer = payer, + space = Composer::SIZE, + seeds = [COMPOSER_SEED, params.oft_pda.as_ref()], + bump + )] + pub composer: Account<'info, Composer>, + + /// PDA storing all fields for the `lz_compose_types` instruction. + #[account( + init, + payer = payer, + space = LzComposeTypesAccounts::SIZE, + seeds = [LZ_COMPOSE_TYPES_SEED, composer.key().as_ref()], + bump + )] + pub lz_compose_types_accounts: Account<'info, LzComposeTypesAccounts>, + + #[account(mut)] + pub payer: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct InitComposerParams { + pub oft_pda: Pubkey, + pub endpoint_pda: Pubkey, + pub endpoint_program: Pubkey, + pub token_program: Pubkey, + pub token_program_2022: Pubkey, + pub clmm_program: Pubkey, + pub amm_config: Pubkey, + pub pool_state: Pubkey, + pub input_vault: Pubkey, + pub output_vault: Pubkey, + pub observation_state: Pubkey, + pub tick_bitmap: Pubkey, + pub tick_array_lower: Pubkey, + pub tick_array_current: Pubkey, + pub tick_array_upper: Pubkey, + pub input_token_account: Pubkey, + pub output_token_account: Pubkey, + pub input_vault_mint: Pubkey, + pub output_vault_mint: Pubkey, +} + +impl InitComposer<'_> { + pub fn apply(ctx: &mut Context, params: &InitComposerParams) -> Result<()> { + let c = &mut ctx.accounts.composer; + c.oft_pda = params.oft_pda; + c.endpoint_pda = params.endpoint_pda; + c.bump = ctx.bumps.composer; + c.endpoint_program = params.endpoint_program; + c.token_program = params.token_program; + c.token_program_2022 = params.token_program_2022; + c.clmm_program = params.clmm_program; + c.amm_config = params.amm_config; + c.pool_state = params.pool_state; + c.input_vault = params.input_vault; + c.output_vault = params.output_vault; + c.observation_state = params.observation_state; + c.tick_bitmap = params.tick_bitmap; + c.tick_array_lower = params.tick_array_lower; + c.tick_array_current = params.tick_array_current; + c.tick_array_upper = params.tick_array_upper; + c.input_token_account = params.input_token_account; + c.output_token_account = params.output_token_account; + c.input_vault_mint = params.input_vault_mint; + c.output_vault_mint = params.output_vault_mint; + + // Seed the LzComposeTypesAccounts PDA with every field + let t = &mut ctx.accounts.lz_compose_types_accounts; + // 0) endpoint program + t.endpoint_program = params.endpoint_program; + // 1) token program + t.token_program = params.token_program; + // 2) token program 2022 + t.token_program_2022 = params.token_program_2022; + // 3) composer + t.composer = c.key(); + // 4) clmm program + t.clmm_program = params.clmm_program; + // 5) amm config + t.amm_config = params.amm_config; + // 6) pool state + t.pool_state = params.pool_state; + // 7) input token account + t.input_token_account = params.input_token_account; + // 8) output token account + t.output_token_account = params.output_token_account; + // 9) input vault + t.input_vault = params.input_vault; + // 10) output vault + t.output_vault = params.output_vault; + // 11) observation state + t.observation_state = params.observation_state; + // 12) tick bitmap + t.tick_bitmap = params.tick_bitmap; + // 13) tick array lower + t.tick_array_lower = params.tick_array_lower; + // 14) tick array current + t.tick_array_current = params.tick_array_current; + // 15) tick array upper + t.tick_array_upper = params.tick_array_upper; + // 16) memo program + t.memo_program = spl_memo::id(); + // 17) input vault mint + t.input_vault_mint = params.input_vault_mint; + // 18) output vault mint + t.output_vault_mint = params.output_vault_mint; + Ok(()) + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs new file mode 100644 index 000000000..d919364ec --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose.rs @@ -0,0 +1,233 @@ +use crate::state::composer::ComposerError; +use crate::*; +use oapp::endpoint::program::Endpoint; + +use anchor_lang::prelude::*; +use anchor_spl::associated_token::get_associated_token_address; +use anchor_spl::associated_token::AssociatedToken; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; + +use raydium_clmm_cpi::cpi::accounts::SwapSingleV2; +use raydium_clmm_cpi::cpi::swap_v2; +use raydium_clmm_cpi::{ + program::RaydiumClmm, + states::{AmmConfig, ObservationState, PoolState}, +}; + +use oapp::endpoint::{instructions::ClearComposeParams, ID as ENDPOINT_ID}; +use oapp::endpoint_cpi::clear_compose; +use oapp::LzComposeParams; + +use crate::compose_msg_codec::{amount_ld, compose_msg}; + +use spl_memo; + +#[derive(Accounts)] +#[instruction(params: LzComposeParams)] +pub struct LzCompose<'info> { + /// 0) The user paying for swap & any ATA creation (will be the signer) + #[account(mut)] + pub payer: Signer<'info>, + + /// 1) The LayerZero endpoint program this composer was initialized with + #[account(address = composer.endpoint_program)] + pub endpoint_program: Program<'info, Endpoint>, + + /// 2,3) SPL Token programs + pub token_program: Program<'info, Token>, + pub token_program_2022: Program<'info, Token2022>, + + /// 4) Our single-PDA storing all the needed pubkeys + #[account( + mut, + seeds = [COMPOSER_SEED, &composer.oft_pda.to_bytes()], + bump = composer.bump + )] + pub composer: Account<'info, Composer>, + + /// 5–7) Raydium CPI program & state + pub clmm_program: Program<'info, RaydiumClmm>, + #[account(address = composer.amm_config)] + pub amm_config: Box>, + #[account(mut)] + pub pool_state: AccountLoader<'info, PoolState>, + + /// 8,9) User token ATAs (input=A, output=B) + #[account(mut)] + pub input_token_account: Box>, + #[account(mut)] + pub output_token_account: Box>, + + /// 10,11) Pool vaults + #[account(mut)] + pub input_vault: Box>, + #[account(mut)] + pub output_vault: Box>, + + /// 12–16) Price oracle & tick arrays + #[account(mut, address = composer.observation_state)] + pub observation_state: AccountLoader<'info, ObservationState>, + #[account(mut)] + pub tick_bitmap: AccountInfo<'info>, + #[account(mut)] + pub tick_array_lower: AccountInfo<'info>, + #[account(mut)] + pub tick_array_current: AccountInfo<'info>, + #[account(mut)] + pub tick_array_upper: AccountInfo<'info>, + + /// 17) SPL Memo program (for the swap) + #[account(address = spl_memo::id())] + pub memo_program: UncheckedAccount<'info>, + + /// 18,19) Vault mints + #[account(address = input_vault.mint)] + pub input_vault_mint: Box>, + #[account(address = output_vault.mint)] + pub output_vault_mint: Box>, + + /// 20) The ultimate receiver of token B + #[account(mut)] + pub to_address: UncheckedAccount<'info>, + + /// 21) Receiver’s ATA for token B; init_if_needed if absent + #[account( + init_if_needed, + payer = payer, + associated_token::mint = output_vault_mint, + associated_token::authority = to_address, + associated_token::token_program = token_program + )] + pub to_token_account: Box>, + + /// 22) **Associated Token program** — **must** be exactly here + pub associated_token_program: Program<'info, AssociatedToken>, + + /// 23–24) System & Rent + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent>, +} + +impl LzCompose<'_> { + pub fn apply(ctx: &mut Context, params: &LzComposeParams) -> Result<()> { + // 1) Validate origin & destination + require!( + params.from == ctx.accounts.composer.oft_pda, + ComposerError::InvalidFrom + ); + require!( + params.to == ctx.accounts.composer.key(), + ComposerError::InvalidTo + ); + + // 2) Turn your Vec into a slice, then pull‐off both parts of the payload: + let msg = params.message.as_slice(); + + // a) the “amount_ld” header (bytes 12..20 of the original message) + let amount_ld_val = amount_ld(msg); + + // b) the post-header “compose_msg” blob (everything from byte 52 onward) + // which is exactly the solidityPacked [uint64,bytes32] + let payload = compose_msg(msg); + + // sanity check: that blob *must* be exactly 8+32 = 40 bytes + require!(payload.len() == 40, ComposerError::Paused); + + // c) parse your two JS-packed fields: + // • bytes 0..8 = min_amount_out (u64 BE) + // • bytes 8..40 = receiver pubkey + let min_amount_out = u64::from_be_bytes(payload[0..8].try_into().unwrap()); + let receiver = Pubkey::new_from_array(payload[8..40].try_into().unwrap()); + + // check: the client must have passed this same receiver in `to_address` + require_keys_eq!( + ctx.accounts.to_address.key(), + receiver, + ComposerError::InvalidTo + ); + + // 3) Build Raydium CPI context + let cpi_accounts = SwapSingleV2 { + payer: ctx.accounts.payer.to_account_info(), + amm_config: ctx.accounts.amm_config.to_account_info(), + pool_state: ctx.accounts.pool_state.to_account_info(), + input_token_account: ctx.accounts.input_token_account.to_account_info(), + output_token_account: ctx.accounts.to_token_account.to_account_info(), + input_vault: ctx.accounts.input_vault.to_account_info(), + output_vault: ctx.accounts.output_vault.to_account_info(), + observation_state: ctx.accounts.observation_state.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + token_program_2022: ctx.accounts.token_program_2022.to_account_info(), + memo_program: ctx.accounts.memo_program.to_account_info(), + input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), + output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), + }; + let cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) + .with_remaining_accounts(vec![ + ctx.accounts.tick_bitmap.clone(), + ctx.accounts.tick_array_lower.clone(), + ctx.accounts.tick_array_current.clone(), + ctx.accounts.tick_array_upper.clone(), + ]); + + // 4) Attempt swap, refund on error + if let Err(_) = swap_v2(cpi_ctx, amount_ld_val, min_amount_out, 0u128, true) { + // Refund token A back to receiver’s ATA for mint A + // (Anchor init_if_needed below ensures the ATA exists) + let composer_ata = get_associated_token_address( + &ctx.accounts.composer.key(), + &ctx.accounts.composer.oft_pda, + ); + let receiver_ata = get_associated_token_address( + &ctx.accounts.to_address.key(), + &ctx.accounts.composer.oft_pda, + ); + + let refund_ix = anchor_spl::token::spl_token::instruction::transfer( + &ctx.accounts.token_program.key(), + &composer_ata, + &receiver_ata, + &ctx.accounts.composer.key(), + &[], + amount_ld_val, + )?; + + anchor_lang::solana_program::program::invoke_signed( + &refund_ix, + &[ + // composer ATA, receiver ATA, token_program + ctx.accounts.composer.to_account_info(), + ctx.accounts.to_address.to_account_info(), + ctx.accounts.token_program.to_account_info(), + ], + &[&[ + COMPOSER_SEED, + &ctx.accounts.composer.oft_pda.to_bytes(), + &[ctx.accounts.composer.bump], + ]], + )?; + } + + // 5) Clear the message on success *or* after refund + let seeds: &[&[u8]] = &[ + COMPOSER_SEED, + &ctx.accounts.composer.oft_pda.to_bytes(), + &ctx.accounts.composer.endpoint_pda.to_bytes(), // include endpoint PDA + &[ctx.accounts.composer.bump], + ]; + let clear_params = ClearComposeParams { + from: params.from, + guid: params.guid, + index: params.index, + message: params.message.clone(), + }; + clear_compose( + ENDPOINT_ID, + ctx.accounts.composer.key(), + &ctx.remaining_accounts, + seeds, + clear_params, + ) + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs new file mode 100644 index 000000000..008e94b1a --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/lz_compose_types.rs @@ -0,0 +1,265 @@ +use crate::*; +use anchor_lang::prelude::*; +use anchor_spl::token::Token; +use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; + +use oapp::endpoint_cpi::{get_accounts_for_clear_compose, LzAccount}; +use oapp::LzComposeParams; +use raydium_clmm_cpi::{ + program::RaydiumClmm, + states::{AmmConfig, ObservationState, PoolState}, +}; + +/// This struct must list **in the exact same order** the 18 pubkeys your PDA was seeded with, +/// because the @layerzerolabs/lz-solana-sdk-v2 will simulate by shoving *exactly* those into the `keys` array. +/// **MUST** match the 18‐pubkey seed order **exactly**! +#[derive(Accounts)] +pub struct LzComposeTypes<'info> { + /// 1) LayerZero endpoint program + pub endpoint_program: Program<'info, oapp::endpoint::program::Endpoint>, + + /// 2) SPL Token program v1 + pub token_program: Program<'info, Token>, + + /// 3) SPL Token program v2 (2022) + pub token_program_2022: Program<'info, Token2022>, + + /// 4) Composer PDA + #[account( + seeds = [COMPOSER_SEED, &composer.oft_pda.to_bytes()], + bump = composer.bump, + )] + pub composer: Account<'info, Composer>, + + /// 5) Raydium CLMM CPI program + pub clmm_program: Program<'info, RaydiumClmm>, + + /// 6) Raydium AMM config + #[account(address = composer.amm_config)] + pub amm_config: Box>, + + /// 7) Raydium PoolState (writable) + pub pool_state: AccountLoader<'info, PoolState>, + + /// 8) User’s ATA for input token (writable) + pub input_token_account: Box>, + + /// 9) User’s ATA for output token (writable) + pub output_token_account: Box>, + + /// 10) Pool vault A (writable) + pub input_vault: Box>, + + /// 11) Pool vault B (writable) + pub output_vault: Box>, + + /// 12) Raydium ObservationState (writable) + pub observation_state: AccountLoader<'info, ObservationState>, + + /// 13) Raydium TickBitmap (writable) + pub tick_bitmap: AccountInfo<'info>, + + /// 14) Raydium TickArrayLower (writable) + pub tick_array_lower: AccountInfo<'info>, + + /// 15) Raydium TickArrayCurrent (writable) + pub tick_array_current: AccountInfo<'info>, + + /// 16) Raydium TickArrayUpper (writable) + pub tick_array_upper: AccountInfo<'info>, + + /// 17) SPL Memo program (for the swap) + #[account(address = spl_memo::id())] + pub memo_program: UncheckedAccount<'info>, + + /// 18) Vault A mint + pub input_vault_mint: Box>, + + /// 19) Vault B mint + pub output_vault_mint: Box>, +} + +impl<'info> LzComposeTypes<'info> { + pub fn apply( + ctx: &Context, + params: &LzComposeParams, + ) -> Result> { + let a = &ctx.accounts; + + // reserve for 18 static + 6 clear_compose + let mut accounts = Vec::with_capacity(18 + 6); + + // ─── Static Pubkeys (slots 0–19) ────────────────────────────── + // 0) payer placeholder + accounts.push(LzAccount { + pubkey: Pubkey::default(), + is_signer: true, + is_writable: true, + }); + // 1) endpoint program + accounts.push(LzAccount { + pubkey: a.endpoint_program.key(), + is_signer: false, + is_writable: false, + }); + // 2) token_program v1 + accounts.push(LzAccount { + pubkey: a.token_program.key(), + is_signer: false, + is_writable: false, + }); + // 3) token_program v2 (2022) + accounts.push(LzAccount { + pubkey: a.token_program_2022.key(), + is_signer: false, + is_writable: false, + }); + // 4) composer PDA + accounts.push(LzAccount { + pubkey: a.composer.key(), + is_signer: false, + is_writable: true, + }); + // 5) CLMM program + accounts.push(LzAccount { + pubkey: a.clmm_program.key(), + is_signer: false, + is_writable: false, + }); + // 6) AMM config + accounts.push(LzAccount { + pubkey: a.amm_config.key(), + is_signer: false, + is_writable: false, + }); + // 7) PoolState + accounts.push(LzAccount { + pubkey: a.pool_state.key(), + is_signer: false, + is_writable: true, + }); + // 8) user ATA A + accounts.push(LzAccount { + pubkey: a.input_token_account.key(), + is_signer: false, + is_writable: true, + }); + // 9) user ATA B + accounts.push(LzAccount { + pubkey: a.output_token_account.key(), + is_signer: false, + is_writable: true, + }); + // 10) input_vault + accounts.push(LzAccount { + pubkey: a.input_vault.key(), + is_signer: false, + is_writable: true, + }); + // 11) output_vault + accounts.push(LzAccount { + pubkey: a.output_vault.key(), + is_signer: false, + is_writable: true, + }); + // 12) observation_state + accounts.push(LzAccount { + pubkey: a.observation_state.key(), + is_signer: false, + is_writable: true, + }); + // 13) tick_bitmap + accounts.push(LzAccount { + pubkey: a.tick_bitmap.key(), + is_signer: false, + is_writable: true, + }); + // 14) tick_array_lower + accounts.push(LzAccount { + pubkey: a.tick_array_lower.key(), + is_signer: false, + is_writable: true, + }); + // 15) tick_array_current + accounts.push(LzAccount { + pubkey: a.tick_array_current.key(), + is_signer: false, + is_writable: true, + }); + // 16) tick_array_upper + accounts.push(LzAccount { + pubkey: a.tick_array_upper.key(), + is_signer: false, + is_writable: true, + }); + // 17) SPL Memo + accounts.push(LzAccount { + pubkey: spl_memo::id(), + is_signer: false, + is_writable: false, + }); + // 18) vault A mint + accounts.push(LzAccount { + pubkey: a.input_vault_mint.key(), + is_signer: false, + is_writable: false, + }); + // 19) vault B mint + accounts.push(LzAccount { + pubkey: a.output_vault_mint.key(), + is_signer: false, + is_writable: false, + }); + // 20) to_address (the ultimate receiver, from params.to) + accounts.push(LzAccount { + pubkey: params.to, + is_signer: false, + is_writable: true, + }); + + // 21) to_token_account (their ATA for token B) + let to_ata = anchor_spl::associated_token::get_associated_token_address( + ¶ms.to, + &a.output_vault_mint.key(), + ); + accounts.push(LzAccount { + pubkey: to_ata, + is_signer: false, + is_writable: true, + }); + + // 22) Associated Token program + accounts.push(LzAccount { + pubkey: anchor_spl::associated_token::ID, + is_signer: false, + is_writable: false, + }); + + // 23) System program + accounts.push(LzAccount { + pubkey: anchor_lang::solana_program::system_program::ID, + is_signer: false, + is_writable: false, + }); + + // 24) Rent sysvar + accounts.push(LzAccount { + pubkey: anchor_lang::solana_program::sysvar::rent::ID, + is_signer: false, + is_writable: false, + }); + + // ─── LayerZero clear_compose (slots 20–25) ──────────────────── + let mut extra = get_accounts_for_clear_compose( + ctx.accounts.endpoint_program.key(), + ¶ms.from, + &ctx.accounts.composer.key(), + ¶ms.guid, + params.index, + ¶ms.message, + ); + accounts.append(&mut extra); + + Ok(accounts) + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/instructions/mod.rs b/examples/oft-solana-composer-library/programs/composer/src/instructions/mod.rs new file mode 100644 index 000000000..7712604e4 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/instructions/mod.rs @@ -0,0 +1,7 @@ +pub mod lz_compose; +pub mod lz_compose_types; +pub mod init_composer; + +pub use lz_compose::*; +pub use lz_compose_types::*; +pub use init_composer::*; diff --git a/examples/oft-solana-composer-library/programs/composer/src/lib.rs b/examples/oft-solana-composer-library/programs/composer/src/lib.rs new file mode 100644 index 000000000..53cedd19d --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/lib.rs @@ -0,0 +1,32 @@ +mod instructions; +mod state; + +use anchor_lang::prelude::*; +use instructions::*; + +use oapp::{endpoint_cpi::LzAccount, LzComposeParams}; + +use state::*; + +declare_id!("6xhpdhwyzpjxc6n2KqQS8a3W7Busn6nqGYaobVV25kN5"); + +const COMPOSER_SEED: &[u8] = b"Composer"; + +pub mod compose_msg_codec; + +#[program] +pub mod composer { + use super::*; + pub fn init_composer(mut ctx: Context, params: InitComposerParams) -> Result<()> { + InitComposer::apply(&mut ctx, ¶ms) + } + pub fn lz_compose(mut ctx: Context, params: LzComposeParams) -> Result<()> { + LzCompose::apply(&mut ctx, ¶ms) + } + pub fn lz_compose_types( + ctx: Context, + params: LzComposeParams, + ) -> Result> { + LzComposeTypes::apply(&ctx, ¶ms) + } +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs new file mode 100644 index 000000000..c4cb9a7cb --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/state/composer.rs @@ -0,0 +1,94 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum ComposerError { + #[msg("Invalid sender address")] + InvalidFrom, + #[msg("Invalid recipient address")] + InvalidTo, + #[msg("Invalid mint authority")] + InvalidMintAuthority, + #[msg("Invalid token destination")] + InvalidTokenDest, + #[msg("Invalid sender")] + InvalidSender, + #[msg("Program is paused")] + Paused, +} + +#[account] +pub struct Composer { + pub oft_pda: Pubkey, + pub endpoint_pda: Pubkey, + pub endpoint_program: Pubkey, + pub token_program: Pubkey, + pub token_program_2022: Pubkey, + pub clmm_program: Pubkey, + pub amm_config: Pubkey, + pub pool_state: Pubkey, + pub input_vault: Pubkey, + pub output_vault: Pubkey, + pub observation_state: Pubkey, + pub tick_bitmap: Pubkey, + pub tick_array_lower: Pubkey, + pub tick_array_current: Pubkey, + pub tick_array_upper: Pubkey, + pub input_token_account: Pubkey, + pub output_token_account: Pubkey, + pub input_vault_mint: Pubkey, + pub output_vault_mint: Pubkey, + pub bump: u8, +} + +impl Composer { + // 8 (discriminator) + 19*32 + 1 + pub const SIZE: usize = 8 + 19 * 32 + 1; +} + +/// This PDA holds all of the static pubkeys that `lz_compose_types` will consume. +#[account] +pub struct LzComposeTypesAccounts { + // 0) endpoint program + pub endpoint_program: Pubkey, + // 1) token program + pub token_program: Pubkey, + // 2) token program 2022 + pub token_program_2022: Pubkey, + // 3) composer + pub composer: Pubkey, + // 4) clmm program + pub clmm_program: Pubkey, + // 5) amm config + pub amm_config: Pubkey, + // 6) pool state + pub pool_state: Pubkey, + // 7) input token account + pub input_token_account: Pubkey, + // 8) output token account + pub output_token_account: Pubkey, + // 9) input vault + pub input_vault: Pubkey, + // 10) output vault + pub output_vault: Pubkey, + // 11) observation state + pub observation_state: Pubkey, + // 12) tick bitmap + pub tick_bitmap: Pubkey, + // 13) tick array lower + pub tick_array_lower: Pubkey, + // 14) tick array current + pub tick_array_current: Pubkey, + // 15) tick array upper + pub tick_array_upper: Pubkey, + // 16) memo program + pub memo_program: Pubkey, + // 17) input vault mint + pub input_vault_mint: Pubkey, + // 18) output vault mint + pub output_vault_mint: Pubkey, +} + +impl LzComposeTypesAccounts { + // Discriminator (8) + size_of::() + pub const SIZE: usize = 8 + std::mem::size_of::(); +} diff --git a/examples/oft-solana-composer-library/programs/composer/src/state/mod.rs b/examples/oft-solana-composer-library/programs/composer/src/state/mod.rs new file mode 100644 index 000000000..9ffd78ab4 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/composer/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod composer; + +pub use composer::*; \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/Cargo.toml b/examples/oft-solana-composer-library/programs/endpoint-mock/Cargo.toml new file mode 100644 index 000000000..756ebf747 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "endpoint-mock" +version = "0.1.0" +description = "Endpoint Mock" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "endpoint" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build"] + +[dependencies] +anchor-lang = { version = "0.29.0", features = ["event-cpi"] } +solana-program = "=1.17.31" +cpi-helper = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/Xargo.toml b/examples/oft-solana-composer-library/programs/endpoint-mock/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/mod.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/mod.rs new file mode 100644 index 000000000..7e79a19da --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/mod.rs @@ -0,0 +1,3 @@ +pub mod oapp; + +pub use oapp::*; diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/mod.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/mod.rs new file mode 100644 index 000000000..54d7fe213 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/mod.rs @@ -0,0 +1,3 @@ +pub mod register_oapp; + +pub use register_oapp::*; diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/register_oapp.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/register_oapp.rs new file mode 100644 index 000000000..5da8dfd3e --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/instructions/oapp/register_oapp.rs @@ -0,0 +1,35 @@ +use crate::*; + +use cpi_helper::CpiContext; + +#[event_cpi] +#[derive(CpiContext, Accounts)] +#[instruction(params: RegisterOAppParams)] +pub struct RegisterOApp<'info> { + #[account(mut)] + pub payer: Signer<'info>, + /// The PDA of the OApp + pub oapp: Signer<'info>, + #[account( + init, + payer = payer, + space = 8 + OAppRegistry::INIT_SPACE, + seeds = [OAPP_SEED, oapp.key.as_ref()], + bump + )] + pub oapp_registry: Account<'info, OAppRegistry>, + pub system_program: Program<'info, System>, +} + +impl RegisterOApp<'_> { + pub fn apply(ctx: &mut Context, params: &RegisterOAppParams) -> Result<()> { + ctx.accounts.oapp_registry.delegate = params.delegate; + ctx.accounts.oapp_registry.bump = ctx.bumps.oapp_registry; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct RegisterOAppParams { + pub delegate: Pubkey, +} diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs new file mode 100644 index 000000000..cfb3919d2 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/lib.rs @@ -0,0 +1,19 @@ +pub mod instructions; +pub mod state; + +use anchor_lang::prelude::*; +use instructions::*; +use state::*; + +declare_id!("7zjiaGZ99nED4KSXH4hCNo84RyFQcCGjVkb73GtVuxP5"); + +pub const OAPP_SEED: &[u8] = b"OApp"; + +#[program] +pub mod endpoint { + use super::*; + + pub fn register_oapp(mut ctx: Context, params: RegisterOAppParams) -> Result<()> { + RegisterOApp::apply(&mut ctx, ¶ms) + } +} diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/endpoint.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/endpoint.rs new file mode 100644 index 000000000..42ff5cbd6 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/endpoint.rs @@ -0,0 +1,8 @@ +use crate::*; + +#[account] +#[derive(InitSpace)] +pub struct OAppRegistry { + pub delegate: Pubkey, + pub bump: u8, +} diff --git a/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/mod.rs b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/mod.rs new file mode 100644 index 000000000..86edb11f4 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/endpoint-mock/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod endpoint; + +pub use endpoint::*; diff --git a/examples/oft-solana-composer-library/programs/oft/Cargo.toml b/examples/oft-solana-composer-library/programs/oft/Cargo.toml new file mode 100644 index 000000000..457f6ac54 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "oft" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "oft" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build", "oapp/idl-build"] + +[dependencies] +anchor-lang = { version = "0.29.0", features = ["init-if-needed"] } +anchor-spl = "0.29.0" +oapp = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +utils = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", rev = "34321ac15e47e0dafd25d66659e2f3d1b9b6db8f" } +solana-helper = "0.1.0" \ No newline at end of file diff --git a/examples/oft-solana-composer-library/programs/oft/Xargo.toml b/examples/oft-solana-composer-library/programs/oft/Xargo.toml new file mode 100644 index 000000000..475fb71ed --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/examples/oft-solana-composer-library/programs/oft/build.rs b/examples/oft-solana-composer-library/programs/oft/build.rs new file mode 100644 index 000000000..8859933ee --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rerun-if-env-changed=OFT_ID"); +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/compose_msg_codec.rs b/examples/oft-solana-composer-library/programs/oft/src/compose_msg_codec.rs new file mode 100644 index 000000000..7716b3732 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/compose_msg_codec.rs @@ -0,0 +1,51 @@ +const NONCE_OFFSET: usize = 0; +const SRC_EID_OFFSET: usize = 8; +const AMOUNT_LD_OFFSET: usize = 12; +const COMPOSE_FROM_OFFSET: usize = 20; +const COMPOSE_MSG_OFFSET: usize = 52; + +pub fn encode( + nonce: u64, + src_eid: u32, + amount_ld: u64, + compose_msg: &Vec, // [composeFrom][composeMsg] +) -> Vec { + let mut encoded = Vec::with_capacity(20 + compose_msg.len()); // 8 + 4 + 8 + encoded.extend_from_slice(&nonce.to_be_bytes()); + encoded.extend_from_slice(&src_eid.to_be_bytes()); + encoded.extend_from_slice(&amount_ld.to_be_bytes()); + encoded.extend_from_slice(&compose_msg); + encoded +} + +pub fn nonce(message: &[u8]) -> u64 { + let mut nonce_bytes = [0; 8]; + nonce_bytes.copy_from_slice(&message[NONCE_OFFSET..SRC_EID_OFFSET]); + u64::from_be_bytes(nonce_bytes) +} + +pub fn src_eid(message: &[u8]) -> u32 { + let mut src_eid_bytes = [0; 4]; + src_eid_bytes.copy_from_slice(&message[SRC_EID_OFFSET..AMOUNT_LD_OFFSET]); + u32::from_be_bytes(src_eid_bytes) +} + +pub fn amount_ld(message: &[u8]) -> u64 { + let mut amount_ld_bytes = [0; 8]; + amount_ld_bytes.copy_from_slice(&message[AMOUNT_LD_OFFSET..COMPOSE_FROM_OFFSET]); + u64::from_be_bytes(amount_ld_bytes) +} + +pub fn compose_from(message: &[u8]) -> [u8; 32] { + let mut compose_from = [0; 32]; + compose_from.copy_from_slice(&message[COMPOSE_FROM_OFFSET..COMPOSE_MSG_OFFSET]); + compose_from +} + +pub fn compose_msg(message: &[u8]) -> Vec { + if message.len() > COMPOSE_MSG_OFFSET { + message[COMPOSE_MSG_OFFSET..].to_vec() + } else { + Vec::new() + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/errors.rs b/examples/oft-solana-composer-library/programs/oft/src/errors.rs new file mode 100644 index 000000000..92d764507 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/errors.rs @@ -0,0 +1,14 @@ +use anchor_lang::prelude::error_code; + +#[error_code] +pub enum OFTError { + Unauthorized, + InvalidSender, + InvalidDecimals, + SlippageExceeded, + InvalidTokenDest, + RateLimitExceeded, + InvalidFee, + InvalidMintAuthority, + Paused, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/events.rs b/examples/oft-solana-composer-library/programs/oft/src/events.rs new file mode 100644 index 000000000..367df4572 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/events.rs @@ -0,0 +1,18 @@ +use crate::*; + +#[event] +pub struct OFTSent { + pub guid: [u8; 32], + pub dst_eid: u32, + pub from: Pubkey, + pub amount_sent_ld: u64, + pub amount_received_ld: u64, +} + +#[event] +pub struct OFTReceived { + pub guid: [u8; 32], + pub src_eid: u32, + pub to: Pubkey, + pub amount_received_ld: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/init_oft.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/init_oft.rs new file mode 100644 index 000000000..95ba9966f --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/init_oft.rs @@ -0,0 +1,86 @@ +use crate::*; +use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; +use oapp::endpoint::{instructions::RegisterOAppParams, ID as ENDPOINT_ID}; + +#[derive(Accounts)] +pub struct InitOFT<'info> { + #[account(mut)] + pub payer: Signer<'info>, + #[account( + init, + payer = payer, + space = 8 + OFTStore::INIT_SPACE, + seeds = [OFT_SEED, token_escrow.key().as_ref()], + bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + init, + payer = payer, + space = 8 + LzReceiveTypesAccounts::INIT_SPACE, + seeds = [LZ_RECEIVE_TYPES_SEED, oft_store.key().as_ref()], + bump + )] + pub lz_receive_types_accounts: Account<'info, LzReceiveTypesAccounts>, + #[account(mint::token_program = token_program)] + pub token_mint: InterfaceAccount<'info, Mint>, + #[account( + init, + payer = payer, + token::authority = oft_store, + token::mint = token_mint, + token::token_program = token_program, + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + pub token_program: Interface<'info, TokenInterface>, + pub system_program: Program<'info, System>, +} + +impl InitOFT<'_> { + pub fn apply(ctx: &mut Context, params: &InitOFTParams) -> Result<()> { + // Initialize the oft_store + ctx.accounts.oft_store.oft_type = params.oft_type.clone(); + require!( + ctx.accounts.token_mint.decimals >= params.shared_decimals, + OFTError::InvalidDecimals + ); + ctx.accounts.oft_store.ld2sd_rate = + 10u64.pow((ctx.accounts.token_mint.decimals - params.shared_decimals) as u32); + ctx.accounts.oft_store.token_mint = ctx.accounts.token_mint.key(); + ctx.accounts.oft_store.token_escrow = ctx.accounts.token_escrow.key(); + ctx.accounts.oft_store.endpoint_program = + if let Some(endpoint_program) = params.endpoint_program { + endpoint_program + } else { + ENDPOINT_ID + }; + ctx.accounts.oft_store.bump = ctx.bumps.oft_store; + ctx.accounts.oft_store.tvl_ld = 0; + ctx.accounts.oft_store.admin = params.admin; + ctx.accounts.oft_store.default_fee_bps = 0; + ctx.accounts.oft_store.paused = false; + ctx.accounts.oft_store.pauser = None; + ctx.accounts.oft_store.unpauser = None; + + // Initialize the lz_receive_types_accounts + ctx.accounts.lz_receive_types_accounts.oft_store = ctx.accounts.oft_store.key(); + ctx.accounts.lz_receive_types_accounts.token_mint = ctx.accounts.token_mint.key(); + + // Register the oapp + oapp::endpoint_cpi::register_oapp( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + ctx.remaining_accounts, + &[OFT_SEED, ctx.accounts.token_escrow.key().as_ref(), &[ctx.bumps.oft_store]], + RegisterOAppParams { delegate: params.admin }, + ) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct InitOFTParams { + pub oft_type: OFTType, + pub admin: Pubkey, + pub shared_decimals: u8, + pub endpoint_program: Option, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive.rs new file mode 100644 index 000000000..2059d7adf --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive.rs @@ -0,0 +1,183 @@ +use crate::*; +use anchor_lang::solana_program; +use anchor_spl::{ + associated_token::AssociatedToken, + token_2022::spl_token_2022::{self, solana_program::program_option::COption}, + token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked}, +}; +use oapp::endpoint::{ + cpi::accounts::Clear, + instructions::{ClearParams, SendComposeParams}, + ConstructCPIContext, +}; + +#[event_cpi] +#[derive(Accounts)] +#[instruction(params: LzReceiveParams)] +pub struct LzReceive<'info> { + #[account(mut)] + pub payer: Signer<'info>, + #[account( + mut, + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.src_eid.to_be_bytes() + ], + bump = peer.bump, + constraint = peer.peer_address == params.sender @OFTError::InvalidSender + )] + pub peer: Account<'info, PeerConfig>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + mut, + address = oft_store.token_escrow, + token::authority = oft_store, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + /// CHECK: the wallet address to receive the token + #[account(address = Pubkey::from(msg_codec::send_to(¶ms.message)) @OFTError::InvalidTokenDest)] + pub to_address: AccountInfo<'info>, + #[account( + init_if_needed, + payer = payer, + associated_token::mint = token_mint, + associated_token::authority = to_address, + associated_token::token_program = token_program + )] + pub token_dest: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + address = oft_store.token_mint, + mint::token_program = token_program + )] + pub token_mint: InterfaceAccount<'info, Mint>, + // Only used for native mint, the mint authority can be: + // 1. a spl-token multisig account with oft_store as one of the signers, and the quorum **MUST** be 1-of-n. (recommended) + // 2. or the mint_authority is oft_store itself. + #[account(constraint = token_mint.mint_authority == COption::Some(mint_authority.key()) @OFTError::InvalidMintAuthority)] + pub mint_authority: Option>, + pub token_program: Interface<'info, TokenInterface>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, +} + +impl LzReceive<'_> { + pub fn apply(ctx: &mut Context, params: &LzReceiveParams) -> Result<()> { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let oft_store_seed = ctx.accounts.token_escrow.key(); + let seeds: &[&[u8]] = &[OFT_SEED, oft_store_seed.as_ref(), &[ctx.accounts.oft_store.bump]]; + + // Validate and clear the payload + let accounts_for_clear = &ctx.remaining_accounts[0..Clear::MIN_ACCOUNTS_LEN]; + let _ = oapp::endpoint_cpi::clear( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + accounts_for_clear, + seeds, + ClearParams { + receiver: ctx.accounts.oft_store.key(), + src_eid: params.src_eid, + sender: params.sender, + nonce: params.nonce, + guid: params.guid, + message: params.message.clone(), + }, + )?; + + // Convert the amount from sd to ld + let amount_sd = msg_codec::amount_sd(¶ms.message); + let mut amount_received_ld = ctx.accounts.oft_store.sd2ld(amount_sd); + + // Consume the inbound rate limiter + if let Some(rate_limiter) = ctx.accounts.peer.inbound_rate_limiter.as_mut() { + rate_limiter.try_consume(amount_received_ld)?; + } + // Refill the outbound rate limiter + if let Some(rate_limiter) = ctx.accounts.peer.outbound_rate_limiter.as_mut() { + rate_limiter.refill(amount_received_ld)?; + } + + if ctx.accounts.oft_store.oft_type == OFTType::Adapter { + // unlock from escrow + ctx.accounts.oft_store.tvl_ld -= amount_received_ld; + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_escrow.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_dest.to_account_info(), + authority: ctx.accounts.oft_store.to_account_info(), + }, + ) + .with_signer(&[&seeds]), + amount_received_ld, + ctx.accounts.token_mint.decimals, + )?; + + // update the amount_received_ld with the post transfer fee amount + amount_received_ld = + get_post_fee_amount_ld(&ctx.accounts.token_mint, amount_received_ld)? + } else if let Some(mint_authority) = &ctx.accounts.mint_authority { + // Native type + // mint + let ix = spl_token_2022::instruction::mint_to( + ctx.accounts.token_program.key, + &ctx.accounts.token_mint.key(), + &ctx.accounts.token_dest.key(), + mint_authority.key, + &[&ctx.accounts.oft_store.key()], + amount_received_ld, + )?; + solana_program::program::invoke_signed( + &ix, + &[ + ctx.accounts.token_dest.to_account_info(), + ctx.accounts.token_mint.to_account_info(), + mint_authority.to_account_info(), + ctx.accounts.oft_store.to_account_info(), + ], + &[&seeds], + )?; + } else { + return Err(OFTError::InvalidMintAuthority.into()); + } + + if let Some(message) = msg_codec::compose_msg(¶ms.message) { + oapp::endpoint_cpi::send_compose( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + &ctx.remaining_accounts[Clear::MIN_ACCOUNTS_LEN..], + seeds, + SendComposeParams { + to: ctx.accounts.to_address.key(), + guid: params.guid, + index: 0, // only 1 compose msg per lzReceive + message: compose_msg_codec::encode( + params.nonce, + params.src_eid, + amount_received_ld, + &message, + ), + }, + )?; + } + + emit_cpi!(OFTReceived { + guid: params.guid, + src_eid: params.src_eid, + to: ctx.accounts.to_address.key(), + amount_received_ld, + }); + Ok(()) + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive_types.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive_types.rs new file mode 100644 index 000000000..a63ee6133 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/lz_receive_types.rs @@ -0,0 +1,139 @@ +use crate::*; +use anchor_lang::solana_program; +use anchor_spl::{ + associated_token::{get_associated_token_address_with_program_id, ID as ASSOCIATED_TOKEN_ID}, + token_2022::spl_token_2022::solana_program::program_option::COption, + token_interface::Mint, +}; +use oapp::endpoint_cpi::LzAccount; + +#[derive(Accounts)] +pub struct LzReceiveTypes<'info> { + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account(address = oft_store.token_mint)] + pub token_mint: InterfaceAccount<'info, Mint>, +} + +// account structure +// account 0 - payer (executor) +// account 1 - peer +// account 2 - oft store +// account 3 - token escrow +// account 4 - to address / wallet address +// account 5 - token dest +// account 6 - token mint +// account 7 - mint authority (optional) +// account 8 - token program +// account 9 - associated token program +// account 10 - system program +// account 11 - event authority +// account 12 - this program +// account remaining accounts +// 0..9 - accounts for clear +// 9..16 - accounts for compose +impl LzReceiveTypes<'_> { + pub fn apply( + ctx: &Context, + params: &LzReceiveParams, + ) -> Result> { + let (peer, _) = Pubkey::find_program_address( + &[PEER_SEED, ctx.accounts.oft_store.key().as_ref(), ¶ms.src_eid.to_be_bytes()], + ctx.program_id, + ); + + // account 0..3 + let mut accounts = vec![ + LzAccount { pubkey: Pubkey::default(), is_signer: true, is_writable: true }, // 0 + LzAccount { pubkey: peer, is_signer: false, is_writable: true }, // 1 + LzAccount { pubkey: ctx.accounts.oft_store.key(), is_signer: false, is_writable: true }, // 2 + LzAccount { + pubkey: ctx.accounts.oft_store.token_escrow.key(), + is_signer: false, + is_writable: true, + }, // 3 + ]; + + // account 4..9 + let to_address = Pubkey::from(msg_codec::send_to(¶ms.message)); + let token_program = ctx.accounts.token_mint.to_account_info().owner; + let token_dest = get_associated_token_address_with_program_id( + &to_address, + &ctx.accounts.oft_store.token_mint, + token_program, + ); + let mint_authority = + if let COption::Some(mint_authority) = ctx.accounts.token_mint.mint_authority { + mint_authority + } else { + ctx.program_id.key() + }; + accounts.extend_from_slice(&[ + LzAccount { pubkey: to_address, is_signer: false, is_writable: false }, // 4 + LzAccount { pubkey: token_dest, is_signer: false, is_writable: true }, // 5 + LzAccount { + pubkey: ctx.accounts.token_mint.key(), + is_signer: false, + is_writable: true, + }, // 6 + LzAccount { pubkey: mint_authority, is_signer: false, is_writable: false }, // 7 + LzAccount { pubkey: *token_program, is_signer: false, is_writable: false }, // 8 + LzAccount { pubkey: ASSOCIATED_TOKEN_ID, is_signer: false, is_writable: false }, // 9 + ]); + + // account 10..12 + let (event_authority_account, _) = + Pubkey::find_program_address(&[oapp::endpoint_cpi::EVENT_SEED], &ctx.program_id); + accounts.extend_from_slice(&[ + LzAccount { + pubkey: solana_program::system_program::ID, + is_signer: false, + is_writable: false, + }, // 10 + LzAccount { pubkey: event_authority_account, is_signer: false, is_writable: false }, // 11 + LzAccount { pubkey: ctx.program_id.key(), is_signer: false, is_writable: false }, // 12 + ]); + + let endpoint_program = ctx.accounts.oft_store.endpoint_program; + // remaining accounts 0..9 + let accounts_for_clear = oapp::endpoint_cpi::get_accounts_for_clear( + endpoint_program, + &ctx.accounts.oft_store.key(), + params.src_eid, + ¶ms.sender, + params.nonce, + ); + accounts.extend(accounts_for_clear); + + // remaining accounts 9..16 + if let Some(message) = msg_codec::compose_msg(¶ms.message) { + let amount_sd = msg_codec::amount_sd(¶ms.message); + let amount_ld = ctx.accounts.oft_store.sd2ld(amount_sd); + let amount_received_ld = if ctx.accounts.oft_store.oft_type == OFTType::Native { + amount_ld + } else { + get_post_fee_amount_ld(&ctx.accounts.token_mint, amount_ld)? + }; + + let accounts_for_composing = oapp::endpoint_cpi::get_accounts_for_send_compose( + endpoint_program, + &ctx.accounts.oft_store.key(), + &to_address, + ¶ms.guid, + 0, + &compose_msg_codec::encode( + params.nonce, + params.src_eid, + amount_received_ld, + &message, + ), + ); + accounts.extend(accounts_for_composing); + } + + Ok(accounts) + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/mod.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/mod.rs new file mode 100644 index 000000000..7030177db --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/mod.rs @@ -0,0 +1,21 @@ +pub mod init_oft; +pub mod lz_receive; +pub mod lz_receive_types; +pub mod quote_oft; +pub mod quote_send; +pub mod send; +pub mod set_oft_config; +pub mod set_pause; +pub mod set_peer_config; +pub mod withdraw_fee; + +pub use init_oft::*; +pub use lz_receive::*; +pub use lz_receive_types::*; +pub use quote_oft::*; +pub use quote_send::*; +pub use send::*; +pub use set_oft_config::*; +pub use set_pause::*; +pub use set_peer_config::*; +pub use withdraw_fee::*; diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_oft.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_oft.rs new file mode 100644 index 000000000..aba5707a7 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_oft.rs @@ -0,0 +1,92 @@ +use crate::*; +use anchor_spl::token_interface::Mint; + +#[derive(Accounts)] +#[instruction(params: QuoteOFTParams)] +pub struct QuoteOFT<'info> { + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.dst_eid.to_be_bytes() + ], + bump = peer.bump + )] + pub peer: Account<'info, PeerConfig>, + #[account(address = oft_store.token_mint)] + pub token_mint: InterfaceAccount<'info, Mint>, +} + +impl QuoteOFT<'_> { + pub fn apply(ctx: &Context, params: &QuoteOFTParams) -> Result { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let (amount_sent_ld, amount_received_ld, oft_fee_ld) = compute_fee_and_adjust_amount( + params.amount_ld, + &ctx.accounts.oft_store, + &ctx.accounts.token_mint, + ctx.accounts.peer.fee_bps, + )?; + require!(amount_received_ld >= params.min_amount_ld, OFTError::SlippageExceeded); + + let oft_limits = OFTLimits { min_amount_ld: 0, max_amount_ld: 0xffffffffffffffff }; + let mut oft_fee_details = if amount_received_ld + oft_fee_ld < amount_sent_ld { + vec![OFTFeeDetail { + fee_amount_ld: amount_sent_ld - oft_fee_ld - amount_received_ld, + description: "Token2022 Transfer Fee".to_string(), + }] + } else { + vec![] + }; + // cross chain fee + if oft_fee_ld > 0 { + oft_fee_details.push(OFTFeeDetail { + fee_amount_ld: oft_fee_ld, + description: "Cross Chain Fee".to_string(), + }); + } + let oft_receipt = OFTReceipt { amount_sent_ld, amount_received_ld }; + Ok(QuoteOFTResult { oft_limits, oft_fee_details, oft_receipt }) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct QuoteOFTParams { + pub dst_eid: u32, + pub to: [u8; 32], + pub amount_ld: u64, + pub min_amount_ld: u64, + pub options: Vec, + pub compose_msg: Option>, + pub pay_in_lz_token: bool, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct QuoteOFTResult { + pub oft_limits: OFTLimits, + pub oft_fee_details: Vec, + pub oft_receipt: OFTReceipt, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct OFTFeeDetail { + pub fee_amount_ld: u64, + pub description: String, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct OFTReceipt { + pub amount_sent_ld: u64, + pub amount_received_ld: u64, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct OFTLimits { + pub min_amount_ld: u64, + pub max_amount_ld: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_send.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_send.rs new file mode 100644 index 000000000..efedefb9b --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/quote_send.rs @@ -0,0 +1,193 @@ +use crate::*; +use oapp::endpoint::{instructions::QuoteParams, MessagingFee}; + +use anchor_spl::{ + token_2022::spl_token_2022::{ + extension::{ + transfer_fee::{TransferFee, TransferFeeConfig}, + BaseStateWithExtensions, StateWithExtensions, + }, + state::Mint as MintState, + }, + token_interface::Mint, +}; + +#[derive(Accounts)] +#[instruction(params: QuoteSendParams)] +pub struct QuoteSend<'info> { + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.dst_eid.to_be_bytes() + ], + bump = peer.bump + )] + pub peer: Account<'info, PeerConfig>, + #[account(address = oft_store.token_mint)] + pub token_mint: InterfaceAccount<'info, Mint>, +} + +impl QuoteSend<'_> { + pub fn apply(ctx: &Context, params: &QuoteSendParams) -> Result { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let (_, amount_received_ld, _) = compute_fee_and_adjust_amount( + params.amount_ld, + &ctx.accounts.oft_store, + &ctx.accounts.token_mint, + ctx.accounts.peer.fee_bps, + )?; + require!(amount_received_ld >= params.min_amount_ld, OFTError::SlippageExceeded); + + // calling endpoint cpi + oapp::endpoint_cpi::quote( + ctx.accounts.oft_store.endpoint_program, + ctx.remaining_accounts, + QuoteParams { + sender: ctx.accounts.oft_store.key(), + dst_eid: params.dst_eid, + receiver: ctx.accounts.peer.peer_address, + message: msg_codec::encode( + params.to, + amount_received_ld, + Pubkey::default(), + ¶ms.compose_msg, + ), + pay_in_lz_token: params.pay_in_lz_token, + options: ctx + .accounts + .peer + .enforced_options + .combine_options(¶ms.compose_msg, ¶ms.options)?, + }, + ) + } +} + +pub fn compute_fee_and_adjust_amount( + amount_ld: u64, + oft_store: &OFTStore, + token_mint: &InterfaceAccount, + fee_bps: Option, +) -> Result<(u64, u64, u64)> { + let (amount_sent_ld, amount_received_ld, oft_fee_ld) = if OFTType::Adapter == oft_store.oft_type + { + let mut amount_received_ld = + oft_store.remove_dust(get_post_fee_amount_ld(token_mint, amount_ld)?); + let amount_sent_ld = get_pre_fee_amount_ld(token_mint, amount_received_ld)?; + + // remove the oft fee from the amount_received_ld + let oft_fee_ld = oft_store.remove_dust(calculate_fee( + amount_received_ld, + oft_store.default_fee_bps, + fee_bps, + )); + amount_received_ld -= oft_fee_ld; + (amount_sent_ld, amount_received_ld, oft_fee_ld) + } else { + // if it is Native OFT, there is no transfer fee + let amount_sent_ld = oft_store.remove_dust(amount_ld); + let oft_fee_ld = oft_store.remove_dust(calculate_fee( + amount_sent_ld, + oft_store.default_fee_bps, + fee_bps, + )); + let amount_received_ld = amount_sent_ld - oft_fee_ld; + (amount_sent_ld, amount_received_ld, oft_fee_ld) + }; + Ok((amount_sent_ld, amount_received_ld, oft_fee_ld)) +} + +fn calculate_fee(pre_fee_amount: u64, default_fee_bps: u16, fee_bps: Option) -> u64 { + let final_fee_bps = if let Some(bps) = fee_bps { bps as u128 } else { default_fee_bps as u128 }; + if final_fee_bps == 0 || pre_fee_amount == 0 { + 0 + } else { + // pre_fee_amount * final_fee_bps / ONE_IN_BASIS_POINTS + let fee = (pre_fee_amount as u128) * final_fee_bps; + (fee / ONE_IN_BASIS_POINTS) as u64 + } +} + +pub fn get_post_fee_amount_ld(token_mint: &InterfaceAccount, amount_ld: u64) -> Result { + let token_mint_info = token_mint.to_account_info(); + let token_mint_data = token_mint_info.try_borrow_data()?; + let token_mint_ext = StateWithExtensions::::unpack(&token_mint_data)?; + let post_amount_ld = + if let Ok(transfer_fee_config) = token_mint_ext.get_extension::() { + transfer_fee_config + .get_epoch_fee(Clock::get()?.epoch) + .calculate_post_fee_amount(amount_ld) + .ok_or(ProgramError::InvalidArgument)? + } else { + amount_ld + }; + Ok(post_amount_ld) +} + +// Calculate the amount_sent_ld necessary to receive amount_received_ld +// Does *not* de-dust any inputs or outputs. +fn get_pre_fee_amount_ld(token_mint: &InterfaceAccount, amount_ld: u64) -> Result { + let token_mint_info = token_mint.to_account_info(); + let token_mint_data = token_mint_info.try_borrow_data()?; + let token_mint_ext = StateWithExtensions::::unpack(&token_mint_data)?; + let pre_amount_ld = + if let Ok(transfer_fee) = token_mint_ext.get_extension::() { + calculate_pre_fee_amount(transfer_fee.get_epoch_fee(Clock::get()?.epoch), amount_ld) + .ok_or(ProgramError::InvalidArgument)? + } else { + amount_ld + }; + Ok(pre_amount_ld) +} + +// DO NOT CHANGE THIS CODE!!! +// bug reported on token2022: https://github.com/solana-labs/solana-program-library/pull/6704/files +// copy code over as fix has not been published +pub const MAX_FEE_BASIS_POINTS: u16 = 10_000; +const ONE_IN_BASIS_POINTS: u128 = MAX_FEE_BASIS_POINTS as u128; +fn calculate_pre_fee_amount(fee: &TransferFee, post_fee_amount: u64) -> Option { + let maximum_fee = u64::from(fee.maximum_fee); + let transfer_fee_basis_points = u16::from(fee.transfer_fee_basis_points) as u128; + match (transfer_fee_basis_points, post_fee_amount) { + // no fee, same amount + (0, _) => Some(post_fee_amount), + // 0 zero out, 0 in + (_, 0) => Some(0), + // 100%, cap at max fee + (ONE_IN_BASIS_POINTS, _) => maximum_fee.checked_add(post_fee_amount), + _ => { + let numerator = (post_fee_amount as u128).checked_mul(ONE_IN_BASIS_POINTS)?; + let denominator = ONE_IN_BASIS_POINTS.checked_sub(transfer_fee_basis_points)?; + let raw_pre_fee_amount = ceil_div(numerator, denominator)?; + + if raw_pre_fee_amount.checked_sub(post_fee_amount as u128)? >= maximum_fee as u128 { + post_fee_amount.checked_add(maximum_fee) + } else { + // should return `None` if `pre_fee_amount` overflows + u64::try_from(raw_pre_fee_amount).ok() + } + }, + } +} + +fn ceil_div(numerator: u128, denominator: u128) -> Option { + numerator.checked_add(denominator)?.checked_sub(1)?.checked_div(denominator) +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct QuoteSendParams { + pub dst_eid: u32, + pub to: [u8; 32], + pub amount_ld: u64, + pub min_amount_ld: u64, + pub options: Vec, + pub compose_msg: Option>, + pub pay_in_lz_token: bool, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/send.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/send.rs new file mode 100644 index 000000000..4a839ca18 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/send.rs @@ -0,0 +1,175 @@ +use crate::*; +use anchor_spl::token_interface::{ + self, Burn, Mint, TokenAccount, TokenInterface, TransferChecked, +}; +use oapp::endpoint::{instructions::SendParams as EndpointSendParams, MessagingReceipt}; + +#[event_cpi] +#[derive(Accounts)] +#[instruction(params: SendParams)] +pub struct Send<'info> { + pub signer: Signer<'info>, + #[account( + mut, + seeds = [ + PEER_SEED, + oft_store.key().as_ref(), + ¶ms.dst_eid.to_be_bytes() + ], + bump = peer.bump + )] + pub peer: Account<'info, PeerConfig>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + mut, + token::authority = signer, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_source: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + address = oft_store.token_escrow, + token::authority = oft_store.key(), + token::mint = token_mint, + token::token_program = token_program + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + address = oft_store.token_mint, + mint::token_program = token_program + )] + pub token_mint: InterfaceAccount<'info, Mint>, + pub token_program: Interface<'info, TokenInterface>, +} + +impl Send<'_> { + pub fn apply( + ctx: &mut Context, + params: &SendParams, + ) -> Result<(MessagingReceipt, OFTReceipt)> { + require!(!ctx.accounts.oft_store.paused, OFTError::Paused); + + let (amount_sent_ld, amount_received_ld, oft_fee_ld) = compute_fee_and_adjust_amount( + params.amount_ld, + &ctx.accounts.oft_store, + &ctx.accounts.token_mint, + ctx.accounts.peer.fee_bps, + )?; + require!(amount_received_ld >= params.min_amount_ld, OFTError::SlippageExceeded); + + if let Some(rate_limiter) = ctx.accounts.peer.outbound_rate_limiter.as_mut() { + rate_limiter.try_consume(amount_received_ld)?; + } + if let Some(rate_limiter) = ctx.accounts.peer.inbound_rate_limiter.as_mut() { + rate_limiter.refill(amount_received_ld)?; + } + + if ctx.accounts.oft_store.oft_type == OFTType::Adapter { + // transfer all tokens to escrow with fee + ctx.accounts.oft_store.tvl_ld += amount_received_ld; + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_source.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_escrow.to_account_info(), + authority: ctx.accounts.signer.to_account_info(), + }, + ), + amount_sent_ld, + ctx.accounts.token_mint.decimals, + )?; + } else { + // Native type + // burn + token_interface::burn( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + Burn { + mint: ctx.accounts.token_mint.to_account_info(), + from: ctx.accounts.token_source.to_account_info(), + authority: ctx.accounts.signer.to_account_info(), + }, + ), + amount_sent_ld - oft_fee_ld, + )?; + + // transfer fee to escrow + if oft_fee_ld > 0 { + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_source.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_escrow.to_account_info(), + authority: ctx.accounts.signer.to_account_info(), + }, + ), + oft_fee_ld, + ctx.accounts.token_mint.decimals, + )?; + } + } + + // send message to endpoint + require!( + ctx.accounts.oft_store.key() == ctx.remaining_accounts[1].key(), + OFTError::InvalidSender + ); + let amount_sd = ctx.accounts.oft_store.ld2sd(amount_received_ld); + let msg_receipt = oapp::endpoint_cpi::send( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + ctx.remaining_accounts, + &[OFT_SEED, ctx.accounts.token_escrow.key().as_ref(), &[ctx.accounts.oft_store.bump]], + EndpointSendParams { + dst_eid: params.dst_eid, + receiver: ctx.accounts.peer.peer_address, + message: msg_codec::encode( + params.to, + amount_sd, + ctx.accounts.signer.key(), + ¶ms.compose_msg, + ), + options: ctx + .accounts + .peer + .enforced_options + .combine_options(¶ms.compose_msg, ¶ms.options)?, + native_fee: params.native_fee, + lz_token_fee: params.lz_token_fee, + }, + )?; + + emit_cpi!(OFTSent { + guid: msg_receipt.guid, + dst_eid: params.dst_eid, + from: ctx.accounts.token_source.key(), + amount_sent_ld, + amount_received_ld + }); + + Ok((msg_receipt, OFTReceipt { amount_sent_ld, amount_received_ld })) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct SendParams { + pub dst_eid: u32, + pub to: [u8; 32], + pub amount_ld: u64, + pub min_amount_ld: u64, + pub options: Vec, + pub compose_msg: Option>, + pub native_fee: u64, + pub lz_token_fee: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/set_oft_config.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_oft_config.rs new file mode 100644 index 000000000..d554a69ab --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_oft_config.rs @@ -0,0 +1,60 @@ +use crate::*; +use oapp::endpoint::instructions::SetDelegateParams; + +#[derive(Accounts)] +pub struct SetOFTConfig<'info> { + pub admin: Signer<'info>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + has_one = admin @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, +} + +impl SetOFTConfig<'_> { + pub fn apply(ctx: &mut Context, params: &SetOFTConfigParams) -> Result<()> { + match params.clone() { + SetOFTConfigParams::Admin(admin) => { + ctx.accounts.oft_store.admin = admin; + }, + SetOFTConfigParams::Delegate(delegate) => { + let oft_store_seed = ctx.accounts.oft_store.token_escrow.key(); + let seeds: &[&[u8]] = + &[OFT_SEED, &oft_store_seed.to_bytes(), &[ctx.accounts.oft_store.bump]]; + let _ = oapp::endpoint_cpi::set_delegate( + ctx.accounts.oft_store.endpoint_program, + ctx.accounts.oft_store.key(), + &ctx.remaining_accounts, + seeds, + SetDelegateParams { delegate }, + )?; + }, + SetOFTConfigParams::DefaultFee(fee_bps) => { + require!(fee_bps < MAX_FEE_BASIS_POINTS, OFTError::InvalidFee); + ctx.accounts.oft_store.default_fee_bps = fee_bps; + }, + SetOFTConfigParams::Paused(paused) => { + ctx.accounts.oft_store.paused = paused; + }, + SetOFTConfigParams::Pauser(pauser) => { + ctx.accounts.oft_store.pauser = pauser; + }, + SetOFTConfigParams::Unpauser(unpauser) => { + ctx.accounts.oft_store.unpauser = unpauser; + }, + } + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub enum SetOFTConfigParams { + Admin(Pubkey), + Delegate(Pubkey), // OApp delegate for the endpoint + DefaultFee(u16), + Paused(bool), + Pauser(Option), + Unpauser(Option), +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/set_pause.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_pause.rs new file mode 100644 index 000000000..dfac3a113 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_pause.rs @@ -0,0 +1,35 @@ +use crate::*; + +#[derive(Accounts)] +#[instruction(params: SetPauseParams)] +pub struct SetPause<'info> { + /// pauser or unpauser + pub signer: Signer<'info>, + #[account( + mut, + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + constraint = is_valid_signer(signer.key(), &oft_store, params.paused) @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, +} + +impl SetPause<'_> { + pub fn apply(ctx: &mut Context, params: &SetPauseParams) -> Result<()> { + ctx.accounts.oft_store.paused = params.paused; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct SetPauseParams { + pub paused: bool, +} + +fn is_valid_signer(signer: Pubkey, oft_store: &OFTStore, paused: bool) -> bool { + if paused { + oft_store.pauser == Some(signer) + } else { + oft_store.unpauser == Some(signer) + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/set_peer_config.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_peer_config.rs new file mode 100644 index 000000000..d4d4ee58c --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/set_peer_config.rs @@ -0,0 +1,99 @@ +use crate::*; + +#[derive(Accounts)] +#[instruction(params: SetPeerConfigParams)] +pub struct SetPeerConfig<'info> { + #[account(mut)] + pub admin: Signer<'info>, + #[account( + init_if_needed, + payer = admin, + space = 8 + PeerConfig::INIT_SPACE, + seeds = [PEER_SEED, oft_store.key().as_ref(), ¶ms.remote_eid.to_be_bytes()], + bump + )] + pub peer: Account<'info, PeerConfig>, + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + has_one = admin @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, + pub system_program: Program<'info, System>, +} + +impl SetPeerConfig<'_> { + pub fn apply(ctx: &mut Context, params: &SetPeerConfigParams) -> Result<()> { + match params.config.clone() { + PeerConfigParam::PeerAddress(peer_address) => { + ctx.accounts.peer.peer_address = peer_address; + }, + PeerConfigParam::FeeBps(fee_bps) => { + if let Some(fee_bps) = fee_bps { + require!(fee_bps < MAX_FEE_BASIS_POINTS, OFTError::InvalidFee); + } + ctx.accounts.peer.fee_bps = fee_bps; + }, + PeerConfigParam::EnforcedOptions { send, send_and_call } => { + oapp::options::assert_type_3(&send)?; + ctx.accounts.peer.enforced_options.send = send; + oapp::options::assert_type_3(&send_and_call)?; + ctx.accounts.peer.enforced_options.send_and_call = send_and_call; + }, + PeerConfigParam::OutboundRateLimit(rate_limit_params) => { + Self::update_rate_limiter( + &mut ctx.accounts.peer.outbound_rate_limiter, + &rate_limit_params, + )?; + }, + PeerConfigParam::InboundRateLimit(rate_limit_params) => { + Self::update_rate_limiter( + &mut ctx.accounts.peer.inbound_rate_limiter, + &rate_limit_params, + )?; + }, + } + ctx.accounts.peer.bump = ctx.bumps.peer; + Ok(()) + } + + fn update_rate_limiter( + rate_limiter: &mut Option, + params: &Option, + ) -> Result<()> { + if let Some(param) = params { + let mut limiter = rate_limiter.clone().unwrap_or_default(); + if let Some(capacity) = param.capacity { + limiter.set_capacity(capacity)?; + } + if let Some(refill_rate) = param.refill_per_second { + limiter.set_rate(refill_rate)?; + } + *rate_limiter = Some(limiter); + } else { + *rate_limiter = None; + } + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct SetPeerConfigParams { + pub remote_eid: u32, + pub config: PeerConfigParam, +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub enum PeerConfigParam { + PeerAddress([u8; 32]), + FeeBps(Option), + EnforcedOptions { send: Vec, send_and_call: Vec }, + OutboundRateLimit(Option), + InboundRateLimit(Option), +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct RateLimitParams { + pub refill_per_second: Option, + pub capacity: Option, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/instructions/withdraw_fee.rs b/examples/oft-solana-composer-library/programs/oft/src/instructions/withdraw_fee.rs new file mode 100644 index 000000000..762c46ae6 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/instructions/withdraw_fee.rs @@ -0,0 +1,67 @@ +use crate::*; +use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked}; + +#[derive(Accounts)] +pub struct WithdrawFee<'info> { + pub admin: Signer<'info>, + #[account( + seeds = [OFT_SEED, oft_store.token_escrow.as_ref()], + bump = oft_store.bump, + has_one = admin @OFTError::Unauthorized + )] + pub oft_store: Account<'info, OFTStore>, + #[account( + address = oft_store.token_mint, + mint::token_program = token_program + )] + pub token_mint: InterfaceAccount<'info, Mint>, + #[account( + mut, + address = oft_store.token_escrow, + token::authority = oft_store, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_escrow: InterfaceAccount<'info, TokenAccount>, + #[account( + mut, + token::mint = token_mint, + token::token_program = token_program + )] + pub token_dest: InterfaceAccount<'info, TokenAccount>, + pub token_program: Interface<'info, TokenInterface>, +} + +impl WithdrawFee<'_> { + pub fn apply(ctx: &mut Context, params: &WithdrawFeeParams) -> Result<()> { + require!( + ctx.accounts.token_escrow.amount - ctx.accounts.oft_store.tvl_ld >= params.fee_ld, + OFTError::InvalidFee + ); + let seeds: &[&[u8]] = &[ + OFT_SEED, + &ctx.accounts.token_escrow.key().to_bytes(), + &[ctx.accounts.oft_store.bump], + ]; + token_interface::transfer_checked( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + TransferChecked { + from: ctx.accounts.token_escrow.to_account_info(), + mint: ctx.accounts.token_mint.to_account_info(), + to: ctx.accounts.token_dest.to_account_info(), + authority: ctx.accounts.oft_store.to_account_info(), + }, + ) + .with_signer(&[&seeds]), + params.fee_ld, + ctx.accounts.token_mint.decimals, + )?; + Ok(()) + } +} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct WithdrawFeeParams { + pub fee_ld: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/lib.rs b/examples/oft-solana-composer-library/programs/oft/src/lib.rs new file mode 100644 index 000000000..68b54b928 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/lib.rs @@ -0,0 +1,101 @@ +use anchor_lang::prelude::*; + +pub mod compose_msg_codec; +pub mod errors; +pub mod events; +pub mod instructions; +pub mod msg_codec; +pub mod state; + +use errors::*; +use events::*; +use instructions::*; +use oapp::{ + endpoint::{MessagingFee, MessagingReceipt}, + LzReceiveParams, +}; +use solana_helper::program_id_from_env; +use state::*; + +declare_id!(Pubkey::new_from_array(program_id_from_env!( + "OFT_ID", + "9UovNrJD8pQyBLheeHNayuG1wJSEAoxkmM14vw5gcsTT" +))); + +pub const OFT_SEED: &[u8] = b"OFT"; +pub const PEER_SEED: &[u8] = b"Peer"; +pub const ENFORCED_OPTIONS_SEED: &[u8] = b"EnforcedOptions"; +pub const LZ_RECEIVE_TYPES_SEED: &[u8] = oapp::LZ_RECEIVE_TYPES_SEED; + +#[program] +pub mod oft { + use super::*; + + pub fn oft_version(_ctx: Context) -> Result { + Ok(Version { interface: 2, message: 1 }) + } + + pub fn init_oft(mut ctx: Context, params: InitOFTParams) -> Result<()> { + InitOFT::apply(&mut ctx, ¶ms) + } + + // ============================== Admin ============================== + pub fn set_oft_config( + mut ctx: Context, + params: SetOFTConfigParams, + ) -> Result<()> { + SetOFTConfig::apply(&mut ctx, ¶ms) + } + + pub fn set_peer_config( + mut ctx: Context, + params: SetPeerConfigParams, + ) -> Result<()> { + SetPeerConfig::apply(&mut ctx, ¶ms) + } + + pub fn set_pause(mut ctx: Context, params: SetPauseParams) -> Result<()> { + SetPause::apply(&mut ctx, ¶ms) + } + + pub fn withdraw_fee(mut ctx: Context, params: WithdrawFeeParams) -> Result<()> { + WithdrawFee::apply(&mut ctx, ¶ms) + } + + // ============================== Public ============================== + + pub fn quote_oft(ctx: Context, params: QuoteOFTParams) -> Result { + QuoteOFT::apply(&ctx, ¶ms) + } + + pub fn quote_send(ctx: Context, params: QuoteSendParams) -> Result { + QuoteSend::apply(&ctx, ¶ms) + } + + pub fn send( + mut ctx: Context, + params: SendParams, + ) -> Result<(MessagingReceipt, OFTReceipt)> { + Send::apply(&mut ctx, ¶ms) + } + + pub fn lz_receive(mut ctx: Context, params: LzReceiveParams) -> Result<()> { + LzReceive::apply(&mut ctx, ¶ms) + } + + pub fn lz_receive_types( + ctx: Context, + params: LzReceiveParams, + ) -> Result> { + LzReceiveTypes::apply(&ctx, ¶ms) + } +} + +#[derive(Accounts)] +pub struct OFTVersion {} + +#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +pub struct Version { + pub interface: u64, + pub message: u64, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/msg_codec.rs b/examples/oft-solana-composer-library/programs/oft/src/msg_codec.rs new file mode 100644 index 000000000..47771d590 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/msg_codec.rs @@ -0,0 +1,46 @@ +use crate::*; + +const SEND_TO_OFFSET: usize = 0; +const SEND_AMOUNT_SD_OFFSET: usize = 32; +const COMPOSE_MSG_OFFSET: usize = 40; + +pub fn encode( + send_to: [u8; 32], + amount_sd: u64, + sender: Pubkey, + compose_msg: &Option>, +) -> Vec { + if let Some(msg) = compose_msg { + let mut encoded = Vec::with_capacity(72 + msg.len()); // 32 + 8 + 32 + encoded.extend_from_slice(&send_to); + encoded.extend_from_slice(&amount_sd.to_be_bytes()); + encoded.extend_from_slice(sender.to_bytes().as_ref()); + encoded.extend_from_slice(&msg); + encoded + } else { + let mut encoded = Vec::with_capacity(40); // 32 + 8 + encoded.extend_from_slice(&send_to); + encoded.extend_from_slice(&amount_sd.to_be_bytes()); + encoded + } +} + +pub fn send_to(message: &[u8]) -> [u8; 32] { + let mut send_to = [0; 32]; + send_to.copy_from_slice(&message[SEND_TO_OFFSET..SEND_AMOUNT_SD_OFFSET]); + send_to +} + +pub fn amount_sd(message: &[u8]) -> u64 { + let mut amount_sd_bytes = [0; 8]; + amount_sd_bytes.copy_from_slice(&message[SEND_AMOUNT_SD_OFFSET..COMPOSE_MSG_OFFSET]); + u64::from_be_bytes(amount_sd_bytes) +} + +pub fn compose_msg(message: &[u8]) -> Option> { + if message.len() > COMPOSE_MSG_OFFSET { + Some(message[COMPOSE_MSG_OFFSET..].to_vec()) + } else { + None + } +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/state/mod.rs b/examples/oft-solana-composer-library/programs/oft/src/state/mod.rs new file mode 100644 index 000000000..66db5699b --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/state/mod.rs @@ -0,0 +1,5 @@ +pub mod oft; +pub mod peer_config; + +pub use oft::*; +pub use peer_config::*; diff --git a/examples/oft-solana-composer-library/programs/oft/src/state/oft.rs b/examples/oft-solana-composer-library/programs/oft/src/state/oft.rs new file mode 100644 index 000000000..6cb97b5da --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/state/oft.rs @@ -0,0 +1,50 @@ +use crate::*; + +#[account] +#[derive(InitSpace)] +pub struct OFTStore { + // immutable + pub oft_type: OFTType, + pub ld2sd_rate: u64, + pub token_mint: Pubkey, + pub token_escrow: Pubkey, // this account is used to hold TVL and fees + pub endpoint_program: Pubkey, + pub bump: u8, + // mutable + pub tvl_ld: u64, // total value locked. if oft_type is Native, it is always 0. + // configurable + pub admin: Pubkey, + pub default_fee_bps: u16, + pub paused: bool, + pub pauser: Option, + pub unpauser: Option, +} + +#[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub enum OFTType { + Native, + Adapter, +} + +impl OFTStore { + pub fn ld2sd(&self, amount_ld: u64) -> u64 { + amount_ld / self.ld2sd_rate + } + + pub fn sd2ld(&self, amount_sd: u64) -> u64 { + amount_sd * self.ld2sd_rate + } + + pub fn remove_dust(&self, amount_ld: u64) -> u64 { + amount_ld - amount_ld % self.ld2sd_rate + } +} + +/// LzReceiveTypesAccounts includes accounts that are used in the LzReceiveTypes +/// instruction. +#[account] +#[derive(InitSpace)] +pub struct LzReceiveTypesAccounts { + pub oft_store: Pubkey, + pub token_mint: Pubkey, +} diff --git a/examples/oft-solana-composer-library/programs/oft/src/state/peer_config.rs b/examples/oft-solana-composer-library/programs/oft/src/state/peer_config.rs new file mode 100644 index 000000000..2a6434087 --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/src/state/peer_config.rs @@ -0,0 +1,92 @@ +use crate::*; + +pub const ENFORCED_OPTIONS_SEND_MAX_LEN: usize = 512; +pub const ENFORCED_OPTIONS_SEND_AND_CALL_MAX_LEN: usize = 1024; + +#[account] +#[derive(InitSpace)] +pub struct PeerConfig { + pub peer_address: [u8; 32], + pub enforced_options: EnforcedOptions, + pub outbound_rate_limiter: Option, + pub inbound_rate_limiter: Option, + pub fee_bps: Option, + pub bump: u8, +} + +#[derive(Clone, Default, AnchorSerialize, AnchorDeserialize, InitSpace)] +pub struct RateLimiter { + pub capacity: u64, + pub tokens: u64, + pub refill_per_second: u64, + pub last_refill_time: u64, +} + +impl RateLimiter { + pub fn set_rate(&mut self, refill_per_second: u64) -> Result<()> { + self.refill(0)?; + self.refill_per_second = refill_per_second; + Ok(()) + } + + pub fn set_capacity(&mut self, capacity: u64) -> Result<()> { + self.capacity = capacity; + self.tokens = capacity; + self.last_refill_time = Clock::get()?.unix_timestamp.try_into().unwrap(); + Ok(()) + } + + pub fn refill(&mut self, extra_tokens: u64) -> Result<()> { + let mut new_tokens = extra_tokens; + let current_time: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); + if current_time > self.last_refill_time { + let time_elapsed_in_seconds = current_time - self.last_refill_time; + new_tokens = new_tokens + .saturating_add(time_elapsed_in_seconds.saturating_mul(self.refill_per_second)); + } + self.tokens = std::cmp::min(self.capacity, self.tokens.saturating_add(new_tokens)); + + self.last_refill_time = current_time; + Ok(()) + } + + pub fn try_consume(&mut self, amount: u64) -> Result<()> { + self.refill(0)?; + match self.tokens.checked_sub(amount) { + Some(new_tokens) => { + self.tokens = new_tokens; + Ok(()) + }, + None => Err(error!(OFTError::RateLimitExceeded)), + } + } +} + +#[derive(Clone, Default, AnchorSerialize, AnchorDeserialize, InitSpace)] +pub struct EnforcedOptions { + #[max_len(ENFORCED_OPTIONS_SEND_MAX_LEN)] + pub send: Vec, + #[max_len(ENFORCED_OPTIONS_SEND_AND_CALL_MAX_LEN)] + pub send_and_call: Vec, +} + +impl EnforcedOptions { + pub fn get_enforced_options(&self, composed_msg: &Option>) -> Vec { + if composed_msg.is_none() { + self.send.clone() + } else { + self.send_and_call.clone() + } + } + + pub fn combine_options( + &self, + compose_msg: &Option>, + extra_options: &Vec, + ) -> Result> { + let enforced_options = self.get_enforced_options(compose_msg); + oapp::options::combine_options(enforced_options, extra_options) + } +} + +utils::generate_account_size_test!(EnforcedOptions, enforced_options_test); diff --git a/examples/oft-solana-composer-library/programs/oft/tests/msg_codec.rs b/examples/oft-solana-composer-library/programs/oft/tests/msg_codec.rs new file mode 100644 index 000000000..edf8f236c --- /dev/null +++ b/examples/oft-solana-composer-library/programs/oft/tests/msg_codec.rs @@ -0,0 +1,55 @@ +#[cfg(test)] +mod test_msg_codec { + use anchor_lang::prelude::Pubkey; + use oft::compose_msg_codec; + use oft::msg_codec; + + #[test] + fn test_msg_codec_with_compose_msg() { + let send_to: [u8; 32] = [1; 32]; + let amount_sd: u64 = 123456789; + let sender: Pubkey = Pubkey::new_unique(); + let compose_msg: Option> = Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + let encoded = msg_codec::encode(send_to, amount_sd, sender, &compose_msg); + assert_eq!(encoded.len(), 72 + compose_msg.clone().unwrap().len()); + assert_eq!(msg_codec::send_to(&encoded), send_to); + assert_eq!(msg_codec::amount_sd(&encoded), amount_sd); + assert_eq!( + msg_codec::compose_msg(&encoded), + Some([sender.to_bytes().as_ref(), compose_msg.unwrap().as_slice()].concat()) + ); + } + + #[test] + fn test_msg_codec_without_compose_msg() { + let send_to: [u8; 32] = [1; 32]; + let amount_sd: u64 = 123456789; + let sender: Pubkey = Pubkey::new_unique(); + let compose_msg: Option> = None; + let encoded = msg_codec::encode(send_to, amount_sd, sender, &compose_msg); + assert_eq!(encoded.len(), 40); + assert_eq!(msg_codec::send_to(&encoded), send_to); + assert_eq!(msg_codec::amount_sd(&encoded), amount_sd); + assert_eq!(msg_codec::compose_msg(&encoded), None); + } + + #[test] + fn test_compose_msg_codec() { + let nonce: u64 = 123456789; + let src_eid: u32 = 987654321; + let amount_ld: u64 = 123456789; + let compose_from: [u8; 32] = [1; 32]; + let compose_msg: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + let encoded = compose_msg_codec::encode( + nonce, + src_eid, + amount_ld, + &[&compose_from[..], &compose_msg].concat(), + ); + assert_eq!(encoded.len(), 20 + [&compose_from[..], &compose_msg].concat().len()); + assert_eq!(compose_msg_codec::nonce(&encoded), nonce); + assert_eq!(compose_msg_codec::src_eid(&encoded), src_eid); + assert_eq!(compose_msg_codec::amount_ld(&encoded), amount_ld); + assert_eq!(compose_msg_codec::compose_msg(&encoded), compose_msg); + } +} diff --git a/examples/oft-solana-composer-library/runbooks/README.md b/examples/oft-solana-composer-library/runbooks/README.md new file mode 100644 index 000000000..d35fd0ccd --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/README.md @@ -0,0 +1,49 @@ +# oft-solana-composer-library Runbooks + +[![Txtx](https://img.shields.io/badge/Operated%20with-Txtx-gree?labelColor=gray)](https://txtx.sh) + +## Runbooks available + +### deployment +Deploy programs + +## Getting Started + +This repository is using [txtx](https://txtx.sh) for handling its on-chain operations. + +`txtx` takes its inspiration from a battle tested devops best practice named `infrastructure as code`, that have transformed cloud architectures. + +`txtx` simplifies and streamlines Smart Contract Infrastructure management across blockchains, focusing on robustness, reproducibility and composability. + +### Installation + +```console +$ curl -sL https://install.txtx.sh/ | bash +``` + +### Scaffold a new runbook + +```console +$ txtx new +``` + +Access tutorials and documentation at [docs.txtx.sh](https://docs.txtx.sh) to understand the syntax and discover the powerful features of txtx. + +Additionally, the [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=txtx.txtx) will make writing runbooks easier. + +### List runbooks available in this repository +```console +$ txtx ls +Name ID Description +BNS Multisig bns-multisig Register a BNS name using a multisig signer +``` + +### Execute an existing runbook +```console +$ txtx run bns-multisig +``` + +### Update the README documentation +```console +$ txtx docs --update +``` diff --git a/examples/oft-solana-composer-library/runbooks/deployment/main.tx b/examples/oft-solana-composer-library/runbooks/deployment/main.tx new file mode 100644 index 000000000..9c08b4f12 --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/main.tx @@ -0,0 +1,60 @@ +################################################################ +# Manage oft-solana-composer-library deployment through Crypto Infrastructure as Code +################################################################ + +addon "svm" { + rpc_api_url = input.rpc_api_url + network_id = input.network_id +} +variable "composer" { + value = svm::get_program_from_anchor_project("composer") +} + +variable "initComposerTx" { + value = "c7c2de2b94f8c2ab3aad2be6167477f2fc6864f5623e3791aa2f25cbcb5207a202ff3c383bdc379f1c5eae5faa88478e8729bc45057eb95154f0f7ec4a3ec80503c62b8b7cdd97cc" +} + +action "deploy_composer" "svm::deploy_program" { + description = "Deploy composer program" + program = variable.composer + authority = signer.authority + payer = signer.payer +} + +action "program_call" "svm::process_instructions" { + description = "Invoke composer init instructions" + program = variable.composer + instruction { + program_idl = variable.composer.idl + instruction_name = "initComposer" + instruction_args = [{ + oftPda: "4x3oQtX4MhjTKGBeXDZbtTSLZ9cUWo5waN2UChAuthtS", + endpointPda: "2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3" + }] + composer { + public_key = "7XMGP9XXajZHXQpWoXZqqLawD5pUbY7tzxmiTga23mi9" // You'll need to provide the composer account public key + } + lzComposeTypesAccounts { + public_key = "2SwnA7KLCVwsQweHx6oiiW7Yo8WmLPjGgAiV6J8souDD" // You'll need to provide the lz_compose_types_accounts public key + } + payer { + public_key = signer.payer.public_key + } + systemProgram { + public_key = svm::system_program_id() + } + } + signers = [signer.payer] + depends_on = [action.deploy_composer] +} + +// action "send_transaction" "svm::sign_transaction" { +// description = "Send transaction" +// transaction_bytes = variable.initComposerTx +// signers = [signer.payer] +// depends_on = [action.deploy_composer] +// } + +// output "program_call_signature" { +// value = action.program_call.signature +// } \ No newline at end of file diff --git a/examples/oft-solana-composer-library/runbooks/deployment/signers.devnet.tx b/examples/oft-solana-composer-library/runbooks/deployment/signers.devnet.tx new file mode 100644 index 000000000..e838b87ed --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/signers.devnet.tx @@ -0,0 +1,8 @@ + +signer "payer" "svm::web_wallet" { + // expected_address = input.expected_payer_address +} + +signer "authority" "svm::web_wallet" { + // expected_address = input.expected_payer_address +} diff --git a/examples/oft-solana-composer-library/runbooks/deployment/signers.localnet.tx b/examples/oft-solana-composer-library/runbooks/deployment/signers.localnet.tx new file mode 100644 index 000000000..98c2a6b70 --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/signers.localnet.tx @@ -0,0 +1,8 @@ + +signer "payer" "svm::secret_key" { + keypair_json = input.authority_keypair_json +} + +signer "authority" "svm::secret_key" { + keypair_json = input.authority_keypair_json +} diff --git a/examples/oft-solana-composer-library/runbooks/deployment/signers.mainnet.tx b/examples/oft-solana-composer-library/runbooks/deployment/signers.mainnet.tx new file mode 100644 index 000000000..6f2212000 --- /dev/null +++ b/examples/oft-solana-composer-library/runbooks/deployment/signers.mainnet.tx @@ -0,0 +1,10 @@ + +# For mainnet deployment, use web wallets, hardware wallets, or multisig for key security. + +# signer "payer" "svm::web_wallet" { +# address = "YOUR_WEB_WALLET_PUBLIC_KEY" +# } + +# signer "authority" "svm::squads" { +# address = "YOUR_SQUAD_PUBLIC_KEY" +# } diff --git a/examples/oft-solana-composer-library/rust-toolchain.toml b/examples/oft-solana-composer-library/rust-toolchain.toml new file mode 100644 index 000000000..7897a24d1 --- /dev/null +++ b/examples/oft-solana-composer-library/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.75.0" diff --git a/examples/oft-solana-composer-library/solhint.config.js b/examples/oft-solana-composer-library/solhint.config.js new file mode 100644 index 000000000..52efe629c --- /dev/null +++ b/examples/oft-solana-composer-library/solhint.config.js @@ -0,0 +1 @@ +module.exports = require('@layerzerolabs/solhint-config'); diff --git a/examples/oft-solana-composer-library/tasks/common/config.get.ts b/examples/oft-solana-composer-library/tasks/common/config.get.ts new file mode 100644 index 000000000..9b2f3e411 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/config.get.ts @@ -0,0 +1,200 @@ +import { PublicKey } from '@solana/web3.js' +import { task } from 'hardhat/config' +import { ActionType } from 'hardhat/types' + +import { OmniPoint, createDefaultApplicative } from '@layerzerolabs/devtools' +import { createConnectedContractFactory, getNetworkNameForEid } from '@layerzerolabs/devtools-evm-hardhat' +import { createLogger, printCrossTable, setDefaultLogLevel } from '@layerzerolabs/io-devtools' +import { ChainType, EndpointId, endpointIdToChainType, getNetworkForChainId } from '@layerzerolabs/lz-definitions' +import { EndpointV2 } from '@layerzerolabs/protocol-devtools-solana' +import { OAppOmniGraph } from '@layerzerolabs/ua-devtools' +import { createOAppFactory } from '@layerzerolabs/ua-devtools-evm' +import { + OAppOmniGraphHardhatSchema, + SUBTASK_LZ_OAPP_CONFIG_LOAD, + type SubtaskLoadConfigTaskArgs, + TASK_LZ_OAPP_CONFIG_GET, +} from '@layerzerolabs/ua-devtools-evm-hardhat' +import { getReceiveConfig, getSendConfig } from '@layerzerolabs/ua-devtools-evm-hardhat' + +import { getSolanaReceiveConfig, getSolanaSendConfig } from './taskHelper' +import { createSolanaConnectionFactory } from './utils' + +interface TaskArgs { + logLevel?: string + oappConfig: string +} + +/** + * Helper function to determine if the point is Solana + * @param point {OmniPoint} + */ +const isSolana = (point: OmniPoint) => endpointIdToChainType(point.eid) === ChainType.SOLANA + +/** + * Helper function to get the hardhat.config.ts network name for a given endpoint id, or use the convention of + * networkName-environment for Solana. + * @param eid {EndpointId} + */ +const getEid = (eid: EndpointId) => { + switch (eid) { + // In the case of solana-testnet and solana-mainnet, we'll use the convention of networkName-environment + case EndpointId.SOLANA_V2_TESTNET: + case EndpointId.SOLANA_V2_MAINNET: { + const { chainName, env } = getNetworkForChainId(eid) + return `${chainName}-${env}` + } + default: + // For all other chains, we'll use the network name from hardhat.config.ts + return getNetworkNameForEid(eid) + } +} + +const action: ActionType = async ({ logLevel = 'info', oappConfig }, hre) => { + setDefaultLogLevel(logLevel) + const logger = createLogger(logLevel) + + const graph: OAppOmniGraph = await hre.run(SUBTASK_LZ_OAPP_CONFIG_LOAD, { + configPath: oappConfig, + schema: OAppOmniGraphHardhatSchema, + task: TASK_LZ_OAPP_CONFIG_GET, + } satisfies SubtaskLoadConfigTaskArgs) + + const evmSdkFactory = createOAppFactory(createConnectedContractFactory()) + const configs: Record> = {} + + // Iterate over the graph of connections not from Solana + const tasks = graph.connections + .filter(({ vector: { from } }) => !isSolana(from)) + .map(({ vector: { from, to } }) => async () => { + const endpointV2Sdk = await (await evmSdkFactory(from)).getEndpointSDK() + + // OApp User Set Config + const receiveCustomConfig = await getReceiveConfig(endpointV2Sdk, to.eid, from.address, true) + const sendCustomConfig = await getSendConfig(endpointV2Sdk, to.eid, from.address, true) + const [sendCustomLibrary, sendCustomUlnConfig, sendCustomExecutorConfig] = sendCustomConfig ?? [] + const [receiveCustomLibrary, receiveCustomUlnConfig] = receiveCustomConfig ?? [] + + // Default Config + const receiveDefaultConfig = await getReceiveConfig(endpointV2Sdk, to.eid) + const sendDefaultConfig = await getSendConfig(endpointV2Sdk, to.eid) + const [sendDefaultLibrary, sendDefaultUlnConfig, sendDefaultExecutorConfig] = sendDefaultConfig ?? [] + const [receiveDefaultLibrary, receiveDefaultUlnConfig] = receiveDefaultConfig ?? [] + + // OApp Config + const receiveOAppConfig = await getReceiveConfig(endpointV2Sdk, to.eid, from.address) + const sendOAppConfig = await getSendConfig(endpointV2Sdk, to.eid, from.address) + const [sendOAppLibrary, sendOAppUlnConfig, sendOAppExecutorConfig] = sendOAppConfig ?? [] + const [receiveOAppLibrary, receiveOAppUlnConfig] = receiveOAppConfig ?? [] + + const localNetworkName = getEid(from.eid) + const remoteNetworkName = getEid(to.eid) + + // Update the global state + configs[localNetworkName] = { + ...configs[localNetworkName], + [remoteNetworkName]: { + defaultSendLibrary: sendOAppLibrary, + defaultReceiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + } + + console.log( + printCrossTable( + [ + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendCustomLibrary, + receiveLibrary: receiveCustomLibrary, + sendUlnConfig: sendCustomUlnConfig, + sendExecutorConfig: sendCustomExecutorConfig, + receiveUlnConfig: receiveCustomUlnConfig, + }, + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendDefaultLibrary, + receiveLibrary: receiveDefaultLibrary, + sendUlnConfig: sendDefaultUlnConfig, + sendExecutorConfig: sendDefaultExecutorConfig, + receiveUlnConfig: receiveDefaultUlnConfig, + }, + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendOAppLibrary, + receiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + ], + ['', 'Custom OApp Config', 'Default OApp Config', 'Active OApp Config'] + ) + ) + }) + // Iterate over the graph of connections from Solana + const solTasks = graph.connections + .filter(({ vector: { from } }) => isSolana(from)) + .map(({ vector: { from, to } }) => async () => { + const endpointV2Sdk = new EndpointV2( + await createSolanaConnectionFactory()(from.eid), + from, + new PublicKey(from.address) // doesn't matter as we are not sending transactions + ) + // OApp Config + const receiveOAppConfig = await getSolanaReceiveConfig(endpointV2Sdk, to.eid, from.address) + const sendOAppConfig = await getSolanaSendConfig(endpointV2Sdk, to.eid, from.address) + const [sendOAppLibrary, sendOAppUlnConfig, sendOAppExecutorConfig] = sendOAppConfig ?? [] + const [receiveOAppLibrary, receiveOAppUlnConfig] = receiveOAppConfig ?? [] + + const localNetworkName = getEid(from.eid) + const remoteNetworkName = getEid(to.eid) + + // Update the global state + configs[localNetworkName] = { + ...configs[localNetworkName], + [remoteNetworkName]: { + defaultSendLibrary: sendOAppLibrary, + defaultReceiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + } + // Defaults are treated much differently in Solana, so we only output the active OApp config. + console.log( + printCrossTable( + [ + { + localNetworkName, + remoteNetworkName, + sendLibrary: sendOAppLibrary, + receiveLibrary: receiveOAppLibrary, + sendUlnConfig: sendOAppUlnConfig, + sendExecutorConfig: sendOAppExecutorConfig, + receiveUlnConfig: receiveOAppUlnConfig, + }, + ], + ['', 'Active OApp Config'] + ) + ) + }) + + // We allow this script to be executed either in parallel or in series + const applicative = createDefaultApplicative(logger) + await applicative(tasks) + await applicative(solTasks) + + return configs +} + +task( + TASK_LZ_OAPP_CONFIG_GET, + 'Outputs Custom OApp Config, Default OApp Config, and Active OApp Config. Each config contains Send & Receive Libraries, Send Uln & Executor Configs, and Receive Executor Configs', + action +) diff --git a/examples/oft-solana-composer-library/tasks/common/taskHelper.ts b/examples/oft-solana-composer-library/tasks/common/taskHelper.ts new file mode 100644 index 000000000..ca763a8fe --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/taskHelper.ts @@ -0,0 +1,56 @@ +import { OmniAddress } from '@layerzerolabs/devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { UlnProgram } from '@layerzerolabs/lz-solana-sdk-v2' +import { + IEndpointV2, + Timeout, + Uln302ConfigType, + Uln302ExecutorConfig, + Uln302UlnConfig, +} from '@layerzerolabs/protocol-devtools' + +/** + * Get the receive config for a Solana OApp + * @param endpointV2Sdk {IEndpointV2} SDK for the endpoint + * @param remoteEid {EndpointId} remote eid + * @param address {OmniAddress} address of the OApp + */ +export async function getSolanaReceiveConfig( + endpointV2Sdk: IEndpointV2, + remoteEid: EndpointId, + address: OmniAddress +): Promise<[OmniAddress, Uln302UlnConfig, Timeout] | undefined> { + const [receiveLibrary] = await endpointV2Sdk.getReceiveLibrary(address, remoteEid) + return [ + receiveLibrary ?? UlnProgram.PROGRAM_ADDRESS, + await endpointV2Sdk.getAppUlnConfig( + address, + UlnProgram.PROGRAM_ID.toBase58(), + remoteEid, + Uln302ConfigType.Receive + ), + { + lib: UlnProgram.PROGRAM_ID.toBase58(), + expiry: 0n, // unsupported for Solana + }, + ] +} + +/** + * Get the send config for a Solana OApp. + * @param endpointV2Sdk {IEndpointV2} SDK for the endpoint + * @param eid {EndpointId} remote eid + * @param address {OmniAddress} address of the OApp + */ +export async function getSolanaSendConfig( + endpointV2Sdk: IEndpointV2, + eid: EndpointId, + address: OmniAddress +): Promise<[OmniAddress, Uln302UlnConfig, Uln302ExecutorConfig] | undefined> { + const sendLibrary = (await endpointV2Sdk.getSendLibrary(address, eid)) ?? UlnProgram.PROGRAM_ADDRESS + return [ + sendLibrary, + await endpointV2Sdk.getAppUlnConfig(address, UlnProgram.PROGRAM_ID.toBase58(), eid, Uln302ConfigType.Send), + await endpointV2Sdk.getAppExecutorConfig(address, sendLibrary, eid), + ] +} diff --git a/examples/oft-solana-composer-library/tasks/common/types.ts b/examples/oft-solana-composer-library/tasks/common/types.ts new file mode 100644 index 000000000..bf0b4bc87 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/types.ts @@ -0,0 +1,19 @@ +import { decode } from '@coral-xyz/anchor/dist/cjs/utils/bytes/bs58' +import { Keypair, PublicKey } from '@solana/web3.js' +import { CLIArgumentType } from 'hardhat/types' + +export const keyPair: CLIArgumentType = { + name: 'keyPair', + parse(name: string, value: string) { + return Keypair.fromSecretKey(decode(value)) + }, + validate() {}, +} + +export const publicKey: CLIArgumentType = { + name: 'keyPair', + parse(name: string, value: string) { + return new PublicKey(value) + }, + validate() {}, +} diff --git a/examples/oft-solana-composer-library/tasks/common/utils.ts b/examples/oft-solana-composer-library/tasks/common/utils.ts new file mode 100644 index 000000000..48a13e64b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/utils.ts @@ -0,0 +1,203 @@ +import assert from 'assert' + +import { Connection, Keypair, PublicKey } from '@solana/web3.js' + +import { + OmniPoint, + OmniSigner, + OmniTransactionReceipt, + OmniTransactionResponse, + firstFactory, + formatEid, +} from '@layerzerolabs/devtools' +import { createConnectedContractFactory } from '@layerzerolabs/devtools-evm-hardhat' +import { + OmniSignerSolana, + OmniSignerSolanaSquads, + createConnectionFactory, + createRpcUrlFactory, +} from '@layerzerolabs/devtools-solana' +import { ChainType, EndpointId, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +import { UlnProgram } from '@layerzerolabs/lz-solana-sdk-v2' +import { Options } from '@layerzerolabs/lz-v2-utilities' +import { IOApp } from '@layerzerolabs/ua-devtools' +import { createOAppFactory } from '@layerzerolabs/ua-devtools-evm' +import { createOFTFactory } from '@layerzerolabs/ua-devtools-solana' + +export const createSolanaConnectionFactory = () => + createConnectionFactory( + createRpcUrlFactory({ + [EndpointId.SOLANA_V2_MAINNET]: process.env.RPC_URL_SOLANA, + [EndpointId.SOLANA_V2_TESTNET]: process.env.RPC_URL_SOLANA_TESTNET, + }) + ) + +export const createSdkFactory = ( + userAccount: PublicKey, + programId: PublicKey, + connectionFactory = createSolanaConnectionFactory() +) => { + // To create a EVM/Solana SDK factory we need to merge the EVM and the Solana factories into one + // + // We do this by using the firstFactory helper function that is provided by the devtools package. + // This function will try to execute the factories one by one and return the first one that succeeds. + const evmSdkfactory = createOAppFactory(createConnectedContractFactory()) + const solanaSdkFactory = createOFTFactory( + // The first parameter to createOFTFactory is a user account factory + // + // This is a function that receives an OmniPoint ({ eid, address } object) + // and returns a user account to be used with that SDK. + // + // For our purposes this will always be the user account coming from the secret key passed in + () => userAccount, + // The second parameter is a program ID factory + // + // This is a function that receives an OmniPoint ({ eid, address } object) + // and returns a program ID to be used with that SDK. + // + // Since we only have one OFT deployed, this will always be the program ID passed as a CLI parameter. + // + // In situations where we might have multiple configs with OFTs using multiple program IDs, + // this function needs to decide which one to use. + () => programId, + // Last but not least the SDK will require a connection + connectionFactory + ) + + // We now "merge" the two SDK factories into one. + // + // We do this by using the firstFactory helper function that is provided by the devtools package. + // This function will try to execute the factories one by one and return the first one that succeeds. + return firstFactory<[OmniPoint], IOApp>(evmSdkfactory, solanaSdkFactory) +} + +export const createSolanaSignerFactory = ( + wallet: Keypair, + connectionFactory = createSolanaConnectionFactory(), + multisigKey?: PublicKey +) => { + return async (eid: EndpointId): Promise>> => { + assert( + endpointIdToChainType(eid) === ChainType.SOLANA, + `Solana signer factory can only create signers for Solana networks. Received ${formatEid(eid)}` + ) + + return multisigKey + ? new OmniSignerSolanaSquads(eid, await connectionFactory(eid), multisigKey, wallet) + : new OmniSignerSolana(eid, await connectionFactory(eid), wallet) + } +} + +export function uint8ArrayToHex(uint8Array: Uint8Array, prefix = false): string { + const hexString = Buffer.from(uint8Array).toString('hex') + return prefix ? `0x${hexString}` : hexString +} + +function formatBigIntForDisplay(n: bigint) { + return n.toLocaleString().replace(/,/g, '_') +} + +export function decodeLzReceiveOptions(hex: string): string { + try { + // Handle empty/undefined values first + if (!hex || hex === '0x') return 'No options set' + const options = Options.fromOptions(hex) + const lzReceiveOpt = options.decodeExecutorLzReceiveOption() + return lzReceiveOpt + ? `gas: ${formatBigIntForDisplay(lzReceiveOpt.gas)} , value: ${formatBigIntForDisplay(lzReceiveOpt.value)} wei` + : 'No executor options' + } catch (e) { + return `Invalid options (${hex.slice(0, 12)}...)` + } +} + +export async function getSolanaUlnConfigPDAs( + remote: EndpointId, + connection: Connection, + ulnAddress: PublicKey, + oftStore: PublicKey +) { + const uln = new UlnProgram.Uln(new PublicKey(ulnAddress)) + const sendConfig = uln.getSendConfigState(connection, new PublicKey(oftStore), remote) + + const receiveConfig = uln.getReceiveConfigState(connection, new PublicKey(oftStore), remote) + + return await Promise.all([sendConfig, receiveConfig]) +} + +export class DebugLogger { + static keyValue(key: string, value: any, indentLevel = 0) { + const indent = ' '.repeat(indentLevel * 2) + console.log(`${indent}\x1b[33m${key}:\x1b[0m ${value}`) + } + + static keyHeader(key: string, indentLevel = 0) { + const indent = ' '.repeat(indentLevel * 2) + console.log(`${indent}\x1b[33m${key}:\x1b[0m`) + } + + static header(text: string) { + console.log(`\x1b[36m${text}\x1b[0m`) + } + + static separator() { + console.log('\x1b[90m----------------------------------------\x1b[0m') + } + + /** + * Logs an error (in red) and corresponding fix suggestion (in blue). + * Uses the ERRORS_FIXES_MAP to retrieve text based on the known error type. + * + * @param type Required KnownErrors enum member + * @param errorMsg Optional string message to append to the error. + */ + static printErrorAndFixSuggestion(type: KnownErrors, errorMsg?: string) { + const fixInfo = ERRORS_FIXES_MAP[type] + if (!fixInfo) { + // Fallback if the error type is not recognized + console.log(`\x1b[31mError:\x1b[0m Unknown error type "${type}"`) + return + } + + // If errorMsg is specified, append it in parentheses + const errorOutput = errorMsg ? `${type}: (${errorMsg})` : type + + // Print the error type in red + console.log(`\x1b[31mError:\x1b[0m ${errorOutput}`) + + // Print the tip in green + console.log(`\x1b[32mFix suggestion:\x1b[0m ${fixInfo.tip}`) + + // Print the info in blue + if (fixInfo.info) { + console.log(`\x1b[34mElaboration:\x1b[0m ${fixInfo.info}`) + } + + // log empty line to separate error messages + console.log() + } +} + +export enum KnownErrors { + // variable name format: _ + // e.g. If the user forgets to deploy the OFT Program, the variable name should be: + // FIX_SUGGESTION_OFT_PROGRAM_NOT_DEPLOYED + ULN_INIT_CONFIG_SKIPPED = 'ULN_INIT_CONFIG_SKIPPED', + SOLANA_DEPLOYMENT_NOT_FOUND = 'SOLANA_DEPLOYMENT_NOT_FOUND', +} + +interface ErrorFixInfo { + tip: string + info?: string +} + +export const ERRORS_FIXES_MAP: Record = { + [KnownErrors.ULN_INIT_CONFIG_SKIPPED]: { + tip: 'Did you run `npx hardhat lz:oft:solana:init-config --oapp-config --solana-eid ` ?', + info: 'You must run lz:oft:solana:init-config once before you run lz:oapp:wire. If you have added new pathways, you must also run lz:oft:solana:init-config again.', + }, + [KnownErrors.SOLANA_DEPLOYMENT_NOT_FOUND]: { + tip: 'Did you run `npx hardhat lz:oft:solana:create` ?', + info: 'The Solana deployment file is required to run config tasks. The default path is ./deployments/solana-/OFT.json', + }, +} diff --git a/examples/oft-solana-composer-library/tasks/common/wire.ts b/examples/oft-solana-composer-library/tasks/common/wire.ts new file mode 100644 index 000000000..1b280760a --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/common/wire.ts @@ -0,0 +1,203 @@ +import { PublicKey } from '@solana/web3.js' +import { subtask, task } from 'hardhat/config' + +import { firstFactory } from '@layerzerolabs/devtools' +import { SUBTASK_LZ_SIGN_AND_SEND, types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { setTransactionSizeBuffer } from '@layerzerolabs/devtools-solana' +import { type LogLevel, createLogger } from '@layerzerolabs/io-devtools' +import { ChainType, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +import { type IOApp, type OAppConfigurator, type OAppOmniGraph, configureOwnable } from '@layerzerolabs/ua-devtools' +import { + SUBTASK_LZ_OAPP_WIRE_CONFIGURE, + type SubtaskConfigureTaskArgs, + TASK_LZ_OAPP_WIRE, + TASK_LZ_OWNABLE_TRANSFER_OWNERSHIP, +} from '@layerzerolabs/ua-devtools-evm-hardhat' + +import { getSolanaDeployment, useWeb3Js } from '../solana' +import { findSolanaEndpointIdInGraph } from '../solana/utils' + +import { publicKey as publicKeyType } from './types' +import { + DebugLogger, + KnownErrors, + createSdkFactory, + createSolanaConnectionFactory, + createSolanaSignerFactory, + getSolanaUlnConfigPDAs, +} from './utils' + +import type { SignAndSendTaskArgs } from '@layerzerolabs/devtools-evm-hardhat/tasks' + +/** + * Additional CLI arguments for our custom wire task + */ +interface Args { + logLevel: LogLevel + multisigKey?: PublicKey + isSolanaInitConfig: boolean // For internal use only. This helps us to control which code runs depdending on whether the task ran is wire or init-config + oappConfig: string + internalConfigurator?: OAppConfigurator +} + +/** + * We extend the default wiring task to add functionality required by Solana + */ +task(TASK_LZ_OAPP_WIRE) + .addParam('multisigKey', 'The MultiSig key', undefined, publicKeyType, true) + // We use this argument to get around the fact that we want to both override the task action for the wiring task + // and wrap this task with custom configurators + // + // By default, this argument will be left empty and the default OApp configurator will be used. + // The tasks that are using custom configurators will override this argument with the configurator of their choice + .addParam('internalConfigurator', 'FOR INTERNAL USE ONLY', undefined, devtoolsTypes.fn, true) + .addParam('isSolanaInitConfig', 'FOR INTERNAL USE ONLY', undefined, devtoolsTypes.boolean, true) + .setAction(async (args: Args, hre, runSuper) => { + const logger = createLogger(args.logLevel) + + // + // + // ENVIRONMENT SETUP + // + // + + // The Solana transaction size estimation algorithm is not very accurate, so we increase its tolerance by 192 bytes + setTransactionSizeBuffer(192) + + // + // + // USER INPUT + // + // + + // construct the user's keypair via the SOLANA_PRIVATE_KEY env var + const keypair = (await useWeb3Js()).web3JsKeypair // note: this can be replaced with getSolanaKeypair() if we are okay to export that + const userAccount = keypair.publicKey + + const solanaEid = await findSolanaEndpointIdInGraph(hre, args.oappConfig) + const solanaDeployment = getSolanaDeployment(solanaEid) + + // Then we grab the programId from the args + const programId = new PublicKey(solanaDeployment.programId) + + // TODO: refactor to instead use a function such as verifySolanaDeployment that also checks for oftStore key + if (!programId) { + logger.error('Missing programId in solana deployment') + return + } + + const configurator = args.internalConfigurator + + // + // + // TOOLING SETUP + // + // + + // We'll need a connection factory to be able to query the Solana network + // + // If you haven't set RPC_URL_SOLANA and/or RPC_URL_SOLANA_TESTNET environment variables, + // the factory will use the default public RPC URLs + const connectionFactory = createSolanaConnectionFactory() + + // We'll need SDKs to be able to use devtools + const sdkFactory = createSdkFactory(userAccount, programId, connectionFactory) + + // We'll also need a signer factory + const solanaSignerFactory = createSolanaSignerFactory(keypair, connectionFactory, args.multisigKey) + + // + // + // SUBTASK OVERRIDES + // + // + + // We'll need to override the default implementation of the configure subtask + // (responsible for collecting the on-chain configuration of the contracts + // and coming up with the transactions that need to be sent to the network) + // + // The only thing we are overriding is the sdkFactory parameter - we supply the SDK factory we created above + subtask( + SUBTASK_LZ_OAPP_WIRE_CONFIGURE, + 'Configure OFT', + async (subtaskArgs: SubtaskConfigureTaskArgs, _hre, runSuper) => { + // start of pre-wiring checks. we only do this when the current task is wire. if the current task is init-config, we shouldn't run this. + if (!args.isSolanaInitConfig) { + logger.debug('Running pre-wiring checks...') + const { graph } = subtaskArgs + for (const connection of graph.connections) { + // check if from Solana Endpoint + if (endpointIdToChainType(connection.vector.from.eid) === ChainType.SOLANA) { + if (connection.config?.sendLibrary) { + // if from Solana Endpoint, ensure the PeerConfig account was already initialized + const solanaConnection = await connectionFactory(connection.vector.from.eid) + + const [sendConfig, receiveConfig] = await getSolanaUlnConfigPDAs( + connection.vector.to.eid, + solanaConnection, + new PublicKey(connection.config.sendLibrary), + new PublicKey(solanaDeployment.oftStore) + ) + + if (sendConfig == null) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ULN_INIT_CONFIG_SKIPPED, + `SendConfig on ${connection.vector.from.eid} not initialized for remote ${connection.vector.to.eid}.` + ) + } + + if (receiveConfig == null) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ULN_INIT_CONFIG_SKIPPED, + `ReceiveConfig on ${connection.vector.from.eid} not initialized for remote ${connection.vector.to.eid}.` + ) + } + + if (sendConfig == null || receiveConfig == null) { + throw new Error('SendConfig or ReceiveConfig not initialized. ') + } + } else { + logger.debug( + `No sendLibrary found in connection config for ${connection.vector.from.eid} -> ${connection.vector.to.eid}` + ) + } + } + } + // end of pre-wiring checks + } + + return runSuper({ + ...subtaskArgs, + configurator: configurator ?? subtaskArgs.configurator, + sdkFactory, + }) + } + ) + + // We'll also need to override the default implementation of the signAndSend subtask + // (responsible for sending transactions to the network and waiting for confirmations) + // + // In this subtask we need to override the createSigner function so that it uses the Solana + // signer for all Solana transactions + subtask(SUBTASK_LZ_SIGN_AND_SEND, 'Sign OFT transactions', (args: SignAndSendTaskArgs, _hre, runSuper) => + runSuper({ + ...args, + createSigner: firstFactory(solanaSignerFactory, args.createSigner), + }) + ) + + return runSuper(args) + }) + +// We'll change the default ownership transfer task to use our wire implementation +// +// The reason for this is the fact that the ownership transfer task has a deficiency +// and that is the fact that it does not support a custom SDK factory as of yet +// +// The two tasks are identical and the only drawback of this approach is the fact +// that the logs will say "Wiring OApp" instead of "Transferring ownership" +task(TASK_LZ_OWNABLE_TRANSFER_OWNERSHIP) + .addParam('multisigKey', 'The MultiSig key', undefined, publicKeyType, true) + .setAction(async (args: Args, hre) => { + return hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: configureOwnable }) + }) diff --git a/examples/oft-solana-composer-library/tasks/evm/send.ts b/examples/oft-solana-composer-library/tasks/evm/send.ts new file mode 100644 index 000000000..5e500355c --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/evm/send.ts @@ -0,0 +1,83 @@ +import bs58 from 'bs58' +import { BigNumber, ethers } from 'ethers' +import { task, types } from 'hardhat/config' +import { ActionType, HardhatRuntimeEnvironment } from 'hardhat/types' + +import { makeBytes32 } from '@layerzerolabs/devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { Options } from '@layerzerolabs/lz-v2-utilities' + +import { getLayerZeroScanLink } from '../solana' + +interface TaskArguments { + dstEid: number + amount: string + minAmount: string + composer: string + solanaReceiver: string + contractName: string +} + +const action: ActionType = async ( + { dstEid, amount, minAmount, composer, solanaReceiver, contractName }, + hre: HardhatRuntimeEnvironment +) => { + const signer = await hre.ethers.getNamedSigner('deployer') + + // ─── Point at the *existing* USDE IOFT on BSC ───────────────────────────── + const USDE_ADDRESS = '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34' + // we expect `contractName` to match the ABI / interface of an IOFT + const oft = await hre.ethers.getContractAt(contractName, USDE_ADDRESS, signer) + // ───────────────────────────────────────────────────────────────────────── + + const amountLD = BigNumber.from(amount) + // 1) turn your Solana base58 address into a 32-byte hex string // Uint8Array of length 32 + // 2) solidity-pack your two fields: + // - a 64-bit BE integer for minAmount + // - a full 32-byte receiver pubkey + const composeMsg = ethers.utils.solidityPack( + ['uint64', 'bytes32'], + [BigNumber.from(minAmount), makeBytes32(bs58.decode(solanaReceiver))] + ) + const sendParam = { + dstEid, + to: makeBytes32(bs58.decode(composer)), + amountLD: amountLD.toString(), + minAmountLD: amountLD.toString(), + extraOptions: Options.newOptions() + .addExecutorLzReceiveOption(200_000, 2_500_000) + .addExecutorComposeOption(0, 200_000, 2_500_000) + .toBytes(), + composeMsg: composeMsg, // you can build your 112-byte composeMsg here + oftCmd: '0x', + } + + // quote how much LayerZero fees you must pay + const [msgFee] = await oft.functions.quoteSend(sendParam, false) + + // now actually send + const txResponse = await oft.functions.send( + sendParam, + msgFee, + signer.address, // refund address for any dust + { + value: msgFee.nativeFee, + gasLimit: 500_000, + } + ) + const txReceipt = await txResponse.wait() + + console.log(`▶ send ${amount} → ${solanaReceiver}: ${txReceipt.transactionHash}`) + console.log( + `🔗 Track it: ${getLayerZeroScanLink(txReceipt.transactionHash, dstEid === EndpointId.SOLANA_V2_TESTNET)}` + ) +} + +task('send', 'Send USDE via LayerZero') + .addParam('dstEid', 'Destination endpoint ID', undefined, types.int, false) + .addParam('amount', 'Amount to send (in LD)', undefined, types.string, false) + .addParam('solanaReceiver', 'Solana receiver (base58)', undefined, types.string, false) + .addParam('composer', 'Solana composer (base58)', undefined, types.string, false) + .addParam('minAmount', 'Minimum amount to receive (in LD)', undefined, types.string, false) + .addOptionalParam('contractName', 'Name of the IOFT contract interface', 'MyOFT', types.string) + .setAction(action) diff --git a/examples/oft-solana-composer-library/tasks/index.ts b/examples/oft-solana-composer-library/tasks/index.ts new file mode 100644 index 000000000..5a77bc367 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/index.ts @@ -0,0 +1,21 @@ +import './common/config.get' +import './common/wire' +import './evm/send' +import './solana/initConfig' +import './solana/createOFT' +import './solana/createOFTAdapter' +import './solana/debug' +import './solana/getRateLimits' +import './solana/retryPayload' +import './solana/sendOFT' +import './solana/setAuthority' +import './solana/updateMetadata' +import './solana/setUpdateAuthority' +import './solana/getPrioFees' +import './solana/base58' +import './solana/setInboundRateLimit' +import './solana/setOutboundRateLimit' +import './solana/getRaydiumPools' +import './solana/getKeypair' +import './solana/initComposer' +import './solana/lzCompose' diff --git a/examples/oft-solana-composer-library/tasks/solana/base58.ts b/examples/oft-solana-composer-library/tasks/solana/base58.ts new file mode 100644 index 000000000..ea7ed20a4 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/base58.ts @@ -0,0 +1,36 @@ +import assert from 'assert' +import fs from 'fs' +import path from 'path' + +import { Keypair } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' + +interface Base58FeesTaskArgs { + /** + * The path to the keypair file to be used. + */ + keypairFile: string +} + +assert(process.env.HOME != undefined, 'process.env.HOME needs to be defined') + +const defaultKeypairFile = path.resolve(process.env.HOME, '.config/solana/id.json') + +task('lz:solana:base-58', 'Outputs the base58 string for a keypair') + .addParam( + 'keypairFile', + 'The path to the keypair file to be used. Defaults to ~/.config/solana/id.json', + defaultKeypairFile, + devtoolsTypes.string + ) + .setAction(async ({ keypairFile }: Base58FeesTaskArgs) => { + assert(fs.existsSync(keypairFile), `Keypair file not found: ${keypairFile}`) + const data = fs.readFileSync(keypairFile, 'utf8') + const keypairJson = JSON.parse(data) + const keypair = Keypair.fromSecretKey(Uint8Array.from(keypairJson)) + const base58EncodedPrivateKey = bs58.encode(keypair.secretKey) + console.log(`Base58 encoded private key: ${base58EncodedPrivateKey}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/createOFT.ts b/examples/oft-solana-composer-library/tasks/solana/createOFT.ts new file mode 100644 index 000000000..fc9187a72 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/createOFT.ts @@ -0,0 +1,339 @@ +import { + CreateV1InstructionAccounts, + CreateV1InstructionArgs, + TokenStandard, + createV1, + mintV1, +} from '@metaplex-foundation/mpl-token-metadata' +import { AuthorityType, setAuthority } from '@metaplex-foundation/mpl-toolbox' +import { + createNoopSigner, + createSignerFromKeypair, + percentAmount, + publicKey, + transactionBuilder, +} from '@metaplex-foundation/umi' +import { fromWeb3JsPublicKey, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { promptToContinue } from '@layerzerolabs/io-devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OFT_DECIMALS as DEFAULT_SHARED_DECIMALS, oft } from '@layerzerolabs/oft-v2-solana-sdk' + +import { checkMultisigSigners, createMintAuthorityMultisig } from './multisig' +import { assertAccountInitialized } from './utils' + +import { + TransactionType, + addComputeUnitInstructions, + deriveConnection, + deriveKeys, + getExplorerTxLink, + saveSolanaDeployment, +} from './index' + +const DEFAULT_LOCAL_DECIMALS = 9 + +interface CreateOFTTaskArgs { + /** + * The initial supply to mint on solana. + */ + amount: number + + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + + /** + * The number of decimal places to use for the token. + */ + localDecimals: number + + /** + * OFT shared decimals. + */ + sharedDecimals: number + + /** + * The optional token mint ID, for Mint-And-Burn-Adapter only. + */ + mint?: string + + /** + * The name of the token. + */ + name: string + + /** + * The program ID for the OFT program. + */ + programId: string + + /** + * The seller fee basis points. + */ + sellerFeeBasisPoints: number + + /** + * The symbol of the token. + */ + symbol: string + + /** + * Whether the token metadata is mutable. + */ + tokenMetadataIsMutable: boolean + + /** + * The CSV list of additional minters. + */ + additionalMinters?: string[] + + /** + * The token program ID, for Mint-And-Burn-Adapter only. + */ + tokenProgram: string + + /** + * If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in + * losing the ability to mint new tokens for everything but the OFTStore. You should really be intentional about + * using this flag, as it is not reversible. + */ + onlyOftStore: boolean + + /** + * The URI for the token metadata. + */ + uri: string + + computeUnitPriceScaleFactor: number +} + +// Define a Hardhat task for creating OFT on Solana +// * Create the SPL Multisig account for mint authority +// * Mint the new SPL Token +// * Initialize the OFT Store account +// * Set the mint authority to the multisig account. If not in only OFT Store mode, also set the freeze authority to the multisig account. +// Note: Only supports SPL Token Standard. +task('lz:oft:solana:create', 'Mints new SPL Token and creates new OFT Store account') + .addOptionalParam('amount', 'The initial supply to mint on solana', undefined, devtoolsTypes.int) + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addOptionalParam('localDecimals', 'Token local decimals (default=9)', DEFAULT_LOCAL_DECIMALS, devtoolsTypes.int) + .addOptionalParam('sharedDecimals', 'OFT shared decimals (default=6)', DEFAULT_SHARED_DECIMALS, devtoolsTypes.int) + .addParam('name', 'Token Name', 'MockOFT', devtoolsTypes.string) + .addParam('mint', 'The Token mint public key (used for MABA only)', '', devtoolsTypes.string) + .addParam('programId', 'The OFT Program id') + .addParam('sellerFeeBasisPoints', 'Seller fee basis points', 0, devtoolsTypes.int) + .addParam('symbol', 'Token Symbol', 'MOFT', devtoolsTypes.string) + .addParam('tokenMetadataIsMutable', 'Token metadata is mutable', true, devtoolsTypes.boolean) + .addParam('additionalMinters', 'Comma-separated list of additional minters', undefined, devtoolsTypes.csv, true) + .addOptionalParam( + 'onlyOftStore', + 'If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in losing the ability to mint new tokens by everything but the OFTStore.', + false, + devtoolsTypes.boolean + ) + .addParam( + 'tokenProgram', + 'The Token Program public key (used for MABA only)', + TOKEN_PROGRAM_ID.toBase58(), + devtoolsTypes.string + ) + .addParam('uri', 'URI for token metadata', '', devtoolsTypes.string) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction( + async ({ + amount, + eid, + localDecimals: decimals, + sharedDecimals, + mint: mintStr, + name, + programId: programIdStr, + sellerFeeBasisPoints, + symbol, + tokenMetadataIsMutable: isMutable, + additionalMinters: additionalMintersAsStrings, + onlyOftStore, + tokenProgram: tokenProgramStr, + uri, + computeUnitPriceScaleFactor, + }: CreateOFTTaskArgs) => { + const isMABA = !!mintStr // the difference between MABA and OFT Adapter is that MABA uses mint/burn mechanism whereas OFT Adapter uses lock/unlock mechanism + if (tokenProgramStr !== TOKEN_PROGRAM_ID.toBase58() && !isMABA) { + throw new Error('Non-Mint-And-Burn-Adapter does not support custom token programs') + } + if (isMABA && amount) { + throw new Error('Mint-And-Burn-Adapter does not support minting tokens') + } + if (decimals < sharedDecimals) { + throw new Error('Solana token local decimals must be greater than or equal to OFT shared decimals') + } + const tokenProgramId = publicKey(tokenProgramStr) + const { connection, umi, umiWalletKeyPair, umiWalletSigner } = await deriveConnection(eid) + const { programId, lockBox, escrowPK, oftStorePda, eddsa } = deriveKeys(programIdStr) + if (!additionalMintersAsStrings) { + if (!onlyOftStore) { + throw new Error( + 'If you want to proceed with only the OFT Store having the ability to mint, please specify --only-oft-store true. Note that this also means the Freeze Authority will be immediately renounced.' + ) + } + } + + if (onlyOftStore) { + const continueWithOnlyOftStore = await promptToContinue( + 'You have chosen `--only-oft-store true`. This means that only the OFT Store will be able to mint new tokens and that the Freeze Authority will be immediately renounced. Continue?' + ) + if (!continueWithOnlyOftStore) { + return + } + } + + const additionalMinters = additionalMintersAsStrings?.map((minter) => new PublicKey(minter)) ?? [] + + let mintAuthorityPublicKey: PublicKey = toWeb3JsPublicKey(oftStorePda) // we default to the OFT Store as the Mint Authority when there are no additional minters + + if (additionalMintersAsStrings) { + // we only need a multisig when we have additional minters + mintAuthorityPublicKey = await createMintAuthorityMultisig( + connection, + umi, + eid, + umiWalletSigner, + toWeb3JsPublicKey(oftStorePda), + toWeb3JsPublicKey(tokenProgramId), // Only configurable for MABA + additionalMinters, + computeUnitPriceScaleFactor + ) + console.log(`created SPL multisig @ ${mintAuthorityPublicKey.toBase58()}`) + await checkMultisigSigners(connection, mintAuthorityPublicKey, [ + toWeb3JsPublicKey(oftStorePda), + ...additionalMinters, + ]) + } + + const mint = isMABA + ? createNoopSigner(publicKey(mintStr)) + : createSignerFromKeypair(umi, eddsa.generateKeypair()) + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + if (!isMABA) { + const createV1Args: CreateV1InstructionAccounts & CreateV1InstructionArgs = { + mint, + name, + symbol, + decimals, + uri, + isMutable, + sellerFeeBasisPoints: percentAmount(sellerFeeBasisPoints), + authority: umiWalletSigner, // authority is transferred later + tokenStandard: TokenStandard.Fungible, + } + let txBuilder = transactionBuilder().add(createV1(umi, createV1Args)) + if (amount) { + // recreate txBuilder since it is immutable + txBuilder = transactionBuilder() + .add(txBuilder) + .add( + mintV1(umi, { + ...createV1Args, + mint: publicKey(createV1Args.mint), + authority: umiWalletSigner, + amount, + tokenOwner: umiWalletSigner.publicKey, + tokenStandard: TokenStandard.Fungible, + }) + ) + } + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.CreateToken + ) + const createTokenTx = await txBuilder.sendAndConfirm(umi) + await assertAccountInitialized(connection, toWeb3JsPublicKey(mint.publicKey)) + console.log(`createTokenTx: ${getExplorerTxLink(bs58.encode(createTokenTx.signature), isTestnet)}`) + } + + const lockboxSigner = createSignerFromKeypair({ eddsa: eddsa }, lockBox) + let txBuilder = transactionBuilder().add( + oft.initOft( + { + payer: umiWalletSigner, + admin: umiWalletKeyPair.publicKey, + mint: mint.publicKey, + escrow: lockboxSigner, + }, + oft.types.OFTType.Native, + sharedDecimals, + { + oft: programId, + token: tokenProgramId, + } + ) + ) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.InitOft + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log(`initOftTx: ${getExplorerTxLink(bs58.encode(signature), isTestnet)}`) + + if (!isMABA) { + let txBuilder = transactionBuilder() + .add( + setAuthority(umi, { + owned: mint.publicKey, + owner: umiWalletSigner, + newAuthority: fromWeb3JsPublicKey(mintAuthorityPublicKey), + authorityType: AuthorityType.MintTokens, + }) + ) + .add( + setAuthority(umi, { + owned: mint.publicKey, + owner: umiWalletSigner, + newAuthority: onlyOftStore ? null : fromWeb3JsPublicKey(mintAuthorityPublicKey), + authorityType: AuthorityType.FreezeAccount, + }) + ) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.SetAuthority + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log(`setAuthorityTx: ${getExplorerTxLink(bs58.encode(signature), isTestnet)}`) + } + if (isMABA) { + console.log( + `Please note that for MABA mode, you must carry out the change of Mint Authority before making any cross-chain transfers. For more details: https://github.com/LayerZero-Labs/devtools/tree/main/examples/oft-solana#for-oft-mint-and-burn-adapter-maba` + ) + } + saveSolanaDeployment( + eid, + programIdStr, + mint.publicKey, + mintAuthorityPublicKey.toBase58(), + escrowPK, + oftStorePda + ) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/createOFTAdapter.ts b/examples/oft-solana-composer-library/tasks/solana/createOFTAdapter.ts new file mode 100644 index 000000000..219cdf4d9 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/createOFTAdapter.ts @@ -0,0 +1,106 @@ +import { createSignerFromKeypair, publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import { TOKEN_PROGRAM_ID, getMint } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OFT_DECIMALS, oft } from '@layerzerolabs/oft-v2-solana-sdk' + +import { + TransactionType, + addComputeUnitInstructions, + deriveConnection, + deriveKeys, + getExplorerTxLink, + saveSolanaDeployment, +} from './index' + +interface CreateOFTAdapterTaskArgs { + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + + /** + * The token mint public key. + */ + mint: string + + /** + * The OFT Program id. + */ + programId: string + + /** + * The Token Program public key. + */ + tokenProgram: string + + computeUnitPriceScaleFactor: number +} + +// Define a Hardhat task for creating OFTAdapter on Solana +task('lz:oft-adapter:solana:create', 'Creates new OFT Adapter (OFT Store PDA)') + .addParam('mint', 'The Token Mint public key') + .addParam('programId', 'The OFT program ID') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('tokenProgram', 'The Token Program public key', TOKEN_PROGRAM_ID.toBase58(), devtoolsTypes.string, true) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction( + async ({ + eid, + mint: mintStr, + programId: programIdStr, + tokenProgram: tokenProgramStr, + computeUnitPriceScaleFactor, + }: CreateOFTAdapterTaskArgs) => { + const { connection, umi, umiWalletKeyPair, umiWalletSigner } = await deriveConnection(eid) + const { programId, lockBox, escrowPK, oftStorePda, eddsa } = deriveKeys(programIdStr) + + const tokenProgram = publicKey(tokenProgramStr) + const mint = publicKey(mintStr) + + const mintPDA = await getMint(connection, new PublicKey(mintStr), undefined, new PublicKey(tokenProgramStr)) + + const mintAuthority = mintPDA.mintAuthority + + let txBuilder = transactionBuilder().add( + oft.initOft( + { + payer: createSignerFromKeypair({ eddsa: eddsa }, umiWalletKeyPair), + admin: umiWalletKeyPair.publicKey, + mint: mint, + escrow: createSignerFromKeypair({ eddsa: eddsa }, lockBox), + }, + oft.types.OFTType.Adapter, + OFT_DECIMALS, + { + oft: programId, + token: tokenProgram ? publicKey(tokenProgram) : undefined, + } + ) + ) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.InitOft + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log(`initOftTx: ${getExplorerTxLink(bs58.encode(signature), eid == EndpointId.SOLANA_V2_TESTNET)}`) + + saveSolanaDeployment( + eid, + programIdStr, + mint, + mintAuthority ? mintAuthority.toBase58() : '', + escrowPK, + oftStorePda + ) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/debug.ts b/examples/oft-solana-composer-library/tasks/solana/debug.ts new file mode 100644 index 000000000..a2a50c613 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/debug.ts @@ -0,0 +1,281 @@ +import { fetchMint } from '@metaplex-foundation/mpl-toolbox' +import { publicKey, unwrapOption } from '@metaplex-foundation/umi' +import { toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Keypair, PublicKey } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import { OmniPoint, denormalizePeer } from '@layerzerolabs/devtools' +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId, getNetworkForChainId } from '@layerzerolabs/lz-definitions' +import { EndpointPDADeriver, EndpointProgram } from '@layerzerolabs/lz-solana-sdk-v2' +import { OftPDA, oft } from '@layerzerolabs/oft-v2-solana-sdk' +import { EndpointV2 } from '@layerzerolabs/protocol-devtools-solana' + +import { getSolanaReceiveConfig, getSolanaSendConfig } from '../common/taskHelper' +import { DebugLogger, createSolanaConnectionFactory, decodeLzReceiveOptions, uint8ArrayToHex } from '../common/utils' + +import { deriveConnection, getSolanaDeployment } from './index' + +const DEBUG_ACTIONS = { + OFT_STORE: 'oft-store', + GET_ADMIN: 'admin', + GET_DELEGATE: 'delegate', + CHECKS: 'checks', + GET_TOKEN: 'token', + GET_PEERS: 'peers', +} + +/** + * Get the OFTStore account from the task arguments, the deployment file, or throw an error. + * @param {EndpointId} eid + * @param {string} oftStore + */ +const getOftStore = (eid: EndpointId, oftStore?: string) => publicKey(oftStore ?? getSolanaDeployment(eid).oftStore) + +type DebugTaskArgs = { + eid: EndpointId + oftStore?: string + endpoint: string + dstEids: EndpointId[] + action?: string +} + +task('lz:oft:solana:debug', 'Manages OFTStore and OAppRegistry information') + .addParam( + 'eid', + 'Solana mainnet (30168) or testnet (40168). Defaults to mainnet.', + EndpointId.SOLANA_V2_MAINNET, + types.eid + ) + .addParam( + 'oftStore', + 'The OFTStore public key. Derived from deployments if not provided.', + undefined, + types.string, + true + ) + .addParam('endpoint', 'The Endpoint public key', EndpointProgram.PROGRAM_ID.toBase58(), types.string) + .addOptionalParam('dstEids', 'Destination eids to check (comma-separated list)', [], types.csv) + .addOptionalParam( + 'action', + `The action to perform: ${Object.keys(DEBUG_ACTIONS).join(', ')} (defaults to all)`, + undefined, + types.string + ) + .setAction(async (taskArgs: DebugTaskArgs) => { + const { eid, oftStore: oftStoreArg, endpoint, dstEids, action } = taskArgs + const { umi, connection } = await deriveConnection(eid, true) + const oftStore = getOftStore(eid, oftStoreArg) + + let oftStoreInfo + try { + oftStoreInfo = await oft.accounts.fetchOFTStore(umi, oftStore) + } catch (e) { + console.error(`Failed to fetch OFTStore at ${oftStore.toString()}:`, e) + return + } + + const mintAccount = await fetchMint(umi, publicKey(oftStoreInfo.tokenMint)) + + const epDeriver = new EndpointPDADeriver(new PublicKey(endpoint)) + const [oAppRegistry] = epDeriver.oappRegistry(toWeb3JsPublicKey(oftStore)) + const oAppRegistryInfo = await EndpointProgram.accounts.OAppRegistry.fromAccountAddress( + connection, + oAppRegistry + ) + + if (!oAppRegistryInfo) { + console.warn('OAppRegistry info not found.') + return + } + + const oftDeriver = new OftPDA(oftStoreInfo.header.owner) + + const printOftStore = async () => { + DebugLogger.header('OFT Store Information') + DebugLogger.keyValue('Owner', oftStoreInfo.header.owner) + DebugLogger.keyValue('OFT Type', oft.types.OFTType[oftStoreInfo.oftType]) + DebugLogger.keyValue('Admin', oftStoreInfo.admin) + DebugLogger.keyValue('Token Mint', oftStoreInfo.tokenMint) + DebugLogger.keyValue('Token Escrow', oftStoreInfo.tokenEscrow) + DebugLogger.keyValue('Endpoint Program', oftStoreInfo.endpointProgram) + DebugLogger.separator() + } + + const printAdmin = async () => { + const admin = oftStoreInfo.admin + DebugLogger.keyValue('Admin', admin) + } + + const printDelegate = async () => { + const delegate = oAppRegistryInfo?.delegate?.toBase58() + DebugLogger.header('OApp Registry Information') + DebugLogger.keyValue('Delegate', delegate) + DebugLogger.separator() + } + + const printToken = async () => { + DebugLogger.header('Token Information') + DebugLogger.keyValue('Mint Authority', unwrapOption(mintAccount.mintAuthority)) + DebugLogger.keyValue( + 'Freeze Authority', + unwrapOption(mintAccount.freezeAuthority, () => 'None') + ) + DebugLogger.separator() + } + + const printChecks = async () => { + const delegate = oAppRegistryInfo?.delegate?.toBase58() + + DebugLogger.header('Checks') + DebugLogger.keyValue('Admin (Owner) same as Delegate', oftStoreInfo.admin === delegate) + DebugLogger.keyValue( + 'Token Mint Authority is OFT Store', + unwrapOption(mintAccount.mintAuthority) === oftStore + ) + DebugLogger.separator() + } + + const printPeerConfigs = async () => { + const peerConfigs = dstEids.map((dstEid) => { + const peerConfig = oftDeriver.peer(oftStore, dstEid) + return publicKey(peerConfig) + }) + const mockKeypair = new Keypair() + const point: OmniPoint = { + eid, + address: oftStore.toString(), + } + const endpointV2Sdk = new EndpointV2( + await createSolanaConnectionFactory()(eid), + point, + mockKeypair.publicKey // doesn't matter as we are not sending transactions + ) + + DebugLogger.header('Peer Configurations') + + const peerConfigInfos = await oft.accounts.safeFetchAllPeerConfig(umi, peerConfigs) + for (let index = 0; index < dstEids.length; index++) { + const dstEid = dstEids[index] + const info = peerConfigInfos[index] + const network = getNetworkForChainId(dstEid) + const oAppReceiveConfig = await getSolanaReceiveConfig(endpointV2Sdk, dstEid, oftStore) + const oAppSendConfig = await getSolanaSendConfig(endpointV2Sdk, dstEid, oftStore) + + // Show the chain info + DebugLogger.header(`${dstEid} (${network.chainName})`) + + if (info) { + // Existing PeerConfig info + DebugLogger.keyValue('PeerConfig Account', peerConfigs[index].toString()) + DebugLogger.keyValue('Peer Address', denormalizePeer(info.peerAddress, dstEid)) + DebugLogger.keyHeader('Enforced Options') + DebugLogger.keyValue( + 'Send', + decodeLzReceiveOptions(uint8ArrayToHex(info.enforcedOptions.send, true)), + 2 + ) + DebugLogger.keyValue( + 'SendAndCall', + decodeLzReceiveOptions(uint8ArrayToHex(info.enforcedOptions.sendAndCall, true)), + 2 + ) + + printOAppReceiveConfigs(oAppReceiveConfig, network.chainName) + printOAppSendConfigs(oAppSendConfig, network.chainName) + } else { + // No PeerConfig account + console.log(`No PeerConfig account found for ${dstEid} (${network.chainName}).`) + } + + DebugLogger.separator() + } + } + if (action) { + switch (action) { + case DEBUG_ACTIONS.OFT_STORE: + await printOftStore() + break + case DEBUG_ACTIONS.GET_ADMIN: + await printAdmin() + break + case DEBUG_ACTIONS.GET_DELEGATE: + await printDelegate() + break + case DEBUG_ACTIONS.CHECKS: + await printChecks() + break + case DEBUG_ACTIONS.GET_TOKEN: + await printToken() + break + case DEBUG_ACTIONS.GET_PEERS: + await printPeerConfigs() + break + default: + console.error(`Invalid action specified. Use any of ${Object.keys(DEBUG_ACTIONS)}.`) + } + } else { + await printOftStore() + await printDelegate() + await printToken() + if (dstEids.length > 0) await printPeerConfigs() + await printChecks() + } + }) + +function printOAppReceiveConfigs( + oAppReceiveConfig: Awaited>, + peerChainName: string +) { + const oAppReceiveConfigIndexesToKeys: Record = { + 0: 'receiveLibrary', + 1: 'receiveUlnConfig', + 2: 'receiveLibraryTimeoutConfig', + } + + if (!oAppReceiveConfig) { + console.log('No receive configs found.') + return + } + + DebugLogger.keyValue(`Receive Configs (${peerChainName} to solana)`, '') + for (let i = 0; i < oAppReceiveConfig.length; i++) { + const item = oAppReceiveConfig[i] + if (typeof item === 'object' && item !== null) { + // Print each property in the object + DebugLogger.keyValue(`${oAppReceiveConfigIndexesToKeys[i]}`, '', 2) + for (const [propKey, propVal] of Object.entries(item)) { + DebugLogger.keyValue(`${propKey}`, String(propVal), 3) + } + } else { + // Print a primitive (string, number, etc.) + DebugLogger.keyValue(`${oAppReceiveConfigIndexesToKeys[i]}`, String(item), 2) + } + } +} + +function printOAppSendConfigs(oAppSendConfig: Awaited>, peerChainName: string) { + const sendOappConfigIndexesToKeys: Record = { + 0: 'sendLibrary', + 1: 'sendUlnConfig', + 2: 'sendExecutorConfig', + } + + if (!oAppSendConfig) { + console.log('No send configs found.') + return + } + + DebugLogger.keyValue(`Send Configs (solana to ${peerChainName})`, '') + for (let i = 0; i < oAppSendConfig.length; i++) { + const item = oAppSendConfig[i] + if (typeof item === 'object' && item !== null) { + DebugLogger.keyValue(`${sendOappConfigIndexesToKeys[i]}`, '', 2) + for (const [propKey, propVal] of Object.entries(item)) { + DebugLogger.keyValue(`${propKey}`, String(propVal), 3) + } + } else { + DebugLogger.keyValue(`${sendOappConfigIndexesToKeys[i]}`, String(item), 2) + } + } +} diff --git a/examples/oft-solana-composer-library/tasks/solana/getKeypair.ts b/examples/oft-solana-composer-library/tasks/solana/getKeypair.ts new file mode 100644 index 000000000..b81af8eb7 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getKeypair.ts @@ -0,0 +1,16 @@ +import fs from 'fs' + +import bs58 from 'bs58' +import { task, types } from 'hardhat/config' +/** + * New helper task: decode a base58‐encoded Solana secret key into its raw byte array. + */ +task('decode-key', 'Decode a Base58‐encoded Solana secret key and print the raw byte array as JSON') + .addParam('secret', 'Base58-encoded secret key', undefined, types.string) + .addOptionalParam('out', 'Output file path', 'decoded-key.json', types.string) + .setAction(async ({ secret, out }: { secret: string; out: string }) => { + const bytes = bs58.decode(secret) + const arr = Array.from(bytes) + fs.writeFileSync(out, JSON.stringify(arr, null, 2)) + console.log(`Wrote ${arr.length} bytes to ${out}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/getPrioFees.ts b/examples/oft-solana-composer-library/tasks/solana/getPrioFees.ts new file mode 100644 index 000000000..d3dff02c8 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getPrioFees.ts @@ -0,0 +1,33 @@ +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import getPrioritizationFees from '../utils/getFee' + +import { deriveConnection } from './index' + +interface GetPrioFeesTaskArgs { + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + /** + * The program ID or account address that will be written to. + */ + address: string +} + +task('lz:solana:get-priority-fees', 'Fetches prioritization fees from the Solana network') + .addParam('eid', 'The endpoint ID for the Solana network', undefined, devtoolsTypes.eid) + .addOptionalParam( + 'address', + 'The address (program ID or account address) that will be written to', + undefined, + devtoolsTypes.string + ) + .setAction(async ({ eid, address }: GetPrioFeesTaskArgs) => { + const { connection } = await deriveConnection(eid) + const fees = await getPrioritizationFees(connection, address) + console.log('Prioritization Fees:', fees) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/getRateLimits.ts b/examples/oft-solana-composer-library/tasks/solana/getRateLimits.ts new file mode 100644 index 000000000..4448ad671 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getRateLimits.ts @@ -0,0 +1,35 @@ +import { mplToolbox } from '@metaplex-foundation/mpl-toolbox' +import { publicKey } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA, accounts } from '@layerzerolabs/oft-v2-solana-sdk' + +import { createSolanaConnectionFactory } from '../common/utils' + +interface Args { + mint: string + eid: EndpointId + dstEid: EndpointId + programId: string + oftStore: string +} + +task('lz:oft:solana:get-rate-limits', 'Gets the Solana inbound / outbound rate limits') + .addParam('mint', 'The OFT token mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, types.eid) + .addParam('dstEid', 'The destination endpoint ID', undefined, types.eid) + .addParam('oftStore', 'The OFTStore account') + .setAction(async (taskArgs: Args, _) => { + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + + const [peer] = new OftPDA(publicKey(taskArgs.programId)).peer(publicKey(taskArgs.oftStore), taskArgs.dstEid) + const peerInfo = await accounts.fetchPeerConfig({ rpc: umi.rpc }, peer) + console.log(`Peer info between ${taskArgs.eid} and ${taskArgs.dstEid}`) + console.dir({ peerInfo }, { depth: null }) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts b/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts new file mode 100644 index 000000000..8434704dd --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/getRaydiumPools.ts @@ -0,0 +1,106 @@ +import { + Raydium, + TICK_ARRAY_SIZE, + TickUtils, + getPdaAmmConfigId, + getPdaExBitmapAccount, + getPdaTickArrayAddress, +} from '@raydium-io/raydium-sdk-v2' +import { PublicKey } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' + +import { deriveConnection } from './index' // <— your helper + +import type { EndpointId } from '@layerzerolabs/lz-definitions' + +interface GetPoolsTaskArgs { + eid: EndpointId + mint1: string + mint2: string +} + +task('lz:raydium:get-pools', 'Fetches Raydium CLMM pool PDAs') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('mint1', 'First mint address', undefined, devtoolsTypes.string) + .addParam('mint2', 'Second mint address', undefined, devtoolsTypes.string) + .setAction(async ({ eid, mint1, mint2 }: GetPoolsTaskArgs) => { + // 1) Derive a connection + wallet (Keypair) for the given endpoint + const { connection } = await deriveConnection(eid) + + // 2) Load the Raydium SDK (defaults to mainnet for fetchPoolByMints, but uses your connection URL) + const raydium = await Raydium.load({ + connection, + disableLoadToken: true, + }) + + // 3) Find the pool matching your two mints + const { data: pools } = await raydium.api.fetchPoolByMints({ + mint1: new PublicKey(mint1), + mint2: new PublicKey(mint2), + }) + if (pools.length === 0) { + console.error('❌ No Raydium CLMM pool found for these mints') + return + } + const poolId = pools[0].id + console.log('→ Found Pool ID:', poolId) + + // 4) Fetch the on-chain state AND all the PDAs (tick arrays, vaults, etc.) in one RPC call + const { poolInfo, poolKeys } = await raydium.clmm.getPoolInfoFromRpc(poolId) + + // poolKeys.id is the Pool State PDA + const poolStatePda = new PublicKey(poolKeys.id) + + // poolInfo.programId (or poolKeys.programId in the raw on-chain output) is the CLMM program + const clmmProgramId = new PublicKey(poolInfo.programId) + + // Derive the authority PDA + const [authorityPda] = PublicKey.findProgramAddressSync([poolStatePda.toBuffer()], clmmProgramId) + + // derive the amm config PDA + const ammConfigPda = getPdaAmmConfigId(clmmProgramId, poolInfo.config.index).publicKey + + // derive the tick-array–bitmap PDA + bump + const { publicKey: tickBitmapPda, nonce: tickBitmapBump } = getPdaExBitmapAccount(clmmProgramId, poolStatePda) + + console.log('Pool Info:', JSON.stringify(poolInfo, null, 2)) + + // 2) extract tick info + const { + config: { tickSpacing }, + } = poolInfo + const tickCurrent = (poolInfo as any).tickCurrent + const centralStart = TickUtils.getTickArrayStartIndexByTick(tickCurrent, tickSpacing) + const fullWidth = tickSpacing * TICK_ARRAY_SIZE + + // 3) derive lower & upper + const lowerIndex = centralStart - fullWidth + const upperIndex = centralStart + fullWidth + const tickLowerPDA = getPdaTickArrayAddress(clmmProgramId, poolStatePda, lowerIndex).publicKey + const tickUpperPDA = getPdaTickArrayAddress(clmmProgramId, poolStatePda, upperIndex).publicKey + const tickCurrentPDA = getPdaTickArrayAddress(clmmProgramId, poolStatePda, centralStart).publicKey + + // …after you do: + // const { poolInfo, poolKeys } = await raydium.clmm.getPoolInfoFromRpc(poolId); + // 5) Print them out + console.log('\n🔑 Raydium CLMM PDAs:') + console.log(' Pool Program ID: ', poolInfo.programId) + console.log(' Pool ID: ', poolId) + console.log(' Pool State PDA: ', poolStatePda) + console.log(' AMM Config PDA: ', ammConfigPda) + console.log(' Observation PDA: ', poolKeys.observationId) + console.log(' Tick Arrays:') + console.log(' Tick Lower PDA: ', tickLowerPDA) + console.log(' Tick Current PDA: ', tickCurrentPDA) + console.log(' Tick Upper PDA: ', tickUpperPDA) + console.log('tick bitmap PDA:', tickBitmapPda.toBase58(), 'bump:', tickBitmapBump) + console.log('tickBitmapPda', tickBitmapPda.toString()) + // for (const [startTick, addr] of Object.entries(poolKeys.tickArrays)) { + // console.log(` @ startTick=${startTick}: ${addr.toBase58()}`); + // } + console.log(' Vault A PDA: ', poolKeys.vault.A) + console.log(' Vault B PDA: ', poolKeys.vault.B) + console.log(' Pool Authority: ', authorityPda) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/index.ts b/examples/oft-solana-composer-library/tasks/solana/index.ts new file mode 100644 index 000000000..df3b1a58b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/index.ts @@ -0,0 +1,391 @@ +import assert from 'assert' +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import path from 'node:path' + +import { + fetchAddressLookupTable, + mplToolbox, + setComputeUnitLimit, + setComputeUnitPrice, +} from '@metaplex-foundation/mpl-toolbox' +import { + AddressLookupTableInput, + EddsaInterface, + Instruction, + KeypairSigner, + PublicKey, + TransactionBuilder, + Umi, + createSignerFromKeypair, + publicKey, + signerIdentity, + transactionBuilder, +} from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { createWeb3JsEddsa } from '@metaplex-foundation/umi-eddsa-web3js' +import { toWeb3JsInstruction, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { AddressLookupTableAccount, Connection, Keypair } from '@solana/web3.js' +import { getKeypairFromEnvironment, getKeypairFromFile, getSimulationComputeUnits } from '@solana-developers/helpers' +import { backOff } from 'exponential-backoff' + +import { formatEid } from '@layerzerolabs/devtools' +import { createLogger, promptToContinue } from '@layerzerolabs/io-devtools' +import { EndpointId, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { OftPDA } from '@layerzerolabs/oft-v2-solana-sdk' + +import { DebugLogger, KnownErrors, createSolanaConnectionFactory } from '../common/utils' +import getFee from '../utils/getFee' + +const LOOKUP_TABLE_ADDRESS: Partial> = { + [EndpointId.SOLANA_V2_MAINNET]: publicKey('AokBxha6VMLLgf97B5VYHEtqztamWmYERBmmFvjuTzJB'), + [EndpointId.SOLANA_V2_TESTNET]: publicKey('9thqPdbR27A1yLWw2spwJLySemiGMXxPnEvfmXVk4KuK'), +} + +/** + * Extracts the value of the given environment variable. + * @todo consider removing this since it's no longer used (replaced by using @solana-developers/helpers) + * @param key The name of the environment variable to extract. + * @param optional Whether the environment variable is optional or not. + * If it is, the function will return undefined if the variable is not defined. + * Otherwise, it will throw an error if the variable is not defined. + * @returns The value of the environment variable, or undefined if optional and not defined. + */ +const getFromEnv = (key: string, optional = false): string | undefined => { + const value = process.env[key] + if (!value && !optional) { + throw new Error(`${key} is not defined in the environment variables.`) + } + return value +} + +// create a safe version of getKeypairFromFile that returns undefined if the file does not exist, for checking the default keypair +async function safeGetKeypairDefaultPath(filePath?: string) { + try { + return await getKeypairFromFile(filePath) + } catch (error) { + // If the error is due to the file not existing, return undefined + if (error instanceof Error && error.message.includes('Could not read keypair')) { + return undefined + } + throw error // Rethrow if it's a different error + } +} + +// TODO in another PR: consider moving keypair related functions to tasks/solana/utils.ts +async function getSolanaKeypair(readOnly = false): Promise { + const logger = createLogger() + + // Early exit if read-only: ephemeral Keypair is enough. + if (readOnly) { + logger.info('Read-only mode: Using ephemeral (randomly generated) keypair.') + return Keypair.generate() + } + + // Attempt to load from each source + const keypairEnvPrivate = process.env.SOLANA_PRIVATE_KEY + ? getKeypairFromEnvironment('SOLANA_PRIVATE_KEY') + : undefined // #1 SOLANA_PRIVATE_KEY + const keypairEnvPath = process.env.SOLANA_KEYPAIR_PATH + ? await getKeypairFromFile(process.env.SOLANA_KEYPAIR_PATH) + : undefined // #2 SOLANA_KEYPAIR_PATH + const keypairDefaultPath = await safeGetKeypairDefaultPath() // #3 ~/.config/solana/id.json + + // Throw if no keypair is found via all 3 methods + if (!keypairEnvPrivate && !keypairEnvPath && !keypairDefaultPath) { + throw new Error( + 'No Solana keypair found. Provide SOLANA_PRIVATE_KEY, ' + + 'SOLANA_KEYPAIR_PATH, or place a valid keypair at ~/.config/solana/id.json.' + ) + } + + // If both environment-based keys exist, ensure they match + if (keypairEnvPrivate && keypairEnvPath) { + if (keypairEnvPrivate.publicKey.equals(keypairEnvPath.publicKey)) { + logger.info('Both SOLANA_PRIVATE_KEY and SOLANA_KEYPAIR_PATH match. Using environment-based keypair.') + return keypairEnvPrivate + } else { + throw new Error( + `Conflict: SOLANA_PRIVATE_KEY and SOLANA_KEYPAIR_PATH are different keypairs.\n` + + `Path: ${process.env.SOLANA_KEYPAIR_PATH} => ${keypairEnvPath.publicKey.toBase58()}\n` + + `Env : ${keypairEnvPrivate.publicKey.toBase58()}` + ) + } + } + + // If exactly one environment-based keypair is found, use it immediately + if (keypairEnvPrivate) { + logger.info(`Using Solana keypair from SOLANA_PRIVATE_KEY => ${keypairEnvPrivate.publicKey.toBase58()}`) + return keypairEnvPrivate + } + + if (keypairEnvPath) { + logger.info( + `Using Solana keypair from SOLANA_KEYPAIR_PATH (${process.env.SOLANA_KEYPAIR_PATH}) => ${keypairEnvPath.publicKey.toBase58()}` + ) + return keypairEnvPath + } + + // Otherwise, default path is the last fallback + logger.info( + `No environment-based keypair found. Found keypair at default path => ${keypairDefaultPath.publicKey.toBase58()}` + ) + const doContinue = await promptToContinue( + `Defaulting to ~/.config/solana/id.json with address ${keypairDefaultPath.publicKey.toBase58()}. Use this keypair?` + ) + if (!doContinue) process.exit(1) + + return keypairDefaultPath +} + +/** + * Derive common connection and UMI objects for a given endpoint ID. + * @param eid {EndpointId} + */ +export const deriveConnection = async (eid: EndpointId, readOnly = false) => { + const keypair = await getSolanaKeypair(readOnly) + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(eid) + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletKeyPair = umi.eddsa.createKeypairFromSecretKey(keypair.secretKey) + const umiWalletSigner = createSignerFromKeypair(umi, umiWalletKeyPair) + umi.use(signerIdentity(umiWalletSigner)) + return { + connection, + umi, + umiWalletKeyPair, + umiWalletSigner, + } +} + +export const useWeb3Js = async () => { + // note: if we are okay with exporting getSolanaKeypair, then useWeb3js can be removed + const keypair = await getSolanaKeypair() + return { + web3JsKeypair: keypair, + } +} + +/** + * Derive the keys needed for the OFT program. + * @param programIdStr {string} + */ +export const deriveKeys = (programIdStr: string) => { + const programId = publicKey(programIdStr) + const eddsa: EddsaInterface = createWeb3JsEddsa() + const oftDeriver = new OftPDA(programId) + const lockBox = eddsa.generateKeypair() + const escrowPK = lockBox.publicKey + const [oftStorePda] = oftDeriver.oftStore(escrowPK) + return { + programId, + lockBox, + escrowPK, + oftStorePda, + eddsa, + } +} + +/** + * Outputs the OFT accounts to a JSON file. + * @param eid {EndpointId} + * @param programId {string} + * @param mint {string} + * @param mintAuthority {string} + * @param escrow {string} + * @param oftStore {string} + */ +export const saveSolanaDeployment = ( + eid: EndpointId, + programId: string, + mint: string, + mintAuthority: string, + escrow: string, + oftStore: string +) => { + const outputDir = `./deployments/${endpointIdToNetwork(eid)}` + if (!existsSync(outputDir)) { + mkdirSync(outputDir, { recursive: true }) + } + writeFileSync( + `${outputDir}/OFT.json`, + JSON.stringify( + { + programId, + mint, + mintAuthority, + escrow, + oftStore, + }, + null, + 4 + ) + ) + console.log(`Accounts have been saved to ${outputDir}/OFT.json`) +} + +/** + * Reads the OFT deployment info from disk for the given endpoint ID. + * @param eid {EndpointId} + * @returns The contents of the OFT.json file as a JSON object. + */ +export const getSolanaDeployment = ( + eid: EndpointId +): { + programId: string + mint: string + mintAuthority: string + escrow: string + oftStore: string +} => { + if (!eid) { + throw new Error('eid is required') + } + const outputDir = path.join('deployments', endpointIdToNetwork(eid)) + const filePath = path.join(outputDir, 'OFT.json') // Note: if you have multiple deployments, change this filename to refer to the desired deployment file + + if (!existsSync(filePath)) { + DebugLogger.printErrorAndFixSuggestion(KnownErrors.SOLANA_DEPLOYMENT_NOT_FOUND) + throw new Error(`Could not find Solana deployment file for eid ${eid} at: ${filePath}`) + } + + const fileContents = readFileSync(filePath, 'utf-8') + return JSON.parse(fileContents) +} + +export const getOftStoreAddress = (eid: EndpointId) => { + const { oftStore } = getSolanaDeployment(eid) + if (!oftStore) { + throw new Error('oftStore not defined in the deployment file') + } + return oftStore +} + +// TODO: move below outside of solana folder since it's generic +export const getLayerZeroScanLink = (hash: string, isTestnet = false) => + isTestnet ? `https://testnet.layerzeroscan.com/tx/${hash}` : `https://layerzeroscan.com/tx/${hash}` + +export const getExplorerTxLink = (hash: string, isTestnet = false) => + `https://solscan.io/tx/${hash}?cluster=${isTestnet ? 'devnet' : 'mainnet-beta'}` + +export const getAddressLookupTable = async (connection: Connection, umi: Umi, fromEid: EndpointId) => { + // Lookup Table Address and Priority Fee Calculation + const lookupTableAddress = LOOKUP_TABLE_ADDRESS[fromEid] + assert(lookupTableAddress != null, `No lookup table found for ${formatEid(fromEid)}`) + const addressLookupTableInput: AddressLookupTableInput = await fetchAddressLookupTable(umi, lookupTableAddress) + if (!addressLookupTableInput) { + throw new Error(`No address lookup table found for ${lookupTableAddress}`) + } + const { value: lookupTableAccount } = await connection.getAddressLookupTable(toWeb3JsPublicKey(lookupTableAddress)) + if (!lookupTableAccount) { + throw new Error(`No address lookup table account found for ${lookupTableAddress}`) + } + return { + lookupTableAddress, + addressLookupTableInput, + lookupTableAccount, + } +} + +export enum TransactionType { + CreateToken = 'CreateToken', + CreateMultisig = 'CreateMultisig', + InitOft = 'InitOft', + SetAuthority = 'SetAuthority', + InitConfig = 'InitConfig', + SendOFT = 'SendOFT', +} + +const TransactionCuEstimates: Record = { + // for the sample values, they are: devnet, mainnet + [TransactionType.CreateToken]: 125_000, // actual sample: (59073, 123539), 55785 (more volatile as it has CPI to Metaplex) + [TransactionType.CreateMultisig]: 5_000, // actual sample: 3,230 + [TransactionType.InitOft]: 70_000, // actual sample: 59207, 65198 (note: this is the only transaction that createOFTAdapter does) + [TransactionType.SetAuthority]: 8_000, // actual sample: 6424, 6472 + [TransactionType.InitConfig]: 42_000, // actual sample: 33157, 40657 + [TransactionType.SendOFT]: 230_000, // actual sample: 217,784 +} + +export const getComputeUnitPriceAndLimit = async ( + connection: Connection, + ixs: Instruction[], + wallet: KeypairSigner, + lookupTableAccount: AddressLookupTableAccount, + transactionType: TransactionType +) => { + const { averageFeeExcludingZeros } = await getFee(connection) + const priorityFee = Math.round(averageFeeExcludingZeros) + const computeUnitPrice = BigInt(priorityFee) + + let computeUnits + + try { + computeUnits = await backOff( + () => + getSimulationComputeUnits( + connection, + ixs.map((ix) => toWeb3JsInstruction(ix)), + toWeb3JsPublicKey(wallet.publicKey), + [lookupTableAccount] + ), + { + maxDelay: 10000, + numOfAttempts: 3, + } + ) + } catch (e) { + console.error(`Error retrieving simulations compute units from RPC:`, e) + const continueByUsingHardcodedEstimate = await promptToContinue( + 'Failed to call simulateTransaction on the RPC. This can happen when the network is congested. Would you like to use hardcoded estimates (TransactionCuEstimates) ? This may result in slightly overpaying for the transaction.' + ) + if (!continueByUsingHardcodedEstimate) { + throw new Error( + 'Failed to call simulateTransaction on the RPC and user chose to not continue with hardcoded estimate.' + ) + } + console.log( + `Falling back to hardcoded estimate for ${transactionType}: ${TransactionCuEstimates[transactionType]} CUs` + ) + computeUnits = TransactionCuEstimates[transactionType] + } + + if (!computeUnits) { + throw new Error('Unable to compute units') + } + + return { + computeUnitPrice, + computeUnits, + } +} + +export const addComputeUnitInstructions = async ( + connection: Connection, + umi: Umi, + eid: EndpointId, + txBuilder: TransactionBuilder, + umiWalletSigner: KeypairSigner, + computeUnitPriceScaleFactor: number, + transactionType: TransactionType +) => { + const computeUnitLimitScaleFactor = 1.1 // hardcoded to 1.1 as the estimations are not perfect and can fall slightly short of the actual CU usage on-chain + const { addressLookupTableInput, lookupTableAccount } = await getAddressLookupTable(connection, umi, eid) + const { computeUnitPrice, computeUnits } = await getComputeUnitPriceAndLimit( + connection, + txBuilder.getInstructions(), + umiWalletSigner, + lookupTableAccount, + transactionType + ) + // Since transaction builders are immutable, we must be careful to always assign the result of the add and prepend + // methods to a new variable. + const newTxBuilder = transactionBuilder() + .add( + setComputeUnitPrice(umi, { + microLamports: computeUnitPrice * BigInt(Math.floor(computeUnitPriceScaleFactor)), + }) + ) + .add(setComputeUnitLimit(umi, { units: computeUnits * computeUnitLimitScaleFactor })) + .setAddressLookupTables([addressLookupTableInput]) + .add(txBuilder) + return newTxBuilder +} diff --git a/examples/oft-solana-composer-library/tasks/solana/initComposer.ts b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts new file mode 100644 index 000000000..660d85a9c --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/initComposer.ts @@ -0,0 +1,131 @@ +// tasks/initComposer.ts +import fs from 'fs' + +import * as anchor from '@coral-xyz/anchor' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getOrCreateAssociatedTokenAccount, +} from '@solana/spl-token' +import { Connection, Keypair, PublicKey, SystemProgram, TransactionSignature } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import idl from '../../target/idl/composer.json' + +task('lz:solana:init-composer', 'Initialize the Composer PDA on Solana') + .addOptionalParam( + 'rpcUrl', + 'Solana JSON RPC endpoint', + 'https://mainnet.helius-rpc.com/?api-key=78552846-acd7-40df-8f1c-79439387be5a' + ) + .setAction(async ({ rpcUrl }) => { + // ─── CONFIG ──────────────────────────────────────────────────────────────── + const COMPOSER_ID = new PublicKey('6xhpdhwyzpjxc6n2KqQS8a3W7Busn6nqGYaobVV25kN5') + const OFT_PDA = new PublicKey('4x3oQtX4MhjTKGBeXDZbtTSL9cUWo5waN2UChAuthtS') + const ENDPOINT_PDA = new PublicKey('2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3') + const ENDPOINT_PROGRAM_ID = new PublicKey('76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6') + const CLMM_PROGRAM_ID = new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK') + const RECEIVER_PDA = new PublicKey('6tzUZqC33igPgP7YyDnUxQg6eupMmZGRGKdVAksgRzvk') + + const AMM_CONFIG_PDA = new PublicKey('9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x') + const POOL_STATE_PDA = new PublicKey('D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL') + const OBSERVATION_PDA = new PublicKey('Cv3s57YQzJRp988qvXPfZN5xX7BUFcgB1ycA9jzSrh2n') + const TICK_LOWER_PDA = new PublicKey('J1jEbmdbtsfA26igi1ryQjejnYVQRpxnobZh9tgSaH1h') + const TICK_CURRENT_PDA = new PublicKey('8AQHmKsoFh5Uh8bzYnnQ4Fx2QL2axPao9hjnt1YQBvdS') + const TICK_UPPER_PDA = new PublicKey('3ZUDGLF7xw26gcXUhZK3tL4MU2VizCUkXoHdb2LQ4zDM') + const TICK_BITMAP_PDA = new PublicKey('FHJHGbVtNoJdM5CSqPMQH8mn8D8pGfcN4JmLNqNMPBQu') + + const INPUT_VAULT_PDA = new PublicKey('5Nb7bdyi4jkMdA8Xqmt72HS1fpkGRQ573Gf8PJeGunsy') + const OUTPUT_VAULT_PDA = new PublicKey('sUfgz2jsDhN8qn1PrkWr9pVnUrLqqL1yB7WAnCj62Xz') + + const USDE_MINT = new PublicKey('DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT') + const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') + // ──────────────────────────────────────────────────────────────────────────── + + // 1) Set up Anchor + wallet + const connection = new Connection(rpcUrl, 'confirmed') + const keypair = Keypair.fromSecretKey(new Uint8Array(JSON.parse(fs.readFileSync('./wallet.json', 'utf-8')))) + const wallet = new anchor.Wallet(keypair) + const provider = new anchor.AnchorProvider(connection, wallet, { + commitment: 'confirmed', + }) + anchor.setProvider(provider) + + // 2) Load your on-chain program + const program = new anchor.Program(idl as anchor.Idl, COMPOSER_ID, provider) + + // 3) Derive the Composer PDA + const [composerPda] = PublicKey.findProgramAddressSync( + [Buffer.from('Composer'), OFT_PDA.toBuffer()], + COMPOSER_ID + ) + console.log('⛓ composer PDA:', composerPda.toBase58()) + + const [typesPda] = PublicKey.findProgramAddressSync( + [Buffer.from('LzComposeTypes'), composerPda.toBuffer()], + COMPOSER_ID + ) + console.log('⛓ types PDA:', typesPda.toBase58()) + + // 4) Ensure Composer has ATAs for A & B mints + const composerUsdeAta = await getOrCreateAssociatedTokenAccount( + connection, + keypair, + USDE_MINT, + composerPda, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ) + const composerUsdcAta = await getOrCreateAssociatedTokenAccount( + connection, + keypair, + USDC_MINT, + RECEIVER_PDA, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ) + console.log('✅ Composer ATAs:', { + usde: composerUsdeAta.address.toBase58(), + usdc: composerUsdcAta.address.toBase58(), + }) + + // 5) Fire the initComposer RPC + const sig: TransactionSignature = await program.methods + .initComposer({ + oftPda: OFT_PDA, + endpointPda: ENDPOINT_PDA, + endpointProgram: ENDPOINT_PROGRAM_ID, + tokenProgram: TOKEN_PROGRAM_ID, + tokenProgram2022: TOKEN_2022_PROGRAM_ID, + clmmProgram: CLMM_PROGRAM_ID, + ammConfig: AMM_CONFIG_PDA, + poolState: POOL_STATE_PDA, + inputVault: INPUT_VAULT_PDA, + outputVault: OUTPUT_VAULT_PDA, + observationState: OBSERVATION_PDA, + tickArrayLower: TICK_LOWER_PDA, + tickArrayCurrent: TICK_CURRENT_PDA, + tickArrayUpper: TICK_UPPER_PDA, + tickBitmap: TICK_BITMAP_PDA, + inputTokenAccount: composerUsdeAta.address, + outputTokenAccount: composerUsdcAta.address, + inputVaultMint: USDE_MINT, + outputVaultMint: USDC_MINT, + }) + .accounts({ + composer: composerPda, + lzComposeTypesAccounts: typesPda, + payer: provider.wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + .rpc() + + console.log('✅ initComposer tx:', sig) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/initConfig.ts b/examples/oft-solana-composer-library/tasks/solana/initConfig.ts new file mode 100644 index 000000000..202d18a36 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/initConfig.ts @@ -0,0 +1,31 @@ +import { PublicKey } from '@solana/web3.js' +import { ConfigurableTaskDefinition } from 'hardhat/types' + +import { inheritTask } from '@layerzerolabs/devtools-evm-hardhat' +import { type LogLevel } from '@layerzerolabs/io-devtools' +import { type OAppConfigurator } from '@layerzerolabs/ua-devtools' +import { TASK_LZ_OAPP_WIRE } from '@layerzerolabs/ua-devtools-evm-hardhat' +import { initOFTAccounts } from '@layerzerolabs/ua-devtools-solana' + +// We'll create clones of the wire task and only override the configurator argument +const wireLikeTask = inheritTask(TASK_LZ_OAPP_WIRE) + +// TODO: export from wire.ts instead of re-declaring +/** + * Additional CLI arguments for our custom wire task + */ +interface Args { + logLevel: LogLevel + multisigKey?: PublicKey + internalConfigurator?: OAppConfigurator +} + +// This task will use the `initOFTAccounts` configurator that initializes the Solana accounts +const initConfigTask = wireLikeTask('lz:oft:solana:init-config') as ConfigurableTaskDefinition + +// TODO: currently the message for 'already done' state is "OApp is already wired." which is misleading -> should be changed to "Pathway Config already initialized" +initConfigTask + .setDescription('Initialize OFT accounts for Solana') + .setAction(async (args: Args, hre) => + hre.run(TASK_LZ_OAPP_WIRE, { ...args, internalConfigurator: initOFTAccounts, isSolanaInitConfig: true }) + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts b/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts new file mode 100644 index 000000000..500d41c4b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/lzCompose.ts @@ -0,0 +1,96 @@ +// tasks/solana/clearWithAlt.ts +import { createSignerFromKeypair } from '@metaplex-foundation/umi' +import { toWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Transaction } from '@solana/web3.js' +import { task } from 'hardhat/config' + +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { EndpointProgram, extractComposeSentEventByTxHash, lzCompose } from '@layerzerolabs/lz-solana-sdk-v2' + +import { deriveConnection } from '.' + +interface Args { + srcTxHash: string + mnemonic?: string +} + +task( + 'lz:oapp:solana:clear-with-alt', + 'Fetch a ComposeSent event by source-tx and clear it on Solana in one ALT-packed tx' +) + .addParam('srcTxHash', 'The source transaction hash') + .addOptionalParam('mnemonic', 'Your wallet mnemonic (or set MNEMONIC env var)', process.env.MNEMONIC) + .setAction(async ({ srcTxHash, mnemonic }: Args, hre) => { + // 1) Build UMI + RPC connection + // Set up connection and wallet + // Fetch message metadata + const response = await fetch(`https://scan.layerzero-api.com/v1/messages/tx/${srcTxHash}`) + const data = await response.json() + const message = data.data?.[0] + const dstTxHash = message.destination.tx.txHash + console.log('message', message) + + // Set up connection and wallet + const { umi, umiWalletSigner, connection } = await deriveConnection(message.pathway.dstEid as EndpointId) + const signer = createSignerFromKeypair(umi, umiWalletSigner) + const web3Signer = toWeb3JsKeypair(umiWalletSigner) + + // 2) Look up the ComposeSent event from your source tx + const events = await extractComposeSentEventByTxHash(connection, EndpointProgram.PROGRAM_ID, dstTxHash) + if (!events || events.length === 0) { + throw new Error(`No ComposeSent event found for ${dstTxHash}`) + } + const event = events[0] + + // 4) Build the lzCompose instruction + const ix = await lzCompose(connection, toWeb3JsPublicKey(signer.publicKey), event) + + // 1) Build your Instruction exactly as before + const tx = new Transaction() + tx.add(ix) + tx.feePayer = toWeb3JsPublicKey(signer.publicKey) + + // 2) Fetch a recent blockhash + const { blockhash } = await connection.getLatestBlockhash('confirmed') + tx.recentBlockhash = blockhash + + // 3) Sign the transaction (`payer` must sign; adjust if you have more signers) + tx.sign(web3Signer) + const signedTx = tx + + // 4) Send *without* preflight + const signature = await connection.sendRawTransaction(signedTx.serialize(), { + skipPreflight: true, + preflightCommitment: 'confirmed', + }) + + // 5) Wait for confirmation + await connection.confirmTransaction(signature, 'confirmed') + + // 6) Fetch the *on-chain* logs + const txInfo = await connection.getTransaction(signature, { commitment: 'confirmed' }) + console.log('On-chain logs:\n', txInfo?.meta?.logMessages?.join('\n')) + + // const umiInstruction = { + // programId: publicKey(ix.programId.toBase58()), + // keys: ix.keys.map((key) => ({ + // pubkey: publicKey(key.pubkey.toBase58()), + // isSigner: key.isSigner, + // isWritable: key.isWritable, + // })), + // data: ix.data, + // } + // let txBuilder = transactionBuilder().add({ + // instruction: umiInstruction, + // signers: [umiWalletSigner], // Include all required signers here + // bytesCreatedOnChain: 0, + // }) + + // const { signature } = await txBuilder.sendAndConfirm(umi, { + // skipPreflight: true, + // preflightCommitment: 'confirmed', + // }) + // console.log( + // `lzCompose: ${getExplorerTxLink(bs58.encode(signature), false)}` + // ) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/multisig.ts b/examples/oft-solana-composer-library/tasks/solana/multisig.ts new file mode 100644 index 000000000..2031a1a67 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/multisig.ts @@ -0,0 +1,158 @@ +import { createAccount, initializeMultisig } from '@metaplex-foundation/mpl-toolbox' +import { + KeypairSigner, + Umi, + createSignerFromKeypair, + transactionBuilder, + publicKey as umiPublicKey, +} from '@metaplex-foundation/umi' +import { toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { MULTISIG_SIZE, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Connection, PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' + +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { assertAccountInitialized } from './utils' + +import { TransactionType, addComputeUnitInstructions, getExplorerTxLink } from '.' + +export async function createMultisig( + connection: Connection, + umi: Umi, + eid: EndpointId, + umiWalletSigner: KeypairSigner, + signers: PublicKey[], + m: number, + keypair = umi.eddsa.generateKeypair(), + programId = TOKEN_PROGRAM_ID, + computeUnitPriceScaleFactor?: number +): Promise { + let txBuilder = transactionBuilder() + .add( + createAccount(umi, { + newAccount: createSignerFromKeypair(umi, keypair), + lamports: await umi.rpc.getRent(MULTISIG_SIZE), + space: MULTISIG_SIZE, + programId: umiPublicKey(programId.toBase58()), + }) + ) + .add( + initializeMultisig(umi, { + multisig: keypair.publicKey, + rent: undefined, + m, + }).addRemainingAccounts( + signers.map((signer) => ({ + pubkey: umiPublicKey(signer.toBase58()), + isWritable: false, + isSigner: false, + })) + ) + ) + + if (computeUnitPriceScaleFactor) { + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.CreateMultisig + ) + } + + const multisigPublicKey = toWeb3JsPublicKey(keypair.publicKey) + + const tx = await txBuilder.sendAndConfirm(umi) + await assertAccountInitialized(connection, multisigPublicKey) + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + console.log(`createMultisigTx: ${getExplorerTxLink(bs58.encode(tx.signature), isTestnet)}`) + + return multisigPublicKey +} + +/** + * Creates a (1/N) multisig account for use as the mint authority. + * @param connection {Connection} + * @param payer {Signer} + * @param oftStorePda {PublicKey} will be included as a signer + * @param tokenProgramId {PublicKey} defaults to SPL token program ID + * @param additionalSigners {PublicKey[]} the additionalSigners for the multisig account + */ +export const createMintAuthorityMultisig = async ( + connection: Connection, + umi: Umi, + eid: EndpointId, + umiWalletSigner: KeypairSigner, + oftStorePda: PublicKey, + tokenProgramId: PublicKey = TOKEN_PROGRAM_ID, + additionalSigners: PublicKey[], + computeUnitPriceScaleFactor: number +) => { + return createMultisig( + connection, + umi, + eid, + umiWalletSigner, + [oftStorePda, ...additionalSigners], + 1, // quorum 1/N + undefined, + tokenProgramId, + computeUnitPriceScaleFactor + ) +} + +/** + * Decode the signers of a multisig account and check if the expected signers + * are present and the quorum is 1/N. + * @param connection {Connection} + * @param multisigAddress {PublicKey} + * @param expectedSigners {PublicKey[]} the expected signers + */ +export const checkMultisigSigners = async ( + connection: Connection, + multisigAddress: PublicKey, + expectedSigners: PublicKey[] +) => { + const accountInfo = await assertAccountInitialized(connection, multisigAddress) + + if (!accountInfo.owner.equals(TOKEN_PROGRAM_ID) && !accountInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { + throw new Error('Provided address is not an SPL Token multisig account') + } + + // Multisig accounts have a specific layout based on the Multisig interface: + const data = accountInfo.data + + // Extract the number of required signers (m) and total possible signers (n) + const numRequiredSigners = data[0] + const numTotalSigners = data[1] + + if (numRequiredSigners !== 1) { + throw new Error('Multisig account must have 1 required signer') + } + + // Initialize an array to hold the signers + const signers: PublicKey[] = [] + + // Extract each signer public key based on the Multisig interface + const signerOffset = 3 // Offset to the first signer in the data + const signerSize = 32 // Each signer address is 32 bytes + + for (let i = 0; i < numTotalSigners; i++) { + const start = signerOffset + i * signerSize + const end = start + signerSize + const signerPublicKey = new PublicKey(data.slice(start, end)) + if (!signerPublicKey.equals(PublicKey.default)) { + signers.push(signerPublicKey) + } + } + + for (const signer of expectedSigners) { + if (!signers.find((s) => s.toBase58() == signer.toBase58())) { + throw new Error(`Signer ${signer.toBase58()} not found in multisig account`) + } + } + return signers +} diff --git a/examples/oft-solana-composer-library/tasks/solana/retryPayload.ts b/examples/oft-solana-composer-library/tasks/solana/retryPayload.ts new file mode 100644 index 000000000..0035d0248 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/retryPayload.ts @@ -0,0 +1,100 @@ +import { web3 } from '@coral-xyz/anchor' +import { toWeb3JsKeypair } from '@metaplex-foundation/umi-web3js-adapters' +import { ComputeBudgetProgram, Keypair, sendAndConfirmTransaction } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { makeBytes32 } from '@layerzerolabs/devtools' +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { lzReceive } from '@layerzerolabs/lz-solana-sdk-v2' + +import { deriveConnection, getExplorerTxLink } from './index' + +interface Args { + srcEid: EndpointId + nonce: bigint + sender: string + dstEid: EndpointId + receiver: string + guid: string + payload: string + computeUnits: number + lamports: number + withPriorityFee: number +} + +task('lz:oft:solana:retry-payload', 'Retry a stored payload on Solana') + .addParam('srcEid', 'The source EndpointId', undefined, types.eid) + .addParam('nonce', 'The nonce of the payload', undefined, types.bigint) + .addParam('sender', 'The source OApp address (hex)', undefined, types.string) + .addParam('dstEid', 'The destination EndpointId (Solana chain)', undefined, types.eid) + .addParam('receiver', 'The receiver address on the destination Solana chain (bytes58)', undefined, types.string) + .addParam('guid', 'The GUID of the message (hex)', undefined, types.string) + .addParam('payload', 'The message payload (hex)', undefined, types.string) + .addParam('computeUnits', 'The CU for the lzReceive instruction', undefined, types.int) + .addParam('lamports', 'The lamports for the lzReceive instruction', undefined, types.int) + .addParam('withPriorityFee', 'The priority fee in microLamports', undefined, types.int) + .setAction( + async ({ + srcEid, + nonce, + sender, + dstEid, + receiver, + guid, + payload, + computeUnits, + lamports, + withPriorityFee, + }: Args) => { + if (!process.env.SOLANA_PRIVATE_KEY) { + throw new Error('SOLANA_PRIVATE_KEY is not defined in the environment variables.') + } + + const { connection, umiWalletKeyPair } = await deriveConnection(dstEid) + const signer = toWeb3JsKeypair(umiWalletKeyPair) + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash() + const tx = new web3.Transaction({ + feePayer: signer.publicKey, + blockhash, + lastValidBlockHeight, + }) + + const instruction = await lzReceive( + connection, + signer.publicKey, + { + nonce: nonce.toString(), + srcEid, + sender: makeBytes32(sender), + dstEid, + receiver, + payload: '', // unused; just added to satisfy typing + guid, + message: payload, // referred to as "payload" in scan-api + version: 1, // unused; just added to satisfy typing + }, + Uint8Array.from([computeUnits, lamports]), + 'confirmed' + ) + + if (withPriorityFee) { + tx.add( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: withPriorityFee, + }) + ) + } + tx.add(instruction) + tx.recentBlockhash = blockhash + + const keypair = Keypair.fromSecretKey(bs58.decode(process.env.SOLANA_PRIVATE_KEY)) + tx.sign(keypair) + + const signature = await sendAndConfirmTransaction(connection, tx, [keypair], { skipPreflight: true }) + console.log( + `View Solana transaction here: ${getExplorerTxLink(signature.toString(), dstEid == EndpointId.SOLANA_V2_TESTNET)}` + ) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/sendOFT.ts b/examples/oft-solana-composer-library/tasks/solana/sendOFT.ts new file mode 100644 index 000000000..1c91dba8b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/sendOFT.ts @@ -0,0 +1,136 @@ +import { fetchToken, findAssociatedTokenPda } from '@metaplex-foundation/mpl-toolbox' +import { publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import { fromWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { addressToBytes32 } from '@layerzerolabs/lz-v2-utilities' +import { oft } from '@layerzerolabs/oft-v2-solana-sdk' + +import { + TransactionType, + addComputeUnitInstructions, + deriveConnection, + getExplorerTxLink, + getLayerZeroScanLink, + getSolanaDeployment, +} from './index' + +interface Args { + amount: bigint + to: string + fromEid: EndpointId + toEid: EndpointId + tokenProgram: string + computeUnitPriceScaleFactor: number +} + +// Define a Hardhat task for sending OFT from Solana +task('lz:oft:solana:send', 'Send tokens from Solana to a target EVM chain') + .addParam('amount', 'The amount of tokens to send', undefined, devtoolsTypes.bigint) + .addParam('fromEid', 'The source endpoint ID', undefined, devtoolsTypes.eid) + .addParam('to', 'The recipient address on the destination chain') + .addParam('toEid', 'The destination endpoint ID', undefined, devtoolsTypes.eid) + .addParam('tokenProgram', 'The Token Program public key', TOKEN_PROGRAM_ID.toBase58(), devtoolsTypes.string, true) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction(async (args: Args) => { + const { amount, fromEid, to, toEid, tokenProgram: tokenProgramStr, computeUnitPriceScaleFactor } = args + const { connection, umi, umiWalletSigner } = await deriveConnection(fromEid) + + const solanaDeployment = getSolanaDeployment(fromEid) + + const oftProgramId = publicKey(solanaDeployment.programId) + const mint = publicKey(solanaDeployment.mint) + const umiEscrowPublicKey = publicKey(solanaDeployment.escrow) + const tokenProgramId = tokenProgramStr ? publicKey(tokenProgramStr) : fromWeb3JsPublicKey(TOKEN_PROGRAM_ID) + + const tokenAccount = findAssociatedTokenPda(umi, { + mint, + owner: umiWalletSigner.publicKey, + tokenProgramId, + }) + + if (!tokenAccount) { + throw new Error( + `No token account found for mint ${mint.toString()} and owner ${umiWalletSigner.publicKey} in program ${tokenProgramId}` + ) + } + + const tokenAccountData = await fetchToken(umi, tokenAccount) + const balance = tokenAccountData.amount + + if (amount == BigInt(0) || amount > balance) { + throw new Error( + `Attempting to send ${amount}, but ${umiWalletSigner.publicKey} only has balance of ${balance}` + ) + } + + const recipientAddressBytes32 = addressToBytes32(to) + + const { nativeFee } = await oft.quote( + umi.rpc, + { + payer: umiWalletSigner.publicKey, + tokenMint: mint, + tokenEscrow: umiEscrowPublicKey, + }, + { + payInLzToken: false, + to: Buffer.from(recipientAddressBytes32), + dstEid: toEid, + amountLd: BigInt(amount), + minAmountLd: 1n, + options: Buffer.from(''), + composeMsg: undefined, + }, + { + oft: oftProgramId, + } + ) + + const ix = await oft.send( + umi.rpc, + { + payer: umiWalletSigner, + tokenMint: mint, + tokenEscrow: umiEscrowPublicKey, + tokenSource: tokenAccount[0], + }, + { + to: Buffer.from(recipientAddressBytes32), + dstEid: toEid, + amountLd: BigInt(amount), + minAmountLd: (BigInt(amount) * BigInt(9)) / BigInt(10), + options: Buffer.from(''), + composeMsg: undefined, + nativeFee, + }, + { + oft: oftProgramId, + token: tokenProgramId, + } + ) + + let txBuilder = transactionBuilder().add([ix]) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + fromEid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.SendOFT + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + const transactionSignatureBase58 = bs58.encode(signature) + + console.log(`✅ Sent ${amount} token(s) to destination EID: ${toEid}!`) + const isTestnet = fromEid == EndpointId.SOLANA_V2_TESTNET + console.log( + `View Solana transaction here: ${getExplorerTxLink(transactionSignatureBase58.toString(), isTestnet)}` + ) + console.log(`Track cross-chain transfer here: ${getLayerZeroScanLink(transactionSignatureBase58, isTestnet)}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/setAuthority.ts b/examples/oft-solana-composer-library/tasks/solana/setAuthority.ts new file mode 100644 index 000000000..56d8b8407 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setAuthority.ts @@ -0,0 +1,206 @@ +import { AccountMeta, publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import { fromWeb3JsPublicKey, toWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { AuthorityType, TOKEN_PROGRAM_ID, createSetAuthorityInstruction, getMint } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA } from '@layerzerolabs/oft-v2-solana-sdk' + +import { checkMultisigSigners, createMintAuthorityMultisig } from './multisig' + +import { TransactionType, addComputeUnitInstructions, deriveConnection, getExplorerTxLink } from './index' + +interface SetAuthorityTaskArgs { + /** + * The endpoint ID for the Solana network. + */ + eid: EndpointId + + /** + * The escrow public key. + */ + escrow: string + + /** + * The token mint ID, for Mint-And-Burn-Adapter only. + */ + mint: string + + /** + * The program ID for the OFT program. + */ + programId: string + + /** + * The CSV list of additional minters. + */ + additionalMinters?: string[] + + /** + * The token program ID, for Mint-And-Burn-Adapter only. + */ + tokenProgram: string + + /** + * If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in + * losing the ability to mint new tokens for everything but the OFTStore. You should really be intentional about + * using this flag, as it is not reversible. + */ + onlyOftStore: boolean + + computeUnitPriceScaleFactor: number +} + +/** + * Derive the OFT Store account for a given program and escrow. + * @param programId {string} + * @param escrow {string} + */ +const getOftStore = (programId: string, escrow: string) => { + const oftDeriver = new OftPDA(publicKey(programId)) + const escrowPK = publicKey(escrow) + const [oftStorePda] = oftDeriver.oftStore(escrowPK) + return oftStorePda +} + +/** + * Get the string representation of the authority type. + * @param authorityType {AuthorityType} + */ +const getAuthorityTypeString = (authorityType: AuthorityType) => { + switch (authorityType) { + case AuthorityType.MintTokens: + return 'MintTokens' + case AuthorityType.FreezeAccount: + return 'FreezeAccount' + default: + throw Error(`Unknown authority type: ${authorityType}`) + } +} + +// Define a Hardhat task for creating and setting a new Mint/Freeze Authority +// for OFT on Solana +// * Create SPL Multisig account for mint authority +// * Sanity check the new Multisig account +// * Set Mint Authority +// * Set Freeze Authority +// Note: Only supports SPL Token Standard. +task('lz:oft:solana:setauthority', 'Create a new Mint Authority SPL multisig and set the mint/freeze authority') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168) eid', undefined, devtoolsTypes.eid) + .addParam('mint', 'The Token Mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('escrow', 'The OFT Escrow public key') + .addParam('additionalMinters', 'Comma-separated list of additional minters', undefined, devtoolsTypes.csv, true) + .addOptionalParam( + 'onlyOftStore', + 'If you plan to have only the OFTStore and no additional minters. This is not reversible, and will result in losing the ability to mint new tokens by everything but the OFTStore.', + false, + devtoolsTypes.boolean + ) + .addParam( + 'tokenProgram', + 'The Token Program public key (used for MABA only)', + TOKEN_PROGRAM_ID.toBase58(), + devtoolsTypes.string + ) + .addParam('computeUnitPriceScaleFactor', 'The compute unit price scale factor', 4, devtoolsTypes.float, true) + .setAction( + async ({ + eid, + escrow: escrowStr, + mint: mintStr, + programId: programIdStr, + tokenProgram: tokenProgramStr, + additionalMinters: additionalMintersAsStrings, + onlyOftStore, + computeUnitPriceScaleFactor, + }: SetAuthorityTaskArgs) => { + const { connection, umi, umiWalletKeyPair, umiWalletSigner } = await deriveConnection(eid) + const oftStorePda = getOftStore(programIdStr, escrowStr) + const tokenProgram = publicKey(tokenProgramStr) + if (!additionalMintersAsStrings) { + if (!onlyOftStore) { + throw new Error( + 'If you want to proceed with only the OFTStore, please specify --only-oft-store true' + ) + } + console.log( + 'No additional minters specified. This will result in only the OFTStore being able to mint new tokens.' + ) + } + const additionalMinters = additionalMintersAsStrings?.map((minter) => new PublicKey(minter)) ?? [] + const mint = new PublicKey(mintStr) + const newMintAuthority = await createMintAuthorityMultisig( + connection, + umi, + eid, + umiWalletSigner, + new PublicKey(oftStorePda.toString()), + new PublicKey(tokenProgram.toString()), + additionalMinters, + computeUnitPriceScaleFactor + ) + console.log(`New Mint Authority: ${newMintAuthority.toBase58()}`) + const signers = await checkMultisigSigners(connection, newMintAuthority, [ + toWeb3JsPublicKey(oftStorePda), + ...additionalMinters, + ]) + console.log(`New Mint Authority Signers: ${signers.map((s) => s.toBase58()).join(', ')}`) + for (const authorityType of [AuthorityType.MintTokens, AuthorityType.FreezeAccount]) { + const mintAuthRet = await getMint(connection, mint, undefined, toWeb3JsPublicKey(tokenProgram)) + let currentAuthority + if (authorityType == AuthorityType.MintTokens) { + if (!mintAuthRet.mintAuthority) { + throw new Error(`Mint ${mintStr} has no mint authority`) + } + currentAuthority = fromWeb3JsPublicKey(mintAuthRet.mintAuthority) + } else { + if (!mintAuthRet.freezeAuthority) { + throw new Error(`Mint ${mintStr} has no freeze authority`) + } + currentAuthority = fromWeb3JsPublicKey(mintAuthRet.freezeAuthority) + } + if (authorityType == AuthorityType.FreezeAccount && !mintAuthRet.freezeAuthority) { + throw new Error(`Mint ${mintStr} has no freeze authority`) + } + console.log(`Current ${getAuthorityTypeString(authorityType)} Authority: ${currentAuthority}`) + const ix = createSetAuthorityInstruction( + new PublicKey(mintStr), + toWeb3JsPublicKey(currentAuthority), + authorityType, + newMintAuthority, + [toWeb3JsKeypair(umiWalletKeyPair)] + ) + const umiInstruction = { + programId: publicKey(ix.programId.toBase58()), + keys: ix.keys.map((key) => ({ + pubkey: key.pubkey, + isSigner: key.isSigner, + isWritable: key.isWritable, + })) as unknown as AccountMeta[], + data: ix.data, + } + let txBuilder = transactionBuilder().add({ + instruction: umiInstruction, + signers: [umiWalletSigner], // Include all required signers here + bytesCreatedOnChain: 0, + }) + txBuilder = await addComputeUnitInstructions( + connection, + umi, + eid, + txBuilder, + umiWalletSigner, + computeUnitPriceScaleFactor, + TransactionType.SetAuthority + ) + const { signature } = await txBuilder.sendAndConfirm(umi) + console.log( + `SetAuthorityTx(${getAuthorityTypeString(authorityType)}): ${getExplorerTxLink(bs58.encode(signature), eid == EndpointId.SOLANA_V2_TESTNET)}` + ) + } + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/setInboundRateLimit.ts b/examples/oft-solana-composer-library/tasks/solana/setInboundRateLimit.ts new file mode 100644 index 000000000..a4f668aed --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setInboundRateLimit.ts @@ -0,0 +1,80 @@ +import assert from 'assert' + +import { mplToolbox } from '@metaplex-foundation/mpl-toolbox' +import { createSignerFromKeypair, publicKey, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Keypair, PublicKey, sendAndConfirmTransaction } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { deserializeTransactionMessage } from '@layerzerolabs/devtools-solana' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA, oft } from '@layerzerolabs/oft-v2-solana-sdk' +import { createOFTFactory } from '@layerzerolabs/ua-devtools-solana' + +import { createSolanaConnectionFactory } from '../common/utils' + +interface Args { + mint: string + eid: EndpointId + srcEid: EndpointId + programId: string + oftStore: string + capacity: bigint + refillPerSecond: bigint +} + +task( + 'lz:oft:solana:inbound-rate-limit', + "Sets the Solana and EVM rate limits from './scripts/solana/utils/constants.ts'" +) + .addParam('mint', 'The OFT token mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, types.eid) + .addParam('srcEid', 'The source endpoint ID', undefined, types.eid) + .addParam('oftStore', 'The OFTStore account') + .addParam('capacity', 'The capacity of the rate limit', undefined, types.bigint) + .addParam('refillPerSecond', 'The refill rate of the rate limit', undefined, types.bigint) + .setAction(async (taskArgs: Args, hre) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + const keypair = Keypair.fromSecretKey(bs58.decode(privateKey)) + const umiKeypair = fromWeb3JsKeypair(keypair) + + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) + umi.use(signerIdentity(umiWalletSigner)) + + const solanaSdkFactory = createOFTFactory( + () => toWeb3JsPublicKey(umiWalletSigner.publicKey), + () => new PublicKey(taskArgs.programId), + connectionFactory + ) + const sdk = await solanaSdkFactory({ + address: new PublicKey(taskArgs.oftStore).toBase58(), + eid: taskArgs.eid, + }) + const solanaRateLimits = { + capacity: taskArgs.capacity, + refillPerSecond: taskArgs.refillPerSecond, + } + try { + const tx = deserializeTransactionMessage( + (await sdk.setInboundRateLimit(taskArgs.srcEid, solanaRateLimits)).data + ) + tx.sign(keypair) + const txId = await sendAndConfirmTransaction(connection, tx, [keypair]) + console.log(`Transaction successful with ID: ${txId}`) + const [peer] = new OftPDA(publicKey(taskArgs.programId)).peer(publicKey(taskArgs.oftStore), taskArgs.srcEid) + const peerInfo = await oft.accounts.fetchPeerConfig({ rpc: umi.rpc }, peer) + console.dir({ peerInfo }, { depth: null }) + } catch (error) { + console.error(`setInboundRateLimit failed:`, error) + } + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/setOutboundRateLimit.ts b/examples/oft-solana-composer-library/tasks/solana/setOutboundRateLimit.ts new file mode 100644 index 000000000..4d5abbf51 --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setOutboundRateLimit.ts @@ -0,0 +1,81 @@ +import assert from 'assert' + +import { mplToolbox } from '@metaplex-foundation/mpl-toolbox' +import { createSignerFromKeypair, publicKey, signerIdentity } from '@metaplex-foundation/umi' +import { createUmi } from '@metaplex-foundation/umi-bundle-defaults' +import { fromWeb3JsKeypair, toWeb3JsKeypair, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters' +import { Keypair, PublicKey, sendAndConfirmTransaction } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types } from '@layerzerolabs/devtools-evm-hardhat' +import { deserializeTransactionMessage } from '@layerzerolabs/devtools-solana' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { OftPDA, oft } from '@layerzerolabs/oft-v2-solana-sdk' +import { createOFTFactory } from '@layerzerolabs/ua-devtools-solana' + +import { createSolanaConnectionFactory } from '../common/utils' + +interface Args { + mint: string + eid: EndpointId + dstEid: EndpointId + programId: string + oftStore: string + capacity: bigint + refillPerSecond: bigint +} + +task( + 'lz:oft:solana:outbound-rate-limit', + "Sets the Solana and EVM rate limits from './scripts/solana/utils/constants.ts'" +) + .addParam('mint', 'The OFT token mint public key') + .addParam('programId', 'The OFT Program id') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, types.eid) + .addParam('dstEid', 'The destination endpoint ID', undefined, types.eid) + .addParam('oftStore', 'The OFTStore account') + .addParam('capacity', 'The capacity of the rate limit', undefined, types.bigint) + .addParam('refillPerSecond', 'The refill rate of the rate limit', undefined, types.bigint) + .setAction(async (taskArgs: Args, hre) => { + const privateKey = process.env.SOLANA_PRIVATE_KEY + assert(!!privateKey, 'SOLANA_PRIVATE_KEY is not defined in the environment variables.') + + const keypair = Keypair.fromSecretKey(bs58.decode(privateKey)) + const umiKeypair = fromWeb3JsKeypair(keypair) + const connectionFactory = createSolanaConnectionFactory() + const connection = await connectionFactory(taskArgs.eid) + const umi = createUmi(connection.rpcEndpoint).use(mplToolbox()) + const umiWalletSigner = createSignerFromKeypair(umi, umiKeypair) + const web3WalletKeyPair = toWeb3JsKeypair(umiKeypair) + umi.use(signerIdentity(umiWalletSigner)) + + const solanaSdkFactory = createOFTFactory( + () => toWeb3JsPublicKey(umiWalletSigner.publicKey), + () => new PublicKey(taskArgs.programId), + connectionFactory + ) + + const sdk = await solanaSdkFactory({ + address: new PublicKey(taskArgs.oftStore).toBase58(), + eid: taskArgs.eid, + }) + const solanaRateLimits = { + capacity: taskArgs.capacity, + refillPerSecond: taskArgs.refillPerSecond, + } + // for (const peer of graph.connections.filter((connection) => connection.vector.from.eid === solanaEid)) { + try { + const tx = deserializeTransactionMessage( + (await sdk.setOutboundRateLimit(EndpointId.SEPOLIA_V2_TESTNET, solanaRateLimits)).data + ) + tx.sign(keypair) + const txId = await sendAndConfirmTransaction(connection, tx, [keypair]) + console.log(`Transaction successful with ID: ${txId}`) + const [peer] = new OftPDA(publicKey(taskArgs.programId)).peer(publicKey(taskArgs.oftStore), taskArgs.dstEid) + const peerInfo = await oft.accounts.fetchPeerConfig({ rpc: umi.rpc }, peer) + console.dir({ peerInfo }, { depth: null }) + } catch (error) { + console.error(`setOutboundRateLimit failed:`, error) + } + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/setUpdateAuthority.ts b/examples/oft-solana-composer-library/tasks/solana/setUpdateAuthority.ts new file mode 100644 index 000000000..a042469de --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/setUpdateAuthority.ts @@ -0,0 +1,99 @@ +import { fetchMetadataFromSeeds, updateV1 } from '@metaplex-foundation/mpl-token-metadata' +import { publicKey } from '@metaplex-foundation/umi' +import { SystemProgram } from '@solana/web3.js' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { promptToContinue } from '@layerzerolabs/io-devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { deriveConnection, getExplorerTxLink } from '.' + +interface Args { + mint: string + newUpdateAuthority?: string + renounceUpdateAuthority?: boolean + eid: EndpointId +} + +// sets the update authority via Metaplex +task('lz:oft:solana:set-update-authority', 'Updates the metaplex update authority of the SPL Token') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('mint', 'The Token mint public key', undefined, devtoolsTypes.string) + .addOptionalParam('newUpdateAuthority', 'The new update authority', undefined, devtoolsTypes.string) + .addOptionalParam('renounceUpdateAuthority', 'Renounce update authority', false, devtoolsTypes.boolean) + .setAction( + async ({ eid, mint: mintStr, newUpdateAuthority: newUpdateAuthorityStr, renounceUpdateAuthority }: Args) => { + // if not renouncing, must provide new update authority + if (!renounceUpdateAuthority && !newUpdateAuthorityStr) { + throw new Error( + 'Either specify the new update authority via --new-update-authority or renounce via --renounce-update-authority true' + ) + } + + // if renouncing, must not provide new update authority + if (renounceUpdateAuthority && newUpdateAuthorityStr) { + throw new Error('Cannot provide new update authority if renouncing') + } + + /* + * On why the update authority is set to SystemProgram.programId ("11111111111111111111111111111111") when renouncing: + * The Metaplex Token Metadata program defines the update_authority strictly as a Pubkey: + * https://github.com/metaplex-foundation/mpl-token-metadata/blob/23aee718e723578ee5df411f045184e0ac9a9e63/programs/token-metadata/program/src/state/metadata.rs#L73 + * Hence, the value must always be a Pubkey + * To renounce the update authority, we can to set its value to SystemProgram ID ("11111111111111111111111111111111") + * This is done on top of setting `isMutable` to false + */ + + const updateAuthority = renounceUpdateAuthority + ? publicKey(SystemProgram.programId) + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + publicKey(newUpdateAuthorityStr!) // we already checked that this is defined + + const { umi, umiWalletSigner } = await deriveConnection(eid) + + const mint = publicKey(mintStr) + const initialMetadata = await fetchMetadataFromSeeds(umi, { mint }) + + if (initialMetadata.updateAuthority === SystemProgram.programId.toString()) { + console.log('\nThe update authority has already been renounced\n') + return + } + + if (initialMetadata.updateAuthority !== umiWalletSigner.publicKey.toString()) { + throw new Error('Only the update authority can update the metadata') + } + + console.log(`\nMint Address: ${mintStr}\n`) + console.log(`\nCurrent update authority: ${initialMetadata.updateAuthority}\n`) + console.log(`\nNew update authority: ${updateAuthority.toString()}\n`) + + if (renounceUpdateAuthority) { + const doContinue = await promptToContinue( + 'You have chosen `--renounce-update-authority true`. This means that the Update Authority will be immediately renounced. This is irreversible. Continue?' + ) + if (!doContinue) { + return + } + } + + const isMutable = renounceUpdateAuthority ? false : initialMetadata.isMutable + + // Verify that isMutable is true when not renouncing, can't be too safe. + if (!renounceUpdateAuthority && !isMutable) { + throw new Error('When not renouncing, `isMutable` must be true') + } + + const txn = await updateV1(umi, { + mint, + newUpdateAuthority: updateAuthority, + authority: umiWalletSigner, + isMutable: renounceUpdateAuthority ? false : isMutable, + }).sendAndConfirm(umi) + + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + + console.log(`Txn link: ${getExplorerTxLink(bs58.encode(txn.signature), isTestnet)}`) + } + ) diff --git a/examples/oft-solana-composer-library/tasks/solana/updateMetadata.ts b/examples/oft-solana-composer-library/tasks/solana/updateMetadata.ts new file mode 100644 index 000000000..07a59703c --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/updateMetadata.ts @@ -0,0 +1,66 @@ +import { + UpdateV1InstructionAccounts, + UpdateV1InstructionArgs, + fetchMetadataFromSeeds, + updateV1, +} from '@metaplex-foundation/mpl-token-metadata' +import { publicKey, transactionBuilder } from '@metaplex-foundation/umi' +import bs58 from 'bs58' +import { task } from 'hardhat/config' + +import { types as devtoolsTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { EndpointId } from '@layerzerolabs/lz-definitions' + +import { deriveConnection, getExplorerTxLink } from './index' + +interface UpdateMetadataTaskArgs { + eid: EndpointId + name: string + mint: string + sellerFeeBasisPoints: number + symbol: string + uri: string +} + +// note that if URI is specified, then the name and symbol in there would be used and will override the 'outer' name and symbol +task('lz:oft:solana:update-metadata', 'Updates the metaplex metadata of the SPL Token') + .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) + .addParam('mint', 'The Token mint public key', undefined, devtoolsTypes.string) + .addOptionalParam('name', 'Token Name', undefined, devtoolsTypes.string) + .addOptionalParam('symbol', 'Token Symbol', undefined, devtoolsTypes.string) + .addOptionalParam('sellerFeeBasisPoints', 'Seller fee basis points', undefined, devtoolsTypes.int) + .addOptionalParam('uri', 'URI for token metadata', undefined, devtoolsTypes.string) + .setAction(async ({ eid, name, mint: mintStr, sellerFeeBasisPoints, symbol, uri }: UpdateMetadataTaskArgs) => { + const { umi, umiWalletSigner } = await deriveConnection(eid) + + const mint = publicKey(mintStr) + + const initialMetadata = await fetchMetadataFromSeeds(umi, { mint }) + + if (initialMetadata.updateAuthority !== umiWalletSigner.publicKey.toString()) { + throw new Error('Only the update authority can update the metadata') + } + + if (initialMetadata.isMutable == false) { + throw new Error('Metadata is not mutable') + } + + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + + const updateV1Args: UpdateV1InstructionAccounts & UpdateV1InstructionArgs = { + mint, + authority: umiWalletSigner, + data: { + ...initialMetadata, + name: name || initialMetadata.name, + symbol: symbol || initialMetadata.symbol, + uri: uri || initialMetadata.uri, + sellerFeeBasisPoints: + sellerFeeBasisPoints != undefined ? sellerFeeBasisPoints : initialMetadata.sellerFeeBasisPoints, + }, + } + + const txBuilder = transactionBuilder().add(updateV1(umi, updateV1Args)) + const createTokenTx = await txBuilder.sendAndConfirm(umi) + console.log(`createTokenTx: ${getExplorerTxLink(bs58.encode(createTokenTx.signature), isTestnet)}`) + }) diff --git a/examples/oft-solana-composer-library/tasks/solana/utils.ts b/examples/oft-solana-composer-library/tasks/solana/utils.ts new file mode 100644 index 000000000..fd9a33b0b --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/solana/utils.ts @@ -0,0 +1,77 @@ +import { Connection, PublicKey } from '@solana/web3.js' +import { backOff } from 'exponential-backoff' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { ChainType, EndpointId, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +import { OAppOmniGraph } from '@layerzerolabs/ua-devtools' +import { + OAppOmniGraphHardhatSchema, + SUBTASK_LZ_OAPP_CONFIG_LOAD, + SubtaskLoadConfigTaskArgs, + TASK_LZ_OAPP_CONFIG_GET, +} from '@layerzerolabs/ua-devtools-evm-hardhat' + +/** + * Assert that the account is initialized on the Solana blockchain. Due to eventual consistency, there is a race + * between account creation and initialization. This function will retry 10 times with backoff to ensure the account is + * initialized. + * @param connection {Connection} + * @param publicKey {PublicKey} + */ +export const assertAccountInitialized = async (connection: Connection, publicKey: PublicKey) => { + return backOff( + async () => { + const accountInfo = await connection.getAccountInfo(publicKey) + if (!accountInfo) { + throw new Error('Multisig account not found') + } + return accountInfo + }, + { + maxDelay: 30000, + numOfAttempts: 10, + startingDelay: 5000, + } + ) +} + +export const findSolanaEndpointIdInGraph = async ( + hre: HardhatRuntimeEnvironment, + oappConfig: string +): Promise => { + if (!oappConfig) throw new Error('Missing oappConfig') + + let graph: OAppOmniGraph + try { + graph = await hre.run(SUBTASK_LZ_OAPP_CONFIG_LOAD, { + configPath: oappConfig, + schema: OAppOmniGraphHardhatSchema, + task: TASK_LZ_OAPP_CONFIG_GET, + } satisfies SubtaskLoadConfigTaskArgs) + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to load OApp configuration: ${error.message}`) + } else { + throw new Error('Failed to load OApp configuration: Unknown error') + } + } + + let solanaEid: EndpointId | null = null + + const checkSolanaEndpoint = (eid: EndpointId) => { + if (endpointIdToChainType(eid) === ChainType.SOLANA) { + if (solanaEid && solanaEid !== eid) { + throw new Error(`Multiple Solana Endpoint IDs found: ${solanaEid}, ${eid}`) + } + solanaEid = eid + } + } + + for (const { vector } of graph.connections) { + checkSolanaEndpoint(vector.from.eid) + checkSolanaEndpoint(vector.to.eid) + if (solanaEid) return solanaEid + } + + throw new Error('No Solana Endpoint ID found. Ensure your OApp configuration includes a valid Solana endpoint.') +} diff --git a/examples/oft-solana-composer-library/tasks/utils/getFee.ts b/examples/oft-solana-composer-library/tasks/utils/getFee.ts new file mode 100644 index 000000000..ef83ec4cd --- /dev/null +++ b/examples/oft-solana-composer-library/tasks/utils/getFee.ts @@ -0,0 +1,75 @@ +import { Connection, PublicKey } from '@solana/web3.js' + +// Define interfaces for more explicit typing +interface PrioritizationFeeObject { + slot: number + prioritizationFee: number +} + +interface Config { + lockedWritableAccounts: PublicKey[] +} + +const getPrioritizationFees = async ( + connection: Connection, + programId?: string // TODO: change to array of addresses / public keys to match lockedWritableAccounts' type +): Promise<{ + averageFeeIncludingZeros: number + averageFeeExcludingZeros: number + medianFee: number +}> => { + try { + const publicKey = new PublicKey(programId || PublicKey.default) // the account that will be written to + + const config: Config = { + lockedWritableAccounts: [publicKey], + } + + const prioritizationFeeObjects = (await connection.getRecentPrioritizationFees( + config + )) as PrioritizationFeeObject[] + + if (prioritizationFeeObjects.length === 0) { + console.log('No prioritization fee data available.') + return { averageFeeIncludingZeros: 0, averageFeeExcludingZeros: 0, medianFee: 0 } + } + + // Extract slots and sort them + // const slots = prioritizationFeeObjects.map((feeObject) => feeObject.slot).sort((a, b) => a - b) + + // Calculate the average including zero fees + const averageFeeIncludingZeros = + prioritizationFeeObjects.length > 0 + ? Math.floor( + prioritizationFeeObjects.reduce((acc, feeObject) => acc + feeObject.prioritizationFee, 0) / + prioritizationFeeObjects.length + ) + : 0 + + // Filter out prioritization fees that are equal to 0 for other calculations + const nonZeroFees = prioritizationFeeObjects + .map((feeObject) => feeObject.prioritizationFee) + .filter((fee) => fee !== 0) + + // Calculate the average of the non-zero fees + const averageFeeExcludingZeros = + nonZeroFees.length > 0 ? Math.floor(nonZeroFees.reduce((acc, fee) => acc + fee, 0) / nonZeroFees.length) : 0 + + // Calculate the median of the non-zero fees + const sortedFees = nonZeroFees.sort((a, b) => a - b) + let medianFee = 0 + if (sortedFees.length > 0) { + const midIndex = Math.floor(sortedFees.length / 2) + medianFee = + sortedFees.length % 2 !== 0 + ? sortedFees[midIndex] + : Math.floor((sortedFees[midIndex - 1] + sortedFees[midIndex]) / 2) + } + return { averageFeeIncludingZeros, averageFeeExcludingZeros, medianFee } + } catch (error) { + console.error('Error fetching prioritization fees:', error) + return { averageFeeIncludingZeros: 0, averageFeeExcludingZeros: 0, medianFee: 0 } + } +} + +export default getPrioritizationFees diff --git a/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts b/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts new file mode 100644 index 000000000..376ba10c3 --- /dev/null +++ b/examples/oft-solana-composer-library/test/anchor/lz_compose_local.test.ts @@ -0,0 +1,335 @@ +// Rewritten version of the test using local validator instead of LiteSVM +import * as anchor from '@coral-xyz/anchor' +import { MEMO_PROGRAM_ID } from '@solana/spl-memo' +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getAccount, + getOrCreateAssociatedTokenAccount, +} from '@solana/spl-token' +import { Connection, Keypair, PublicKey, SYSVAR_RENT_PUBKEY, SystemProgram, Transaction } from '@solana/web3.js' + +import idl from '../../target/idl/composer.json' + +const RAYDIUM_CLMM_ID = new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK') +const COMPOSER_ID = new PublicKey('4CDvcxbE21FD3yd5AtVGmAndXxricN5aKBADz8UNyoQq') +const COMPOSER_SEED = Buffer.from('Composer') +const LZ_TYPES_SEED = Buffer.from('LzComposeTypes') +const USDE_MINT = new PublicKey('DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT') +const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') + +const connection = new Connection('http://localhost:8899', 'confirmed') + +// Add these constants for the program owners +const BPF_LOADER_UPGRADEABLE = new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111') + +async function verifyAccount(connection: Connection, address: PublicKey, expectedOwner: PublicKey, name: string) { + const accountInfo = await connection.getAccountInfo(address) + if (!accountInfo) { + console.error(`❌ ${name} account does not exist:`, address.toString()) + return false + } + + // Special case for program accounts + if (name.toLowerCase().includes('program')) { + if (!accountInfo.owner.equals(BPF_LOADER_UPGRADEABLE)) { + console.error( + `❌ ${name} not a valid program. Expected owner ${BPF_LOADER_UPGRADEABLE.toString()}, got ${accountInfo.owner.toString()}` + ) + return false + } + } else { + // For non-program accounts, check against expected owner + if (!accountInfo.owner.equals(expectedOwner)) { + console.error( + `❌ ${name} owned by wrong program. Expected ${expectedOwner.toString()}, got ${accountInfo.owner.toString()}` + ) + return false + } + } + console.log(`✅ ${name} verified:`, address.toString()) + return true +} + +test('LzCompose -> swap_v2 via local validator', async () => { + const payer = Keypair.generate() + // Create a new provider with the funded payer. + const provider = new anchor.AnchorProvider(connection, new anchor.Wallet(payer), {}) + anchor.setProvider(provider) + const airdropSignature = await connection.requestAirdrop(payer.publicKey, 100e9) + // Wait 5 seconds for airdrop to be processed + await new Promise((resolve) => setTimeout(resolve, 5000)) + + // Wait for confirmation + await connection.confirmTransaction(airdropSignature) + + // Check if a mint exists + const mintInfo = await connection.getAccountInfo(USDE_MINT) + + if (mintInfo === null) { + console.log('Mint does not exist') + } else { + console.log('Mint exists') + // You can also check if it's a valid mint account + const isMint = mintInfo.owner.equals(TOKEN_PROGRAM_ID) + console.log('Is valid mint:', isMint) + } + + const program = new anchor.Program(idl as anchor.Idl, COMPOSER_ID, provider) + + const poolStatePda = new PublicKey('D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL') + const ammConfigPda = new PublicKey('9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x') + const observationPda = new PublicKey('Cv3s57YQzJRp988qvXPfZN5xX7BUFcgB1ycA9jzSrh2n') + const tickLowerPda = new PublicKey('J1jEbmdbtsfA26igi1ryQjejnYVQRpxnobZh9tgSaH1h') + const tickCurrentPda = new PublicKey('8AQHmKsoFh5Uh8bzYnnQ4Fx2QL2axPao9hjnt1YQBvdS') + const tickUpperPda = new PublicKey('3ZUDGLF7xw26gcXUhZK3tL4MU2VizCUkXoHdb2LQ4zDM') + const tickBitmapPda = new PublicKey('FHJHGbVtNoJdM5CSqPMQH8mn8D8pGfcN4JmLNqNMPBQu') + + const inputVaultPda = new PublicKey('5Nb7bdyi4jkMdA8Xqmt72HS1fpkGRQ573Gf8PJeGunsy') + const outputVaultPda = new PublicKey('sUfgz2jsDhN8qn1PrkWr9pVnUrLqqL1yB7WAnCj62Xz') + + const oftPda = new PublicKey('4x3oQtX4MhjTKGBeXDZbtTSLZ9cUWo5waN2UChAuthtS') + const endpointPda = new PublicKey('2uk9pQh3tB5ErV7LGQJcbWjb4KeJ2UJki5qJZ8QG56G3') + const [composerPda] = PublicKey.findProgramAddressSync([COMPOSER_SEED, oftPda.toBuffer()], COMPOSER_ID) + const [lzTypesPda] = PublicKey.findProgramAddressSync([LZ_TYPES_SEED, composerPda.toBuffer()], COMPOSER_ID) + console.log('composerPda', composerPda.toString()) + console.log('lzTypesPda', lzTypesPda.toString()) + + // after you've defined inputVaultPda & outputVaultPda + const [inVaultInfo, outVaultInfo] = await Promise.all([ + getAccount(connection, inputVaultPda), + getAccount(connection, outputVaultPda), + ]) + console.log( + `Pool vault balances: input=${inVaultInfo.amount} (decimals ${inVaultInfo}), ` + + `output=${outVaultInfo.amount} (decimals ${outVaultInfo})` + ) + + const composerUsdeAta = await getOrCreateAssociatedTokenAccount( + connection, + payer, + USDE_MINT, + composerPda, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID + ) + const userUsdcAta = await getOrCreateAssociatedTokenAccount( + connection, + payer, + USDC_MINT, + composerPda, + true, + undefined, + undefined, + TOKEN_PROGRAM_ID + ) + await new Promise((resolve) => setTimeout(resolve, 2000)) // wait 2 seconds + console.log('ATAs created') + + try { + const init = await program.methods + .initComposer({ + oftPda: oftPda, + endpointPda: endpointPda, + }) + .accounts({ + composer: composerPda, + lzComposeTypesAccounts: lzTypesPda, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + }) + .signers([payer]) + .rpc() + console.log('initComposer tx:', init) + const initTx = await program.methods + .initComposer({ + oftPda: oftPda, + endpointPda: endpointPda, + }) + .accounts({ + composer: composerPda, + lzComposeTypesAccounts: lzTypesPda, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + }) + .transaction() + console.log('initTx', initTx.instructions[0].data.toString('hex')) + console.log('initTx', initTx) + + // Save transaction data to a file + const fs = require('fs') + const txData = { + instructions: initTx.instructions.map((ix) => ({ + programId: ix.programId.toString(), + keys: ix.keys.map((key) => ({ + pubkey: key.pubkey.toString(), + isSigner: key.isSigner, + isWritable: key.isWritable, + })), + data: ix.data.toString('hex'), + })), + recentBlockhash: initTx.recentBlockhash, + feePayer: initTx.feePayer?.toString() || null, + } + fs.writeFileSync('init_tx_data.json', JSON.stringify(txData, null, 2)) + console.log('Transaction data saved to init_tx_data.json') + } catch (error) { + console.error('Failed to initialize composer:', error) + throw error + } + + const programInfo = await connection.getAccountInfo(COMPOSER_ID) + if (!programInfo) { + console.error('Program not found at address:', COMPOSER_ID.toString()) + throw new Error('Program not deployed') + } + console.log('Program data length:', programInfo.data.length) + + if (!programInfo.executable) { + console.error('Program is not marked as executable') + throw new Error('Program not executable') + } + + // Verify all accounts exist and are owned by the correct programs + console.log('\nVerifying account ownership:') + + const accountChecks = await Promise.all([ + verifyAccount(connection, RAYDIUM_CLMM_ID, BPF_LOADER_UPGRADEABLE, 'Raydium CLMM Program'), + verifyAccount(connection, poolStatePda, RAYDIUM_CLMM_ID, 'Pool State'), + verifyAccount(connection, ammConfigPda, RAYDIUM_CLMM_ID, 'AMM Config'), + verifyAccount(connection, observationPda, RAYDIUM_CLMM_ID, 'Observation State'), + verifyAccount(connection, tickLowerPda, RAYDIUM_CLMM_ID, 'Tick Array Lower'), + verifyAccount(connection, tickCurrentPda, RAYDIUM_CLMM_ID, 'Tick Array Current'), + verifyAccount(connection, tickUpperPda, RAYDIUM_CLMM_ID, 'Tick Array Upper'), + verifyAccount(connection, inputVaultPda, TOKEN_PROGRAM_ID, 'Input Vault'), + verifyAccount(connection, outputVaultPda, TOKEN_PROGRAM_ID, 'Output Vault'), + verifyAccount(connection, USDE_MINT, TOKEN_PROGRAM_ID, 'USDE Mint'), + verifyAccount(connection, USDC_MINT, TOKEN_PROGRAM_ID, 'USDC Mint'), + ]) + + if (accountChecks.some((check) => !check)) { + console.log('\nSome accounts are not properly initialized. Initializing Raydium pool...') + + // Here we'll need to initialize the Raydium pool + // This requires several steps: + // 1. Create the AMM Config if it doesn't exist + // 2. Create the Pool + // 3. Initialize tick arrays + + // We'll need to import the Raydium SDK + // const raydiumSDK = require('@raydium-io/raydium-sdk'); + + // TODO: Add pool initialization code + // For now, let's throw an error with instructions + throw new Error(` + Please initialize the Raydium pool first. You can do this by: + 1. Using an existing pool on devnet/mainnet instead of local + 2. Or initializing a new pool using the Raydium SDK + + For local testing, you might want to use these devnet addresses instead: + - Pool: https://solscan.io/account/D2bXjvT1xDiymTeX2mHqf9WGHkSVr2hPoHJiEg2jfVjL?cluster=devnet + - AMM Config: https://solscan.io/account/9iFER3bpjf1PTTCQCfTRu17EJgvsxo9pVyA9QWwEuX4x?cluster=devnet + + Would you like me to help you: + 1. Switch to using an existing devnet pool + 2. Or implement the pool initialization code? + `) + } + + console.log('\nAll accounts verified, proceeding with swap...') + + const amountLd = new anchor.BN(250_000) + const vaultBAcc = await getAccount(connection, outputVaultPda) + const vaultBBalance = new anchor.BN(vaultBAcc.amount.toString()) + console.log(`vaultB balance=${vaultBBalance.toString()} amountLd=${amountLd.toString()}`) + if (amountLd.gt(vaultBBalance)) { + throw new Error( + `Insufficient liquidity in vaultB: requested ${amountLd.toString()}, but only ${vaultBBalance.toString()} available` + ) + } + // ---------------------------------------------------- + // 2) build the 112‐byte payload: + // [0..32) ignored by our program + // [32..40) amount_ld + // [40..72) compose_from (we can re‐use oftPda here) + // [72..80) min_amount_out + // [80..112) receiver pubkey + // ---------------------------------------------------- + const minOut = new anchor.BN(1) + const message = Buffer.alloc(112) + const receiver = Keypair.generate() + const receiverUsdcAta = await getOrCreateAssociatedTokenAccount( + connection, + payer, // payer funds ATA creation + USDC_MINT, // mint + receiver.publicKey, // authority + false, // allow owner off curve + undefined, + undefined, + TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ) + amountLd.toArrayLike(Buffer, 'be', 8).copy(message, 32) + oftPda.toBuffer().copy(message, 40) + minOut.toArrayLike(Buffer, 'be', 8).copy(message, 72) + receiver.publicKey.toBuffer().copy(message, 80) + + const swapIx = await program.methods + .lzCompose({ + from: oftPda, + to: composerPda, + guid: new Array(32).fill(0), + index: 0, + message: Buffer.from(message), + extraData: Buffer.alloc(0), + }) + .accounts({ + composer: composerPda, + clmmProgram: RAYDIUM_CLMM_ID, + payer: payer.publicKey, + ammConfig: ammConfigPda, + poolState: poolStatePda, + inputTokenAccount: composerUsdeAta.address, + outputTokenAccount: userUsdcAta.address, + inputVault: inputVaultPda, + outputVault: outputVaultPda, + observationState: observationPda, + tokenProgram: TOKEN_PROGRAM_ID, + tokenProgram2022: TOKEN_2022_PROGRAM_ID, + memoProgram: MEMO_PROGRAM_ID, + inputVaultMint: USDE_MINT, + outputVaultMint: USDC_MINT, + tickBitmap: tickBitmapPda, + tickArrayLower: tickLowerPda, + tickArrayCurrent: tickCurrentPda, + tickArrayUpper: tickUpperPda, + toAddress: receiver.publicKey, + toTokenAccount: receiverUsdcAta.address, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + systemProgram: SystemProgram.programId, + rent: SYSVAR_RENT_PUBKEY, + }) + .signers([payer]) + .instruction() + + const tx = new Transaction().add(swapIx) + tx.feePayer = payer.publicKey + tx.recentBlockhash = (await provider.connection.getLatestBlockhash()).blockhash + tx.sign(payer) + // // --- INSERTED: dump every account key + owner to debug the mismatch --- + // console.log("🔍 Checking owners on every account in SwapV2…"); + // for (const { pubkey, isWritable, isSigner } of swapIx.keys) { + // const info = await connection.getAccountInfo(pubkey); + // console.log({ + // pubkey: pubkey.toString(), + // owner: info?.owner.toString() ?? "account not found", + // writable: isWritable, + // signer: isSigner, + // }); + // } + const sig = await provider.sendAndConfirm(tx, [payer]) + console.log('sig', sig) +}, 30000) diff --git a/examples/oft-solana-composer-library/test/foundry/MyOFT.t.sol b/examples/oft-solana-composer-library/test/foundry/MyOFT.t.sol new file mode 100644 index 000000000..f285c0a53 --- /dev/null +++ b/examples/oft-solana-composer-library/test/foundry/MyOFT.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +// Mock imports +import { OFTMock } from "../mocks/OFTMock.sol"; +import { ERC20Mock } from "../mocks/ERC20Mock.sol"; +import { OFTComposerMock } from "../mocks/OFTComposerMock.sol"; + +// OApp imports +import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol"; +import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; + +// OFT imports +import { IOFT, SendParam, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; +import { MessagingFee, MessagingReceipt } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol"; +import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol"; +import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol"; + +// OZ imports +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +// Forge imports +//import "forge-std/console.sol"; + +// DevTools imports +import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; + +contract MyOFTTest is TestHelperOz5 { + using OptionsBuilder for bytes; + + uint32 private aEid = 1; + uint32 private bEid = 2; + + OFTMock private aOFT; + OFTMock private bOFT; + + address private userA = address(0x1); + address private userB = address(0x2); + uint256 private initialBalance = 100 ether; + + function setUp() public virtual override { + vm.deal(userA, 1000 ether); + vm.deal(userB, 1000 ether); + + super.setUp(); + setUpEndpoints(2, LibraryType.UltraLightNode); + + aOFT = OFTMock( + _deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this))) + ); + + bOFT = OFTMock( + _deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this))) + ); + + // config and wire the ofts + address[] memory ofts = new address[](2); + ofts[0] = address(aOFT); + ofts[1] = address(bOFT); + this.wireOApps(ofts); + + // mint tokens + aOFT.mint(userA, initialBalance); + bOFT.mint(userB, initialBalance); + } + + function test_constructor() public { + assertEq(aOFT.owner(), address(this)); + assertEq(bOFT.owner(), address(this)); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + assertEq(aOFT.token(), address(aOFT)); + assertEq(bOFT.token(), address(bOFT)); + } + + function test_send_oft() public { + uint256 tokensToSend = 1 ether; + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + SendParam memory sendParam = SendParam( + bEid, + addressToBytes32(userB), + tokensToSend, + tokensToSend, + options, + "", + "" + ); + MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + vm.prank(userA); + aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this))); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend); + } + + function test_send_oft_compose_msg() public { + uint256 tokensToSend = 1 ether; + + OFTComposerMock composer = new OFTComposerMock(); + + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(200000, 0) + .addExecutorLzComposeOption(0, 500000, 0); + bytes memory composeMsg = hex"1234"; + SendParam memory sendParam = SendParam( + bEid, + addressToBytes32(address(composer)), + tokensToSend, + tokensToSend, + options, + composeMsg, + "" + ); + MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(address(composer)), 0); + + vm.prank(userA); + (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = aOFT.send{ value: fee.nativeFee }( + sendParam, + fee, + payable(address(this)) + ); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + // lzCompose params + uint32 dstEid_ = bEid; + address from_ = address(bOFT); + bytes memory options_ = options; + bytes32 guid_ = msgReceipt.guid; + address to_ = address(composer); + bytes memory composerMsg_ = OFTComposeMsgCodec.encode( + msgReceipt.nonce, + aEid, + oftReceipt.amountReceivedLD, + abi.encodePacked(addressToBytes32(userA), composeMsg) + ); + this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_); + + assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + assertEq(bOFT.balanceOf(address(composer)), tokensToSend); + + assertEq(composer.from(), from_); + assertEq(composer.guid(), guid_); + assertEq(composer.message(), composerMsg_); + assertEq(composer.executor(), address(this)); + assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test + } + + // TODO import the rest of oft tests? +} diff --git a/examples/oft-solana-composer-library/test/hardhat/MyOFT.test.ts b/examples/oft-solana-composer-library/test/hardhat/MyOFT.test.ts new file mode 100644 index 000000000..257bc5218 --- /dev/null +++ b/examples/oft-solana-composer-library/test/hardhat/MyOFT.test.ts @@ -0,0 +1,101 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { Contract, ContractFactory } from 'ethers' +import { deployments, ethers } from 'hardhat' + +import { Options } from '@layerzerolabs/lz-v2-utilities' + +describe('MyOFT Test', function () { + // Constant representing a mock Endpoint ID for testing purposes + const eidA = 1 + const eidB = 2 + // Declaration of variables to be used in the test suite + let MyOFT: ContractFactory + let EndpointV2Mock: ContractFactory + let ownerA: SignerWithAddress + let ownerB: SignerWithAddress + let endpointOwner: SignerWithAddress + let myOFTA: Contract + let myOFTB: Contract + let mockEndpointV2A: Contract + let mockEndpointV2B: Contract + + // Before hook for setup that runs once before all tests in the block + before(async function () { + // Contract factory for our tested contract + // + // We are using a derived contract that exposes a mint() function for testing purposes + MyOFT = await ethers.getContractFactory('MyOFTMock') + + // Fetching the first three signers (accounts) from Hardhat's local Ethereum network + const signers = await ethers.getSigners() + + ;[ownerA, ownerB, endpointOwner] = signers + + // The EndpointV2Mock contract comes from @layerzerolabs/test-devtools-evm-hardhat package + // and its artifacts are connected as external artifacts to this project + // + // Unfortunately, hardhat itself does not yet provide a way of connecting external artifacts, + // so we rely on hardhat-deploy to create a ContractFactory for EndpointV2Mock + // + // See https://github.com/NomicFoundation/hardhat/issues/1040 + const EndpointV2MockArtifact = await deployments.getArtifact('EndpointV2Mock') + EndpointV2Mock = new ContractFactory(EndpointV2MockArtifact.abi, EndpointV2MockArtifact.bytecode, endpointOwner) + }) + + // beforeEach hook for setup that runs before each test in the block + beforeEach(async function () { + // Deploying a mock LZEndpoint with the given Endpoint ID + mockEndpointV2A = await EndpointV2Mock.deploy(eidA) + mockEndpointV2B = await EndpointV2Mock.deploy(eidB) + + // Deploying two instances of MyOFT contract with different identifiers and linking them to the mock LZEndpoint + myOFTA = await MyOFT.deploy('aOFT', 'aOFT', mockEndpointV2A.address, ownerA.address) + myOFTB = await MyOFT.deploy('bOFT', 'bOFT', mockEndpointV2B.address, ownerB.address) + + // Setting destination endpoints in the LZEndpoint mock for each MyOFT instance + await mockEndpointV2A.setDestLzEndpoint(myOFTB.address, mockEndpointV2B.address) + await mockEndpointV2B.setDestLzEndpoint(myOFTA.address, mockEndpointV2A.address) + + // Setting each MyOFT instance as a peer of the other in the mock LZEndpoint + await myOFTA.connect(ownerA).setPeer(eidB, ethers.utils.zeroPad(myOFTB.address, 32)) + await myOFTB.connect(ownerB).setPeer(eidA, ethers.utils.zeroPad(myOFTA.address, 32)) + }) + + // A test case to verify token transfer functionality + it('should send a token from A address to B address via each OFT', async function () { + // Minting an initial amount of tokens to ownerA's address in the myOFTA contract + const initialAmount = ethers.utils.parseEther('100') + await myOFTA.mint(ownerA.address, initialAmount) + + // Defining the amount of tokens to send and constructing the parameters for the send operation + const tokensToSend = ethers.utils.parseEther('1') + + // Defining extra message execution options for the send operation + const options = Options.newOptions().addExecutorLzReceiveOption(200000, 0).toHex().toString() + + const sendParam = [ + eidB, + ethers.utils.zeroPad(ownerB.address, 32), + tokensToSend, + tokensToSend, + options, + '0x', + '0x', + ] + + // Fetching the native fee for the token send operation + const [nativeFee] = await myOFTA.quoteSend(sendParam, false) + + // Executing the send operation from myOFTA contract + await myOFTA.send(sendParam, [nativeFee, 0], ownerA.address, { value: nativeFee }) + + // Fetching the final token balances of ownerA and ownerB + const finalBalanceA = await myOFTA.balanceOf(ownerA.address) + const finalBalanceB = await myOFTB.balanceOf(ownerB.address) + + // Asserting that the final balances are as expected after the send operation + expect(finalBalanceA).eql(initialAmount.sub(tokensToSend)) + expect(finalBalanceB).eql(tokensToSend) + }) +}) diff --git a/examples/oft-solana-composer-library/test/mocks/ERC20Mock.sol b/examples/oft-solana-composer-library/test/mocks/ERC20Mock.sol new file mode 100644 index 000000000..6ce17e418 --- /dev/null +++ b/examples/oft-solana-composer-library/test/mocks/ERC20Mock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} diff --git a/examples/oft-solana-composer-library/test/mocks/OFTComposerMock.sol b/examples/oft-solana-composer-library/test/mocks/OFTComposerMock.sol new file mode 100644 index 000000000..fdd5c2426 --- /dev/null +++ b/examples/oft-solana-composer-library/test/mocks/OFTComposerMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol"; + +contract OFTComposerMock is IOAppComposer { + // default empty values for testing a lzCompose received message + address public from; + bytes32 public guid; + bytes public message; + address public executor; + bytes public extraData; + + function lzCompose( + address _from, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata /*_extraData*/ + ) external payable { + from = _from; + guid = _guid; + message = _message; + executor = _executor; + extraData = _message; + } +} diff --git a/examples/oft-solana-composer-library/test/mocks/OFTMock.sol b/examples/oft-solana-composer-library/test/mocks/OFTMock.sol new file mode 100644 index 000000000..cef8770f5 --- /dev/null +++ b/examples/oft-solana-composer-library/test/mocks/OFTMock.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; +import { SendParam } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol"; + +contract OFTMock is OFT { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) Ownable(_delegate) OFT(_name, _symbol, _lzEndpoint, _delegate) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } + + // @dev expose internal functions for testing purposes + function debit( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debit(msg.sender, _amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function debitView( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public view returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) { + return _removeDust(_amountLD); + } + + function toLD(uint64 _amountSD) public view returns (uint256 amountLD) { + return _toLD(_amountSD); + } + + function toSD(uint256 _amountLD) public view returns (uint64 amountSD) { + return _toSD(_amountLD); + } + + function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) { + return _credit(_to, _amountToCreditLD, _srcEid); + } + + function buildMsgAndOptions( + SendParam calldata _sendParam, + uint256 _amountToCreditLD + ) public view returns (bytes memory message, bytes memory options) { + return _buildMsgAndOptions(_sendParam, _amountToCreditLD); + } +} diff --git a/examples/oft-solana-composer-library/tsconfig.json b/examples/oft-solana-composer-library/tsconfig.json new file mode 100644 index 000000000..5fcf450fd --- /dev/null +++ b/examples/oft-solana-composer-library/tsconfig.json @@ -0,0 +1,13 @@ +{ + "exclude": ["node_modules"], + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/examples/oft-solana-composer-library/turbo.json b/examples/oft-solana-composer-library/turbo.json new file mode 100644 index 000000000..b38dc23a0 --- /dev/null +++ b/examples/oft-solana-composer-library/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "pipeline": { + "compile": { + "outputs": ["target/**"] + } + } +} diff --git a/examples/oft-solana-composer-library/txtx.yml b/examples/oft-solana-composer-library/txtx.yml new file mode 100644 index 000000000..3b1a8be5a --- /dev/null +++ b/examples/oft-solana-composer-library/txtx.yml @@ -0,0 +1,19 @@ +--- +name: oft-solana-composer-library +id: oft-solana-composer-library +runbooks: + - name: deployment + description: Deploy programs + location: runbooks/deployment +environments: + localnet: + network_id: localnet + rpc_api_url: http://127.0.0.1:8899 + payer_keypair_json: ./keypair.json + authority_keypair_json: ./keypair.json + devnet: + network_id: devnet + rpc_api_url: https://api.devnet.solana.com + payer_keypair_json: ./keypair.json + authority_keypair_json: ./keypair.json + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97cd1e61b..6483b3e62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1852,7 +1852,7 @@ importers: version: 3.0.75(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4) '@layerzerolabs/lz-solana-sdk-v2': specifier: 3.0.0 - version: 3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 @@ -2040,6 +2040,247 @@ importers: specifier: ^5.4.4 version: 5.5.3 + examples/oft-solana-composer-library: + dependencies: + '@raydium-io/raydium-sdk-v2': + specifier: 0.1.120-alpha + version: 0.1.120-alpha(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/spl-memo': + specifier: ^0.2.5 + version: 0.2.5(@solana/web3.js@1.95.8) + axios: + specifier: ^1.8.4 + version: 1.8.4(debug@4.3.7) + devDependencies: + '@coral-xyz/anchor': + specifier: ^0.29.0 + version: 0.29.0 + '@ethersproject/bytes': + specifier: ^5.7.0 + version: 5.7.0 + '@layerzerolabs/devtools': + specifier: ~0.4.8 + version: link:../../packages/devtools + '@layerzerolabs/devtools-evm': + specifier: ~1.0.6 + version: link:../../packages/devtools-evm + '@layerzerolabs/devtools-evm-hardhat': + specifier: ^2.0.9 + version: link:../../packages/devtools-evm-hardhat + '@layerzerolabs/devtools-solana': + specifier: ~1.0.8 + version: link:../../packages/devtools-solana + '@layerzerolabs/eslint-config-next': + specifier: ~2.3.39 + version: 2.3.44(typescript@5.5.3) + '@layerzerolabs/io-devtools': + specifier: ~0.1.16 + version: link:../../packages/io-devtools + '@layerzerolabs/lz-definitions': + specifier: ^3.0.75 + version: 3.0.81 + '@layerzerolabs/lz-evm-messagelib-v2': + specifier: ^3.0.75 + version: 3.0.75(@axelar-network/axelar-gmp-sdk-solidity@5.10.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@3.0.75)(@layerzerolabs/lz-evm-v1-0.7@3.0.75)(@openzeppelin/contracts-upgradeable@5.1.0)(@openzeppelin/contracts@5.1.0)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': + specifier: ^3.0.75 + version: 3.0.75(@openzeppelin/contracts-upgradeable@5.1.0)(@openzeppelin/contracts@5.1.0)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': + specifier: ^3.0.75 + version: 3.0.75(@openzeppelin/contracts-upgradeable@5.1.0)(@openzeppelin/contracts@5.1.0)(hardhat-deploy@0.12.4) + '@layerzerolabs/lz-solana-sdk-v2': + specifier: 3.0.0 + version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': + specifier: ^3.0.75 + version: 3.0.81 + '@layerzerolabs/metadata-tools': + specifier: ^0.4.1 + version: link:../../packages/metadata-tools + '@layerzerolabs/oapp-evm': + specifier: ^0.3.2 + version: link:../../packages/oapp-evm + '@layerzerolabs/oft-evm': + specifier: ^3.1.3 + version: link:../../packages/oft-evm + '@layerzerolabs/oft-v2-solana-sdk': + specifier: ^3.0.59 + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/prettier-config-next': + specifier: ^2.3.39 + version: 2.3.44 + '@layerzerolabs/protocol-devtools': + specifier: ^1.1.6 + version: link:../../packages/protocol-devtools + '@layerzerolabs/protocol-devtools-evm': + specifier: ~3.0.7 + version: link:../../packages/protocol-devtools-evm + '@layerzerolabs/protocol-devtools-solana': + specifier: ^4.0.9 + version: link:../../packages/protocol-devtools-solana + '@layerzerolabs/solhint-config': + specifier: ^3.0.12 + version: 3.0.12(typescript@5.5.3) + '@layerzerolabs/test-devtools-evm-foundry': + specifier: ~6.0.3 + version: link:../../packages/test-devtools-evm-foundry + '@layerzerolabs/test-devtools-evm-hardhat': + specifier: ~0.5.2 + version: link:../../packages/test-devtools-evm-hardhat + '@layerzerolabs/toolbox-foundry': + specifier: ~0.1.12 + version: link:../../packages/toolbox-foundry + '@layerzerolabs/toolbox-hardhat': + specifier: ~0.6.9 + version: link:../../packages/toolbox-hardhat + '@layerzerolabs/ua-devtools': + specifier: ~3.0.6 + version: link:../../packages/ua-devtools + '@layerzerolabs/ua-devtools-evm': + specifier: ~5.0.7 + version: link:../../packages/ua-devtools-evm + '@layerzerolabs/ua-devtools-evm-hardhat': + specifier: ~6.0.11 + version: link:../../packages/ua-devtools-evm-hardhat + '@layerzerolabs/ua-devtools-solana': + specifier: ~4.1.2 + version: link:../../packages/ua-devtools-solana + '@metaplex-foundation/mpl-token-metadata': + specifier: ^3.2.1 + version: 3.2.1(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/mpl-toolbox': + specifier: ^0.9.4 + version: 0.9.4(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi': + specifier: ^0.9.2 + version: 0.9.2 + '@metaplex-foundation/umi-bundle-defaults': + specifier: ^0.9.2 + version: 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-eddsa-web3js': + specifier: ^0.9.2 + version: 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-public-keys': + specifier: ^0.8.9 + version: 0.8.9 + '@metaplex-foundation/umi-web3js-adapters': + specifier: ^0.9.2 + version: 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.5 + version: 3.0.5(ethers@5.7.2)(hardhat@2.22.12) + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.12) + '@nomiclabs/hardhat-waffle': + specifier: ^2.0.6 + version: 2.0.6(@nomiclabs/hardhat-ethers@2.2.3)(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10)(ethers@5.7.2)(hardhat@2.22.12) + '@openzeppelin/contracts': + specifier: ^5.0.2 + version: 5.1.0 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.2 + version: 5.1.0(@openzeppelin/contracts@5.1.0) + '@rushstack/eslint-patch': + specifier: ^1.7.0 + version: 1.7.0 + '@solana-developers/helpers': + specifier: ~2.8.1 + version: 2.8.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/spl-token': + specifier: ^0.4.8 + version: 0.4.12(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': + specifier: ~1.95.8 + version: 1.95.8 + '@sqds/sdk': + specifier: ^2.0.4 + version: 2.0.4 + '@swc/core': + specifier: ^1.4.0 + version: 1.4.0 + '@swc/jest': + specifier: ^0.2.36 + version: 0.2.36(@swc/core@1.4.0) + '@types/chai': + specifier: ^4.3.11 + version: 4.3.20 + '@types/jest': + specifier: ^29.5.12 + version: 29.5.12 + '@types/mocha': + specifier: ^10.0.6 + version: 10.0.6 + '@types/node': + specifier: ~18.18.14 + version: 18.18.14 + bs58: + specifier: ^6.0.0 + version: 6.0.0 + chai: + specifier: ^4.4.1 + version: 4.5.0 + concurrently: + specifier: ~9.1.0 + version: 9.1.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + eslint: + specifier: ^8.55.0 + version: 8.57.1 + eslint-plugin-jest-extended: + specifier: ~2.0.0 + version: 2.0.0(eslint@8.57.1)(typescript@5.5.3) + ethereumjs-util: + specifier: ^7.1.5 + version: 7.1.5 + ethers: + specifier: ^5.7.2 + version: 5.7.2 + exponential-backoff: + specifier: ~3.1.1 + version: 3.1.1 + fp-ts: + specifier: ^2.16.2 + version: 2.16.2 + hardhat: + specifier: ^2.22.10 + version: 2.22.12(ts-node@10.9.2)(typescript@5.5.3) + hardhat-contract-sizer: + specifier: ^2.10.0 + version: 2.10.0(hardhat@2.22.12) + hardhat-deploy: + specifier: ^0.12.1 + version: 0.12.4 + hardhat-deploy-ethers: + specifier: ^0.4.2 + version: 0.4.2(@nomicfoundation/hardhat-ethers@3.0.5)(hardhat-deploy@0.12.4)(hardhat@2.22.12) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + litesvm: + specifier: ^0.2.0 + version: 0.2.0 + mocha: + specifier: ^10.2.0 + version: 10.2.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + solhint: + specifier: ^4.1.1 + version: 4.1.1(typescript@5.5.3) + solidity-bytes-utils: + specifier: ^0.8.2 + version: 0.8.2 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + typescript: + specifier: ^5.4.4 + version: 5.5.3 + examples/oft-upgradeable: devDependencies: '@babel/core': @@ -3352,7 +3593,7 @@ importers: version: 3.3.0 '@ton/ton': specifier: npm:@layerzerolabs/ton@15.2.0-rc.3 - version: /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) + version: /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) p-memoize: specifier: ~4.0.4 version: 4.0.4 @@ -4060,7 +4301,7 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.0 - version: 3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 @@ -4687,7 +4928,7 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.59 - version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 @@ -5582,7 +5823,7 @@ packages: resolution: {integrity: sha512-kJsoy4fAPTOhzVr7Vwq8s/AUg6BQiJDa7WOqRzev4zsuIS3+JCuIZ6vUd7UBsjnxtmguJJulMRs9qWCzVBt2XA==} engines: {node: '>=15.10.0'} dependencies: - axios: 1.7.4(debug@4.3.7) + axios: 1.7.4 got: 11.8.6 transitivePeerDependencies: - debug @@ -7418,7 +7659,7 @@ packages: '@ledgerhq/hw-transport-webhid': 6.30.0 '@ledgerhq/hw-transport-webusb': 6.29.4 '@mysten/bcs': 1.2.0 - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) bech32: 2.0.0 bignumber.js: 9.1.2 bip32: 5.0.0-rc.0(typescript@5.5.3) @@ -8010,29 +8251,41 @@ packages: /@layerzerolabs/lz-core@3.0.81: resolution: {integrity: sha512-fY5KOwXEl7V38Nah5AmRsfExy26pTs9e1+5TOTAmvGHScTTOLpqMy+JRVTGMyf/wSbDGmtpysHJy6tGODaIdAw==} - /@layerzerolabs/lz-corekit-solana@3.0.0: - resolution: {integrity: sha512-vJtDiS7QM77BN/VuZJsM/xH6ZJmUY3vDlHOmRMHsjHf5sfXqMafNL4+FKCRIijPVPGR0DCJARiWrRtgZchv9+w==} + /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} dependencies: - '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-core': 3.0.66 + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 '@solana/web3.js': 1.95.8 bip39: 3.1.0 ed25519-hd-key: 1.3.0 - mem: 8.1.1 tiny-invariant: 1.3.3 transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' - bufferutil + - chai + - debug - encoding + - supports-color + - typescript - utf-8-validate dev: true - /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): - resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} + /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-16rLQA33OyQlfpv8UX4mNnby8XJGNXJEy5okASQJQv3am+N7jLW11dvZxaoSFAlie//w9TbaNAAZe1iuwMLJ6w==} dependencies: - '@layerzerolabs/lz-core': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@metaplex-foundation/umi': 0.9.2 '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) @@ -8043,6 +8296,7 @@ packages: '@solana/web3.js': 1.95.8 bip39: 3.1.0 ed25519-hd-key: 1.3.0 + memoizee: 0.4.17 tiny-invariant: 1.3.3 transitivePeerDependencies: - '@jest/globals' @@ -8058,11 +8312,11 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-corekit-solana@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): - resolution: {integrity: sha512-KsstEb0oZnMWSU0sng/8dUlAFv6cahS+1caLDDaEm0c8G6JmzOmfAg4LYYNKsxRM9uzc3FvVq45DCD8XmBzhQQ==} + /@layerzerolabs/lz-corekit-solana@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-16rLQA33OyQlfpv8UX4mNnby8XJGNXJEy5okASQJQv3am+N7jLW11dvZxaoSFAlie//w9TbaNAAZe1iuwMLJ6w==} dependencies: - '@layerzerolabs/lz-core': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@metaplex-foundation/umi': 0.9.2 '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) @@ -8073,6 +8327,7 @@ packages: '@solana/web3.js': 1.95.8 bip39: 3.1.0 ed25519-hd-key: 1.3.0 + memoizee: 0.4.17 tiny-invariant: 1.3.3 transitivePeerDependencies: - '@jest/globals' @@ -8569,8 +8824,8 @@ packages: /@layerzerolabs/lz-foundation@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): resolution: {integrity: sha512-0WDrlJ+DZ3GGxKPgcConoxQdTB7icpoOYjeTcwmBtJcUnXIE7G3vxzKUAKsUccGegFdgHMEj1K4D+3NPZbNQ2A==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 @@ -8592,7 +8847,7 @@ packages: /@layerzerolabs/lz-foundation@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 @@ -8612,11 +8867,11 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-foundation@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-foundation@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 @@ -8638,7 +8893,7 @@ packages: /@layerzerolabs/lz-foundation@3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-ZJC/PRPhF5LHt32dGIj3HGNAD+ReGNYsdLzfR2P8JMuAph1T0W3FsniVjjDMEVxyKOrXVHOt0GFg9o56lYCcEw==} dependencies: - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@layerzerolabs/lz-utilities': 3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@noble/ed25519': 1.7.3 '@noble/hashes': 1.7.1 @@ -8658,6 +8913,56 @@ packages: - utf-8-validate dev: false + /@layerzerolabs/lz-foundation@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-rGbjBzm7qeu1OmdaxpSFWsh8QulZGk1DiB+Sow9iUDJPkJKXqAjm40vQ1gj5KQiETGz/hAxOjgmilEjg8MFduA==} + dependencies: + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@noble/ed25519': 1.7.3 + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.2.4 + bech32: 2.0.0 + memoizee: 0.4.17 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-foundation@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-rGbjBzm7qeu1OmdaxpSFWsh8QulZGk1DiB+Sow9iUDJPkJKXqAjm40vQ1gj5KQiETGz/hAxOjgmilEjg8MFduA==} + dependencies: + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@noble/ed25519': 1.7.3 + '@noble/hashes': 1.7.1 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.2.4 + bech32: 2.0.0 + memoizee: 0.4.17 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/lz-foundation@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-rGbjBzm7qeu1OmdaxpSFWsh8QulZGk1DiB+Sow9iUDJPkJKXqAjm40vQ1gj5KQiETGz/hAxOjgmilEjg8MFduA==} dependencies: @@ -8708,6 +9013,64 @@ packages: resolution: {integrity: sha512-kk/0pPKuNzIXL1Fh8caH3daYA/Lv2OIPeD7z/VIBANdyCBiYgncF6QK8muoFJ1iIvoQSnpAmgwgww1DlnYa2NA==} dev: true + /@layerzerolabs/lz-serdes@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-edzKOV0sA5+jbqzHYFMX4XOPj7nhnaiZVxl1DbsLlboTBVX8lyJlCmxrEM31+iWB47cuBmyJdO3UkDpaRuQbLQ==} + dependencies: + '@coral-xyz/anchor': 0.29.0 + '@layerzerolabs/lz-aptos-sdk-v1': 3.0.81 + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/tron-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 + tiny-invariant: 1.3.3 + tronweb: 5.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-serdes@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-edzKOV0sA5+jbqzHYFMX4XOPj7nhnaiZVxl1DbsLlboTBVX8lyJlCmxrEM31+iWB47cuBmyJdO3UkDpaRuQbLQ==} + dependencies: + '@coral-xyz/anchor': 0.29.0 + '@layerzerolabs/lz-aptos-sdk-v1': 3.0.81 + '@layerzerolabs/lz-core': 3.0.81 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/tron-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 + tiny-invariant: 1.3.3 + tronweb: 5.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/lz-serdes@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-edzKOV0sA5+jbqzHYFMX4XOPj7nhnaiZVxl1DbsLlboTBVX8lyJlCmxrEM31+iWB47cuBmyJdO3UkDpaRuQbLQ==} dependencies: @@ -8736,7 +9099,7 @@ packages: - typescript - utf-8-validate - /@layerzerolabs/lz-solana-sdk-v2@3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} dependencies: '@ethersproject/abi': 5.7.0 @@ -8746,9 +9109,9 @@ packages: '@ethersproject/keccak256': 5.7.0 '@ethersproject/sha2': 5.7.0 '@ethersproject/solidity': 5.7.0 - '@layerzerolabs/lz-corekit-solana': 3.0.0 - '@layerzerolabs/lz-definitions': 3.0.75 - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/solita': 0.20.1 @@ -8759,7 +9122,13 @@ packages: ethereumjs-util: 7.1.5 tiny-invariant: 1.3.3 transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' - bufferutil + - chai + - debug - encoding - fastestsmallesttextencoderdecoder - supports-color @@ -8767,15 +9136,52 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.0(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/solidity': 5.7.0 + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/solita': 0.20.1 + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + bs58: 5.0.0 + ethereumjs-util: 7.1.5 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} dependencies: - '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 - '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@layerzerolabs/lz-serdes': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -8803,15 +9209,51 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): - resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} + /@layerzerolabs/lz-solana-sdk-v2@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-xUogDY3IhPm65R+7DisfAPGofDTb38gsCW8k+V290zbOmTRSWKVFMsZlNxrVMH6+7jsKsMYQ/2bqMCqlJKkNEQ==} dependencies: - '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 - '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-serdes': 3.0.66 - '@layerzerolabs/lz-utilities': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-foundation': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-serdes': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + bs58: 5.0.0 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/lz-solana-sdk-v2@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-xUogDY3IhPm65R+7DisfAPGofDTb38gsCW8k+V290zbOmTRSWKVFMsZlNxrVMH6+7jsKsMYQ/2bqMCqlJKkNEQ==} + dependencies: + '@layerzerolabs/lz-corekit-solana': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@layerzerolabs/lz-foundation': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-serdes': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -8869,35 +9311,94 @@ packages: - chai - debug - encoding - - fastestsmallesttextencoderdecoder + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + + /@layerzerolabs/lz-ton-sdk-v2@3.0.27: + resolution: {integrity: sha512-AU1uOzmLjWvyHdJGTo689bXLsCS/QAmfQSjvZ4544muLfpGVLl3l6lOl8DwmN1UQuwKKK94C5rEvoElVUYf0zQ==} + dependencies: + '@ton/core': 0.59.0(@ton/crypto@3.3.0) + '@ton/crypto': 3.3.0 + bigint-buffer: 1.1.5 + crc-32: 1.2.2 + ethers: 5.7.2 + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@layerzerolabs/lz-utilities@3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@initia/initia.js': 0.2.32(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@solana/web3.js': 1.95.8 + '@ton/core': 0.59.0(@ton/crypto@3.3.0) + '@ton/crypto': 3.3.0 + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@jest/globals@29.7.0)(@layerzerolabs/ton@15.2.0-rc.3)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 + winston: 3.11.0 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + + /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@initia/initia.js': 0.2.32(typescript@5.5.3) + '@layerzerolabs/lz-definitions': 3.0.81 + '@solana/web3.js': 1.95.8 + '@ton/core': 0.59.0(@ton/crypto@3.3.0) + '@ton/crypto': 3.3.0 + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + aptos: 1.21.0 + bip39: 3.1.0 + ed25519-hd-key: 1.3.0 + ethers: 5.7.2 + memoizee: 0.4.17 + winston: 3.11.0 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding - supports-color - typescript - utf-8-validate - - /@layerzerolabs/lz-ton-sdk-v2@3.0.27: - resolution: {integrity: sha512-AU1uOzmLjWvyHdJGTo689bXLsCS/QAmfQSjvZ4544muLfpGVLl3l6lOl8DwmN1UQuwKKK94C5rEvoElVUYf0zQ==} - dependencies: - '@ton/core': 0.59.0(@ton/crypto@3.3.0) - '@ton/crypto': 3.3.0 - bigint-buffer: 1.1.5 - crc-32: 1.2.2 - ethers: 5.7.2 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate dev: true - /@layerzerolabs/lz-utilities@3.0.66(@jest/globals@29.7.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} dependencies: '@ethersproject/bytes': 5.7.0 '@initia/initia.js': 0.2.32(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@jest/globals@29.7.0)(@layerzerolabs/ton@15.2.0-rc.3)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 ed25519-hd-key: 1.3.0 @@ -8916,23 +9417,26 @@ packages: - supports-color - typescript - utf-8-validate + dev: true - /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): - resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-QDGV2DAP8qbbAGTOcbsREr6CK8LsmPzHu9q96i7dXTXCvUq9FsibjjFVYfy3ctVMngVbUVOK9fJ+KtOnYLunCA==} dependencies: '@ethersproject/bytes': 5.7.0 '@initia/initia.js': 0.2.32(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 + dayjs: 1.11.13 ed25519-hd-key: 1.3.0 ethers: 5.7.2 memoizee: 0.4.17 - winston: 3.11.0 + picocolors: 1.0.0 + pino: 8.21.0 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -8947,22 +9451,24 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-utilities@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): - resolution: {integrity: sha512-PDc3YAdUcNCnFGbQ7oDg1Pja/wt0O+FyRbhwoyTBQdqjNT/hG35KI7sz1yZgAQG0IItzgqKGI/5n4LKMvjZH/Q==} + /@layerzerolabs/lz-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): + resolution: {integrity: sha512-QDGV2DAP8qbbAGTOcbsREr6CK8LsmPzHu9q96i7dXTXCvUq9FsibjjFVYfy3ctVMngVbUVOK9fJ+KtOnYLunCA==} dependencies: '@ethersproject/bytes': 5.7.0 '@initia/initia.js': 0.2.32(typescript@5.5.3) - '@layerzerolabs/lz-definitions': 3.0.75 + '@layerzerolabs/lz-definitions': 3.0.81 '@solana/web3.js': 1.95.8 '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) aptos: 1.21.0 bip39: 3.1.0 + dayjs: 1.11.13 ed25519-hd-key: 1.3.0 ethers: 5.7.2 memoizee: 0.4.17 - winston: 3.11.0 + picocolors: 1.0.0 + pino: 8.21.0 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -9043,8 +9549,8 @@ packages: dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -9074,7 +9580,38 @@ packages: dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) + '@solana/web3.js': 1.95.8 + bn.js: 5.2.1 + dotenv: 16.4.7 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - fastestsmallesttextencoderdecoder + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-ijbvj6/Gc4O4WLHfqnrBuKUtIhpaYws/ORj2apZFT1RKSgAX8CCJ9aZmn0ClamEG98i+PpXoroPPU46LMOMZyA==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.75 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -9104,9 +9641,9 @@ packages: resolution: {integrity: sha512-ijbvj6/Gc4O4WLHfqnrBuKUtIhpaYws/ORj2apZFT1RKSgAX8CCJ9aZmn0ClamEG98i+PpXoroPPU46LMOMZyA==} dependencies: '@ethersproject/bytes': 5.7.0 - '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -9115,7 +9652,7 @@ packages: '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@solana/web3.js': 1.95.8 bn.js: 5.2.1 - dotenv: 16.4.5 + dotenv: 16.4.7 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -9137,7 +9674,7 @@ packages: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.66(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@layerzerolabs/lz-solana-sdk-v2': 3.0.81(@types/node@18.18.14)(chai@4.5.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) - '@layerzerolabs/lz-v2-utilities': 3.0.75 + '@layerzerolabs/lz-v2-utilities': 3.0.81 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 '@metaplex-foundation/umi': 0.9.2 @@ -9146,7 +9683,7 @@ packages: '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.8) '@solana/web3.js': 1.95.8 bn.js: 5.2.1 - dotenv: 16.4.5 + dotenv: 16.4.7 transitivePeerDependencies: - '@jest/globals' - '@swc/core' @@ -9240,7 +9777,7 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@jest/globals@29.7.0)(@ton/core@0.59.0)(chai@4.5.0) - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -9268,7 +9805,7 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@ton/core@0.59.0)(chai@4.4.1) - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -9286,7 +9823,7 @@ packages: - typescript dev: true - /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-Aq7htcWn31DxSYs8l0sJcQ76uAJRPy50lg6M2G+qNLSXpO01sPcUSb8oK/TmCKo+S+rDIFWUNKFjT0B/rll1YQ==} peerDependencies: '@ton/core': '>=0.59.0' @@ -9297,7 +9834,7 @@ packages: '@ton/crypto': 3.3.0 '@ton/sandbox': 0.22.0(@ton/core@0.59.0)(@ton/crypto@3.3.0) '@ton/test-utils': 0.4.2(@jest/globals@29.7.0)(@ton/core@0.59.0)(chai@4.5.0) - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) dataloader: 2.2.2 symbol.inspect: 1.0.1 teslabot: 1.5.0 @@ -9314,6 +9851,48 @@ packages: - supports-color - typescript + /@layerzerolabs/tron-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3): + resolution: {integrity: sha512-bWv3GJJ6J/Wu79zlYEe6Cqv7UwzxC8tkA4rcHv6REnrbR/7iEaLcqLrVA/G1KWhOiUj5Rjqdws0bFpjJHdxXxQ==} + dependencies: + '@ethersproject/providers': 5.7.2 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.4.1)(typescript@5.5.3) + ethers: 5.7.2 + tronweb: 5.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + + /@layerzerolabs/tron-utilities@3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + resolution: {integrity: sha512-bWv3GJJ6J/Wu79zlYEe6Cqv7UwzxC8tkA4rcHv6REnrbR/7iEaLcqLrVA/G1KWhOiUj5Rjqdws0bFpjJHdxXxQ==} + dependencies: + '@ethersproject/providers': 5.7.2 + '@layerzerolabs/lz-utilities': 3.0.81(@swc/core@1.4.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) + ethers: 5.7.2 + tronweb: 5.3.3 + transitivePeerDependencies: + - '@jest/globals' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - chai + - debug + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /@layerzerolabs/tron-utilities@3.0.81(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3): resolution: {integrity: sha512-bWv3GJJ6J/Wu79zlYEe6Cqv7UwzxC8tkA4rcHv6REnrbR/7iEaLcqLrVA/G1KWhOiUj5Rjqdws0bFpjJHdxXxQ==} dependencies: @@ -9470,7 +10049,7 @@ packages: '@ethersproject/address': 5.7.0 '@matterlabs/hardhat-zksync-solc': 1.2.5(hardhat@2.22.12) '@nomicfoundation/hardhat-verify': 2.0.11(hardhat@2.22.12) - axios: 1.7.4(debug@4.3.7) + axios: 1.8.4(debug@4.3.7) cbor: 9.0.2 chai: 4.5.0 chalk: 4.1.2 @@ -10175,7 +10754,7 @@ packages: dependencies: amazon-cognito-identity-js: 6.3.12 async-retry: 1.3.3 - axios: 1.7.4(debug@4.3.5) + axios: 1.8.4(debug@4.3.5) lodash: 4.17.21 node-fetch: 2.7.0 transitivePeerDependencies: @@ -10304,6 +10883,30 @@ packages: /@protobufjs/utf8@1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + /@raydium-io/raydium-sdk-v2@0.1.120-alpha(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): + resolution: {integrity: sha512-53YSohrCexzkHubjlohcP8LPdWJXD75nS5NS11iW0qWraWHMJ/RUBDzMHk02RCMyQj7k+SBIsqrlZimhniBdJw==} + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/spl-token': 0.4.12(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) + '@solana/web3.js': 1.95.8 + axios: 1.8.4(debug@4.3.7) + big.js: 6.2.2 + bn.js: 5.2.1 + dayjs: 1.11.13 + decimal.js-light: 2.5.1 + jsonfile: 6.1.0 + lodash: 4.17.21 + toformat: 2.0.0 + tsconfig-paths: 4.2.0 + transitivePeerDependencies: + - bufferutil + - debug + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + dev: false + /@resolver-engine/core@0.3.3: resolution: {integrity: sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ==} dependencies: @@ -10472,7 +11075,7 @@ packages: '@ethersproject/solidity': 5.7.0 '@safe-global/safe-deployments': 1.33.0 ethereumjs-util: 7.1.5 - semver: 7.6.0 + semver: 7.6.3 web3: 1.10.4 web3-core: 1.10.4 web3-utils: 1.10.4 @@ -10486,6 +11089,7 @@ packages: /@safe-global/safe-core-sdk-types@2.3.0: resolution: {integrity: sha512-dU0KkDV1KJNf11ajbUjWiSi4ygdyWfhk1M50lTJWUdCn1/2Bsb/hICM8LoEk6DCoFumxaoCet02SmYakXsW2CA==} + deprecated: 'WARNING: This project has been renamed to @safe-global/types-kit. Please, migrate from @safe-global/safe-core-sdk-types@5.1.0 to @safe-global/types-kit@1.0.0.' dependencies: '@ethersproject/bignumber': 5.7.0 '@ethersproject/contracts': 5.7.0 @@ -10499,7 +11103,7 @@ packages: /@safe-global/safe-deployments@1.33.0: resolution: {integrity: sha512-G9qGMsha6idMnDuk98dE//inQL09w97hcQ5ZTdSWIHCzJ9mFdN0K4DH2afjZOwdt+Y4g8gZmY3z+kR38MPEToQ==} dependencies: - semver: 7.6.2 + semver: 7.6.3 /@scure/base@1.1.5: resolution: {integrity: sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==} @@ -10949,6 +11553,16 @@ packages: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + /@solana/spl-memo@0.2.5(@solana/web3.js@1.95.8): + resolution: {integrity: sha512-0Zx5t3gAdcHlRTt2O3RgGlni1x7vV7Xq7j4z9q8kKOMgU03PyoTbFQ/BSYCcICHzkaqD7ZxAiaJ6dlXolg01oA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.91.6 + dependencies: + '@solana/web3.js': 1.95.8 + buffer: 6.0.3 + dev: false + /@solana/spl-token-group@0.0.5(@solana/web3.js@1.95.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==} engines: {node: '>=16'} @@ -10974,7 +11588,6 @@ packages: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - typescript - dev: false /@solana/spl-token-group@0.0.7(@solana/web3.js@1.98.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==} @@ -11074,7 +11687,6 @@ packages: - fastestsmallesttextencoderdecoder - typescript - utf-8-validate - dev: false /@solana/spl-token@0.4.12(@solana/web3.js@1.98.0)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-K6CxzSoO1vC+WBys25zlSDaW0w4UFZO/IvEZquEI35A/PjqXNQHeVigmDCZYEJfESvYarKwsr8tYr/29lPtvaw==} @@ -11416,10 +12028,10 @@ packages: '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 '@ton/tolk-js': 0.6.0 - '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(typescript@5.5.3) + '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@layerzerolabs/ton@15.2.0-rc.3)(@swc/core@1.4.0)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@tonconnect/sdk': 2.2.0 arg: 5.0.2 - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) chalk: 4.1.2 dotenv: 16.4.7 inquirer: 8.2.6 @@ -11450,7 +12062,7 @@ packages: '@ton/ton': /@layerzerolabs/ton@15.2.0-rc.3(@jest/globals@29.7.0)(@layerzerolabs/ton@15.2.0-rc.3)(@ton/core@0.59.0)(@ton/crypto@3.3.0)(@types/node@18.18.14)(chai@4.5.0)(typescript@5.5.3) '@tonconnect/sdk': 2.2.0 arg: 5.0.2 - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) chalk: 4.1.2 dotenv: 16.4.7 inquirer: 8.2.6 @@ -11668,7 +12280,6 @@ packages: resolution: {integrity: sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==} dependencies: '@types/node': 18.18.14 - dev: true /@types/cacheable-request@6.0.3: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} @@ -12686,21 +13297,12 @@ packages: /axios@0.25.0: resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} dependencies: - follow-redirects: 1.15.6(debug@4.3.5) + follow-redirects: 1.15.9(debug@4.3.7) transitivePeerDependencies: - debug dev: true - /axios@1.7.4(debug@4.3.5): - resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} - dependencies: - follow-redirects: 1.15.9(debug@4.3.5) - form-data: 4.0.1 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - /axios@1.7.4(debug@4.3.7): + /axios@1.7.4: resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} dependencies: follow-redirects: 1.15.9(debug@4.3.7) @@ -12709,16 +13311,16 @@ packages: transitivePeerDependencies: - debug - /axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + /axios@1.8.4(debug@4.3.5): + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} dependencies: - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9(debug@4.3.5) form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - /axios@1.8.4: + /axios@1.8.4(debug@4.3.7): resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} dependencies: follow-redirects: 1.15.9(debug@4.3.7) @@ -12833,6 +13435,10 @@ packages: is-windows: 1.0.2 dev: true + /big.js@6.2.2: + resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} + dev: false + /bigint-buffer@1.1.5: resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} engines: {node: '>= 10.0.0'} @@ -13969,6 +14575,10 @@ packages: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} + /decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + dev: false + /decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} @@ -15213,7 +15823,7 @@ packages: optional: true dependencies: '@solidity-parser/parser': 0.14.5 - axios: 1.7.9 + axios: 1.8.4(debug@4.3.7) cli-table3: 0.5.1 colors: 1.4.0 ethereum-cryptography: 1.2.0 @@ -15249,7 +15859,7 @@ packages: resolution: {integrity: sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==} dependencies: bn.js: 4.12.0 - elliptic: 6.5.4 + elliptic: 6.6.1 xhr-request-promise: 0.1.3 /ethereum-bloom-filters@1.0.10: @@ -18222,6 +18832,68 @@ packages: wrap-ansi: 9.0.0 dev: true + /litesvm-darwin-arm64@0.2.0: + resolution: {integrity: sha512-fqGE7Z6iT+5Xob9o1xs0N7Tf3c1meJxDERf7Z6T7Loqm0seyUsfjM0gTOvMJHiWvvnOx+qIo/Cjhtp84Kb9v8w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /litesvm-darwin-universal@0.2.0: + resolution: {integrity: sha512-CNgVrQ9tknv8JbtpmOO0SNXuOQj769A3ufWHrMFEBuFhCr9CJ6RQI9a/HxPWjMHTC7EzlcF8klMupPYpqM8KGA==} + engines: {node: '>= 10'} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /litesvm-darwin-x64@0.2.0: + resolution: {integrity: sha512-blRxD4y0FIx3E27Iv7oX0uz9x3v9KPD+++HLatBx5K5bh57qrAStUINsgT6MchYrxxOE0T2xktJaiSQSEMxWSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /litesvm-linux-x64-gnu@0.2.0: + resolution: {integrity: sha512-2PQcLvn5c8GLGPp/vLCFaladWe61wbtwwTlIMLEfPeU5f88jBPOaEryO85+0FHZS1awbT97caa55sFDpAO4CWw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /litesvm-linux-x64-musl@0.2.0: + resolution: {integrity: sha512-9vAYFIicGjZIlwoCBii6yJmfIUVS4cdF0CxoAL+DGexTBLECk+Tr+WEFEXsvsYe4s81jxP04TTiUnasj/EaIcw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /litesvm@0.2.0: + resolution: {integrity: sha512-75+ZMkSFY5ynI3S+vCMnZv1wcILg5iNEj21B+XE/G3/P2dfTPj+rbdNM1q3eLFdlnXdewhiB4BLypKsBnQTSOw==} + engines: {node: '>= 10'} + dependencies: + '@solana/web3.js': 1.95.8 + bs58: 4.0.1 + optionalDependencies: + litesvm-darwin-arm64: 0.2.0 + litesvm-darwin-universal: 0.2.0 + litesvm-darwin-x64: 0.2.0 + litesvm-linux-x64-gnu: 0.2.0 + litesvm-linux-x64-musl: 0.2.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + /load-json-file@1.1.0: resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} engines: {node: '>=0.10.0'} @@ -18374,6 +19046,7 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 + dev: true /lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} @@ -18412,6 +19085,7 @@ packages: engines: {node: '>=6'} dependencies: p-defer: 1.0.0 + dev: false /markdown-table@1.1.3: resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==} @@ -18436,14 +19110,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - /mem@8.1.1: - resolution: {integrity: sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==} - engines: {node: '>=10'} - dependencies: - map-age-cleaner: 0.1.3 - mimic-fn: 3.1.0 - dev: true - /memdown@5.1.0: resolution: {integrity: sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==} engines: {node: '>=6'} @@ -18561,6 +19227,7 @@ packages: /mimic-fn@3.1.0: resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} engines: {node: '>=8'} + dev: false /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} @@ -18674,7 +19341,7 @@ packages: engines: {node: '>=4'} deprecated: This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that. dependencies: - mkdirp: 1.0.4 + mkdirp: 3.0.1 /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} @@ -18686,6 +19353,7 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + dev: true /mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} @@ -18696,7 +19364,6 @@ packages: resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} hasBin: true - dev: true /mnemonist@0.38.5: resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} @@ -19150,6 +19817,7 @@ packages: /p-defer@1.0.0: resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} engines: {node: '>=4'} + dev: false /p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} @@ -19831,7 +20499,7 @@ packages: resolution: {integrity: sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==} dependencies: shell-quote: 1.8.1 - ws: 7.5.9 + ws: 7.5.10 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -20353,17 +21021,11 @@ packages: lru-cache: 6.0.0 dev: true - /semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - /semver@7.6.2: resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} engines: {node: '>=10'} hasBin: true + dev: true /semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} @@ -21022,7 +21684,6 @@ packages: /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - dev: true /strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} @@ -21379,6 +22040,10 @@ packages: dependencies: is-number: 7.0.0 + /toformat@2.0.0: + resolution: {integrity: sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ==} + dev: false + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -21457,7 +22122,7 @@ packages: '@babel/runtime': 7.25.6 '@ethersproject/abi': 5.7.0 '@tronweb3/google-protobuf': 3.21.4 - axios: 1.8.4 + axios: 1.8.4(debug@4.3.7) bignumber.js: 9.1.2 ethereum-cryptography: 2.1.3 ethers: 5.7.2 @@ -21588,6 +22253,15 @@ packages: strip-bom: 3.0.0 dev: true + /tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: false + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -22154,7 +22828,7 @@ packages: resolution: {integrity: sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww==} engines: {node: '>=8.0.0'} dependencies: - '@types/bn.js': 5.1.5 + '@types/bn.js': 5.1.6 '@types/node': 12.20.55 bignumber.js: 9.1.2 web3-core-helpers: 1.10.4 @@ -22194,7 +22868,7 @@ packages: resolution: {integrity: sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A==} engines: {node: '>=8.0.0'} dependencies: - '@types/bn.js': 5.1.5 + '@types/bn.js': 5.1.6 web3-core: 1.10.4 web3-core-helpers: 1.10.4 web3-core-method: 1.10.4 @@ -22600,18 +23274,6 @@ packages: utf-8-validate: optional: true - /ws@7.5.9: - resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - /ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -22666,12 +23328,14 @@ packages: /yaeti@0.0.6: resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} engines: {node: '>=0.10.32'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}