From d23dfc34656ffdcf1df02328e18f3fa137a7b252 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 06:50:24 +0900 Subject: [PATCH 1/8] Implement DOOM INDEX Contract V1 mint flow --- .gitignore | 2 + Cargo.lock | 5631 +++++++++++++---- docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md | 509 ++ package.json | 8 +- programs/doom-nft-program/Cargo.toml | 8 +- programs/doom-nft-program/src/constants.rs | 4 + programs/doom-nft-program/src/error.rs | 25 + programs/doom-nft-program/src/events.rs | 27 + .../src/instructions/initialize_collection.rs | 65 + .../instructions/initialize_global_config.rs | 42 + .../src/instructions/mint_doom_index_nft.rs | 108 + .../doom-nft-program/src/instructions/mod.rs | 17 + .../src/instructions/reserve_token_id.rs | 57 + .../src/instructions/set_mint_paused.rs | 21 + .../src/instructions/set_upgrade_authority.rs | 35 + .../src/instructions/transfer_admin.rs | 21 + .../instructions/update_base_metadata_url.rs | 37 + programs/doom-nft-program/src/lib.rs | 180 +- .../src/state/global_config.rs | 15 + .../src/state/mint_reservation.rs | 10 + programs/doom-nft-program/src/state/mod.rs | 5 + programs/doom-nft-program/src/utils.rs | 24 + scripts/build-test-sbf.sh | 12 + scripts/devnet/common.ts | 273 + scripts/devnet/init.ts | 60 + scripts/devnet/mint.ts | 82 + scripts/devnet/reserve.ts | 23 + scripts/test-contract-v1.sh | 7 + tests/Cargo.toml | 6 +- tests/src/lib.rs | 529 +- 30 files changed, 6335 insertions(+), 1508 deletions(-) create mode 100644 docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md create mode 100644 programs/doom-nft-program/src/constants.rs create mode 100644 programs/doom-nft-program/src/error.rs create mode 100644 programs/doom-nft-program/src/events.rs create mode 100644 programs/doom-nft-program/src/instructions/initialize_collection.rs create mode 100644 programs/doom-nft-program/src/instructions/initialize_global_config.rs create mode 100644 programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs create mode 100644 programs/doom-nft-program/src/instructions/mod.rs create mode 100644 programs/doom-nft-program/src/instructions/reserve_token_id.rs create mode 100644 programs/doom-nft-program/src/instructions/set_mint_paused.rs create mode 100644 programs/doom-nft-program/src/instructions/set_upgrade_authority.rs create mode 100644 programs/doom-nft-program/src/instructions/transfer_admin.rs create mode 100644 programs/doom-nft-program/src/instructions/update_base_metadata_url.rs create mode 100644 programs/doom-nft-program/src/state/global_config.rs create mode 100644 programs/doom-nft-program/src/state/mint_reservation.rs create mode 100644 programs/doom-nft-program/src/state/mod.rs create mode 100644 programs/doom-nft-program/src/utils.rs create mode 100755 scripts/build-test-sbf.sh create mode 100644 scripts/devnet/common.ts create mode 100644 scripts/devnet/init.ts create mode 100644 scripts/devnet/mint.ts create mode 100644 scripts/devnet/reserve.ts create mode 100755 scripts/test-contract-v1.sh diff --git a/.gitignore b/.gitignore index 2e0446b..7a60902 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ target node_modules test-ledger .yarn + +.agents/memory/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e754a53..4640585 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,16 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "adler2" version = "2.0.1" @@ -20,30 +10,30 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "crypto-common", "generic-array", ] [[package]] name = "aes" -version = "0.7.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", - "opaque-debug", ] [[package]] name = "aes-gcm-siv" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ "aead", "aes", @@ -54,6 +44,82 @@ dependencies = [ "zeroize", ] +[[package]] +name = "agave-feature-set" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a2c365c0245cbb8959de725fc2b44c754b673fdf34c9a7f9d4a25c35a7bf1" +dependencies = [ + "ahash 0.8.12", + "solana-epoch-schedule", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", + "solana-svm-feature-set", +] + +[[package]] +name = "agave-io-uring" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10b918a355bc78764aceb688dbbb6af72425f62be9dbfb7beb00b6d3803a0bd" +dependencies = [ + "io-uring", + "libc", + "log", + "slab", + "smallvec", +] + +[[package]] +name = "agave-precompiles" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60d73657792af7f2464e9181d13c3979e94bb09841d9ffa014eef4ef0492b77" +dependencies = [ + "agave-feature-set", + "bincode", + "digest 0.10.7", + "ed25519-dalek", + "libsecp256k1", + "openssl", + "sha3", + "solana-ed25519-program", + "solana-message", + "solana-precompile-error", + "solana-pubkey", + "solana-sdk-ids", + "solana-secp256k1-program", + "solana-secp256r1-program", +] + +[[package]] +name = "agave-reserved-account-keys" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8289c8a8a2ef5aa10ce49a070f360f4e035ee3410b8d8f3580fb39d8cf042581" +dependencies = [ + "agave-feature-set", + "solana-pubkey", + "solana-sdk-ids", +] + +[[package]] +name = "agave-transaction-view" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e8f8ca0615dc3684c63f3aceacea30be8c60986cd41a1e795878ea17df2a4" +dependencies = [ + "solana-hash", + "solana-message", + "solana-packet", + "solana-pubkey", + "solana-sdk-ids", + "solana-short-vec", + "solana-signature", + "solana-svm-transaction", +] + [[package]] name = "ahash" version = "0.7.8" @@ -102,17 +168,11 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "anchor-attribute-access-control" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e" +checksum = "3f70fd141a4d18adf11253026b32504f885447048c7494faf5fa83b01af9c0cf" dependencies = [ "anchor-syn", "proc-macro2", @@ -122,12 +182,12 @@ dependencies = [ [[package]] name = "anchor-attribute-account" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400" +checksum = "715a261c57c7679581e06f07a74fa2af874ac30f86bd8ea07cca4a7e5388a064" dependencies = [ "anchor-syn", - "bs58 0.5.1", + "bs58", "proc-macro2", "quote", "syn 1.0.109", @@ -135,9 +195,9 @@ dependencies = [ [[package]] name = "anchor-attribute-constant" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7" +checksum = "730d6df8ae120321c5c25e0779e61789e4b70dc8297102248902022f286102e4" dependencies = [ "anchor-syn", "quote", @@ -146,9 +206,9 @@ dependencies = [ [[package]] name = "anchor-attribute-error" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241" +checksum = "27e6e449cc3a37b2880b74dcafb8e5a17b954c0e58e376432d7adc646fb333ef" dependencies = [ "anchor-syn", "quote", @@ -157,9 +217,9 @@ dependencies = [ [[package]] name = "anchor-attribute-event" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2" +checksum = "d7710e4c54adf485affcd9be9adec5ef8846d9c71d7f31e16ba86ff9fc1dd49f" dependencies = [ "anchor-syn", "proc-macro2", @@ -169,39 +229,26 @@ dependencies = [ [[package]] name = "anchor-attribute-program" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f" +checksum = "05ecfd49b2aeadeb32f35262230db402abed76ce87e27562b34f61318b2ec83c" dependencies = [ + "anchor-lang-idl", "anchor-syn", + "anyhow", + "bs58", + "heck 0.3.3", + "proc-macro2", "quote", + "serde_json", "syn 1.0.109", ] -[[package]] -name = "anchor-client" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb48c4a7911038da546dc752655a29fa49f6bd50ebc1edca218bac8da1012acd" -dependencies = [ - "anchor-lang", - "anyhow", - "futures", - "regex", - "serde", - "solana-account-decoder", - "solana-client", - "solana-sdk", - "thiserror", - "tokio", - "url", -] - [[package]] name = "anchor-derive-accounts" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c" +checksum = "be89d160793a88495af462a7010b3978e48e30a630c91de47ce2c1d3cb7a6149" dependencies = [ "anchor-syn", "quote", @@ -210,12 +257,12 @@ dependencies = [ [[package]] name = "anchor-derive-serde" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe" +checksum = "abc6ee78acb7bfe0c2dd2abc677aaa4789c0281a0c0ef01dbf6fe85e0fd9e6e4" dependencies = [ "anchor-syn", - "borsh-derive-internal 0.10.4", + "borsh-derive-internal", "proc-macro2", "quote", "syn 1.0.109", @@ -223,9 +270,9 @@ dependencies = [ [[package]] name = "anchor-derive-space" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419" +checksum = "134a01c0703f6fd355a0e472c033f6f3e41fac1ef6e370b20c50f4c8d022cea7" dependencies = [ "proc-macro2", "quote", @@ -234,9 +281,9 @@ dependencies = [ [[package]] name = "anchor-lang" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad" +checksum = "e6bab117055905e930f762c196e08f861f8dfe7241b92cee46677a3b15561a0a" dependencies = [ "anchor-attribute-access-control", "anchor-attribute-account", @@ -247,38 +294,49 @@ dependencies = [ "anchor-derive-accounts", "anchor-derive-serde", "anchor-derive-space", - "anchor-syn", - "arrayref", - "base64 0.13.1", + "anchor-lang-idl", + "base64 0.21.7", "bincode", "borsh 0.10.4", "bytemuck", - "getrandom 0.2.16", "solana-program", - "thiserror", + "thiserror 1.0.69", ] [[package]] -name = "anchor-spl" -version = "0.29.0" +name = "anchor-lang-idl" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a" +checksum = "32e8599d21995f68e296265aa5ab0c3cef582fd58afec014d01bd0bce18a4418" dependencies = [ - "anchor-lang", - "solana-program", - "spl-associated-token-account", - "spl-token", - "spl-token-2022 0.9.0", + "anchor-lang-idl-spec", + "anyhow", + "heck 0.3.3", + "regex", + "serde", + "serde_json", + "sha2 0.10.9", +] + +[[package]] +name = "anchor-lang-idl-spec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdf143115440fe621bdac3a29a1f7472e09f6cd82b2aa569429a0c13f103838" +dependencies = [ + "anyhow", + "serde", ] [[package]] name = "anchor-syn" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825" +checksum = "5dc7a6d90cc643df0ed2744862cdf180587d1e5d28936538c18fc8908489ed67" dependencies = [ "anyhow", - "bs58 0.5.1", + "bs58", + "cargo_toml", "heck 0.3.3", "proc-macro2", "quote", @@ -286,7 +344,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "syn 1.0.109", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -298,15 +356,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anstream" version = "0.6.21" @@ -363,6 +412,20 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "ark-bn254" version = "0.4.0" @@ -386,7 +449,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.5", "num-traits", "zeroize", ] @@ -403,7 +466,7 @@ dependencies = [ "ark-std", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.5", "num-bigint 0.4.6", "num-traits", "paste", @@ -510,7 +573,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -550,7 +613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] @@ -568,12 +631,14 @@ dependencies = [ ] [[package]] -name = "async-mutex" -version = "1.4.1" +name = "async-lock" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73112ce9e1059d8604242af62c7ec8e5975ac58ac251686c8403b45e8a6fe778" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener", + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", ] [[package]] @@ -587,6 +652,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -623,10 +694,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "base64ct" -version = "1.8.0" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" @@ -637,12 +708,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.10.0" @@ -681,7 +746,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", "generic-array", ] @@ -694,22 +758,6 @@ 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" @@ -730,27 +778,14 @@ dependencies = [ "cfg_aliases", ] -[[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", + "borsh-derive-internal", + "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", "syn 1.0.109", @@ -769,17 +804,6 @@ dependencies = [ "syn 2.0.110", ] -[[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" @@ -791,17 +815,6 @@ dependencies = [ "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" @@ -834,12 +847,6 @@ dependencies = [ "alloc-stdlib", ] -[[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" @@ -896,6 +903,29 @@ name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] [[package]] name = "caps" @@ -906,6 +936,16 @@ dependencies = [ "libc", ] +[[package]] +name = "cargo_toml" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a98356df42a2eb1bd8f1793ae4ee4de48e384dd974ce5eac8eee802edb7492be" +dependencies = [ + "serde", + "toml 0.8.23", +] + [[package]] name = "cc" version = "1.2.47" @@ -918,6 +958,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -930,6 +976,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "chrono" version = "0.4.42" @@ -937,51 +994,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", - "js-sys", "num-traits", - "serde", - "wasm-bindgen", "windows-link", ] [[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array", -] - -[[package]] -name = "clap" -version = "2.34.0" +name = "chrono-humanize" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b" dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim 0.8.0", - "textwrap 0.11.0", - "unicode-width 0.1.14", - "vec_map", + "chrono", ] [[package]] -name = "clap" -version = "3.2.25" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "once_cell", - "strsim 0.10.0", - "termcolor", - "textwrap 0.16.2", + "crypto-common", + "inout", ] [[package]] @@ -1002,8 +1035,8 @@ checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.6", - "strsim 0.11.1", + "clap_lex", + "strsim", ] [[package]] @@ -1018,15 +1051,6 @@ dependencies = [ "syn 2.0.110", ] -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "clap_lex" version = "0.7.6" @@ -1052,6 +1076,16 @@ dependencies = [ "unreachable", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compression-codecs" version = "0.4.32" @@ -1088,7 +1122,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.2", + "unicode-width", "windows-sys 0.59.0", ] @@ -1112,12 +1146,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - [[package]] name = "constant_time_eq" version = "0.3.1" @@ -1126,9 +1154,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -1205,6 +1233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1220,32 +1249,60 @@ dependencies = [ [[package]] name = "ctr" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher", ] [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "serde", "subtle", "zeroize", ] [[package]] -name = "darling" -version = "0.20.11" +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rand_core 0.6.4", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "darling" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ "darling_core", "darling_macro", @@ -1253,23 +1310,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.110", ] [[package]] name = "darling_macro" -version = "0.20.11" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", @@ -1287,6 +1344,7 @@ dependencies = [ "lock_api", "once_cell", "parking_lot_core", + "rayon", ] [[package]] @@ -1295,15 +1353,6 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - [[package]] name = "der-parser" version = "8.2.0" @@ -1345,16 +1394,10 @@ dependencies = [ ] [[package]] -name = "dialoguer" -version = "0.10.4" +name = "difflib" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" -dependencies = [ - "console", - "shell-words", - "tempfile", - "zeroize", -] +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" @@ -1376,6 +1419,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dir-diff" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ad16bf5f84253b50d6557681c58c3ab67c47c77d39fed9aeb56e947290bd10" +dependencies = [ + "walkdir", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1415,7 +1467,7 @@ name = "doom-nft-program" version = "0.1.0" dependencies = [ "anchor-lang", - "anchor-spl", + "mpl-core", "solana-program", ] @@ -1424,9 +1476,15 @@ name = "doom-nft-program-migration" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.53", + "clap", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "eager" version = "0.1.0" @@ -1448,7 +1506,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 3.2.0", "ed25519", "rand 0.7.3", "serde", @@ -1468,6 +1526,18 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "educe" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "either" version = "1.15.0" @@ -1480,15 +1550,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "enum-iterator" version = "1.5.0" @@ -1509,6 +1570,19 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "enum-ordinalize" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -1544,6 +1618,39 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher 1.0.2", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1556,12 +1663,53 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "five8" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b8549488b4715defcb0d8a8a1c1c76a80661b5fa106b4ca0e7fce59d7d875" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_const" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" + [[package]] name = "flate2" version = "1.1.5" @@ -1572,6 +1720,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1579,10 +1736,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "foldhash" -version = "0.1.5" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" @@ -1593,6 +1759,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" + [[package]] name = "futures" version = "0.3.31" @@ -1664,6 +1836,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -1688,7 +1866,6 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "serde", "typenum", "version_check", ] @@ -1736,65 +1913,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] -name = "goblin" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7666983ed0dd8d21a6f6576ee00053ca0926fb281a5522577a4dbd0f1b54143" -dependencies = [ - "log", - "plain", - "scroll", -] - -[[package]] -name = "h2" -version = "0.3.27" +name = "governor" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.12.1", - "slab", - "tokio", - "tokio-util", - "tracing", + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand 0.8.5", + "smallvec", + "spinning_top", ] [[package]] name = "hash32" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.13.2" @@ -1810,17 +1973,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -1836,6 +1988,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1904,14 +2062,36 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "http", + "futures-core", + "http 1.4.0", + "http-body", "pin-project-lite", ] @@ -1921,12 +2101,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "humantime" version = "2.3.0" @@ -1935,40 +2109,63 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "0.14.32" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2", - "http", + "http 1.4.0", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.10", + "pin-utils", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper", + "hyper-util", + "rustls 0.23.37", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.6", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", "futures-util", - "http", + "http 1.4.0", + "http-body", "hyper", - "rustls", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.1", "tokio", - "tokio-rustls", + "tower-service", + "tracing", ] [[package]] @@ -2120,13 +2317,22 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "include_dir" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", ] [[package]] @@ -2148,21 +2354,51 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width 0.2.2", + "unicode-width", "web-time", ] [[package]] -name = "ipnet" -version = "2.11.0" +name = "inout" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "io-uring" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -2173,12 +2409,43 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine 4.6.7", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -2214,6 +2481,27 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kaigan" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba15de5aeb137f0f65aa3bf82187647f1285abfe5b20c80c2c37f7007ad519a" +dependencies = [ + "borsh 0.10.4", + "serde", +] + +[[package]] +name = "kaigan" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f25ded719a2354f6b1a51d0c0741c25bc7afe038617664eb37f6418439eb084" +dependencies = [ + "anchor-lang", + "borsh 0.10.4", + "serde", +] + [[package]] name = "keccak" version = "0.1.5" @@ -2235,6 +2523,24 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.7.3", +] + [[package]] name = "libsecp256k1" version = "0.6.0" @@ -2292,7 +2598,7 @@ dependencies = [ "ark-bn254", "ark-ff", "num-bigint 0.4.6", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2322,6 +2628,40 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "lru" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +dependencies = [ + "hashbrown 0.12.3", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "memchr" version = "2.7.6" @@ -2338,12 +2678,12 @@ dependencies = [ ] [[package]] -name = "memoffset" -version = "0.7.1" +name = "memmap2" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ - "autocfg", + "libc", ] [[package]] @@ -2367,12 +2707,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2400,19 +2734,92 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "mpl-core" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502b2373d5c32f944dc095a250a5c5e5d283cad1a0f55d9e18ee267235b40e89" +dependencies = [ + "anchor-lang", + "base64 0.22.1", + "borsh 0.10.4", + "kaigan 0.3.0", + "modular-bitfield", + "num-derive 0.3.3", + "num-traits", + "rmp-serde", + "serde_json", + "solana-program", + "thiserror 1.0.69", +] + [[package]] name = "nix" -version = "0.26.4" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", + "cfg_aliases", "libc", - "memoffset 0.7.1", - "pin-utils", + "memoffset", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "nom" version = "7.1.3" @@ -2423,6 +2830,18 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num" version = "0.2.1" @@ -2547,37 +2966,16 @@ dependencies = [ "libc", ] -[[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ - "num_enum_derive 0.7.5", + "num_enum_derive", "rustversion", ] -[[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.110", -] - [[package]] name = "num_enum_derive" version = "0.7.5" @@ -2623,17 +3021,84 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand 0.8.5", + "thiserror 1.0.69", +] [[package]] -name = "os_str_bytes" -version = "6.6.1" +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -2653,7 +3118,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -2664,15 +3129,6 @@ 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" @@ -2706,6 +3162,26 @@ dependencies = [ "num", ] +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2718,17 +3194,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -2743,9 +3208,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "polyval" -version = "0.5.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", @@ -2784,22 +3249,42 @@ dependencies = [ ] [[package]] -name = "proc-macro-crate" -version = "0.1.5" +name = "predicates" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools 0.10.5", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ - "toml", + "predicates-core", + "termtree", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "toml 0.5.11", ] [[package]] @@ -2811,6 +3296,27 @@ dependencies = [ "toml_edit 0.23.7", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -2840,52 +3346,76 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quinn" -version = "0.10.2" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", - "thiserror", + "rustls 0.23.37", + "socket2 0.6.1", + "thiserror 2.0.18", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.10.6" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", - "rand 0.8.5", - "ring 0.16.20", + "fastbloom", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", "rustc-hash", - "rustls", - "rustls-native-certs", + "rustls 0.23.37", + "rustls-pki-types", + "rustls-platform-verifier", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.4.1" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ - "bytes", + "cfg_aliases", "libc", - "socket2 0.5.10", + "once_cell", + "socket2 0.6.1", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] @@ -2927,6 +3457,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2947,6 +3487,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2965,6 +3515,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2983,6 +3542,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + [[package]] name = "rayon" version = "1.11.0" @@ -3004,24 +3572,21 @@ dependencies = [ ] [[package]] -name = "rcgen" -version = "0.10.0" +name = "redox_syscall" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "pem", - "ring 0.16.20", - "time", - "yasna", + "bitflags", ] [[package]] name = "redox_syscall" -version = "0.5.18" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags 2.10.0", + "bitflags", ] [[package]] @@ -3055,60 +3620,57 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "async-compression", - "base64 0.21.7", + "base64 0.22.1", "bytes", - "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2", - "http", + "http 1.4.0", "http-body", + "http-body-util", "hyper", "hyper-rustls", - "ipnet", + "hyper-util", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "quinn", + "rustls 0.23.37", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", - "tokio-rustls", - "tokio-util", + "tokio-rustls 0.26.4", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.4", - "winreg", + "webpki-roots 1.0.6", ] [[package]] -name = "ring" -version = "0.16.20" +name = "reqwest-middleware" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted 0.7.1", - "web-sys", - "winapi", + "anyhow", + "async-trait", + "http 1.4.0", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", ] [[package]] @@ -3121,29 +3683,27 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] [[package]] -name = "rpassword" -version = "7.4.0" +name = "rmp" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "libc", - "rtoolbox", - "windows-sys 0.59.0", + "num-traits", ] [[package]] -name = "rtoolbox" -version = "0.0.3" +name = "rmp-serde" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" dependencies = [ - "libc", - "windows-sys 0.52.0", + "rmp", + "serde", ] [[package]] @@ -3154,9 +3714,9 @@ checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -3182,7 +3742,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.10.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -3196,40 +3756,93 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.14", - "rustls-webpki", + "ring", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.9", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustls-pki-types" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "base64 0.21.7", + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -3244,6 +3857,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.28" @@ -3259,43 +3881,23 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scroll" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - [[package]] name = "sct" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] name = "security-framework" -version = "2.11.1" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -3318,6 +3920,15 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "seqlock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c67b6f14ecc5b86c66fa63d76b5092352678545a8a3cdae80aef5128371910" +dependencies = [ + "parking_lot", +] + [[package]] name = "serde" version = "1.0.228" @@ -3328,6 +3939,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.19" @@ -3371,6 +3991,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3385,19 +4014,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.3" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ - "serde", + "serde_core", "serde_with_macros", ] [[package]] name = "serde_with_macros" -version = "2.3.3" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ "darling", "proc-macro2", @@ -3440,18 +4069,6 @@ dependencies = [ "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" @@ -3463,10 +4080,13 @@ dependencies = [ ] [[package]] -name = "shell-words" -version = "1.1.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] [[package]] name = "shlex" @@ -3474,6 +4094,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.7" @@ -3501,6 +4131,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "sized-chunks" version = "0.6.5" @@ -3544,1013 +4180,3069 @@ dependencies = [ ] [[package]] -name = "solana-account-decoder" -version = "1.18.26" +name = "solana-account" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b109fd3a106e079005167e5b0e6f6d2c88bbedec32530837b584791a8b5abf36" +checksum = "0f949fe4edaeaea78c844023bfc1c898e0b1f5a100f8a8d2d0f85d0a7b090258" dependencies = [ - "Inflector", - "base64 0.21.7", "bincode", - "bs58 0.4.0", - "bv", - "lazy_static", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-sysvar", +] + +[[package]] +name = "solana-account-decoder-client-types" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5519e8343325b707f17fbed54fcefb325131b692506d0af9e08a539d15e4f8cf" +dependencies = [ + "base64 0.22.1", + "bs58", "serde", "serde_derive", "serde_json", - "solana-config-program", - "solana-sdk", - "spl-token", - "spl-token-2022 1.0.0", - "spl-token-group-interface", - "spl-token-metadata-interface", - "thiserror", + "solana-account", + "solana-pubkey", "zstd", ] [[package]] -name = "solana-clap-utils" -version = "1.18.26" +name = "solana-account-info" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074ef478856a45d5627270fbc6b331f91de9aae7128242d9e423931013fb8a2a" +checksum = "c8f5152a288ef1912300fc6efa6c2d1f9bb55d9398eb6c72326360b8063987da" dependencies = [ - "chrono", - "clap 2.34.0", - "rpassword", - "solana-remote-wallet", - "solana-sdk", - "thiserror", - "tiny-bip39", - "uriparse", - "url", + "bincode", + "serde", + "solana-program-error", + "solana-program-memory", + "solana-pubkey", ] [[package]] -name = "solana-client" -version = "1.18.26" +name = "solana-accounts-db" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a9f32c42402c4b9484d5868ac74b7e0a746e3905d8bfd756e1203e50cbb87e" +checksum = "dbbe35141711500d113dfc7aa79eb250c4458f04e759a67ba4bffc3e6cddc402" dependencies = [ - "async-trait", + "agave-io-uring", + "ahash 0.8.12", + "bincode", + "blake3", + "bv", + "bytemuck", + "bytemuck_derive", + "bzip2", + "crossbeam-channel", + "dashmap", + "indexmap", + "io-uring", + "itertools 0.12.1", + "log", + "lz4", + "memmap2 0.9.10", + "modular-bitfield", + "num_cpus", + "num_enum", + "rand 0.8.5", + "rayon", + "seqlock", + "serde", + "serde_derive", + "slab", + "smallvec", + "solana-account", + "solana-address-lookup-table-interface", + "solana-bucket-map", + "solana-clock", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-genesis-config", + "solana-hash", + "solana-lattice-hash", + "solana-measure", + "solana-message", + "solana-metrics", + "solana-nohash-hasher", + "solana-pubkey", + "solana-rayon-threadlimit", + "solana-rent-collector", + "solana-reward-info", + "solana-sha256-hasher", + "solana-slot-hashes", + "solana-svm-transaction", + "solana-system-interface", + "solana-sysvar", + "solana-time-utils", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "spl-generic-token", + "static_assertions", + "tar", + "tempfile", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-address-lookup-table-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1673f67efe870b64a65cb39e6194be5b26527691ce5922909939961a6e6b395" +dependencies = [ + "bincode", + "bytemuck", + "serde", + "serde_derive", + "solana-clock", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-slot-hashes", +] + +[[package]] +name = "solana-atomic-u64" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52e52720efe60465b052b9e7445a01c17550666beec855cce66f44766697bc2" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-banks-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68548570c38a021c724b5aa0112f45a54bdf7ff1b041a042848e034a95a96994" +dependencies = [ + "borsh 1.5.7", + "futures", + "solana-account", + "solana-banks-interface", + "solana-clock", + "solana-commitment-config", + "solana-hash", + "solana-message", + "solana-program-pack", + "solana-pubkey", + "solana-rent", + "solana-signature", + "solana-sysvar", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "tarpc", + "thiserror 2.0.18", + "tokio", + "tokio-serde", +] + +[[package]] +name = "solana-banks-interface" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d90edc435bf488ef7abed4dcb1f94fa1970102cbabb25688f58417fd948286" +dependencies = [ + "serde", + "serde_derive", + "solana-account", + "solana-clock", + "solana-commitment-config", + "solana-hash", + "solana-message", + "solana-pubkey", + "solana-signature", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "tarpc", +] + +[[package]] +name = "solana-banks-server" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36080e4a97afe47f8b56356a0cabc3b1dadfb09efb4ea8c44d79d19a4e7d6534" +dependencies = [ + "agave-feature-set", + "bincode", + "crossbeam-channel", + "futures", + "solana-account", + "solana-banks-interface", + "solana-client", + "solana-clock", + "solana-commitment-config", + "solana-hash", + "solana-message", + "solana-pubkey", + "solana-runtime", + "solana-runtime-transaction", + "solana-send-transaction-service", + "solana-signature", + "solana-svm", + "solana-transaction", + "solana-transaction-error", + "tarpc", + "tokio", + "tokio-serde", +] + +[[package]] +name = "solana-big-mod-exp" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "solana-define-syscall", +] + +[[package]] +name = "solana-bincode" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" +dependencies = [ + "bincode", + "serde", + "solana-instruction", +] + +[[package]] +name = "solana-blake3-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0801e25a1b31a14494fc80882a036be0ffd290efc4c2d640bfcca120a4672" +dependencies = [ + "blake3", + "solana-define-syscall", + "solana-hash", + "solana-sanitize", +] + +[[package]] +name = "solana-bn254" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4420f125118732833f36facf96a27e7b78314b2d642ba07fa9ffdacd8d79e243" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "bytemuck", + "solana-define-syscall", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-borsh" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718333bcd0a1a7aed6655aa66bef8d7fb047944922b2d3a18f49cbc13e73d004" +dependencies = [ + "borsh 0.10.4", + "borsh 1.5.7", +] + +[[package]] +name = "solana-bpf-loader-program" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aec57dcd80d0f6879956cad28854a6eebaed6b346ce56908ea01a9f36ab259" +dependencies = [ + "bincode", + "libsecp256k1", + "num-traits", + "qualifier_attr", + "scopeguard", + "solana-account", + "solana-account-info", + "solana-big-mod-exp", + "solana-bincode", + "solana-blake3-hasher", + "solana-bn254", + "solana-clock", + "solana-cpi", + "solana-curve25519", + "solana-hash", + "solana-instruction", + "solana-keccak-hasher", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-log-collector", + "solana-measure", + "solana-packet", + "solana-poseidon", + "solana-program-entrypoint", + "solana-program-runtime", + "solana-pubkey", + "solana-sbpf", + "solana-sdk-ids", + "solana-secp256k1-recover", + "solana-sha256-hasher", + "solana-stable-layout", + "solana-svm-feature-set", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", + "solana-timings", + "solana-transaction-context", + "solana-type-overrides", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-bucket-map" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e067a30c43dc66f300584034ce1526da882d3100d45a10613a4e554b3e1e3937" +dependencies = [ + "bv", + "bytemuck", + "bytemuck_derive", + "memmap2 0.9.10", + "modular-bitfield", + "num_enum", + "rand 0.8.5", + "solana-clock", + "solana-measure", + "solana-pubkey", + "tempfile", +] + +[[package]] +name = "solana-builtins" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d61a31b63b52b0d268cbcd56c76f50314867d7f8e07a0f2c62ee7c9886e07b2" +dependencies = [ + "agave-feature-set", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-hash", + "solana-loader-v4-program", + "solana-program-runtime", + "solana-pubkey", + "solana-sdk-ids", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", + "solana-zk-elgamal-proof-program", + "solana-zk-token-proof-program", +] + +[[package]] +name = "solana-builtins-default-costs" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ca69a299a6c969b18ea381a02b40c9e4dda04b2af0d15a007c1184c82163bbb" +dependencies = [ + "agave-feature-set", + "ahash 0.8.12", + "log", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-loader-v4-program", + "solana-pubkey", + "solana-sdk-ids", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", +] + +[[package]] +name = "solana-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc55d1f263e0be4127daf33378d313ea0977f9ffd3fba50fa544ca26722fc695" +dependencies = [ + "async-trait", "bincode", "dashmap", "futures", "futures-util", - "indexmap 2.12.1", + "indexmap", "indicatif", "log", "quinn", "rayon", + "solana-account", + "solana-client-traits", + "solana-commitment-config", "solana-connection-cache", + "solana-epoch-info", + "solana-hash", + "solana-instruction", + "solana-keypair", "solana-measure", - "solana-metrics", + "solana-message", + "solana-pubkey", "solana-pubsub-client", "solana-quic-client", + "solana-quic-definitions", "solana-rpc-client", "solana-rpc-client-api", "solana-rpc-client-nonce-utils", - "solana-sdk", + "solana-signature", + "solana-signer", "solana-streamer", "solana-thin-client", + "solana-time-utils", "solana-tpu-client", + "solana-transaction", + "solana-transaction-error", "solana-udp-client", - "thiserror", + "thiserror 2.0.18", "tokio", ] [[package]] -name = "solana-config-program" -version = "1.18.26" +name = "solana-client-traits" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d75b803860c0098e021a26f0624129007c15badd5b0bc2fbd9f0e1a73060d3b" +checksum = "83f0071874e629f29e0eb3dab8a863e98502ac7aba55b7e0df1803fc5cac72a7" +dependencies = [ + "solana-account", + "solana-commitment-config", + "solana-epoch-info", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-pubkey", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction", + "solana-transaction-error", +] + +[[package]] +name = "solana-clock" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8584296123df8fe229b95e2ebfd37ae637fe9db9b7d4dd677ac5a78e80dbfce" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-cluster-type" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ace9fea2daa28354d107ea879cff107181d85cd4e0f78a2bedb10e1a428c97e" +dependencies = [ + "serde", + "serde_derive", + "solana-hash", +] + +[[package]] +name = "solana-commitment-config" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac49c4dde3edfa832de1697e9bcdb7c3b3f7cb7a1981b7c62526c8bb6700fb73" dependencies = [ - "bincode", - "chrono", "serde", "serde_derive", +] + +[[package]] +name = "solana-compute-budget" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f4fc63bc2276a1618ca0bfc609da7448534ecb43a1cb387cdf9eaa2dc7bc272" +dependencies = [ + "solana-fee-structure", + "solana-program-runtime", +] + +[[package]] +name = "solana-compute-budget-instruction" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d94430f6d3c5ac1e1fa6a342c1c714d5b03c800999e7b6cf235298f0b5341" +dependencies = [ + "agave-feature-set", + "log", + "solana-borsh", + "solana-builtins-default-costs", + "solana-compute-budget", + "solana-compute-budget-interface", + "solana-instruction", + "solana-packet", + "solana-pubkey", + "solana-sdk-ids", + "solana-svm-transaction", + "solana-transaction-error", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-compute-budget-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8432d2c4c22d0499aa06d62e4f7e333f81777b3d7c96050ae9e5cb71a8c3aee4" +dependencies = [ + "borsh 1.5.7", + "serde", + "serde_derive", + "solana-instruction", + "solana-sdk-ids", +] + +[[package]] +name = "solana-compute-budget-program" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072b02beed1862c6b7b7a8a699379594c4470a9371c711856a0a3c266dcf57e5" +dependencies = [ "solana-program-runtime", - "solana-sdk", +] + +[[package]] +name = "solana-config-program-client" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53aceac36f105fd4922e29b4f0c1f785b69d7b3e7e387e384b8985c8e0c3595e" +dependencies = [ + "bincode", + "borsh 0.10.4", + "kaigan 0.2.6", + "serde", + "solana-program", ] [[package]] name = "solana-connection-cache" -version = "1.18.26" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9306ede13e8ceeab8a096bcf5fa7126731e44c201ca1721ea3c38d89bcd4111" +checksum = "45c1cff5ebb26aefff52f1a8e476de70ec1683f8cc6e4a8c86b615842d91f436" dependencies = [ "async-trait", "bincode", "crossbeam-channel", "futures-util", - "indexmap 2.12.1", + "indexmap", "log", "rand 0.8.5", "rayon", - "rcgen", + "solana-keypair", "solana-measure", "solana-metrics", - "solana-sdk", - "thiserror", + "solana-time-utils", + "solana-transaction-error", + "thiserror 2.0.18", "tokio", ] [[package]] -name = "solana-frozen-abi" -version = "1.18.26" +name = "solana-cost-model" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ab2c30c15311b511c0d1151e4ab6bc9a3e080a37e7c6e7c2d96f5784cf9434" +checksum = "b24b35813c678ed40ca91f989a3c9e1780e6aef0139e15731785bca1189443c3" dependencies = [ - "block-buffer 0.10.4", - "bs58 0.4.0", - "bv", - "either", - "generic-array", - "im", - "lazy_static", + "agave-feature-set", + "ahash 0.8.12", "log", - "memmap2", - "rustc_version", - "serde", - "serde_bytes", - "serde_derive", + "solana-bincode", + "solana-borsh", + "solana-builtins-default-costs", + "solana-clock", + "solana-compute-budget", + "solana-compute-budget-instruction", + "solana-compute-budget-interface", + "solana-fee-structure", + "solana-metrics", + "solana-packet", + "solana-pubkey", + "solana-runtime-transaction", + "solana-sdk-ids", + "solana-svm-transaction", + "solana-system-interface", + "solana-transaction-error", + "solana-vote-program", +] + +[[package]] +name = "solana-cpi" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" +dependencies = [ + "solana-account-info", + "solana-define-syscall", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-stable-layout", +] + +[[package]] +name = "solana-curve25519" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae4261b9a8613d10e77ac831a8fa60b6fa52b9b103df46d641deff9f9812a23" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "solana-define-syscall", + "subtle", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-decode-error" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c781686a18db2f942e70913f7ca15dc120ec38dcab42ff7557db2c70c625a35" +dependencies = [ + "num-traits", +] + +[[package]] +name = "solana-define-syscall" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2" + +[[package]] +name = "solana-derivation-path" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939756d798b25c5ec3cca10e06212bdca3b1443cb9bb740a38124f58b258737b" +dependencies = [ + "derivation-path", + "qstring", + "uriparse", +] + +[[package]] +name = "solana-ed25519-program" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feafa1691ea3ae588f99056f4bdd1293212c7ece28243d7da257c443e84753" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "ed25519-dalek", + "solana-feature-set", + "solana-instruction", + "solana-precompile-error", + "solana-sdk-ids", +] + +[[package]] +name = "solana-epoch-info" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ef6f0b449290b0b9f32973eefd95af35b01c5c0c34c569f936c34c5b20d77b" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-epoch-rewards" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b575d3dd323b9ea10bb6fe89bf6bf93e249b215ba8ed7f68f1a3633f384db7" +dependencies = [ + "serde", + "serde_derive", + "solana-hash", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-epoch-rewards-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c5fd2662ae7574810904585fd443545ed2b568dbd304b25a31e79ccc76e81b" +dependencies = [ + "siphasher 0.3.11", + "solana-hash", + "solana-pubkey", +] + +[[package]] +name = "solana-epoch-schedule" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fce071fbddecc55d727b1d7ed16a629afe4f6e4c217bc8d00af3b785f6f67ed" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-example-mocks" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3" +dependencies = [ + "serde", + "serde_derive", + "solana-address-lookup-table-interface", + "solana-clock", + "solana-hash", + "solana-instruction", + "solana-keccak-hasher", + "solana-message", + "solana-nonce", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-feature-gate-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f5c5382b449e8e4e3016fb05e418c53d57782d8b5c30aa372fc265654b956d" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account", + "solana-account-info", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-system-interface", +] + +[[package]] +name = "solana-feature-set" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b93971e289d6425f88e6e3cb6668c4b05df78b3c518c249be55ced8efd6b6d" +dependencies = [ + "ahash 0.8.12", + "lazy_static", + "solana-epoch-schedule", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", +] + +[[package]] +name = "solana-fee" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16beda37597046b1edd1cea6fa7caaed033c091f99ec783fe59c82828bc2adb8" +dependencies = [ + "agave-feature-set", + "solana-fee-structure", + "solana-svm-transaction", +] + +[[package]] +name = "solana-fee-calculator" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89bc408da0fb3812bc3008189d148b4d3e08252c79ad810b245482a3f70cd8d" +dependencies = [ + "log", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-fee-structure" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33adf673581c38e810bf618f745bf31b683a0a4a4377682e6aaac5d9a058dd4e" +dependencies = [ + "serde", + "serde_derive", + "solana-message", + "solana-native-token", +] + +[[package]] +name = "solana-genesis-config" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3725085d47b96d37fef07a29d78d2787fc89a0b9004c66eed7753d1e554989f" +dependencies = [ + "bincode", + "chrono", + "memmap2 0.5.10", + "serde", + "serde_derive", + "solana-account", + "solana-clock", + "solana-cluster-type", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-hash", + "solana-inflation", + "solana-keypair", + "solana-logger", + "solana-poh-config", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-sha256-hasher", + "solana-shred-version", + "solana-signer", + "solana-time-utils", +] + +[[package]] +name = "solana-hard-forks" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c28371f878e2ead55611d8ba1b5fb879847156d04edea13693700ad1a28baf" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-hash" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b96e9f0300fa287b545613f007dfe20043d7812bee255f418c1eb649c93b63" +dependencies = [ + "borsh 1.5.7", + "bytemuck", + "bytemuck_derive", + "five8", + "js-sys", + "serde", + "serde_derive", + "solana-atomic-u64", + "solana-sanitize", + "wasm-bindgen", +] + +[[package]] +name = "solana-inflation" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23eef6a09eb8e568ce6839573e4966850e85e9ce71e6ae1a6c930c1c43947de3" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-instruction" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab5682934bd1f65f8d2c16f21cb532526fcc1a09f796e2cacdb091eee5774ad" +dependencies = [ + "bincode", + "borsh 1.5.7", + "getrandom 0.2.16", + "js-sys", + "num-traits", + "serde", + "serde_derive", + "serde_json", + "solana-define-syscall", + "solana-pubkey", + "wasm-bindgen", +] + +[[package]] +name = "solana-instructions-sysvar" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0e85a6fad5c2d0c4f5b91d34b8ca47118fc593af706e523cdbedf846a954f57" +dependencies = [ + "bitflags", + "solana-account-info", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-sanitize", + "solana-sdk-ids", + "solana-serialize-utils", + "solana-sysvar-id", +] + +[[package]] +name = "solana-keccak-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7aeb957fbd42a451b99235df4942d96db7ef678e8d5061ef34c9b34cae12f79" +dependencies = [ + "sha3", + "solana-define-syscall", + "solana-hash", + "solana-sanitize", +] + +[[package]] +name = "solana-keypair" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd3f04aa1a05c535e93e121a95f66e7dcccf57e007282e8255535d24bf1e98bb" +dependencies = [ + "ed25519-dalek", + "ed25519-dalek-bip32", + "five8", + "rand 0.7.3", + "solana-derivation-path", + "solana-pubkey", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "wasm-bindgen", +] + +[[package]] +name = "solana-last-restart-slot" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6360ac2fdc72e7463565cd256eedcf10d7ef0c28a1249d261ec168c1b55cdd" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-lattice-hash" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6effe24897d8e02484ad87272634028d096f0e061b66b298f8df5031ff7fc0" +dependencies = [ + "base64 0.22.1", + "blake3", + "bs58", + "bytemuck", +] + +[[package]] +name = "solana-loader-v2-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ab08006dad78ae7cd30df8eea0539e207d08d91eaefb3e1d49a446e1c49654" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", +] + +[[package]] +name = "solana-loader-v3-interface" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f7162a05b8b0773156b443bccd674ea78bb9aa406325b467ea78c06c99a63a2" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", +] + +[[package]] +name = "solana-loader-v4-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706a777242f1f39a83e2a96a2a6cb034cb41169c6ecbee2cf09cb873d9659e7e" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", +] + +[[package]] +name = "solana-loader-v4-program" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ab01855d851fa2fb6034b0d48de33d77d5c5f5fb4b0353d8e4a934cc03d48a" +dependencies = [ + "log", + "qualifier_attr", + "solana-account", + "solana-bincode", + "solana-bpf-loader-program", + "solana-instruction", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-log-collector", + "solana-measure", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-sbpf", + "solana-sdk-ids", + "solana-transaction-context", + "solana-type-overrides", +] + +[[package]] +name = "solana-log-collector" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d945b1cf5bf7cbd6f5b78795beda7376370c827640df43bb2a1c17b492dc106" +dependencies = [ + "log", +] + +[[package]] +name = "solana-logger" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8e777ec1afd733939b532a42492d888ec7c88d8b4127a5d867eb45c6eb5cd5" +dependencies = [ + "env_logger", + "lazy_static", + "libc", + "log", + "signal-hook", +] + +[[package]] +name = "solana-measure" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11dcd67cd2ae6065e494b64e861e0498d046d95a61cbbf1ae3d58be1ea0f42ed" + +[[package]] +name = "solana-message" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1796aabce376ff74bf89b78d268fa5e683d7d7a96a0a4e4813ec34de49d5314b" +dependencies = [ + "bincode", + "blake3", + "lazy_static", + "serde", + "serde_derive", + "solana-bincode", + "solana-hash", + "solana-instruction", + "solana-pubkey", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-system-interface", + "solana-transaction-error", + "wasm-bindgen", +] + +[[package]] +name = "solana-metrics" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0375159d8460f423d39e5103dcff6e07796a5ec1850ee1fcfacfd2482a8f34b5" +dependencies = [ + "crossbeam-channel", + "gethostname", + "log", + "reqwest", + "solana-cluster-type", + "solana-sha256-hasher", + "solana-time-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-msg" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092" +dependencies = [ + "solana-define-syscall", +] + +[[package]] +name = "solana-native-token" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61515b880c36974053dd499c0510066783f0cc6ac17def0c7ef2a244874cf4a9" + +[[package]] +name = "solana-net-utils" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a9e831d0f09bd92135d48c5bc79071bb59c0537b9459f1b4dec17ecc0558fa" +dependencies = [ + "anyhow", + "bincode", + "bytes", + "itertools 0.12.1", + "log", + "nix", + "rand 0.8.5", + "serde", + "serde_derive", + "socket2 0.5.10", + "solana-serde", + "tokio", + "url", +] + +[[package]] +name = "solana-nohash-hasher" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" + +[[package]] +name = "solana-nonce" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703e22eb185537e06204a5bd9d509b948f0066f2d1d814a6f475dafb3ddf1325" +dependencies = [ + "serde", + "serde_derive", + "solana-fee-calculator", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", +] + +[[package]] +name = "solana-nonce-account" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde971a20b8dbf60144d6a84439dda86b5466e00e2843091fe731083cda614da" +dependencies = [ + "solana-account", + "solana-hash", + "solana-nonce", + "solana-sdk-ids", +] + +[[package]] +name = "solana-offchain-message" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b526398ade5dea37f1f147ce55dae49aa017a5d7326606359b0445ca8d946581" +dependencies = [ + "num_enum", + "solana-hash", + "solana-packet", + "solana-pubkey", + "solana-sanitize", + "solana-sha256-hasher", + "solana-signature", + "solana-signer", +] + +[[package]] +name = "solana-packet" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004f2d2daf407b3ec1a1ca5ec34b3ccdfd6866dd2d3c7d0715004a96e4b6d127" +dependencies = [ + "bincode", + "bitflags", + "cfg_eval", + "serde", + "serde_derive", + "serde_with", +] + +[[package]] +name = "solana-perf" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37192c0be5c222ca49dbc5667288c5a8bb14837051dd98e541ee4dad160a5da9" +dependencies = [ + "ahash 0.8.12", + "bincode", + "bv", + "bytes", + "caps", + "curve25519-dalek 4.1.3", + "dlopen2", + "fnv", + "libc", + "log", + "nix", + "rand 0.8.5", + "rayon", + "serde", + "solana-hash", + "solana-message", + "solana-metrics", + "solana-packet", + "solana-pubkey", + "solana-rayon-threadlimit", + "solana-sdk-ids", + "solana-short-vec", + "solana-signature", + "solana-time-utils", +] + +[[package]] +name = "solana-poh-config" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d650c3b4b9060082ac6b0efbbb66865089c58405bfb45de449f3f2b91eccee75" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-poseidon" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbac4eb90016eeb1d37fa36e592d3a64421510c49666f81020736611c319faff" +dependencies = [ + "ark-bn254", + "light-poseidon", + "solana-define-syscall", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-precompile-error" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d87b2c1f5de77dfe2b175ee8dd318d196aaca4d0f66f02842f80c852811f9f8" +dependencies = [ + "num-traits", + "solana-decode-error", +] + +[[package]] +name = "solana-precompiles" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e92768a57c652edb0f5d1b30a7d0bc64192139c517967c18600debe9ae3832" +dependencies = [ + "lazy_static", + "solana-ed25519-program", + "solana-feature-set", + "solana-message", + "solana-precompile-error", + "solana-pubkey", + "solana-sdk-ids", + "solana-secp256k1-program", + "solana-secp256r1-program", +] + +[[package]] +name = "solana-presigner" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a57a24e6a4125fc69510b6774cd93402b943191b6cddad05de7281491c90fe" +dependencies = [ + "solana-pubkey", + "solana-signature", + "solana-signer", +] + +[[package]] +name = "solana-program" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98eca145bd3545e2fbb07166e895370576e47a00a7d824e325390d33bf467210" +dependencies = [ + "bincode", + "blake3", + "borsh 0.10.4", + "borsh 1.5.7", + "bs58", + "bytemuck", + "console_error_panic_hook", + "console_log", + "getrandom 0.2.16", + "lazy_static", + "log", + "memoffset", + "num-bigint 0.4.6", + "num-derive 0.4.2", + "num-traits", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info", + "solana-address-lookup-table-interface", + "solana-atomic-u64", + "solana-big-mod-exp", + "solana-bincode", + "solana-blake3-hasher", + "solana-borsh", + "solana-clock", + "solana-cpi", + "solana-decode-error", + "solana-define-syscall", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-example-mocks", + "solana-feature-gate-interface", + "solana-fee-calculator", + "solana-hash", + "solana-instruction", + "solana-instructions-sysvar", + "solana-keccak-hasher", + "solana-last-restart-slot", + "solana-loader-v2-interface", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-message", + "solana-msg", + "solana-native-token", + "solana-nonce", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-program-option", + "solana-program-pack", + "solana-pubkey", + "solana-rent", + "solana-sanitize", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-secp256k1-recover", + "solana-serde-varint", + "solana-serialize-utils", + "solana-sha256-hasher", + "solana-short-vec", + "solana-slot-hashes", + "solana-slot-history", + "solana-stable-layout", + "solana-stake-interface", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", + "solana-vote-interface", + "thiserror 2.0.18", + "wasm-bindgen", +] + +[[package]] +name = "solana-program-entrypoint" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ce041b1a0ed275290a5008ee1a4a6c48f5054c8a3d78d313c08958a06aedbd" +dependencies = [ + "solana-account-info", + "solana-msg", + "solana-program-error", + "solana-pubkey", +] + +[[package]] +name = "solana-program-error" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee2e0217d642e2ea4bee237f37bd61bb02aec60da3647c48ff88f6556ade775" +dependencies = [ + "borsh 1.5.7", + "num-traits", + "serde", + "serde_derive", + "solana-decode-error", + "solana-instruction", + "solana-msg", + "solana-pubkey", +] + +[[package]] +name = "solana-program-memory" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a5426090c6f3fd6cfdc10685322fede9ca8e5af43cd6a59e98bfe4e91671712" +dependencies = [ + "solana-define-syscall", +] + +[[package]] +name = "solana-program-option" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc677a2e9bc616eda6dbdab834d463372b92848b2bfe4a1ed4e4b4adba3397d0" + +[[package]] +name = "solana-program-pack" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" +dependencies = [ + "solana-program-error", +] + +[[package]] +name = "solana-program-runtime" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5653001e07b657c9de6f0417cf9add1cf4325903732c480d415655e10cc86704" +dependencies = [ + "base64 0.22.1", + "bincode", + "enum-iterator", + "itertools 0.12.1", + "log", + "percentage", + "rand 0.8.5", + "serde", + "solana-account", + "solana-clock", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-structure", + "solana-hash", + "solana-instruction", + "solana-last-restart-slot", + "solana-log-collector", + "solana-measure", + "solana-metrics", + "solana-program-entrypoint", + "solana-pubkey", + "solana-rent", + "solana-sbpf", + "solana-sdk-ids", + "solana-slot-hashes", + "solana-stable-layout", + "solana-svm-callback", + "solana-svm-feature-set", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", + "solana-timings", + "solana-transaction-context", + "solana-type-overrides", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-program-test" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3cff7a296c11ff2f02ff391eb4b5c641d09c8eed8a7a674d235b2ccb575b9ca" +dependencies = [ + "agave-feature-set", + "assert_matches", + "async-trait", + "base64 0.22.1", + "bincode", + "chrono-humanize", + "crossbeam-channel", + "log", + "serde", + "solana-account", + "solana-account-info", + "solana-accounts-db", + "solana-banks-client", + "solana-banks-interface", + "solana-banks-server", + "solana-clock", + "solana-commitment-config", + "solana-compute-budget", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-genesis-config", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-loader-v3-interface", + "solana-log-collector", + "solana-logger", + "solana-message", + "solana-msg", + "solana-native-token", + "solana-poh-config", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-runtime", + "solana-pubkey", + "solana-rent", + "solana-runtime", + "solana-sbpf", + "solana-sdk-ids", + "solana-signer", + "solana-stable-layout", + "solana-stake-interface", + "solana-svm", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", + "solana-timings", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "solana-vote-program", + "spl-generic-token", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "solana-pubkey" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b62adb9c3261a052ca1f999398c388f1daf558a1b492f60a6d9e64857db4ff1" +dependencies = [ + "borsh 0.10.4", + "borsh 1.5.7", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "five8", + "five8_const", + "getrandom 0.2.16", + "js-sys", + "num-traits", + "rand 0.8.5", + "serde", + "serde_derive", + "solana-atomic-u64", + "solana-decode-error", + "solana-define-syscall", + "solana-sanitize", + "solana-sha256-hasher", + "wasm-bindgen", +] + +[[package]] +name = "solana-pubsub-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18a7476e1d2e8df5093816afd8fffee94fbb6e442d9be8e6bd3e85f88ce8d5c" +dependencies = [ + "crossbeam-channel", + "futures-util", + "http 0.2.12", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-clock", + "solana-pubkey", + "solana-rpc-client-types", + "solana-signature", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tungstenite", + "url", +] + +[[package]] +name = "solana-quic-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feb5f4a97494459c435aa56de810500cc24e22d0afc632990a8e54a07c05a4" +dependencies = [ + "async-lock", + "async-trait", + "futures", + "itertools 0.12.1", + "log", + "quinn", + "quinn-proto", + "rustls 0.23.37", + "solana-connection-cache", + "solana-keypair", + "solana-measure", + "solana-metrics", + "solana-net-utils", + "solana-pubkey", + "solana-quic-definitions", + "solana-rpc-client-api", + "solana-signer", + "solana-streamer", + "solana-tls-utils", + "solana-transaction-error", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "solana-quic-definitions" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf0d4d5b049eb1d0c35f7b18f305a27c8986fc5c0c9b383e97adaa35334379e" +dependencies = [ + "solana-keypair", +] + +[[package]] +name = "solana-rayon-threadlimit" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cc2a4cae3ef7bb6346b35a60756d2622c297d5fa204f96731db9194c0dc75b" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "solana-rent" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1aea8fdea9de98ca6e8c2da5827707fb3842833521b528a713810ca685d2480" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-rent-collector" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "127e6dfa51e8c8ae3aa646d8b2672bc4ac901972a338a9e1cd249e030564fb9d" +dependencies = [ + "serde", + "serde_derive", + "solana-account", + "solana-clock", + "solana-epoch-schedule", + "solana-genesis-config", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", +] + +[[package]] +name = "solana-rent-debits" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6f9113c6003492e74438d1288e30cffa8ccfdc2ef7b49b9e816d8034da18cd" +dependencies = [ + "solana-pubkey", + "solana-reward-info", +] + +[[package]] +name = "solana-reserved-account-keys" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b22ea19ca2a3f28af7cd047c914abf833486bf7a7c4a10fc652fff09b385b1" +dependencies = [ + "lazy_static", + "solana-feature-set", + "solana-pubkey", + "solana-sdk-ids", +] + +[[package]] +name = "solana-reward-info" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18205b69139b1ae0ab8f6e11cdcb627328c0814422ad2482000fa2ca54ae4a2f" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-rpc-client" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d3161ac0918178e674c1f7f1bfac40de3e7ed0383bd65747d63113c156eaeb" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bincode", + "bs58", + "futures", + "indicatif", + "log", + "reqwest", + "reqwest-middleware", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-account-decoder-client-types", + "solana-clock", + "solana-commitment-config", + "solana-epoch-info", + "solana-epoch-schedule", + "solana-feature-gate-interface", + "solana-hash", + "solana-instruction", + "solana-message", + "solana-pubkey", + "solana-rpc-client-api", + "solana-signature", + "solana-transaction", + "solana-transaction-error", + "solana-transaction-status-client-types", + "solana-version", + "solana-vote-interface", + "tokio", +] + +[[package]] +name = "solana-rpc-client-api" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dbc138685c79d88a766a8fd825057a74ea7a21e1dd7f8de275ada899540fff7" +dependencies = [ + "anyhow", + "jsonrpc-core", + "reqwest", + "reqwest-middleware", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-clock", + "solana-rpc-client-types", + "solana-signer", + "solana-transaction-error", + "solana-transaction-status-client-types", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-rpc-client-nonce-utils" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f0ee41b9894ff36adebe546a110b899b0d0294b07845d8acdc73822e6af4b0" +dependencies = [ + "solana-account", + "solana-commitment-config", + "solana-hash", + "solana-message", + "solana-nonce", + "solana-pubkey", + "solana-rpc-client", + "solana-sdk-ids", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-rpc-client-types" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea428a81729255d895ea47fba9b30fd4dacbfe571a080448121bd0592751676" +dependencies = [ + "base64 0.22.1", + "bs58", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-account-decoder-client-types", + "solana-clock", + "solana-commitment-config", + "solana-fee-calculator", + "solana-inflation", + "solana-pubkey", + "solana-transaction-error", + "solana-transaction-status-client-types", + "solana-version", + "spl-generic-token", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-runtime" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3f83d5af95937504ec3447415b13ca5f1326cad3c3f790f2c66ee2153f0919" +dependencies = [ + "agave-feature-set", + "agave-precompiles", + "agave-reserved-account-keys", + "ahash 0.8.12", + "aquamarine", + "arrayref", + "assert_matches", + "base64 0.22.1", + "bincode", + "blake3", + "bv", + "bytemuck", + "bzip2", + "crossbeam-channel", + "dashmap", + "dir-diff", + "flate2", + "fnv", + "im", + "itertools 0.12.1", + "libc", + "log", + "lz4", + "memmap2 0.9.10", + "mockall", + "modular-bitfield", + "num-derive 0.4.2", + "num-traits", + "num_cpus", + "num_enum", + "percentage", + "qualifier_attr", + "rand 0.8.5", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "serde_with", + "solana-account", + "solana-account-info", + "solana-accounts-db", + "solana-address-lookup-table-interface", + "solana-bpf-loader-program", + "solana-bucket-map", + "solana-builtins", + "solana-client-traits", + "solana-clock", + "solana-commitment-config", + "solana-compute-budget", + "solana-compute-budget-instruction", + "solana-compute-budget-interface", + "solana-cost-model", + "solana-cpi", + "solana-ed25519-program", + "solana-epoch-info", + "solana-epoch-rewards-hasher", + "solana-epoch-schedule", + "solana-feature-gate-interface", + "solana-fee", + "solana-fee-calculator", + "solana-fee-structure", + "solana-genesis-config", + "solana-hard-forks", + "solana-hash", + "solana-inflation", + "solana-instruction", + "solana-keypair", + "solana-lattice-hash", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-measure", + "solana-message", + "solana-metrics", + "solana-native-token", + "solana-nohash-hasher", + "solana-nonce", + "solana-nonce-account", + "solana-packet", + "solana-perf", + "solana-poh-config", + "solana-precompile-error", + "solana-program-runtime", + "solana-pubkey", + "solana-rayon-threadlimit", + "solana-rent", + "solana-rent-collector", + "solana-rent-debits", + "solana-reward-info", + "solana-runtime-transaction", + "solana-sdk-ids", + "solana-secp256k1-program", + "solana-seed-derivable", + "solana-serde", + "solana-sha256-hasher", + "solana-signature", + "solana-signer", + "solana-slot-hashes", + "solana-slot-history", + "solana-stake-interface", + "solana-stake-program", + "solana-svm", + "solana-svm-callback", + "solana-svm-rent-collector", + "solana-svm-transaction", + "solana-system-interface", + "solana-system-transaction", + "solana-sysvar", + "solana-sysvar-id", + "solana-time-utils", + "solana-timings", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "solana-transaction-status-client-types", + "solana-unified-scheduler-logic", + "solana-version", + "solana-vote", + "solana-vote-interface", + "solana-vote-program", + "spl-generic-token", + "static_assertions", + "strum", + "strum_macros", + "symlink", + "tar", + "tempfile", + "thiserror 2.0.18", + "zstd", +] + +[[package]] +name = "solana-runtime-transaction" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca52090550885453ac7a26a0fd7d6ffe057dd1d52c350cde17887b004a0ddcd0" +dependencies = [ + "agave-transaction-view", + "log", + "solana-compute-budget", + "solana-compute-budget-instruction", + "solana-hash", + "solana-message", + "solana-pubkey", + "solana-sdk-ids", + "solana-signature", + "solana-svm-transaction", + "solana-transaction", + "solana-transaction-error", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-sanitize" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" + +[[package]] +name = "solana-sbpf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "474a2d95dc819898ded08d24f29642d02189d3e1497bbb442a92a3997b7eb55f" +dependencies = [ + "byteorder", + "combine 3.8.1", + "hash32", + "libc", + "log", + "rand 0.8.5", + "rustc-demangle", + "thiserror 2.0.18", + "winapi", +] + +[[package]] +name = "solana-sdk" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc0e4a7635b902791c44b6581bfb82f3ada32c5bc0929a64f39fe4bb384c86a" +dependencies = [ + "bincode", + "bs58", + "getrandom 0.1.16", + "js-sys", + "serde", + "serde_json", + "solana-account", + "solana-bn254", + "solana-client-traits", + "solana-cluster-type", + "solana-commitment-config", + "solana-compute-budget-interface", + "solana-decode-error", + "solana-derivation-path", + "solana-ed25519-program", + "solana-epoch-info", + "solana-epoch-rewards-hasher", + "solana-feature-set", + "solana-fee-structure", + "solana-genesis-config", + "solana-hard-forks", + "solana-inflation", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-native-token", + "solana-nonce-account", + "solana-offchain-message", + "solana-packet", + "solana-poh-config", + "solana-precompile-error", + "solana-precompiles", + "solana-presigner", + "solana-program", + "solana-program-memory", + "solana-pubkey", + "solana-quic-definitions", + "solana-rent-collector", + "solana-rent-debits", + "solana-reserved-account-keys", + "solana-reward-info", + "solana-sanitize", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-secp256k1-program", + "solana-secp256k1-recover", + "solana-secp256r1-program", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-serde", + "solana-serde-varint", + "solana-short-vec", + "solana-shred-version", + "solana-signature", + "solana-signer", + "solana-system-transaction", + "solana-time-utils", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "solana-validator-exit", + "thiserror 2.0.18", + "wasm-bindgen", +] + +[[package]] +name = "solana-sdk-ids" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" +dependencies = [ + "solana-pubkey", +] + +[[package]] +name = "solana-sdk-macro" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86280da8b99d03560f6ab5aca9de2e38805681df34e0bb8f238e69b29433b9df" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "solana-secp256k1-program" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f19833e4bc21558fe9ec61f239553abe7d05224347b57d65c2218aeeb82d6149" +dependencies = [ + "bincode", + "digest 0.10.7", + "libsecp256k1", + "serde", + "serde_derive", + "sha3", + "solana-feature-set", + "solana-instruction", + "solana-precompile-error", + "solana-sdk-ids", + "solana-signature", +] + +[[package]] +name = "solana-secp256k1-recover" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" +dependencies = [ + "borsh 1.5.7", + "libsecp256k1", + "solana-define-syscall", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-secp256r1-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce0ae46da3071a900f02d367d99b2f3058fe2e90c5062ac50c4f20cfedad8f0f" +dependencies = [ + "bytemuck", + "openssl", + "solana-feature-set", + "solana-instruction", + "solana-precompile-error", + "solana-sdk-ids", +] + +[[package]] +name = "solana-seed-derivable" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beb82b5adb266c6ea90e5cf3967235644848eac476c5a1f2f9283a143b7c97f" +dependencies = [ + "solana-derivation-path", +] + +[[package]] +name = "solana-seed-phrase" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "sha2 0.10.9", +] + +[[package]] +name = "solana-send-transaction-service" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f838b10e5b35e68987de6b2dfec19a3ba9d48509f26110c3d738125e07d2e915" +dependencies = [ + "async-trait", + "crossbeam-channel", + "itertools 0.12.1", + "log", + "solana-client", + "solana-clock", + "solana-connection-cache", + "solana-hash", + "solana-keypair", + "solana-measure", + "solana-metrics", + "solana-nonce-account", + "solana-pubkey", + "solana-quic-definitions", + "solana-runtime", + "solana-signature", + "solana-time-utils", + "solana-tpu-client-next", + "tokio", + "tokio-util 0.7.17", +] + +[[package]] +name = "solana-serde" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1931484a408af466e14171556a47adaa215953c7f48b24e5f6b0282763818b04" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serde-varint" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7e155eba458ecfb0107b98236088c3764a09ddf0201ec29e52a0be40857113" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serialize-utils" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e" +dependencies = [ + "solana-instruction", + "solana-pubkey", + "solana-sanitize", +] + +[[package]] +name = "solana-sha256-hasher" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa3feb32c28765f6aa1ce8f3feac30936f16c5c3f7eb73d63a5b8f6f8ecdc44" +dependencies = [ "sha2 0.10.9", - "solana-frozen-abi-macro", - "subtle", - "thiserror", + "solana-define-syscall", + "solana-hash", ] [[package]] -name = "solana-frozen-abi-macro" -version = "1.18.26" +name = "solana-short-vec" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c142f779c3633ac83c84d04ff06c70e1f558c876f13358bed77ba629c7417932" +checksum = "5c54c66f19b9766a56fa0057d060de8378676cb64987533fa088861858fc5a69" dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.110", + "serde", ] [[package]] -name = "solana-logger" -version = "1.18.26" +name = "solana-shred-version" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121d36ffb3c6b958763312cbc697fbccba46ee837d3a0aa4fc0e90fcb3b884f3" +checksum = "afd3db0461089d1ad1a78d9ba3f15b563899ca2386351d38428faa5350c60a98" dependencies = [ - "env_logger", - "lazy_static", - "log", + "solana-hard-forks", + "solana-hash", + "solana-sha256-hasher", ] [[package]] -name = "solana-measure" -version = "1.18.26" +name = "solana-signature" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c01a7f9cdc9d9d37a3d5651b2fe7ec9d433c2a3470b9f35897e373b421f0737" +checksum = "64c8ec8e657aecfc187522fc67495142c12f35e55ddeca8698edbb738b8dbd8c" dependencies = [ - "log", - "solana-sdk", + "ed25519-dalek", + "five8", + "rand 0.8.5", + "serde", + "serde-big-array", + "serde_derive", + "solana-sanitize", ] [[package]] -name = "solana-metrics" -version = "1.18.26" +name = "solana-signer" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e36052aff6be1536bdf6f737c6e69aca9dbb6a2f3f582e14ecb0ddc0cd66ce" +checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" dependencies = [ - "crossbeam-channel", - "gethostname", - "lazy_static", - "log", - "reqwest", - "solana-sdk", - "thiserror", + "solana-pubkey", + "solana-signature", + "solana-transaction-error", ] [[package]] -name = "solana-net-utils" -version = "1.18.26" +name = "solana-slot-hashes" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1f5c6be9c5b272866673741e1ebc64b2ea2118e5c6301babbce526fdfb15f4" +checksum = "0c8691982114513763e88d04094c9caa0376b867a29577939011331134c301ce" dependencies = [ - "bincode", - "clap 3.2.25", - "crossbeam-channel", - "log", - "nix", - "rand 0.8.5", "serde", "serde_derive", - "socket2 0.5.10", - "solana-logger", - "solana-sdk", - "solana-version", - "tokio", - "url", + "solana-hash", + "solana-sdk-ids", + "solana-sysvar-id", ] [[package]] -name = "solana-perf" -version = "1.18.26" +name = "solana-slot-history" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28acaf22477566a0fbddd67249ea5d859b39bacdb624aff3fadd3c5745e2643c" +checksum = "97ccc1b2067ca22754d5283afb2b0126d61eae734fc616d23871b0943b0d935e" dependencies = [ - "ahash 0.8.12", - "bincode", "bv", - "caps", - "curve25519-dalek", - "dlopen2", - "fnv", - "lazy_static", - "libc", - "log", - "nix", - "rand 0.8.5", - "rayon", - "rustc_version", "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-metrics", - "solana-rayon-threadlimit", - "solana-sdk", - "solana-vote-program", + "serde_derive", + "solana-sdk-ids", + "solana-sysvar-id", ] [[package]] -name = "solana-program" -version = "1.18.26" +name = "solana-stable-layout" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10f4588cefd716b24a1a40dd32c278e43a560ab8ce4de6b5805c9d113afdfa1" +checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff", - "ark-serialize", - "base64 0.21.7", - "bincode", - "bitflags 2.10.0", - "blake3", - "borsh 0.10.4", - "borsh 0.9.3", - "borsh 1.5.7", - "bs58 0.4.0", - "bv", - "bytemuck", - "cc", - "console_error_panic_hook", - "console_log", - "curve25519-dalek", - "getrandom 0.2.16", - "itertools", - "js-sys", - "lazy_static", - "libc", - "libsecp256k1", - "light-poseidon", - "log", - "memoffset 0.9.1", - "num-bigint 0.4.6", - "num-derive 0.4.2", - "num-traits", - "parking_lot", - "rand 0.8.5", - "rustc_version", - "rustversion", - "serde", - "serde_bytes", - "serde_derive", - "serde_json", - "sha2 0.10.9", - "sha3 0.10.8", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk-macro", - "thiserror", - "tiny-bip39", - "wasm-bindgen", - "zeroize", + "solana-instruction", + "solana-pubkey", ] [[package]] -name = "solana-program-runtime" -version = "1.18.26" +name = "solana-stake-interface" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf0c3eab2a80f514289af1f422c121defb030937643c43b117959d6f1932fb5" +checksum = "5269e89fde216b4d7e1d1739cf5303f8398a1ff372a81232abbee80e554a838c" dependencies = [ - "base64 0.21.7", - "bincode", - "eager", - "enum-iterator", - "itertools", - "libc", - "log", - "num-derive 0.4.2", + "borsh 0.10.4", + "borsh 1.5.7", "num-traits", - "percentage", - "rand 0.8.5", - "rustc_version", "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-measure", - "solana-metrics", - "solana-sdk", - "solana_rbpf", - "thiserror", + "serde_derive", + "solana-clock", + "solana-cpi", + "solana-decode-error", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-system-interface", + "solana-sysvar-id", ] [[package]] -name = "solana-pubsub-client" -version = "1.18.26" +name = "solana-stake-program" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b064e76909d33821b80fdd826e6757251934a52958220c92639f634bea90366d" +checksum = "500e9b9d11573f12de91e94f9c4459882cd5ffc692776af49b610d6fcc0b167f" dependencies = [ - "crossbeam-channel", - "futures-util", + "agave-feature-set", + "bincode", "log", - "reqwest", - "semver", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-rpc-client-api", - "solana-sdk", - "thiserror", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tungstenite", - "url", + "solana-account", + "solana-bincode", + "solana-clock", + "solana-config-program-client", + "solana-genesis-config", + "solana-instruction", + "solana-log-collector", + "solana-native-token", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-stake-interface", + "solana-sysvar", + "solana-transaction-context", + "solana-type-overrides", + "solana-vote-interface", ] [[package]] -name = "solana-quic-client" -version = "1.18.26" +name = "solana-streamer" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a90e40ee593f6e9ddd722d296df56743514ae804975a76d47e7afed4e3da244" +checksum = "5643516e5206b89dd4bdf67c39815606d835a51a13260e43349abdb92d241b1d" dependencies = [ - "async-mutex", - "async-trait", + "async-channel", + "bytes", + "crossbeam-channel", + "dashmap", "futures", - "itertools", - "lazy_static", + "futures-util", + "governor", + "histogram", + "indexmap", + "itertools 0.12.1", + "libc", "log", + "nix", + "pem", + "percentage", "quinn", "quinn-proto", - "rcgen", - "rustls", - "solana-connection-cache", + "rand 0.8.5", + "rustls 0.23.37", + "smallvec", + "socket2 0.5.10", + "solana-keypair", "solana-measure", "solana-metrics", "solana-net-utils", - "solana-rpc-client-api", - "solana-sdk", - "solana-streamer", - "thiserror", + "solana-packet", + "solana-perf", + "solana-pubkey", + "solana-quic-definitions", + "solana-signature", + "solana-signer", + "solana-time-utils", + "solana-tls-utils", + "solana-transaction-error", + "solana-transaction-metrics-tracker", + "thiserror 2.0.18", "tokio", + "tokio-util 0.7.17", + "x509-parser", ] [[package]] -name = "solana-rayon-threadlimit" -version = "1.18.26" +name = "solana-svm" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66468f9c014992167de10cc68aad6ac8919a8c8ff428dc88c0d2b4da8c02b8b7" +checksum = "006180b920e8d8c1dab4f6a0fda248b5b97d912eda4c872534d178bc31231bec" dependencies = [ - "lazy_static", - "num_cpus", + "ahash 0.8.12", + "log", + "percentage", + "serde", + "serde_derive", + "solana-account", + "solana-clock", + "solana-fee-structure", + "solana-hash", + "solana-instruction", + "solana-instructions-sysvar", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-loader-v4-program", + "solana-log-collector", + "solana-measure", + "solana-message", + "solana-nonce", + "solana-nonce-account", + "solana-program-entrypoint", + "solana-program-pack", + "solana-program-runtime", + "solana-pubkey", + "solana-rent", + "solana-rent-collector", + "solana-rent-debits", + "solana-sdk-ids", + "solana-svm-callback", + "solana-svm-feature-set", + "solana-svm-rent-collector", + "solana-svm-transaction", + "solana-system-interface", + "solana-sysvar-id", + "solana-timings", + "solana-transaction-context", + "solana-transaction-error", + "solana-type-overrides", + "spl-generic-token", + "thiserror 2.0.18", ] [[package]] -name = "solana-remote-wallet" -version = "1.18.26" +name = "solana-svm-callback" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c191019f4d4f84281a6d0dd9a43181146b33019627fc394e42e08ade8976b431" +checksum = "7cef9f7d5cfb5d375081a6c8ad712a6f0e055a15890081f845acf55d8254a7a2" dependencies = [ - "console", - "dialoguer", - "log", - "num-derive 0.4.2", - "num-traits", - "parking_lot", - "qstring", - "semver", - "solana-sdk", - "thiserror", - "uriparse", + "solana-account", + "solana-precompile-error", + "solana-pubkey", ] [[package]] -name = "solana-rpc-client" -version = "1.18.26" +name = "solana-svm-feature-set" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f24b836eb4d74ec255217bdbe0f24f64a07adeac31aca61f334f91cd4a3b1d5" + +[[package]] +name = "solana-svm-rent-collector" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ed4628e338077c195ddbf790693d410123d17dec0a319b5accb4aaee3fb15c" +checksum = "030200d7f3ce4879f9d8c980ceb9e1d5e9a302866db035776496069b20c427b4" dependencies = [ - "async-trait", - "base64 0.21.7", - "bincode", - "bs58 0.4.0", - "indicatif", - "log", - "reqwest", - "semver", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-rpc-client-api", - "solana-sdk", - "solana-transaction-status", - "solana-version", - "solana-vote-program", - "tokio", + "solana-account", + "solana-clock", + "solana-pubkey", + "solana-rent", + "solana-rent-collector", + "solana-sdk-ids", + "solana-transaction-context", + "solana-transaction-error", ] [[package]] -name = "solana-rpc-client-api" -version = "1.18.26" +name = "solana-svm-transaction" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c913551faa4a1ae4bbfef6af19f3a5cf847285c05b4409e37c8993b3444229" +checksum = "ab717b9539375ebb088872c6c87d1d8832d19f30f154ecc530154d23f60a6f0c" dependencies = [ - "base64 0.21.7", - "bs58 0.4.0", - "jsonrpc-core", - "reqwest", - "semver", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-sdk", - "solana-transaction-status", - "solana-version", - "spl-token-2022 1.0.0", - "thiserror", + "solana-hash", + "solana-message", + "solana-pubkey", + "solana-sdk-ids", + "solana-signature", + "solana-transaction", ] [[package]] -name = "solana-rpc-client-nonce-utils" -version = "1.18.26" +name = "solana-system-interface" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a47b6bb1834e6141a799db62bbdcf80d17a7d58d7bc1684c614e01a7293d7cf" +checksum = "94d7c18cb1a91c6be5f5a8ac9276a1d7c737e39a21beba9ea710ab4b9c63bc90" dependencies = [ - "clap 2.34.0", - "solana-clap-utils", - "solana-rpc-client", - "solana-sdk", - "thiserror", + "js-sys", + "num-traits", + "serde", + "serde_derive", + "solana-decode-error", + "solana-instruction", + "solana-pubkey", + "wasm-bindgen", ] [[package]] -name = "solana-sdk" -version = "1.18.26" +name = "solana-system-program" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580ad66c2f7a4c3cb3244fe21440546bd500f5ecb955ad9826e92a78dded8009" +checksum = "23ca36cef39aea7761be58d4108a56a2e27042fb1e913355fdb142a05fc7eab7" dependencies = [ - "assert_matches", - "base64 0.21.7", "bincode", - "bitflags 2.10.0", - "borsh 1.5.7", - "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.4.2", - "num-traits", - "num_enum 0.7.5", - "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.9", - "sha3 0.10.8", - "siphasher", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-program", - "solana-sdk-macro", - "thiserror", - "uriparse", - "wasm-bindgen", + "solana-account", + "solana-bincode", + "solana-fee-calculator", + "solana-instruction", + "solana-log-collector", + "solana-nonce", + "solana-nonce-account", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", + "solana-sysvar", + "solana-transaction-context", + "solana-type-overrides", ] [[package]] -name = "solana-sdk-macro" -version = "1.18.26" +name = "solana-system-transaction" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b75d0f193a27719257af19144fdaebec0415d1c9e9226ae4bd29b791be5e9bd" +checksum = "5bd98a25e5bcba8b6be8bcbb7b84b24c2a6a8178d7fb0e3077a916855ceba91a" dependencies = [ - "bs58 0.4.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.110", + "solana-hash", + "solana-keypair", + "solana-message", + "solana-pubkey", + "solana-signer", + "solana-system-interface", + "solana-transaction", ] [[package]] -name = "solana-security-txt" -version = "1.1.2" +name = "solana-sysvar" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "156bb61a96c605fa124e052d630dba2f6fb57e08c7d15b757e1e958b3ed7b3fe" +checksum = "b8c3595f95069f3d90f275bb9bd235a1973c4d059028b0a7f81baca2703815db" dependencies = [ - "hashbrown 0.15.2", + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-define-syscall", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-hash", + "solana-instruction", + "solana-instructions-sysvar", + "solana-last-restart-slot", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-pubkey", + "solana-rent", + "solana-sanitize", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-slot-hashes", + "solana-slot-history", + "solana-stake-interface", + "solana-sysvar-id", ] [[package]] -name = "solana-streamer" -version = "1.18.26" +name = "solana-sysvar-id" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8476e41ad94fe492e8c06697ee35912cf3080aae0c9e9ac6430835256ccf056" +checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" dependencies = [ - "async-channel", - "bytes", - "crossbeam-channel", - "futures-util", - "histogram", - "indexmap 2.12.1", - "itertools", - "libc", - "log", - "nix", - "pem", - "percentage", - "pkcs8", - "quinn", - "quinn-proto", - "rand 0.8.5", - "rcgen", - "rustls", - "smallvec", - "solana-metrics", - "solana-perf", - "solana-sdk", - "thiserror", - "tokio", - "x509-parser", + "solana-pubkey", + "solana-sdk-ids", ] [[package]] name = "solana-thin-client" -version = "1.18.26" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c02245d0d232430e79dc0d624aa42d50006097c3aec99ac82ac299eaa3a73f" +checksum = "6c1025715a113e0e2e379b30a6bfe4455770dc0759dabf93f7dbd16646d5acbe" dependencies = [ "bincode", "log", "rayon", + "solana-account", + "solana-client-traits", + "solana-clock", + "solana-commitment-config", "solana-connection-cache", + "solana-epoch-info", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-pubkey", "solana-rpc-client", "solana-rpc-client-api", - "solana-sdk", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction", + "solana-transaction-error", +] + +[[package]] +name = "solana-time-utils" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af261afb0e8c39252a04d026e3ea9c405342b08c871a2ad8aa5448e068c784c" + +[[package]] +name = "solana-timings" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c49b842dfc53c1bf9007eaa6730296dea93b4fce73f457ce1080af43375c0d6" +dependencies = [ + "eager", + "enum-iterator", + "solana-pubkey", +] + +[[package]] +name = "solana-tls-utils" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14494aa87a75a883d1abcfee00f1278a28ecc594a2f030084879eb40570728f6" +dependencies = [ + "rustls 0.23.37", + "solana-keypair", + "solana-pubkey", + "solana-signer", + "x509-parser", ] [[package]] name = "solana-tpu-client" -version = "1.18.26" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67251506ed03de15f1347b46636b45c47da6be75015b4a13f0620b21beb00566" +checksum = "17895ce70fd1dd93add3fbac87d599954ded93c63fa1c66f702d278d96a6da14" dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap 2.12.1", + "indexmap", "indicatif", "log", "rayon", + "solana-client-traits", + "solana-clock", + "solana-commitment-config", "solana-connection-cache", + "solana-epoch-schedule", "solana-measure", - "solana-metrics", + "solana-message", + "solana-net-utils", + "solana-pubkey", "solana-pubsub-client", + "solana-quic-definitions", "solana-rpc-client", "solana-rpc-client-api", - "solana-sdk", - "thiserror", + "solana-signature", + "solana-signer", + "solana-transaction", + "solana-transaction-error", + "thiserror 2.0.18", "tokio", ] [[package]] -name = "solana-transaction-status" -version = "1.18.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3d36db1b2ab2801afd5482aad9fb15ed7959f774c81a77299fdd0ddcf839d4" -dependencies = [ - "Inflector", - "base64 0.21.7", - "bincode", - "borsh 0.10.4", - "bs58 0.4.0", - "lazy_static", - "log", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-sdk", - "spl-associated-token-account", - "spl-memo", - "spl-token", - "spl-token-2022 1.0.0", - "thiserror", -] - -[[package]] -name = "solana-udp-client" -version = "1.18.26" +name = "solana-tpu-client-next" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a754a3c2265eb02e0c35aeaca96643951f03cee6b376afe12e0cf8860ffccd1" +checksum = "418739a37f0c1806c4e273d7705103e53c74b423fc13044a99d9f7884524ae02" dependencies = [ "async-trait", + "log", + "lru", + "quinn", + "rustls 0.23.37", + "solana-clock", "solana-connection-cache", - "solana-net-utils", - "solana-sdk", + "solana-keypair", + "solana-measure", + "solana-metrics", + "solana-quic-definitions", + "solana-rpc-client", "solana-streamer", - "thiserror", + "solana-time-utils", + "solana-tls-utils", + "solana-tpu-client", + "thiserror 2.0.18", "tokio", + "tokio-util 0.7.17", ] [[package]] -name = "solana-version" -version = "1.18.26" +name = "solana-transaction" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44776bd685cc02e67ba264384acc12ef2931d01d1a9f851cb8cdbd3ce455b9e" +checksum = "80657d6088f721148f5d889c828ca60c7daeedac9a8679f9ec215e0c42bcbf41" dependencies = [ - "log", - "rustc_version", - "semver", + "bincode", "serde", "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk", + "solana-bincode", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-precompiles", + "solana-pubkey", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction-error", + "wasm-bindgen", ] [[package]] -name = "solana-vote-program" -version = "1.18.26" +name = "solana-transaction-context" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25810970c91feb579bd3f67dca215fce971522e42bfd59696af89c5dfebd997c" +checksum = "54a312304361987a85b2ef2293920558e6612876a639dd1309daf6d0d59ef2fe" dependencies = [ "bincode", - "log", - "num-derive 0.4.2", - "num-traits", - "rustc_version", "serde", "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-metrics", - "solana-program", - "solana-program-runtime", - "solana-sdk", - "thiserror", + "solana-account", + "solana-instruction", + "solana-instructions-sysvar", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", ] [[package]] -name = "solana-zk-token-sdk" -version = "1.18.26" +name = "solana-transaction-error" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbdf4249b6dfcbba7d84e2b53313698043f60f8e22ce48286e6fbe8a17c8d16" +checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" dependencies = [ - "aes-gcm-siv", - "base64 0.21.7", - "bincode", - "bytemuck", - "byteorder", - "curve25519-dalek", - "getrandom 0.1.16", - "itertools", - "lazy_static", - "merlin", - "num-derive 0.4.2", - "num-traits", - "rand 0.7.3", "serde", - "serde_json", - "sha3 0.9.1", - "solana-program", - "solana-sdk", - "subtle", - "thiserror", - "zeroize", + "serde_derive", + "solana-instruction", + "solana-sanitize", ] [[package]] -name = "solana_rbpf" -version = "0.8.3" +name = "solana-transaction-metrics-tracker" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5d083187e3b3f453e140f292c09186881da8a02a7b5e27f645ee26de3d9cc5" +checksum = "03fc4e1b6252dc724f5ee69db6229feb43070b7318651580d2174da8baefb993" dependencies = [ - "byteorder", - "combine", - "goblin", - "hash32", - "libc", + "base64 0.22.1", + "bincode", "log", "rand 0.8.5", - "rustc-demangle", - "scroll", - "thiserror", - "winapi", + "solana-packet", + "solana-perf", + "solana-short-vec", + "solana-signature", ] [[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spki" -version = "0.5.4" +name = "solana-transaction-status-client-types" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +checksum = "51f1d7c2387c35850848212244d2b225847666cb52d3bd59a5c409d2c300303d" dependencies = [ - "base64ct", - "der", + "base64 0.22.1", + "bincode", + "bs58", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder-client-types", + "solana-commitment-config", + "solana-message", + "solana-reward-info", + "solana-signature", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "thiserror 2.0.18", ] [[package]] -name = "spl-associated-token-account" -version = "2.3.0" +name = "solana-type-overrides" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" +checksum = "41d80c44761eb398a157d809a04840865c347e1831ae3859b6100c0ee457bc1a" dependencies = [ - "assert_matches", - "borsh 0.10.4", - "num-derive 0.4.2", - "num-traits", - "solana-program", - "spl-token", - "spl-token-2022 1.0.0", - "thiserror", + "rand 0.8.5", ] [[package]] -name = "spl-discriminator" -version = "0.1.0" +name = "solana-udp-client" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +checksum = "2dd36227dd3035ac09a89d4239551d2e3d7d9b177b61ccc7c6d393c3974d0efa" dependencies = [ - "bytemuck", - "solana-program", - "spl-discriminator-derive", + "async-trait", + "solana-connection-cache", + "solana-keypair", + "solana-net-utils", + "solana-streamer", + "solana-transaction-error", + "thiserror 2.0.18", + "tokio", ] [[package]] -name = "spl-discriminator-derive" -version = "0.1.2" +name = "solana-unified-scheduler-logic" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" +checksum = "ca8d0560b66257004b5a3497b2b8a09486035a742b888ed4eca0efa9211c932a" dependencies = [ - "quote", - "spl-discriminator-syn", - "syn 2.0.110", + "assert_matches", + "solana-pubkey", + "solana-runtime-transaction", + "solana-transaction", + "static_assertions", + "unwrap_none", ] [[package]] -name = "spl-discriminator-syn" -version = "0.1.2" +name = "solana-validator-exit" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" -dependencies = [ - "proc-macro2", - "quote", - "sha2 0.10.9", - "syn 2.0.110", - "thiserror", -] +checksum = "7bbf6d7a3c0b28dd5335c52c0e9eae49d0ae489a8f324917faf0ded65a812c1d" [[package]] -name = "spl-memo" -version = "4.0.0" +name = "solana-version" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" +checksum = "3324d46c7f7b7f5d34bf7dc71a2883bdc072c7b28ca81d0b2167ecec4cf8da9f" dependencies = [ - "solana-program", + "agave-feature-set", + "rand 0.8.5", + "semver", + "serde", + "serde_derive", + "solana-sanitize", + "solana-serde-varint", ] [[package]] -name = "spl-pod" -version = "0.1.0" +name = "solana-vote" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +checksum = "67f9f6132f699605e11df62631ae4861b21cb2d99f0fca1b852d277c982107f9" 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" + "itertools 0.12.1", + "log", + "serde", + "serde_derive", + "solana-account", + "solana-bincode", + "solana-clock", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-packet", + "solana-pubkey", + "solana-sdk-ids", + "solana-serialize-utils", + "solana-signature", + "solana-signer", + "solana-svm-transaction", + "solana-transaction", + "solana-vote-interface", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-vote-interface" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b80d57478d6599d30acc31cc5ae7f93ec2361a06aefe8ea79bc81739a08af4c3" dependencies = [ + "bincode", "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.9", - "syn 2.0.110", -] - -[[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", + "serde", + "serde_derive", + "solana-clock", + "solana-decode-error", + "solana-hash", + "solana-instruction", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-serde-varint", + "solana-serialize-utils", + "solana-short-vec", + "solana-system-interface", ] [[package]] -name = "spl-tlv-account-resolution" -version = "0.5.1" +name = "solana-vote-program" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615d381f48ddd2bb3c57c7f7fb207591a2a05054639b18a62e785117dd7a8683" +checksum = "908d0e72c8b83e48762eb3e8c9114497cf4b1d66e506e360c46aba9308e71299" dependencies = [ - "bytemuck", - "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-type-length-value", + "agave-feature-set", + "bincode", + "log", + "num-derive 0.4.2", + "num-traits", + "serde", + "serde_derive", + "solana-account", + "solana-bincode", + "solana-clock", + "solana-epoch-schedule", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-signer", + "solana-slot-hashes", + "solana-transaction", + "solana-transaction-context", + "solana-vote-interface", + "thiserror 2.0.18", ] [[package]] -name = "spl-token" -version = "4.0.0" +name = "solana-zk-elgamal-proof-program" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" +checksum = "70cea14481d8efede6b115a2581f27bc7c6fdfba0752c20398456c3ac1245fc4" dependencies = [ - "arrayref", + "agave-feature-set", "bytemuck", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", - "num_enum 0.6.1", - "solana-program", - "thiserror", + "solana-instruction", + "solana-log-collector", + "solana-program-runtime", + "solana-sdk-ids", + "solana-zk-sdk", ] [[package]] -name = "spl-token-2022" -version = "0.9.0" +name = "solana-zk-sdk" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" +checksum = "97b9fc6ec37d16d0dccff708ed1dd6ea9ba61796700c3bb7c3b401973f10f63b" dependencies = [ - "arrayref", + "aes-gcm-siv", + "base64 0.22.1", + "bincode", "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "itertools 0.12.1", + "js-sys", + "merlin", "num-derive 0.4.2", "num-traits", - "num_enum 0.7.5", - "solana-program", - "solana-zk-token-sdk", - "spl-memo", - "spl-pod", - "spl-token", - "spl-token-metadata-interface", - "spl-transfer-hook-interface 0.3.0", - "spl-type-length-value", - "thiserror", + "rand 0.8.5", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-derivation-path", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "subtle", + "thiserror 2.0.18", + "wasm-bindgen", + "zeroize", ] [[package]] -name = "spl-token-2022" -version = "1.0.0" +name = "solana-zk-token-proof-program" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" +checksum = "579752ad6ea2a671995f13c763bf28288c3c895cb857a518cc4ebab93c9a8dde" dependencies = [ - "arrayref", + "agave-feature-set", "bytemuck", "num-derive 0.4.2", "num-traits", - "num_enum 0.7.5", - "solana-program", - "solana-security-txt", + "solana-instruction", + "solana-log-collector", + "solana-program-runtime", + "solana-sdk-ids", "solana-zk-token-sdk", - "spl-memo", - "spl-pod", - "spl-token", - "spl-token-group-interface", - "spl-token-metadata-interface", - "spl-transfer-hook-interface 0.4.1", - "spl-type-length-value", - "thiserror", ] [[package]] -name = "spl-token-group-interface" -version = "0.1.0" +name = "solana-zk-token-sdk" +version = "2.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b889509d49fa74a4a033ca5dae6c2307e9e918122d97e58562f5c4ffa795c75d" +checksum = "5055e5df94abd5badf4f947681c893375bdb6f8f543c05d2a7ab9647a6a9d205" dependencies = [ + "aes-gcm-siv", + "base64 0.22.1", + "bincode", "bytemuck", - "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", -] - -[[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", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "itertools 0.12.1", + "merlin", + "num-derive 0.4.2", + "num-traits", + "rand 0.8.5", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-curve25519", + "solana-derivation-path", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "subtle", + "thiserror 2.0.18", + "zeroize", ] [[package]] -name = "spl-transfer-hook-interface" +name = "spinning_top" 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 0.4.0", - "spl-type-length-value", -] - -[[package]] -name = "spl-transfer-hook-interface" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aabdb7c471566f6ddcee724beb8618449ea24b399e58d464d6b5bc7db550259" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" dependencies = [ - "arrayref", - "bytemuck", - "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", - "spl-tlv-account-resolution 0.5.1", - "spl-type-length-value", + "lock_api", ] [[package]] -name = "spl-type-length-value" -version = "0.3.0" +name = "spl-generic-token" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +checksum = "741a62a566d97c58d33f9ed32337ceedd4e35109a686e31b1866c5dfa56abddc" dependencies = [ "bytemuck", - "solana-program", - "spl-discriminator", - "spl-pod", - "spl-program-error", + "solana-pubkey", ] [[package]] @@ -4560,28 +7252,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "strsim" -version = "0.8.0" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "strsim" -version = "0.11.1" +name = "strum" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symlink" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" [[package]] name = "syn" @@ -4607,9 +7321,12 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -4635,24 +7352,49 @@ dependencies = [ ] [[package]] -name = "system-configuration" -version = "0.5.1" +name = "tar" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", + "filetime", + "libc", + "xattr", ] [[package]] -name = "system-configuration-sys" -version = "0.5.0" +name = "tarpc" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "1c38a012bed6fb9681d3bf71ffaa4f88f3b4b9ed3198cda6e4c8462d24d4bb80" dependencies = [ - "core-foundation-sys", - "libc", + "anyhow", + "fnv", + "futures", + "humantime", + "opentelemetry", + "pin-project", + "rand 0.8.5", + "serde", + "static_assertions", + "tarpc-plugins", + "thiserror 1.0.69", + "tokio", + "tokio-serde", + "tokio-util 0.6.10", + "tracing", + "tracing-opentelemetry", +] + +[[package]] +name = "tarpc-plugins" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee42b4e559f17bce0385ebf511a7beb67d5cc33c12c96b7f4e9789919d9c10f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -4677,49 +7419,73 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "tests" version = "0.1.0" dependencies = [ - "anchor-client", + "anchor-lang", "doom-nft-program", + "mpl-core", + "solana-program-test", + "solana-sdk", + "tokio", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "unicode-width 0.1.14", + "thiserror-impl 1.0.69", ] [[package]] -name = "textwrap" -version = "0.16.2" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] [[package]] -name = "thiserror" +name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn 2.0.110", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.44" @@ -4751,25 +7517,6 @@ dependencies = [ "time-core", ] -[[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 = "tinystr" version = "0.8.2" @@ -4829,10 +7576,36 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.37", "tokio", ] +[[package]] +name = "tokio-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466" +dependencies = [ + "bincode", + "bytes", + "educe", + "futures-core", + "futures-sink", + "pin-project", + "serde", + "serde_json", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -4852,13 +7625,28 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.21.12", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tungstenite", "webpki-roots 0.25.4", ] +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "slab", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.17" @@ -4881,11 +7669,26 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] [[package]] name = "toml_datetime" @@ -4898,13 +7701,16 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.12.1", + "indexmap", + "serde", + "serde_spanned", "toml_datetime 0.6.11", - "winnow 0.5.40", + "toml_write", + "winnow", ] [[package]] @@ -4913,10 +7719,10 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.12.1", + "indexmap", "toml_datetime 0.7.3", "toml_parser", - "winnow 0.7.13", + "winnow", ] [[package]] @@ -4925,9 +7731,59 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "winnow 0.7.13", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body", + "http-body-util", + "iri-string", + "pin-project-lite", + "tokio", + "tokio-util 0.7.17", + "tower", + "tower-layer", + "tower-service", ] +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -4959,11 +7815,36 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" dependencies = [ "once_cell", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] @@ -4981,13 +7862,13 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.12", "httparse", "log", "rand 0.8.5", - "rustls", + "rustls 0.21.12", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", "webpki-roots 0.24.0", @@ -5005,27 +7886,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.2" @@ -5040,11 +7906,11 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "generic-array", + "crypto-common", "subtle", ] @@ -5059,15 +7925,15 @@ dependencies = [ [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "untrusted" -version = "0.9.0" +name = "unwrap_none" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "461d0c5956fcc728ecc03a3a961e4adc9a7975d86f6f8371389a289517c02ca9" [[package]] name = "uriparse" @@ -5110,10 +7976,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "vec_map" -version = "0.8.2" +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" @@ -5127,6 +7999,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -5235,13 +8117,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ - "rustls-webpki", + "rustls-webpki 0.101.7", ] [[package]] @@ -5250,6 +8141,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -5342,11 +8242,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.2", ] [[package]] @@ -5387,17 +8287,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -5435,9 +8335,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -5453,9 +8353,9 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -5471,9 +8371,9 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -5501,9 +8401,9 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -5519,9 +8419,9 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -5537,9 +8437,9 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -5555,9 +8455,9 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -5571,15 +8471,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.13" @@ -5589,16 +8480,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen" version = "0.46.0" @@ -5625,17 +8506,18 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] [[package]] -name = "yasna" -version = "0.5.2" +name = "xattr" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ - "time", + "libc", + "rustix", ] [[package]] @@ -5704,9 +8586,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -5757,20 +8639,19 @@ dependencies = [ [[package]] name = "zstd" -version = "0.11.2+zstd.1.5.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ - "libc", "zstd-sys", ] diff --git a/docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md b/docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md new file mode 100644 index 0000000..00e54b0 --- /dev/null +++ b/docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md @@ -0,0 +1,509 @@ +# DOOM INDEX NFT Mint Contract V1 要件定義書 + +## 1. 目的 + +DOOM INDEX において一定時間ごとに生成される作品を、Solana 上の NFT として mint 可能にする。 +初期段階では仕様変更の可能性が高いため、本仕様の対象 contract は **`DOOM INDEX NFT Mint Contract V1`** として定義する。 +Contract V1 は **upgradeable program** を前提とし、変化しやすい metadata 生成と配信は off-chain に寄せる。 + +本機能の主目的は以下とする。 + +- ユーザーが DOOM INDEX の作品を Solana NFT として取得できること +- NFT ごとにアプリケーション独自の `tokenId` を連番で払い出せること +- Solana の標準的な NFT metadata 仕様に沿って、thumbnail image に加えて 3D model を扱えること +- localnet に加えて devnet 上でも一通りの動作を確認できること +- 将来的な仕様変更に耐えられること + +## 2. 採用方針 + +### 2.1 標準とチェーン + +- NFT 標準は Metaplex Core を採用する +- 実体識別子は Solana / Metaplex Core 上の `Asset Address` とする +- `tokenId` はアプリケーション独自の連番識別子として on-chain で管理する +- 開発時の動作確認環境は `localnet` と `devnet` を対象とする +- Contract V1 は upgradeable program として deploy する +- 将来の Contract V2 以降に移行可能な前提で、state と instruction を最小構成に保つ + +### 2.2 v1 の基本設計 + +- mint 時の metadata URI は backend が都度承認するのではなく、program が `tokenId` から deterministic に決定する +- metadata URI の標準ルールは `"{base_metadata_url}/{tokenId}.json"` とする +- metadata JSON を mint 前に配置するため、mint 前に `tokenId` を確定する reservation フローを採用する +- 同一生成期間内に metadata の内容が同一であってもよい +- ただし URI 自体は tokenId ごとに一意とする +- on-chain に generation state は持たない +- user は任意の URI を指定できない +- business admin と program upgrade authority は分離する +- collection authority は EOA ではなく program 管理 PDA を第一候補とする + +## 3. スコープ + +### 3.1 対象 + +- Metaplex Core Collection の初期化 +- DOOM INDEX NFT の mint +- `tokenId` の連番採番 +- deterministic metadata URI の付与 +- 管理者による運用機能 +- devnet E2E 検証フロー +- standard metadata + 3D model 対応 + +### 3.2 対象外 + +以下は v1 の対象外とする。 + +- on-chain generation boundary の厳密管理 +- cNFT / compressed NFT +- allowlist / WL mint +- per-wallet mint limit +- mint price / treasury +- 二次流通戦略 +- royalties の厳密設計 +- permissionless 以外の承認付き mint +- per-token metadata の on-chain 保持 +- 既存 NFT の自動 batch metadata 差し替え + +## 4. システム概要 + +### 4.1 全体構成 + +本システムは、作品 metadata を off-chain で管理し、mint 時点で program が算出した URI を使って Metaplex Core Asset を発行する構成とする。 + +- on-chain program は `tokenId` 採番、URI 決定、Collection 配下の Asset 作成を担当する +- off-chain backend は thumbnail / 3D model / metadata JSON の生成と配置を担当する +- frontend は user wallet 署名による mint UI を提供する + +### 4.2 識別子 + +- `Asset Address`: Solana / Metaplex Core 上の NFT 実体識別子 +- `tokenId`: DOOM INDEX アプリケーション独自の連番 ID +- `Collection Address`: DOOM INDEX Collection の識別子 + +## 5. On-chain 設計 + +### 5.1 GlobalConfig + +v1 の永続 state は `GlobalConfig` と `MintReservation` とする。 +`GlobalConfig` の PDA seed は `["global_config"]` で固定する。 + +推奨フィールドは以下とする。 + +- `admin: Pubkey` +- `upgrade_authority: Pubkey` +- `next_token_id: u64` +- `mint_paused: bool` +- `base_metadata_url: String` +- `collection: Pubkey` +- `collection_update_authority: Pubkey` +- `bump: u8` + +`next_token_id` は次に払い出す値を保持し、初期値は `1` とする。 +Contract V1 は単一カウンタで採番するため、高並列 mint よりも実装単純性を優先する。 + +### 5.2 MintReservation + +`MintReservation` は mint 前に `tokenId` を確定し、metadata 事前配置と競合しないようにするための一時 state とする。 +PDA seed は `["reservation", token_id_le_bytes]` を標準とする。 + +推奨フィールドは以下とする。 + +- `token_id: u64` +- `reserver: Pubkey` +- `minted: bool` +- `bump: u8` + +### 5.3 Anchor instructions + +#### `initialize_global_config` + +- 実行者は `admin` +- `GlobalConfig` を初期化する +- `admin` と `upgrade_authority` を設定する +- `next_token_id = 1` +- `mint_paused = false` +- `base_metadata_url` を設定する + +#### `initialize_collection` + +- 実行者は `admin` +- Metaplex Core Collection を作成する +- 作成した Collection Address を `GlobalConfig.collection` に保存する +- Collection の update authority は program 管理 PDA を第一候補とする + +#### `reserve_token_id` + +- 実行者は user +- `mint_paused == false` を必須とする +- `tokenId = next_token_id` +- `next_token_id += 1` +- `MintReservation` を作成する +- reservation 作成後、off-chain backend は `"{base_metadata_url}/{tokenId}.json"` を配置する +- v1 では reservation expiry は持たず、未使用 reservation は将来運用または migration で整理可能とする + +#### `mint_doom_index_nft` + +- 実行者は user +- `mint_paused == false` を必須とする +- user 自身の `MintReservation` を必須とする +- `MintReservation.minted == false` を必須とする +- name は `DOOM INDEX #` とする +- uri は `"{base_metadata_url}/{tokenId}.json"` とする +- user を owner とする Metaplex Core Asset を Collection 配下で作成する +- mint 成功時に `MintReservation.minted = true` とする + +#### `update_base_metadata_url` + +- 実行者は `admin` +- `base_metadata_url` を更新する +- 変更は future mint にのみ反映する +- 既存 NFT の on-chain URI は変更しない + +#### `set_mint_paused` + +- 実行者は `admin` +- mint の pause / unpause を切り替える + +#### `transfer_admin` + +- 実行者は現 `admin` +- `admin` を更新する + +#### `set_upgrade_authority` + +- 実行者は現 `upgrade_authority` +- `upgrade_authority` を更新する +- business admin の操作権とは独立に扱う + +## 6. Functional Requirements + +### 6.1 Mint + +- FR-01: user は `reserve_token_id` により mint 前に `tokenId` を確保できること +- FR-02: user は予約済み `tokenId` を使って DOOM INDEX NFT を mint できること +- FR-03: 表示名は `DOOM INDEX #` とすること +- FR-04: mint 時に deterministic な metadata URI を設定すること +- FR-05: user は任意 URI を指定できないこと +- FR-06: NFT は Metaplex Core Asset として発行されること +- FR-07: mint された Asset は DOOM INDEX Collection に属すること +- FR-08: reservation 済みでない `tokenId` では mint できないこと +- FR-09: 同一 reservation を二重使用できないこと + +### 6.2 管理機能 + +- FR-10: 管理者は `GlobalConfig` を初期化できること +- FR-11: 管理者は Collection を初期化できること +- FR-12: 管理者は `base_metadata_url` を更新できること +- FR-13: 管理者は mint を停止 / 再開できること +- FR-14: 管理者は admin 権限を移譲できること +- FR-15: upgrade authority は admin と分離されること + +### 6.3 Devnet 検証 + +- FR-16: program は devnet に deploy 可能であること +- FR-17: devnet 上で Collection 初期化、reservation、metadata 配置、mint、状態確認まで一通り実行できること +- FR-18: devnet 上で発行された NFT は public metadata URI を参照できること +- FR-19: devnet 上で `image` と `animation_url` の両方が取得できること + +## 7. Metadata 仕様 + +### 7.1 基本方針 + +Metaplex Core の off-chain JSON Schema に合わせる。 +Core の JSON metadata は Token Metadata 系の標準に近く、`name` `description` `image` `category` を必須とし、`animation_url` `external_url` `attributes` `properties.files` を任意で持てる。 + +DOOM INDEX では以下を v1 の標準とする。 + +- `image`: thumbnail image の公開 URI +- `animation_url`: 3D model の公開 URI +- `properties.files`: image と 3D model を必ず列挙 +- `external_url`: DOOM INDEX の作品詳細ページ +- `attributes`: 作品生成に使った最低限の識別情報、visual parameter、prompt 情報を格納 +- `doom_index`: DOOM INDEX 独自の補助フィールド。長文 prompt や生値を機械可読に保持したい場合に使う + +### 7.2 Attributes 最低要件 + +v1 では、各 token の metadata に少なくとも以下の情報を含める。 +viewer 互換性のため、表示したい値は `attributes` に入れる。 + +#### Basic information + +- `Generated` +- `ID` +- `Seed` +- `Params Hash` +- `File Size` + +#### Visual parameters + +- `fogDensity` +- `skyTint` +- `reflectivity` +- `blueBalance` +- `vegetationDensity` +- `organicPattern` +- `radiationGlow` +- `debrisIntensity` +- `mechanicalPattern` +- `metallicRatio` +- `fractalDensity` +- `bioluminescence` +- `shadowDepth` +- `redHighlight` +- `lightIntensity` +- `warmHue` + +#### Prompt information + +- `Prompt` +- `Negative Prompt` + +数値は小数のまま保持してよい。 +`Generated` は ISO 8601 UTC 文字列を標準とする。 +`File Size` は viewer 表示を優先し、`"472.62 KB"` のような文字列で保持してよい。 + +長文である `Prompt` と `Negative Prompt` は、`attributes` に加えて top-level custom field にも同値を保持してよい。 + +### 7.3 推奨 JSON 形式 + +```json +{ + "name": "DOOM INDEX #1", + "description": "AI-generated market artwork from DOOM INDEX.", + "image": "https://cdn.example.com/doom-index/1.png", + "animation_url": "https://cdn.example.com/doom-index/1.glb", + "external_url": "https://doomindex.fun/artworks/1", + "category": "vr", + "attributes": [ + { + "trait_type": "Generated", + "value": "2026-03-12T03:00:00Z" + }, + { + "trait_type": "ID", + "value": "DOOM_202603111800_c92dfdb3_0a44d00ee2c7" + }, + { + "trait_type": "Seed", + "value": "0a44d00ee2c7" + }, + { + "trait_type": "Params Hash", + "value": "c92dfdb3" + }, + { + "trait_type": "File Size", + "value": "472.62 KB" + }, + { + "trait_type": "fogDensity", + "value": 0.4745098039215686 + }, + { + "trait_type": "skyTint", + "value": 0.9254901960784314 + }, + { + "trait_type": "reflectivity", + "value": 0.1411764705882353 + }, + { + "trait_type": "blueBalance", + "value": 0.41568627450980394 + }, + { + "trait_type": "vegetationDensity", + "value": 0.4549019607843137 + }, + { + "trait_type": "organicPattern", + "value": 0.6549019607843137 + }, + { + "trait_type": "radiationGlow", + "value": 0.8470588235294118 + }, + { + "trait_type": "debrisIntensity", + "value": 0.6666666666666666 + }, + { + "trait_type": "mechanicalPattern", + "value": 0.9254901960784314 + }, + { + "trait_type": "metallicRatio", + "value": 0.8745098039215686 + }, + { + "trait_type": "fractalDensity", + "value": 1 + }, + { + "trait_type": "bioluminescence", + "value": 0.9764705882352941 + }, + { + "trait_type": "shadowDepth", + "value": 0.9725490196078431 + }, + { + "trait_type": "redHighlight", + "value": 0.6941176470588235 + }, + { + "trait_type": "lightIntensity", + "value": 0.3686274509803922 + }, + { + "trait_type": "warmHue", + "value": 0.6588235294117647 + }, + { + "trait_type": "Prompt", + "value": "Use the reference image as a mysterious ancient symbol reflected in the surface of a mystical pool at the foreground..." + }, + { + "trait_type": "Negative Prompt", + "value": "watermark, text, oversaturated colors, low detail hands, extra limbs" + } + ], + "doom_index": { + "generated_at": "2026-03-12T03:00:00Z", + "source_id": "DOOM_202603111800_c92dfdb3_0a44d00ee2c7", + "seed": "0a44d00ee2c7", + "params_hash": "c92dfdb3", + "file_size_kb": 472.62, + "prompt": "Use the reference image as a mysterious ancient symbol reflected in the surface of a mystical pool at the foreground. Ensure the token logo is subtly integrated into the classical oil painting composition without dominating the overall renaissance master style...", + "negative_prompt": "watermark, text, oversaturated colors, low detail hands, extra limbs", + "visual_parameters": { + "fogDensity": 0.4745098039215686, + "skyTint": 0.9254901960784314, + "reflectivity": 0.1411764705882353, + "blueBalance": 0.41568627450980394, + "vegetationDensity": 0.4549019607843137, + "organicPattern": 0.6549019607843137, + "radiationGlow": 0.8470588235294118, + "debrisIntensity": 0.6666666666666666, + "mechanicalPattern": 0.9254901960784314, + "metallicRatio": 0.8745098039215686, + "fractalDensity": 1, + "bioluminescence": 0.9764705882352941, + "shadowDepth": 0.9725490196078431, + "redHighlight": 0.6941176470588235, + "lightIntensity": 0.3686274509803922, + "warmHue": 0.6588235294117647 + } + }, + "properties": { + "files": [ + { + "uri": "https://cdn.example.com/doom-index/1.png", + "type": "image/png" + }, + { + "uri": "https://cdn.example.com/doom-index/1.glb", + "type": "model/gltf-binary" + } + ] + } +} +``` + +### 7.4 Media 要件 + +- thumbnail は PNG / JPEG のいずれかを標準とする +- 3D model は GLB を標準とする +- 将来的に glTF を扱う場合は MIME type を `model/gltf+json` とする +- metadata URI、image URI、animation_url URI は public HTTPS で参照可能であること +- devnet 検証時も mainnet を意識した URI 形式を用いること + +### 7.5 Collection metadata + +Collection 側の metadata も standard JSON 形式に合わせる。 +最低限以下を持つこと。 + +- `name` +- `description` +- `image` +- `external_url` +- `category` + +## 8. Mint フロー + +1. 作品生成ワーカーが一定期間ごとに作品を生成する +2. user が `reserve_token_id` transaction を送信する +3. on-chain program が `next_token_id` を読み、`tokenId` を採番し、`MintReservation` を作成する +4. backend が reservation 済み `tokenId` に対して thumbnail、3D model、metadata JSON を生成し、`{base_metadata_url}/{tokenId}.json` に配置する +5. user が `mint_doom_index_nft` transaction を送信する +6. program が reservation を検証し、name と URI を決定する +7. program が Collection 配下で Metaplex Core Asset を mint する +8. program が reservation を使用済みに更新する +9. user は Asset Address を受け取り、wallet / explorer / Metaplex Core viewer で確認できる + +## 9. Devnet 動作確認要件 + +### 9.1 必須確認項目 + +- upgradeable program を devnet に deploy できること +- `initialize_global_config` を devnet で実行できること +- `initialize_collection` を devnet で実行できること +- `reserve_token_id` を devnet で実行できること +- user wallet から `mint_doom_index_nft` を devnet で実行できること +- 発行後に Asset Address, owner, collection, name, uri を取得できること +- metadata JSON が取得できること +- thumbnail と 3D model の両方が取得できること +- 同じ reservation で 2 回 mint できないこと + +### 9.2 推奨検証手段 + +- Solana Explorer の devnet +- Metaplex Core viewer +- script / frontend からの fetch 検証 + +### 9.3 実装に含めるべき補助物 + +devnet での確認容易性のため、実装時には以下を同梱する。 + +- devnet 用の初期化スクリプト +- devnet 用の mint スクリプトまたは UI +- Asset / Collection / metadata を検証する確認手順 + +## 10. 非機能要件 + +### 10.1 拡張性 + +- 生成期間は将来的に 10 分以外へ変更できること +- pricing, allowlist, per-wallet limit を将来的に追加できること +- metadata を per-token 固有化したまま運用拡張できること +- 将来的に asset immutability policy を強化できること +- Contract V1 から Contract V2 以降へ段階的に移行できること + +### 10.2 保守性 + +- on-chain state は最小限に保つこと +- metadata 生成ロジックは off-chain に寄せること +- program upgrade で機能追加しやすいこと +- 単一 `next_token_id` カウンタによる直列化を許容し、Contract V1 は高頻度大量 mint を最適化対象としない + +### 10.3 互換性 + +- Solana wallet と標準的な NFT viewer が理解できる metadata 形式を使うこと +- `image` を primary preview としつつ、`animation_url` で 3D model を提供すること + +## 11. Open Items + +以下は Contract V1 定義時点では未確定とする。 + +1. 生成期間の境界を UTC 基準にするか JST 基準にするか +2. storage を IPFS / Arweave / Irys / R2+CDN のどれにするか +3. metadata URI の immutability をどこまで強制するか +4. Asset / Collection の update authority を将来 `None` に落とすか +5. marketplace 表示上で `tokenId` をどこまで明示したいか +6. 未使用 reservation を将来 reclaim / cleanup するか + +## 12. 参考 + +- Metaplex Core は off-chain metadata を参照する `name` + `uri` ベースで Asset を作成する +- Metaplex Core JSON Schema では `animation_url` に GLB を持てる +- `properties.files` には image と 3D model の両方を MIME type 付きで列挙できる diff --git a/package.json b/package.json index bc6b87b..8e3999a 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,19 @@ "format:ts": "prettier . --write --ignore-unknown", "format:ts:check": "prettier . --check --ignore-unknown", "lint": "cargo clippy --workspace --all-targets --all-features -- -D warnings", - "test": "cargo test --workspace", + "build:sbf:test": "./scripts/build-test-sbf.sh", + "test:contract": "./scripts/test-contract-v1.sh", + "devnet:init": "bun run ./scripts/devnet/init.ts", + "devnet:reserve": "bun run ./scripts/devnet/reserve.ts", + "devnet:mint": "bun run ./scripts/devnet/mint.ts", + "test": "cargo test --workspace --exclude tests && bun run test:contract", "check": "bun run format:check && bun run lint && bun run test" }, "dependencies": { "@coral-xyz/anchor": "^0.31.1" }, "devDependencies": { + "@types/node": "^24.0.0", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", diff --git a/programs/doom-nft-program/Cargo.toml b/programs/doom-nft-program/Cargo.toml index b9bafad..f24a3db 100644 --- a/programs/doom-nft-program/Cargo.toml +++ b/programs/doom-nft-program/Cargo.toml @@ -17,12 +17,12 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] [lints.rust] unexpected_cfgs = { level = "allow", check-cfg = ["cfg(target_os, values(\"solana\"))"] } [dependencies] -anchor-lang = "0.29.0" -anchor-spl = "0.29.0" -solana-program = "1.18.26" +anchor-lang = "0.31.1" +mpl-core = { version = "0.11.1", features = ["anchor"] } +solana-program = "2.2.1" diff --git a/programs/doom-nft-program/src/constants.rs b/programs/doom-nft-program/src/constants.rs new file mode 100644 index 0000000..173bd9e --- /dev/null +++ b/programs/doom-nft-program/src/constants.rs @@ -0,0 +1,4 @@ +pub const COLLECTION_NAME: &str = "DOOM INDEX"; +pub const GLOBAL_CONFIG_SEED: &[u8] = b"global_config"; +pub const RESERVATION_SEED: &[u8] = b"reservation"; +pub const COLLECTION_AUTHORITY_SEED: &[u8] = b"collection_authority"; diff --git a/programs/doom-nft-program/src/error.rs b/programs/doom-nft-program/src/error.rs new file mode 100644 index 0000000..527276d --- /dev/null +++ b/programs/doom-nft-program/src/error.rs @@ -0,0 +1,25 @@ +use anchor_lang::prelude::*; + +#[error_code] +pub enum DoomNftProgramError { + #[msg("The caller is not authorized to perform this action.")] + Unauthorized, + #[msg("Minting is currently paused.")] + MintPaused, + #[msg("The collection has not been initialized.")] + CollectionNotInitialized, + #[msg("The collection has already been initialized.")] + CollectionAlreadyInitialized, + #[msg("The reservation has already been used to mint an asset.")] + ReservationAlreadyMinted, + #[msg("The reservation belongs to a different user.")] + ReservationOwnerMismatch, + #[msg("The reservation token id does not match the instruction token id.")] + ReservationTokenMismatch, + #[msg("The configured collection authority does not match the derived PDA.")] + CollectionAuthorityMismatch, + #[msg("The base metadata url is invalid.")] + BaseMetadataUrlInvalid, + #[msg("The token id counter overflowed.")] + TokenIdOverflow, +} diff --git a/programs/doom-nft-program/src/events.rs b/programs/doom-nft-program/src/events.rs new file mode 100644 index 0000000..d750549 --- /dev/null +++ b/programs/doom-nft-program/src/events.rs @@ -0,0 +1,27 @@ +use anchor_lang::prelude::*; + +#[event] +pub struct TokenReserved { + pub token_id: u64, + pub reserver: Pubkey, + pub reservation: Pubkey, +} + +#[event] +pub struct AssetMinted { + pub token_id: u64, + pub asset: Pubkey, + pub owner: Pubkey, +} + +#[event] +pub struct BaseMetadataUrlUpdated { + pub old_base_url: String, + pub new_base_url: String, +} + +#[event] +pub struct UpgradeAuthorityUpdated { + pub old_upgrade_authority: Pubkey, + pub new_upgrade_authority: Pubkey, +} diff --git a/programs/doom-nft-program/src/instructions/initialize_collection.rs b/programs/doom-nft-program/src/instructions/initialize_collection.rs new file mode 100644 index 0000000..c5f9159 --- /dev/null +++ b/programs/doom-nft-program/src/instructions/initialize_collection.rs @@ -0,0 +1,65 @@ +use anchor_lang::prelude::*; +use mpl_core::{instructions::CreateCollectionV2CpiBuilder, ID as MPL_CORE_ID}; + +use crate::{ + constants::{COLLECTION_AUTHORITY_SEED, COLLECTION_NAME, GLOBAL_CONFIG_SEED}, + error::DoomNftProgramError, + state::GlobalConfig, + utils::build_collection_uri, +}; + +#[derive(Accounts)] +pub struct InitializeCollection<'info> { + #[account( + mut, + seeds = [GLOBAL_CONFIG_SEED], + bump = global_config.bump, + has_one = admin @ DoomNftProgramError::Unauthorized + )] + pub global_config: Account<'info, GlobalConfig>, + + #[account(mut)] + pub admin: Signer<'info>, + + /// CHECK: Fresh signer for the new Core collection account. + #[account(mut)] + pub collection: Signer<'info>, + + /// CHECK: PDA used only as the Core collection update authority. + #[account( + seeds = [COLLECTION_AUTHORITY_SEED, global_config.key().as_ref()], + bump + )] + pub collection_update_authority: UncheckedAccount<'info>, + + /// CHECK: Verified against the canonical Metaplex Core program id. + #[account(address = MPL_CORE_ID)] + pub mpl_core_program: UncheckedAccount<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn process_initialize_collection(ctx: Context) -> Result<()> { + let global_config = &mut ctx.accounts.global_config; + require!( + global_config.collection == Pubkey::default(), + DoomNftProgramError::CollectionAlreadyInitialized + ); + + let collection_uri = build_collection_uri(&global_config.base_metadata_url); + CreateCollectionV2CpiBuilder::new(&ctx.accounts.mpl_core_program.to_account_info()) + .collection(&ctx.accounts.collection.to_account_info()) + .update_authority(Some( + &ctx.accounts.collection_update_authority.to_account_info(), + )) + .payer(&ctx.accounts.admin.to_account_info()) + .system_program(&ctx.accounts.system_program.to_account_info()) + .name(COLLECTION_NAME.to_owned()) + .uri(collection_uri) + .invoke()?; + + global_config.collection = ctx.accounts.collection.key(); + global_config.collection_update_authority = ctx.accounts.collection_update_authority.key(); + + Ok(()) +} diff --git a/programs/doom-nft-program/src/instructions/initialize_global_config.rs b/programs/doom-nft-program/src/instructions/initialize_global_config.rs new file mode 100644 index 0000000..bbab6b4 --- /dev/null +++ b/programs/doom-nft-program/src/instructions/initialize_global_config.rs @@ -0,0 +1,42 @@ +use anchor_lang::prelude::*; + +use crate::{ + constants::GLOBAL_CONFIG_SEED, state::GlobalConfig, utils::validate_base_metadata_url, +}; + +#[derive(Accounts)] +pub struct InitializeGlobalConfig<'info> { + #[account( + init, + payer = admin, + space = 8 + GlobalConfig::INIT_SPACE, + seeds = [GLOBAL_CONFIG_SEED], + bump + )] + pub global_config: Account<'info, GlobalConfig>, + + #[account(mut)] + pub admin: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn process_initialize_global_config( + ctx: Context, + base_metadata_url: String, + upgrade_authority: Pubkey, +) -> Result<()> { + validate_base_metadata_url(&base_metadata_url)?; + + let global_config = &mut ctx.accounts.global_config; + global_config.admin = ctx.accounts.admin.key(); + global_config.upgrade_authority = upgrade_authority; + global_config.next_token_id = 1; + global_config.mint_paused = false; + global_config.base_metadata_url = base_metadata_url; + global_config.collection = Pubkey::default(); + global_config.collection_update_authority = Pubkey::default(); + global_config.bump = ctx.bumps.global_config; + + Ok(()) +} diff --git a/programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs b/programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs new file mode 100644 index 0000000..7be0e17 --- /dev/null +++ b/programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs @@ -0,0 +1,108 @@ +use anchor_lang::prelude::*; +use mpl_core::{instructions::CreateV2CpiBuilder, types::DataState, ID as MPL_CORE_ID}; + +use crate::{ + constants::{COLLECTION_AUTHORITY_SEED, GLOBAL_CONFIG_SEED, RESERVATION_SEED}, + error::DoomNftProgramError, + events::AssetMinted, + state::{GlobalConfig, MintReservation}, + utils::{build_asset_name, build_asset_uri}, +}; + +#[derive(Accounts)] +#[instruction(token_id: u64)] +pub struct MintDoomIndexNft<'info> { + #[account( + seeds = [GLOBAL_CONFIG_SEED], + bump = global_config.bump + )] + pub global_config: Account<'info, GlobalConfig>, + + #[account( + mut, + seeds = [RESERVATION_SEED, token_id.to_le_bytes().as_ref()], + bump = reservation.bump + )] + pub reservation: Account<'info, MintReservation>, + + #[account(mut)] + pub user: Signer<'info>, + + /// CHECK: Fresh signer for the new Core asset account. + #[account(mut)] + pub asset: Signer<'info>, + + /// CHECK: PDA signs the Core CPI as collection/asset authority. + #[account( + seeds = [COLLECTION_AUTHORITY_SEED, global_config.key().as_ref()], + bump, + address = global_config.collection_update_authority @ DoomNftProgramError::CollectionAuthorityMismatch + )] + pub collection_update_authority: UncheckedAccount<'info>, + + /// CHECK: Existing Core collection account. Address checked against config. + #[account( + mut, + address = global_config.collection @ DoomNftProgramError::CollectionNotInitialized + )] + pub collection: UncheckedAccount<'info>, + + /// CHECK: Verified against the canonical Metaplex Core program id. + #[account(address = MPL_CORE_ID)] + pub mpl_core_program: UncheckedAccount<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn process_mint_doom_index_nft(ctx: Context, token_id: u64) -> Result<()> { + let global_config = &ctx.accounts.global_config; + require!(!global_config.mint_paused, DoomNftProgramError::MintPaused); + require!( + global_config.collection != Pubkey::default(), + DoomNftProgramError::CollectionNotInitialized + ); + + let reservation = &mut ctx.accounts.reservation; + require!( + reservation.token_id == token_id, + DoomNftProgramError::ReservationTokenMismatch + ); + require!( + reservation.reserver == ctx.accounts.user.key(), + DoomNftProgramError::ReservationOwnerMismatch + ); + require!( + !reservation.minted, + DoomNftProgramError::ReservationAlreadyMinted + ); + + let name = build_asset_name(token_id); + let uri = build_asset_uri(&global_config.base_metadata_url, token_id); + let global_config_key = ctx.accounts.global_config.key(); + let bump = [ctx.bumps.collection_update_authority]; + let signer_seeds: &[&[u8]] = &[COLLECTION_AUTHORITY_SEED, global_config_key.as_ref(), &bump]; + + CreateV2CpiBuilder::new(&ctx.accounts.mpl_core_program.to_account_info()) + .asset(&ctx.accounts.asset.to_account_info()) + .collection(Some(&ctx.accounts.collection.to_account_info())) + .authority(Some( + &ctx.accounts.collection_update_authority.to_account_info(), + )) + .payer(&ctx.accounts.user.to_account_info()) + .owner(Some(&ctx.accounts.user.to_account_info())) + .system_program(&ctx.accounts.system_program.to_account_info()) + .data_state(DataState::AccountState) + .name(name) + .uri(uri) + .invoke_signed(&[signer_seeds])?; + + reservation.minted = true; + + emit!(AssetMinted { + token_id, + asset: ctx.accounts.asset.key(), + owner: ctx.accounts.user.key(), + }); + + Ok(()) +} diff --git a/programs/doom-nft-program/src/instructions/mod.rs b/programs/doom-nft-program/src/instructions/mod.rs new file mode 100644 index 0000000..404622a --- /dev/null +++ b/programs/doom-nft-program/src/instructions/mod.rs @@ -0,0 +1,17 @@ +pub mod initialize_collection; +pub mod initialize_global_config; +pub mod mint_doom_index_nft; +pub mod reserve_token_id; +pub mod set_mint_paused; +pub mod set_upgrade_authority; +pub mod transfer_admin; +pub mod update_base_metadata_url; + +pub use initialize_collection::*; +pub use initialize_global_config::*; +pub use mint_doom_index_nft::*; +pub use reserve_token_id::*; +pub use set_mint_paused::*; +pub use set_upgrade_authority::*; +pub use transfer_admin::*; +pub use update_base_metadata_url::*; diff --git a/programs/doom-nft-program/src/instructions/reserve_token_id.rs b/programs/doom-nft-program/src/instructions/reserve_token_id.rs new file mode 100644 index 0000000..22e874f --- /dev/null +++ b/programs/doom-nft-program/src/instructions/reserve_token_id.rs @@ -0,0 +1,57 @@ +use anchor_lang::prelude::*; + +use crate::{ + constants::{GLOBAL_CONFIG_SEED, RESERVATION_SEED}, + error::DoomNftProgramError, + events::TokenReserved, + state::{GlobalConfig, MintReservation}, +}; + +#[derive(Accounts)] +pub struct ReserveTokenId<'info> { + #[account( + mut, + seeds = [GLOBAL_CONFIG_SEED], + bump = global_config.bump + )] + pub global_config: Account<'info, GlobalConfig>, + + #[account( + init, + payer = user, + space = 8 + MintReservation::INIT_SPACE, + seeds = [RESERVATION_SEED, global_config.next_token_id.to_le_bytes().as_ref()], + bump + )] + pub reservation: Account<'info, MintReservation>, + + #[account(mut)] + pub user: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn process_reserve_token_id(ctx: Context) -> Result<()> { + let global_config = &mut ctx.accounts.global_config; + require!(!global_config.mint_paused, DoomNftProgramError::MintPaused); + + let token_id = global_config.next_token_id; + global_config.next_token_id = global_config + .next_token_id + .checked_add(1) + .ok_or(DoomNftProgramError::TokenIdOverflow)?; + + let reservation = &mut ctx.accounts.reservation; + reservation.token_id = token_id; + reservation.reserver = ctx.accounts.user.key(); + reservation.minted = false; + reservation.bump = ctx.bumps.reservation; + + emit!(TokenReserved { + token_id, + reserver: reservation.reserver, + reservation: ctx.accounts.reservation.key(), + }); + + Ok(()) +} diff --git a/programs/doom-nft-program/src/instructions/set_mint_paused.rs b/programs/doom-nft-program/src/instructions/set_mint_paused.rs new file mode 100644 index 0000000..ea62812 --- /dev/null +++ b/programs/doom-nft-program/src/instructions/set_mint_paused.rs @@ -0,0 +1,21 @@ +use anchor_lang::prelude::*; + +use crate::{constants::GLOBAL_CONFIG_SEED, error::DoomNftProgramError, state::GlobalConfig}; + +#[derive(Accounts)] +pub struct SetMintPaused<'info> { + #[account( + mut, + seeds = [GLOBAL_CONFIG_SEED], + bump = global_config.bump, + has_one = admin @ DoomNftProgramError::Unauthorized + )] + pub global_config: Account<'info, GlobalConfig>, + + pub admin: Signer<'info>, +} + +pub fn process_set_mint_paused(ctx: Context, paused: bool) -> Result<()> { + ctx.accounts.global_config.mint_paused = paused; + Ok(()) +} diff --git a/programs/doom-nft-program/src/instructions/set_upgrade_authority.rs b/programs/doom-nft-program/src/instructions/set_upgrade_authority.rs new file mode 100644 index 0000000..34c2697 --- /dev/null +++ b/programs/doom-nft-program/src/instructions/set_upgrade_authority.rs @@ -0,0 +1,35 @@ +use anchor_lang::prelude::*; + +use crate::{ + constants::GLOBAL_CONFIG_SEED, error::DoomNftProgramError, events::UpgradeAuthorityUpdated, + state::GlobalConfig, +}; + +#[derive(Accounts)] +pub struct SetUpgradeAuthority<'info> { + #[account( + mut, + seeds = [GLOBAL_CONFIG_SEED], + bump = global_config.bump, + has_one = upgrade_authority @ DoomNftProgramError::Unauthorized + )] + pub global_config: Account<'info, GlobalConfig>, + + pub upgrade_authority: Signer<'info>, +} + +pub fn process_set_upgrade_authority( + ctx: Context, + new_upgrade_authority: Pubkey, +) -> Result<()> { + let global_config = &mut ctx.accounts.global_config; + let old_upgrade_authority = global_config.upgrade_authority; + global_config.upgrade_authority = new_upgrade_authority; + + emit!(UpgradeAuthorityUpdated { + old_upgrade_authority, + new_upgrade_authority, + }); + + Ok(()) +} diff --git a/programs/doom-nft-program/src/instructions/transfer_admin.rs b/programs/doom-nft-program/src/instructions/transfer_admin.rs new file mode 100644 index 0000000..3f1187a --- /dev/null +++ b/programs/doom-nft-program/src/instructions/transfer_admin.rs @@ -0,0 +1,21 @@ +use anchor_lang::prelude::*; + +use crate::{constants::GLOBAL_CONFIG_SEED, error::DoomNftProgramError, state::GlobalConfig}; + +#[derive(Accounts)] +pub struct TransferAdmin<'info> { + #[account( + mut, + seeds = [GLOBAL_CONFIG_SEED], + bump = global_config.bump, + has_one = admin @ DoomNftProgramError::Unauthorized + )] + pub global_config: Account<'info, GlobalConfig>, + + pub admin: Signer<'info>, +} + +pub fn process_transfer_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { + ctx.accounts.global_config.admin = new_admin; + Ok(()) +} diff --git a/programs/doom-nft-program/src/instructions/update_base_metadata_url.rs b/programs/doom-nft-program/src/instructions/update_base_metadata_url.rs new file mode 100644 index 0000000..925421c --- /dev/null +++ b/programs/doom-nft-program/src/instructions/update_base_metadata_url.rs @@ -0,0 +1,37 @@ +use anchor_lang::prelude::*; + +use crate::{ + constants::GLOBAL_CONFIG_SEED, error::DoomNftProgramError, events::BaseMetadataUrlUpdated, + state::GlobalConfig, utils::validate_base_metadata_url, +}; + +#[derive(Accounts)] +pub struct UpdateBaseMetadataUrl<'info> { + #[account( + mut, + seeds = [GLOBAL_CONFIG_SEED], + bump = global_config.bump, + has_one = admin @ DoomNftProgramError::Unauthorized + )] + pub global_config: Account<'info, GlobalConfig>, + + pub admin: Signer<'info>, +} + +pub fn process_update_base_metadata_url( + ctx: Context, + base_metadata_url: String, +) -> Result<()> { + validate_base_metadata_url(&base_metadata_url)?; + + let global_config = &mut ctx.accounts.global_config; + let old_base_url = global_config.base_metadata_url.clone(); + global_config.base_metadata_url = base_metadata_url.clone(); + + emit!(BaseMetadataUrlUpdated { + old_base_url, + new_base_url: base_metadata_url, + }); + + Ok(()) +} diff --git a/programs/doom-nft-program/src/lib.rs b/programs/doom-nft-program/src/lib.rs index 7b8dcf4..cbf34bb 100644 --- a/programs/doom-nft-program/src/lib.rs +++ b/programs/doom-nft-program/src/lib.rs @@ -1,8 +1,19 @@ +#![allow(deprecated)] + +pub mod constants; +pub mod error; +pub mod events; +pub mod instructions; +pub mod state; +pub mod utils; + use anchor_lang::prelude::*; -use anchor_spl::{ - associated_token::AssociatedToken, - token::{self, Mint, Token, TokenAccount, Transfer}, -}; + +pub use constants::*; +pub use error::*; +pub use events::*; +pub use instructions::*; +pub use state::*; declare_id!("AavECgzCbVhHeBGAfcUgT1tYEC4N4B96E8XtF9H1fMGt"); @@ -10,134 +21,55 @@ declare_id!("AavECgzCbVhHeBGAfcUgT1tYEC4N4B96E8XtF9H1fMGt"); pub mod doom_nft_program { use super::*; - pub fn create_mint(ctx: Context) -> Result<()> { - msg!("Creating NFT mint: {}", ctx.accounts.mint.key()); - Ok(()) + pub fn initialize_global_config( + ctx: Context, + base_metadata_url: String, + upgrade_authority: Pubkey, + ) -> Result<()> { + instructions::initialize_global_config::process_initialize_global_config( + ctx, + base_metadata_url, + upgrade_authority, + ) } - pub fn mint_token(ctx: Context) -> Result<()> { - msg!("Minting NFT token to: {}", ctx.accounts.token_account.key()); - - // Mint exactly 1 token (NFT) - token::mint_to( - CpiContext::new( - ctx.accounts.token_program.to_account_info(), - token::MintTo { - mint: ctx.accounts.mint.to_account_info(), - to: ctx.accounts.token_account.to_account_info(), - authority: ctx.accounts.mint_authority.to_account_info(), - }, - ), - 1, // NFTなので1個のみ - )?; - - msg!("NFT token minted successfully!"); - Ok(()) + pub fn initialize_collection(ctx: Context) -> Result<()> { + instructions::initialize_collection::process_initialize_collection(ctx) } - pub fn transfer_token(ctx: Context) -> Result<()> { - msg!( - "Transferring NFT from {} to {}", - ctx.accounts.from.key(), - ctx.accounts.to.key() - ); - - // Transfer exactly 1 token (NFT) - token::transfer( - CpiContext::new( - ctx.accounts.token_program.to_account_info(), - Transfer { - from: ctx.accounts.from_token_account.to_account_info(), - to: ctx.accounts.to_token_account.to_account_info(), - authority: ctx.accounts.from.to_account_info(), - }, - ), - 1, // NFTなので1個 - )?; - - msg!("NFT transferred successfully!"); - Ok(()) + pub fn reserve_token_id(ctx: Context) -> Result<()> { + instructions::reserve_token_id::process_reserve_token_id(ctx) } -} - -#[derive(Accounts)] -pub struct CreateMint<'info> { - #[account( - init, - payer = payer, - mint::decimals = 0, // NFTなので小数点以下なし - mint::authority = mint_authority, - mint::freeze_authority = mint_authority, - )] - pub mint: Account<'info, Mint>, - - #[account(mut)] - pub payer: Signer<'info>, - - /// CHECK: The mint authority - pub mint_authority: Signer<'info>, - - pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, -} -#[derive(Accounts)] -pub struct MintToken<'info> { - #[account( - mut, - mint::authority = mint_authority, - mint::freeze_authority = mint_authority, - )] - pub mint: Account<'info, Mint>, - - #[account( - init, - payer = payer, - associated_token::mint = mint, - associated_token::authority = recipient, - )] - pub token_account: Account<'info, TokenAccount>, - - #[account(mut)] - pub payer: Signer<'info>, - - /// CHECK: The mint authority - pub mint_authority: Signer<'info>, - - /// CHECK: The recipient of the NFT - pub recipient: AccountInfo<'info>, - - pub token_program: Program<'info, Token>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, -} - -#[derive(Accounts)] -pub struct TransferToken<'info> { - #[account(mut)] - pub mint: Account<'info, Mint>, - - #[account( - mut, - associated_token::mint = mint, - associated_token::authority = from, - )] - pub from_token_account: Account<'info, TokenAccount>, + pub fn mint_doom_index_nft(ctx: Context, token_id: u64) -> Result<()> { + instructions::mint_doom_index_nft::process_mint_doom_index_nft(ctx, token_id) + } - #[account( - mut, - associated_token::mint = mint, - associated_token::authority = to, - )] - pub to_token_account: Account<'info, TokenAccount>, + pub fn update_base_metadata_url( + ctx: Context, + base_metadata_url: String, + ) -> Result<()> { + instructions::update_base_metadata_url::process_update_base_metadata_url( + ctx, + base_metadata_url, + ) + } - #[account(mut)] - pub from: Signer<'info>, + pub fn set_mint_paused(ctx: Context, paused: bool) -> Result<()> { + instructions::set_mint_paused::process_set_mint_paused(ctx, paused) + } - /// CHECK: The recipient of the NFT - pub to: AccountInfo<'info>, + pub fn transfer_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { + instructions::transfer_admin::process_transfer_admin(ctx, new_admin) + } - pub token_program: Program<'info, Token>, + pub fn set_upgrade_authority( + ctx: Context, + new_upgrade_authority: Pubkey, + ) -> Result<()> { + instructions::set_upgrade_authority::process_set_upgrade_authority( + ctx, + new_upgrade_authority, + ) + } } diff --git a/programs/doom-nft-program/src/state/global_config.rs b/programs/doom-nft-program/src/state/global_config.rs new file mode 100644 index 0000000..48e08e3 --- /dev/null +++ b/programs/doom-nft-program/src/state/global_config.rs @@ -0,0 +1,15 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(InitSpace)] +pub struct GlobalConfig { + pub admin: Pubkey, + pub upgrade_authority: Pubkey, + pub next_token_id: u64, + pub mint_paused: bool, + #[max_len(256)] + pub base_metadata_url: String, + pub collection: Pubkey, + pub collection_update_authority: Pubkey, + pub bump: u8, +} diff --git a/programs/doom-nft-program/src/state/mint_reservation.rs b/programs/doom-nft-program/src/state/mint_reservation.rs new file mode 100644 index 0000000..c689a77 --- /dev/null +++ b/programs/doom-nft-program/src/state/mint_reservation.rs @@ -0,0 +1,10 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(InitSpace)] +pub struct MintReservation { + pub token_id: u64, + pub reserver: Pubkey, + pub minted: bool, + pub bump: u8, +} diff --git a/programs/doom-nft-program/src/state/mod.rs b/programs/doom-nft-program/src/state/mod.rs new file mode 100644 index 0000000..bed9a30 --- /dev/null +++ b/programs/doom-nft-program/src/state/mod.rs @@ -0,0 +1,5 @@ +pub mod global_config; +pub mod mint_reservation; + +pub use global_config::*; +pub use mint_reservation::*; diff --git a/programs/doom-nft-program/src/utils.rs b/programs/doom-nft-program/src/utils.rs new file mode 100644 index 0000000..d90ad6c --- /dev/null +++ b/programs/doom-nft-program/src/utils.rs @@ -0,0 +1,24 @@ +use anchor_lang::prelude::*; + +use crate::error::DoomNftProgramError; + +pub fn validate_base_metadata_url(base_metadata_url: &str) -> Result<()> { + require!( + !base_metadata_url.is_empty() && !base_metadata_url.ends_with('/'), + DoomNftProgramError::BaseMetadataUrlInvalid + ); + + Ok(()) +} + +pub fn build_collection_uri(base_metadata_url: &str) -> String { + format!("{base_metadata_url}/collection.json") +} + +pub fn build_asset_name(token_id: u64) -> String { + format!("DOOM INDEX #{token_id}") +} + +pub fn build_asset_uri(base_metadata_url: &str, token_id: u64) -> String { + format!("{base_metadata_url}/{token_id}.json") +} diff --git a/scripts/build-test-sbf.sh b/scripts/build-test-sbf.sh new file mode 100755 index 0000000..38a8f86 --- /dev/null +++ b/scripts/build-test-sbf.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT_DIR="$ROOT_DIR/target/test-sbf" +MPL_CORE_PROGRAM_ID="CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" + +mkdir -p "$OUT_DIR" + +if [[ ! -f "$OUT_DIR/mpl_core_program.so" ]]; then + solana program dump -u m "$MPL_CORE_PROGRAM_ID" "$OUT_DIR/mpl_core_program.so" +fi diff --git a/scripts/devnet/common.ts b/scripts/devnet/common.ts new file mode 100644 index 0000000..f6a9bbc --- /dev/null +++ b/scripts/devnet/common.ts @@ -0,0 +1,273 @@ +import { BN, web3 } from "@coral-xyz/anchor"; +import * as borsh from "@coral-xyz/borsh"; +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; + +const { + Connection, + Keypair, + PublicKey, + SystemProgram, + Transaction, + TransactionInstruction, + sendAndConfirmTransaction, +} = web3; + +export { Keypair, PublicKey, SystemProgram, TransactionInstruction }; + +export const DEFAULT_PROGRAM_ID = new PublicKey( + process.env.PROGRAM_ID ?? "AavECgzCbVhHeBGAfcUgT1tYEC4N4B96E8XtF9H1fMGt", +); +export const MPL_CORE_PROGRAM_ID = new PublicKey("CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d"); + +const GLOBAL_CONFIG_SEED = Buffer.from("global_config"); +const RESERVATION_SEED = Buffer.from("reservation"); +const COLLECTION_AUTHORITY_SEED = Buffer.from("collection_authority"); +const GLOBAL_CONFIG_DISCRIMINATOR = accountDiscriminator("GlobalConfig"); + +const GLOBAL_CONFIG_LAYOUT = borsh.struct([ + borsh.publicKey("admin"), + borsh.publicKey("upgradeAuthority"), + borsh.u64("nextTokenId"), + borsh.bool("mintPaused"), + borsh.str("baseMetadataUrl"), + borsh.publicKey("collection"), + borsh.publicKey("collectionUpdateAuthority"), + borsh.u8("bump"), +]); + +export type GlobalConfigAccount = { + admin: web3.PublicKey; + upgradeAuthority: web3.PublicKey; + nextTokenId: BN; + mintPaused: boolean; + baseMetadataUrl: string; + collection: web3.PublicKey; + collectionUpdateAuthority: web3.PublicKey; + bump: number; +}; + +export function getConnection(): web3.Connection { + return new Connection(process.env.ANCHOR_PROVIDER_URL ?? "https://api.devnet.solana.com", "confirmed"); +} + +export function loadWallet(): web3.Keypair { + const walletPath = resolve(process.env.ANCHOR_WALLET ?? "~/.config/solana/id.json").replace( + /^~(?=$|\/|\\)/, + process.env.HOME ?? "", + ); + return loadKeypair(walletPath); +} + +export function loadKeypair(filePath: string): web3.Keypair { + const secretKey = Uint8Array.from(JSON.parse(readFileSync(resolve(filePath), "utf8")) as number[]); + return Keypair.fromSecretKey(secretKey); +} + +export function loadOrCreateKeypair(filePath: string): web3.Keypair { + const absolutePath = resolve(filePath); + if (existsSync(absolutePath)) { + return loadKeypair(absolutePath); + } + + mkdirSync(dirname(absolutePath), { recursive: true }); + const keypair = Keypair.generate(); + writeFileSync(absolutePath, JSON.stringify(Array.from(keypair.secretKey)), "utf8"); + return keypair; +} + +export function writeJson(filePath: string, payload: unknown): void { + const absolutePath = resolve(filePath); + mkdirSync(dirname(absolutePath), { recursive: true }); + writeFileSync(absolutePath, JSON.stringify(payload, null, 2), "utf8"); +} + +export function readJson(filePath: string): T { + return JSON.parse(readFileSync(resolve(filePath), "utf8")) as T; +} + +export function globalConfigPda(programId: web3.PublicKey = DEFAULT_PROGRAM_ID): [web3.PublicKey, number] { + return PublicKey.findProgramAddressSync([GLOBAL_CONFIG_SEED], programId); +} + +export function collectionAuthorityPda( + globalConfig: web3.PublicKey, + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): [web3.PublicKey, number] { + return PublicKey.findProgramAddressSync([COLLECTION_AUTHORITY_SEED, globalConfig.toBuffer()], programId); +} + +export function reservationPda( + tokenId: bigint, + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): [web3.PublicKey, number] { + const tokenIdBuffer = Buffer.alloc(8); + tokenIdBuffer.writeBigUInt64LE(tokenId); + return PublicKey.findProgramAddressSync([RESERVATION_SEED, tokenIdBuffer], programId); +} + +export async function fetchGlobalConfig( + connection: web3.Connection, + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): Promise { + const [config] = globalConfigPda(programId); + const account = await connection.getAccountInfo(config, "confirmed"); + if (!account) { + throw new Error(`GlobalConfig not found at ${config.toBase58()}`); + } + + const discriminator = account.data.subarray(0, 8); + if (!discriminator.equals(GLOBAL_CONFIG_DISCRIMINATOR)) { + throw new Error("GlobalConfig discriminator mismatch"); + } + + return GLOBAL_CONFIG_LAYOUT.decode(account.data.subarray(8)) as GlobalConfigAccount; +} + +export function buildMetadataUri(baseMetadataUrl: string, tokenId: bigint): string { + return `${baseMetadataUrl}/${tokenId.toString()}.json`; +} + +export function createInstruction( + name: string, + argsLayout: borsh.Layout, + args: Record, + keys: web3.AccountMeta[], + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): web3.TransactionInstruction { + const discriminator = instructionDiscriminator(name); + const argsBuffer = Buffer.alloc(1024); + const encodedLength = argsLayout.encode(args, argsBuffer); + const data = Buffer.concat([discriminator, argsBuffer.subarray(0, encodedLength)]); + + return new TransactionInstruction({ programId, keys, data }); +} + +export async function sendInstructions( + connection: web3.Connection, + payer: web3.Keypair, + instructions: web3.TransactionInstruction[], + signers: web3.Keypair[] = [], +): Promise { + const transaction = new Transaction().add(...instructions); + const signature = await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], { + commitment: "confirmed", + }); + + return signature; +} + +export function initializeGlobalConfigInstruction( + admin: web3.PublicKey, + baseMetadataUrl: string, + upgradeAuthority: web3.PublicKey, + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): web3.TransactionInstruction { + const [globalConfig] = globalConfigPda(programId); + return createInstruction( + "initialize_global_config", + borsh.struct([borsh.str("baseMetadataUrl"), borsh.publicKey("upgradeAuthority")]), + { baseMetadataUrl, upgradeAuthority }, + [ + { pubkey: globalConfig, isSigner: false, isWritable: true }, + { pubkey: admin, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + ], + programId, + ); +} + +export function initializeCollectionInstruction( + admin: web3.PublicKey, + collection: web3.PublicKey, + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): web3.TransactionInstruction { + const [globalConfig] = globalConfigPda(programId); + const [collectionAuthority] = collectionAuthorityPda(globalConfig, programId); + return createInstruction( + "initialize_collection", + borsh.struct([]), + {}, + [ + { pubkey: globalConfig, isSigner: false, isWritable: true }, + { pubkey: admin, isSigner: true, isWritable: true }, + { pubkey: collection, isSigner: true, isWritable: true }, + { pubkey: collectionAuthority, isSigner: false, isWritable: false }, + { pubkey: MPL_CORE_PROGRAM_ID, isSigner: false, isWritable: false }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + ], + programId, + ); +} + +export async function reserveTokenId( + connection: web3.Connection, + payer: web3.Keypair, + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): Promise<{ + signature: string; + tokenId: bigint; + reservation: web3.PublicKey; + globalConfig: GlobalConfigAccount; +}> { + const before = await fetchGlobalConfig(connection, programId); + const tokenId = BigInt(before.nextTokenId.toString()); + const [globalConfig] = globalConfigPda(programId); + const [reservation] = reservationPda(tokenId, programId); + + const instruction = createInstruction( + "reserve_token_id", + borsh.struct([]), + {}, + [ + { pubkey: globalConfig, isSigner: false, isWritable: true }, + { pubkey: reservation, isSigner: false, isWritable: true }, + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + ], + programId, + ); + + const signature = await sendInstructions(connection, payer, [instruction]); + const after = await fetchGlobalConfig(connection, programId); + + return { signature, tokenId, reservation, globalConfig: after }; +} + +export function mintDoomIndexNftInstruction( + user: web3.PublicKey, + tokenId: bigint, + asset: web3.PublicKey, + collection: web3.PublicKey, + programId: web3.PublicKey = DEFAULT_PROGRAM_ID, +): web3.TransactionInstruction { + const [globalConfig] = globalConfigPda(programId); + const [reservation] = reservationPda(tokenId, programId); + const [collectionAuthority] = collectionAuthorityPda(globalConfig, programId); + + return createInstruction( + "mint_doom_index_nft", + borsh.struct([borsh.u64("tokenId")]), + { tokenId: new BN(tokenId.toString()) }, + [ + { pubkey: globalConfig, isSigner: false, isWritable: false }, + { pubkey: reservation, isSigner: false, isWritable: true }, + { pubkey: user, isSigner: true, isWritable: true }, + { pubkey: asset, isSigner: true, isWritable: true }, + { pubkey: collectionAuthority, isSigner: false, isWritable: false }, + { pubkey: collection, isSigner: false, isWritable: true }, + { pubkey: MPL_CORE_PROGRAM_ID, isSigner: false, isWritable: false }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + ], + programId, + ); +} + +function instructionDiscriminator(name: string): Buffer { + return createHash("sha256").update(`global:${name}`).digest().subarray(0, 8); +} + +function accountDiscriminator(name: string): Buffer { + return createHash("sha256").update(`account:${name}`).digest().subarray(0, 8); +} diff --git a/scripts/devnet/init.ts b/scripts/devnet/init.ts new file mode 100644 index 0000000..ec90880 --- /dev/null +++ b/scripts/devnet/init.ts @@ -0,0 +1,60 @@ +import { + DEFAULT_PROGRAM_ID, + fetchGlobalConfig, + getConnection, + initializeCollectionInstruction, + initializeGlobalConfigInstruction, + loadOrCreateKeypair, + loadWallet, + sendInstructions, + writeJson, +} from "./common"; + +async function main(): Promise { + const baseMetadataUrl = process.env.BASE_METADATA_URL; + if (!baseMetadataUrl) { + throw new Error("BASE_METADATA_URL is required"); + } + + const connection = getConnection(); + const payer = loadWallet(); + const upgradeAuthority = loadOrCreateKeypair( + process.env.UPGRADE_AUTHORITY_KEYPAIR ?? "target/devnet/upgrade-authority.json", + ); + const collection = loadOrCreateKeypair(process.env.COLLECTION_KEYPAIR ?? "target/devnet/collection.json"); + + const initializeConfigIx = initializeGlobalConfigInstruction( + payer.publicKey, + baseMetadataUrl, + upgradeAuthority.publicKey, + DEFAULT_PROGRAM_ID, + ); + const initializeCollectionIx = initializeCollectionInstruction( + payer.publicKey, + collection.publicKey, + DEFAULT_PROGRAM_ID, + ); + + const configSignature = await sendInstructions(connection, payer, [initializeConfigIx]); + const collectionSignature = await sendInstructions(connection, payer, [initializeCollectionIx], [collection]); + + const globalConfig = await fetchGlobalConfig(connection); + const output = { + programId: DEFAULT_PROGRAM_ID.toBase58(), + configSignature, + collectionSignature, + admin: payer.publicKey.toBase58(), + upgradeAuthority: upgradeAuthority.publicKey.toBase58(), + collection: collection.publicKey.toBase58(), + baseMetadataUrl: globalConfig.baseMetadataUrl, + collectionUpdateAuthority: globalConfig.collectionUpdateAuthority.toBase58(), + }; + + writeJson("target/devnet/init.json", output); + console.log(JSON.stringify(output, null, 2)); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/devnet/mint.ts b/scripts/devnet/mint.ts new file mode 100644 index 0000000..88d0118 --- /dev/null +++ b/scripts/devnet/mint.ts @@ -0,0 +1,82 @@ +import { + buildMetadataUri, + fetchGlobalConfig, + getConnection, + Keypair, + loadWallet, + mintDoomIndexNftInstruction, + readJson, + sendInstructions, + writeJson, +} from "./common"; + +type ReservationOutput = { + tokenId: string; +}; + +async function main(): Promise { + const connection = getConnection(); + const payer = loadWallet(); + const globalConfig = await fetchGlobalConfig(connection); + const reservation = + process.env.TOKEN_ID !== undefined + ? { tokenId: process.env.TOKEN_ID } + : readJson("target/devnet/latest-reservation.json"); + const tokenId = BigInt(reservation.tokenId); + const asset = Keypair.generate(); + + const mintInstruction = mintDoomIndexNftInstruction( + payer.publicKey, + tokenId, + asset.publicKey, + globalConfig.collection, + ); + const signature = await sendInstructions(connection, payer, [mintInstruction], [asset]); + + const assetAccount = await connection.getAccountInfo(asset.publicKey, "confirmed"); + if (!assetAccount) { + throw new Error(`Asset account not found at ${asset.publicKey.toBase58()}`); + } + + const metadataUri = buildMetadataUri(globalConfig.baseMetadataUrl, tokenId); + const metadataResponse = await fetch(metadataUri); + if (!metadataResponse.ok) { + throw new Error(`Metadata fetch failed: ${metadataResponse.status} ${metadataResponse.statusText}`); + } + const metadata = (await metadataResponse.json()) as { + image?: string; + animation_url?: string; + }; + if (!metadata.image || !metadata.animation_url) { + throw new Error("Metadata must include both image and animation_url"); + } + + const imageResponse = await fetch(metadata.image, { method: "HEAD" }); + if (!imageResponse.ok) { + throw new Error(`Image fetch failed: ${imageResponse.status} ${imageResponse.statusText}`); + } + + const animationResponse = await fetch(metadata.animation_url, { + method: "HEAD", + }); + if (!animationResponse.ok) { + throw new Error(`animation_url fetch failed: ${animationResponse.status} ${animationResponse.statusText}`); + } + + const output = { + signature, + tokenId: tokenId.toString(), + asset: asset.publicKey.toBase58(), + metadataUri, + image: metadata.image, + animationUrl: metadata.animation_url, + }; + + writeJson("target/devnet/latest-mint.json", output); + console.log(JSON.stringify(output, null, 2)); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/devnet/reserve.ts b/scripts/devnet/reserve.ts new file mode 100644 index 0000000..0966a18 --- /dev/null +++ b/scripts/devnet/reserve.ts @@ -0,0 +1,23 @@ +import { buildMetadataUri, getConnection, loadWallet, reserveTokenId, writeJson } from "./common"; + +async function main(): Promise { + const connection = getConnection(); + const payer = loadWallet(); + const { signature, tokenId, reservation, globalConfig } = await reserveTokenId(connection, payer); + + const output = { + signature, + user: payer.publicKey.toBase58(), + tokenId: tokenId.toString(), + reservation: reservation.toBase58(), + metadataUri: buildMetadataUri(globalConfig.baseMetadataUrl, tokenId), + }; + + writeJson("target/devnet/latest-reservation.json", output); + console.log(JSON.stringify(output, null, 2)); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/test-contract-v1.sh b/scripts/test-contract-v1.sh new file mode 100755 index 0000000..35c0644 --- /dev/null +++ b/scripts/test-contract-v1.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +"$ROOT_DIR/scripts/build-test-sbf.sh" +BPF_OUT_DIR="$ROOT_DIR/target/test-sbf" cargo test -p tests --lib -- --test-threads=1 diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 09f220c..bac04bf 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,5 +5,9 @@ description = "Created with Anchor" edition = "2021" [dependencies] -anchor-client = "0.29.0" +anchor-lang = "0.31.1" doom-nft-program = { version = "0.1.0", path = "../programs/doom-nft-program" } +mpl-core = { version = "0.11.1", features = ["anchor"] } +solana-program-test = "2.2.1" +solana-sdk = "2.2.1" +tokio = { version = "1.43.0", features = ["macros"] } diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 1bd3931..61a55f8 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,4 +1,527 @@ -#[cfg(test)] -mod tests { - // Add your tests here +#![cfg(test)] + +use anchor_lang::{ + prelude::AccountDeserialize, + solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}, + system_program::ID as SYSTEM_PROGRAM_ID, + InstructionData, ToAccountMetas, +}; +use doom_nft_program::{ + accounts::{ + InitializeCollection, InitializeGlobalConfig, MintDoomIndexNft, ReserveTokenId, + SetMintPaused, SetUpgradeAuthority, TransferAdmin, UpdateBaseMetadataUrl, + }, + instruction, GlobalConfig, MintReservation, +}; +use mpl_core::{Asset, Collection}; +use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; +use solana_sdk::{ + instruction::Instruction, + pubkey::Pubkey, + signature::{Keypair, Signature}, + signer::Signer, + transaction::Transaction, +}; + +fn program_test() -> ProgramTest { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let bpf_out_dir = manifest_dir.join("..").join("target").join("test-sbf"); + std::env::set_var("BPF_OUT_DIR", &bpf_out_dir); + + let mut test = ProgramTest::default(); + test.prefer_bpf(false); + test.add_program( + "doom_nft_program", + doom_nft_program::id(), + processor!(doom_nft_program_test_processor), + ); + test.add_upgradeable_program_to_genesis("mpl_core_program", &mpl_core::ID); + test +} + +fn doom_nft_program_test_processor<'a, 'b, 'c, 'd>( + program_id: &'a Pubkey, + accounts: &'b [AccountInfo<'c>], + instruction_data: &'d [u8], +) -> ProgramResult { + // Anchor's generated entrypoint ties the slice lifetime to the inner AccountInfo lifetime. + // ProgramTest uses the looser builtin processor signature, so the wrapper narrows it for this call. + let accounts: &'c [AccountInfo<'c>] = unsafe { std::mem::transmute(accounts) }; + doom_nft_program::entry(program_id, accounts, instruction_data) +} + +fn global_config_pda() -> (Pubkey, u8) { + Pubkey::find_program_address(&[b"global_config"], &doom_nft_program::id()) +} + +fn collection_authority_pda(global_config: Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[b"collection_authority", global_config.as_ref()], + &doom_nft_program::id(), + ) +} + +fn reservation_pda(token_id: u64) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[b"reservation", &token_id.to_le_bytes()], + &doom_nft_program::id(), + ) +} + +async fn process_instruction( + context: &mut ProgramTestContext, + instruction: Instruction, + extra_signers: &[&Keypair], +) -> Result { + let mut signers = vec![&context.payer]; + signers.extend_from_slice(extra_signers); + + let tx = Transaction::new_signed_with_payer( + &[instruction], + Some(&context.payer.pubkey()), + &signers, + context.last_blockhash, + ); + let signature = tx.signatures[0]; + context.banks_client.process_transaction(tx).await?; + Ok(signature) +} + +async fn fetch_global_config(context: &mut ProgramTestContext) -> GlobalConfig { + let (config, _) = global_config_pda(); + let account = context + .banks_client + .get_account(config) + .await + .expect("get config account") + .expect("config account exists"); + + let mut bytes = account.data.as_slice(); + GlobalConfig::try_deserialize(&mut bytes).expect("deserialize global config") +} + +async fn fetch_reservation(context: &mut ProgramTestContext, token_id: u64) -> MintReservation { + let (reservation, _) = reservation_pda(token_id); + let account = context + .banks_client + .get_account(reservation) + .await + .expect("get reservation account") + .expect("reservation account exists"); + + let mut bytes = account.data.as_slice(); + MintReservation::try_deserialize(&mut bytes).expect("deserialize reservation") +} + +fn initialize_global_config_ix( + admin: Pubkey, + upgrade_authority: Pubkey, + base_metadata_url: &str, +) -> Instruction { + let (global_config, _) = global_config_pda(); + + Instruction { + program_id: doom_nft_program::id(), + accounts: InitializeGlobalConfig { + global_config, + admin, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::InitializeGlobalConfig { + base_metadata_url: base_metadata_url.to_owned(), + upgrade_authority, + } + .data(), + } +} + +fn initialize_collection_ix(admin: Pubkey, collection: Pubkey) -> Instruction { + let (global_config, _) = global_config_pda(); + let (collection_update_authority, _) = collection_authority_pda(global_config); + + Instruction { + program_id: doom_nft_program::id(), + accounts: InitializeCollection { + global_config, + admin, + collection, + collection_update_authority, + mpl_core_program: mpl_core::ID, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::InitializeCollection {}.data(), + } +} + +fn reserve_token_id_ix(user: Pubkey, token_id: u64) -> Instruction { + let (global_config, _) = global_config_pda(); + let (reservation, _) = reservation_pda(token_id); + + Instruction { + program_id: doom_nft_program::id(), + accounts: ReserveTokenId { + global_config, + reservation, + user, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::ReserveTokenId {}.data(), + } +} + +fn mint_doom_index_nft_ix( + user: Pubkey, + token_id: u64, + asset: Pubkey, + collection: Pubkey, +) -> Instruction { + let (global_config, _) = global_config_pda(); + let (reservation, _) = reservation_pda(token_id); + let (collection_update_authority, _) = collection_authority_pda(global_config); + let config = global_config; + + Instruction { + program_id: doom_nft_program::id(), + accounts: MintDoomIndexNft { + global_config: config, + reservation, + user, + asset, + collection_update_authority, + collection, + mpl_core_program: mpl_core::ID, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::MintDoomIndexNft { token_id }.data(), + } +} + +#[tokio::test] +async fn initialize_global_config_sets_defaults() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + let base_metadata_url = "https://example.com/doom-index"; + + let signature = process_instruction( + &mut context, + initialize_global_config_ix(payer, upgrade_authority.pubkey(), base_metadata_url), + &[], + ) + .await + .expect("initialize global config"); + + assert_ne!(signature, Signature::default()); + + let config = fetch_global_config(&mut context).await; + assert_eq!(config.admin, context.payer.pubkey()); + assert_eq!(config.upgrade_authority, upgrade_authority.pubkey()); + assert_eq!(config.next_token_id, 1); + assert!(!config.mint_paused); + assert_eq!(config.base_metadata_url, base_metadata_url); +} + +#[tokio::test] +async fn initialize_collection_persists_collection_and_authority() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + let config = fetch_global_config(&mut context).await; + let (collection_authority, _) = collection_authority_pda(global_config_pda().0); + assert_eq!(config.collection, collection.pubkey()); + assert_eq!(config.collection_update_authority, collection_authority); + + let collection_account = context + .banks_client + .get_account(collection.pubkey()) + .await + .expect("get collection account") + .expect("collection account exists"); + let collection = Collection::from_bytes(&collection_account.data).expect("decode collection"); + assert_eq!(collection.base.name, "DOOM INDEX"); + assert_eq!(collection.base.update_authority, collection_authority); +} + +#[tokio::test] +async fn reserve_token_id_creates_reservation_and_increments_counter() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect("reserve token id"); + + let reservation = fetch_reservation(&mut context, 1).await; + assert_eq!(reservation.token_id, 1); + assert_eq!(reservation.reserver, context.payer.pubkey()); + assert!(!reservation.minted); + + let config = fetch_global_config(&mut context).await; + assert_eq!(config.next_token_id, 2); +} + +#[tokio::test] +async fn mint_with_valid_reservation_creates_core_asset() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect("reserve token id"); + + let asset = Keypair::new(); + process_instruction( + &mut context, + mint_doom_index_nft_ix(payer, 1, asset.pubkey(), collection.pubkey()), + &[&asset], + ) + .await + .expect("mint doom index nft"); + + let reservation = fetch_reservation(&mut context, 1).await; + assert!(reservation.minted); + + let asset_account = context + .banks_client + .get_account(asset.pubkey()) + .await + .expect("get asset account") + .expect("asset account exists"); + let asset = Asset::from_bytes(&asset_account.data).expect("decode asset"); + assert_eq!(asset.base.name, "DOOM INDEX #1"); + assert_eq!(asset.base.uri, "https://example.com/base/1.json"); + assert_eq!(asset.base.owner, context.payer.pubkey()); +} + +#[tokio::test] +async fn mint_without_reservation_fails() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + let asset = Keypair::new(); + let error = process_instruction( + &mut context, + mint_doom_index_nft_ix(payer, 1, asset.pubkey(), collection.pubkey()), + &[&asset], + ) + .await + .expect_err("mint should fail without reservation"); + + assert!(matches!(error, BanksClientError::TransactionError(_))); +} + +#[tokio::test] +async fn pause_blocks_reserve_and_mint() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let set_paused = Instruction { + program_id: doom_nft_program::id(), + accounts: SetMintPaused { + global_config: global_config_pda().0, + admin: context.payer.pubkey(), + } + .to_account_metas(None), + data: instruction::SetMintPaused { paused: true }.data(), + }; + process_instruction(&mut context, set_paused, &[]) + .await + .expect("pause mint"); + + let reserve_error = process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect_err("reserve should fail when paused"); + assert!(matches!( + reserve_error, + BanksClientError::TransactionError(_) + )); +} + +#[tokio::test] +async fn admin_controls_update_base_url_and_transfer_admin() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let update_url = Instruction { + program_id: doom_nft_program::id(), + accounts: UpdateBaseMetadataUrl { + global_config: global_config_pda().0, + admin: context.payer.pubkey(), + } + .to_account_metas(None), + data: instruction::UpdateBaseMetadataUrl { + base_metadata_url: "https://example.com/next".to_owned(), + } + .data(), + }; + process_instruction(&mut context, update_url, &[]) + .await + .expect("update base metadata url"); + assert_eq!( + fetch_global_config(&mut context).await.base_metadata_url, + "https://example.com/next" + ); + + let next_admin = Keypair::new(); + let transfer_admin = Instruction { + program_id: doom_nft_program::id(), + accounts: TransferAdmin { + global_config: global_config_pda().0, + admin: context.payer.pubkey(), + } + .to_account_metas(None), + data: instruction::TransferAdmin { + new_admin: next_admin.pubkey(), + } + .data(), + }; + process_instruction(&mut context, transfer_admin, &[]) + .await + .expect("transfer admin"); + assert_eq!( + fetch_global_config(&mut context).await.admin, + next_admin.pubkey() + ); +} + +#[tokio::test] +async fn upgrade_authority_is_independent_from_admin() { + let mut context = program_test().start_with_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let next_upgrade_authority = Keypair::new(); + let set_upgrade_authority = Instruction { + program_id: doom_nft_program::id(), + accounts: SetUpgradeAuthority { + global_config: global_config_pda().0, + upgrade_authority: upgrade_authority.pubkey(), + } + .to_account_metas(None), + data: instruction::SetUpgradeAuthority { + new_upgrade_authority: next_upgrade_authority.pubkey(), + } + .data(), + }; + process_instruction(&mut context, set_upgrade_authority, &[&upgrade_authority]) + .await + .expect("set upgrade authority"); + + let config = fetch_global_config(&mut context).await; + assert_eq!(config.admin, payer); + assert_eq!(config.upgrade_authority, next_upgrade_authority.pubkey()); } From fb0bfbd6466bff328aeccafb9f87bd9c42363e62 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 06:56:57 +0900 Subject: [PATCH 2/8] Use contract test workflow in CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70ab23e..cd987ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,4 +40,4 @@ jobs: run: cargo clippy --workspace --all-targets --all-features -- -D warnings - name: Run tests - run: cargo test --workspace + run: bun run test From 338f10e6d080083363e039ecb9caddbcffb4e327 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 07:02:37 +0900 Subject: [PATCH 3/8] Install Solana CLI in CI --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd987ac..b2ff901 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,11 @@ jobs: with: components: rustfmt, clippy + - name: Install Solana CLI + run: | + sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" + echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH" + - name: Cache Rust uses: Swatinem/rust-cache@v2 From dfa1c6cc688827a7a7dd2c8723645cf4d78a2ba1 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 07:21:31 +0900 Subject: [PATCH 4/8] Use pinned Solana setup action in CI --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2ff901..7a64701 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,10 +24,10 @@ jobs: with: components: rustfmt, clippy - - name: Install Solana CLI - run: | - sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" - echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH" + - name: Setup Solana Tooling + uses: solana-developers/github-actions/setup-all@c6bdbee471a1a4f40490af635e9af66071d36853 # v0.2.7 + with: + solana_version: 2.2.1 - name: Cache Rust uses: Swatinem/rust-cache@v2 @@ -44,5 +44,8 @@ jobs: - name: Run Clippy run: cargo clippy --workspace --all-targets --all-features -- -D warnings - - name: Run tests - run: bun run test + - name: Run Rust tests + run: cargo test --workspace --exclude tests + + - name: Run contract tests + run: bun run test:contract From 43ef4b33eef665b3663aae5cf2b32f67209b92ed Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 07:23:34 +0900 Subject: [PATCH 5/8] Fix Solana setup-all version wiring --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a64701..b6120ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,8 @@ on: jobs: checks: runs-on: ubuntu-latest + env: + SOLANA_VERSION: 2.2.1 steps: - name: Checkout @@ -27,7 +29,7 @@ jobs: - name: Setup Solana Tooling uses: solana-developers/github-actions/setup-all@c6bdbee471a1a4f40490af635e9af66071d36853 # v0.2.7 with: - solana_version: 2.2.1 + solana_version: ${{ env.SOLANA_VERSION }} - name: Cache Rust uses: Swatinem/rust-cache@v2 From 7360b33982ce2ccd7386e4785ea2bd0b60ec66dd Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 07:59:11 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat(doom-nft):=20doom=20index=20contract?= =?UTF-8?q?=20v1=20=E2=80=94=20tests,=20docs,=20devnet=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add instruction tests (init, mint, reserve, admin) - Add test context and instruction helpers - Update DOOM_INDEX_NFT_MINT_REQUIREMENTS, PRODUCT, README - Adjust Anchor.toml, lib.rs, devnet common.ts - Update .agents/memory/todo.md Made-with: Cursor --- .agents/memory/todo.md | 102 ++++ Anchor.toml | 10 +- README.md | 151 ++--- docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md | 424 +++++++------- docs/PRODUCT.md | 6 +- programs/doom-nft-program/src/lib.rs | 2 +- scripts/devnet/common.ts | 2 +- .../src/instructions/initialize_collection.rs | 42 ++ .../instructions/initialize_global_config.rs | 30 + tests/src/instructions/mint_doom_index_nft.rs | 93 ++++ tests/src/instructions/mod.rs | 8 + tests/src/instructions/reserve_token_id.rs | 36 ++ tests/src/instructions/set_mint_paused.rs | 37 ++ .../src/instructions/set_upgrade_authority.rs | 37 ++ tests/src/instructions/transfer_admin.rs | 38 ++ .../instructions/update_base_metadata_url.rs | 37 ++ tests/src/lib.rs | 527 +----------------- tests/src/test_context.rs | 281 ++++++++++ 18 files changed, 1043 insertions(+), 820 deletions(-) create mode 100644 tests/src/instructions/initialize_collection.rs create mode 100644 tests/src/instructions/initialize_global_config.rs create mode 100644 tests/src/instructions/mint_doom_index_nft.rs create mode 100644 tests/src/instructions/mod.rs create mode 100644 tests/src/instructions/reserve_token_id.rs create mode 100644 tests/src/instructions/set_mint_paused.rs create mode 100644 tests/src/instructions/set_upgrade_authority.rs create mode 100644 tests/src/instructions/transfer_admin.rs create mode 100644 tests/src/instructions/update_base_metadata_url.rs create mode 100644 tests/src/test_context.rs diff --git a/.agents/memory/todo.md b/.agents/memory/todo.md index 4e2fd00..ed2c71b 100644 --- a/.agents/memory/todo.md +++ b/.agents/memory/todo.md @@ -1,3 +1,44 @@ +# Task Plan: Documentation English Translation (2026-03-12) + +- [x] Inspect public markdown files and identify Japanese text that should be translated. +- [x] Translate `README.md` and `docs/*.md` to English while preserving technical meaning. +- [x] Verify that no Japanese text remains in the public documentation set. +- [x] Record the translation scope and verification results in the review section. + +# Review: Documentation English Translation (2026-03-12) + +- Rewrote `README.md` in English and aligned it with the current repository structure, Metaplex Core mint flow, and active Bun-based commands. +- Translated `docs/PRODUCT.md` to English while preserving the existing product context and relationship to `doom-index`. +- Translated `docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md` to English while keeping the document structure, technical requirements, and JSON example intact. +- Verified with `rg -n "[ぁ-んァ-ン一-龯]" README.md docs --glob '*.md'`, which returned no matches. + +# Task Plan: Test Module Reorganization (2026-03-12) + +- [x] Inspect the current contract test coverage and map tests to the corresponding source modules. +- [x] Refactor `tests/src` so contract tests are organized by source-aligned modules instead of a single `lib.rs`. +- [x] Run the contract-focused verification commands after the reorganization. +- [x] Record the final structure and verification outcomes in the review section. + +# Review: Test Module Reorganization (2026-03-12) + +- Replaced the monolithic `tests/src/lib.rs` with a thin module entry plus shared helpers in `tests/src/test_context.rs`. +- Organized contract tests under `tests/src/instructions/` so each file now mirrors a program instruction module such as `initialize_collection`, `mint_doom_index_nft`, `transfer_admin`, and `update_base_metadata_url`. +- Split the previous combined admin-control coverage into source-aligned tests for `transfer_admin` and `update_base_metadata_url`, bringing the contract suite to 9 tests. +- Verified with `cargo fmt --all --check`, `cargo test -p tests --lib -- --list`, and `bun run test:contract`. + +# Task Plan: CI Rust + Contract Tests (2026-03-12) + +- [x] Inspect the existing CI workflow and local test scripts to confirm the minimum GitHub Actions changes required. +- [x] Update `.github/workflows/ci.yml` so Rust checks are explicit and contract tests run in CI. +- [x] Run the relevant local verification commands for the updated workflow. +- [x] Record the implementation result and verification outcomes in the review section. + +# Review: CI Rust + Contract Tests (2026-03-12) + +- Kept the existing CI setup intact and made the Rust-side test coverage explicit in `.github/workflows/ci.yml`. +- Replaced the generic `Run tests` step with separate `Run Rust tests` and `Run contract tests` steps so the workflow clearly shows both execution paths. +- Verified locally with `bun run format:ts:check`, `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `cargo test --workspace --exclude tests`, and `bun run test:contract`. + # Task Plan - [x] Reproduce the reported diagnostics with a deterministic command. @@ -71,3 +112,64 @@ # Review: Create PR (2026-03-11) - In progress. + +# Task Plan: NFT Mint Requirements Update (2026-03-12) + +- [x] Inspect current repo docs and determine where to store the requirements/design document +- [x] Verify current official Metaplex Core guidance for metadata JSON and devnet validation expectations +- [x] Draft a repository-local requirements/design document for DOOM INDEX NFT mint v0.2 +- [x] Incorporate deterministic URI minting, standard metadata, and 3D model support +- [x] Add explicit devnet verification requirements and acceptance criteria + +# Review: NFT Mint Requirements Update (2026-03-12) + +- Added `docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md` as the primary requirements/design draft for the new Metaplex Core-based mint flow. +- Fixed the mint architecture around deterministic per-token metadata URIs and removed the earlier backend approval dependency from the spec. +- Standardized metadata around Metaplex Core JSON fields with `image`, `animation_url`, and `properties.files`, including a GLB example for 3D model support. +- Added devnet-specific functional requirements and an end-to-end validation checklist so the implementation can be verified outside localnet. + +# Task Plan: Contract V1 Implementation (2026-03-12) + +- [x] Upgrade the workspace toolchain and dependencies to Anchor 0.31.x / Solana 2.2.x / `mpl-core` 0.11.x. +- [x] Replace the existing SPL-token NFT program with Contract V1 state, errors, events, and instruction handlers. +- [x] Add Rust integration tests for config initialization, collection initialization, reservation, minting, and admin controls. +- [x] Refactor the program into a module structure modeled after official `mpl-core-anchor-examples` (`constants`, `error`, `events`, `instructions`, `state`, `utils`) instead of a monolithic `lib.rs`. +- [x] Align the implementation with the current requirements doc, including the separated contract-level `upgrade_authority` field and instruction if it remains part of the spec. +- [x] Re-run format and Rust tests after the refactor. +- [x] Investigate and document the current local SBF build blocker caused by Solana platform-tools cargo failing on `edition2024` transitive manifests. +- [x] Add devnet smoke scripts for initialize, reserve, and mint verification. +- [x] Run the full verification set that is feasible locally and record exact blockers for anything that cannot be completed. + +# Notes: Contract V1 Implementation (2026-03-12) + +- Reference project for structure and CPI style: `metaplex-foundation/mpl-core-anchor-examples` +- The current local BPF/SBF build path is blocked by the bundled Solana platform-tools cargo failing on a transitive `edition2024` dependency manifest (`rmp 0.8.15`) before tests can run under `cargo-build-sbf`. + +# Review: Contract V1 Implementation (2026-03-12) + +- Refactored the program from a monolithic `lib.rs` into `constants.rs`, `error.rs`, `events.rs`, `instructions/`, `state/`, and `utils.rs`, following the official `metaplex-foundation/mpl-core-anchor-examples` layout. +- Added the contract-level `upgrade_authority` field and `set_upgrade_authority` instruction so the on-chain config now matches the current requirements document. +- Kept the Core mint path compatible with Metaplex Core `CreateCollectionV2` / `CreateV2`, including the collection-specific rule that assets in a collection must not also set an explicit `update_authority`. +- Reworked the Rust integration tests to run the DOOM program as a host builtin while loading only the official `mpl_core` binary, which avoids the local `cargo-build-sbf` blocker and still exercises the real Core CPI path. +- Added devnet helper scripts under `scripts/devnet/` for `init`, `reserve`, and `mint`, with shared PDA/account encoding logic in `scripts/devnet/common.ts`. +- Verified with `cargo fmt --all`, `cargo clippy -p doom-nft-program --all-features --all-targets -- -D warnings`, `./scripts/test-contract-v1.sh`, and `bun x tsc --noEmit`. +- Remaining limitation: the local Solana platform-tools cargo still cannot build this dependency graph to SBF because it chokes on a transitive `edition2024` manifest. The contract test workflow now avoids that path by using the host processor for this program. + +# Task Plan: Devnet Deploy Smoke Test (2026-03-12) + +- [ ] Inspect the current Anchor/devnet configuration, wallet state, and existing smoke scripts. +- [ ] Confirm whether a fresh SBF build is possible from the current source tree or whether deployment must use the checked-in artifact. +- [ ] Deploy the DOOM NFT program to Solana devnet with Anchor-compatible tooling and record the final program id. +- [ ] Run the devnet smoke flow for config initialization, collection initialization, token reservation, and NFT minting. +- [ ] Inspect the on-chain/accounts outputs and metadata fetches to confirm the expected state transitions. +- [ ] Record exact commands, signatures, addresses, and blockers in the review section. + +# Review: Devnet Deploy Smoke Test (2026-03-12) + +- Aligned the repository to the actual deploy keypair returned by `anchor keys list` by switching the program id from `AavECgzCbVhHeBGAfcUgT1tYEC4N4B96E8XtF9H1fMGt` to `u929SRVcCFcGM2iyYkMykDRq7xW4N9ozEMU3Vo1hgfP` in `Anchor.toml`, `programs/doom-nft-program/src/lib.rs`, and `scripts/devnet/common.ts`. +- Added `anchor_version = "0.31.1"` and a `[programs.devnet]` entry to `Anchor.toml` so the workspace config matches the installed deploy key and Anchor crate version. +- Reproduced the original `anchor build` blocker under bundled `platform-tools v1.51` / `cargo 1.84.1`, where `rmp 0.8.15` failed due to the unstabilized `edition2024` manifest requirement. +- Installed newer Solana platform-tools and confirmed `cargo-build-sbf --tools-version v1.53 --manifest-path programs/doom-nft-program/Cargo.toml --sbf-out-dir target/deploy` succeeds, producing an up-to-date SBF artifact for the current source tree. +- Generated a dedicated devnet payer at `target/devnet/deployer.json` (`HmFV8YND3fAqhu1eP2Tii45sCUQu2FMUcaZdgmf1hmd9`) to avoid mutating the user's default Solana wallet. +- Deployment is currently blocked because every attempted faucet path left the payer at `0 SOL`: `solana airdrop` against `api.devnet.solana.com` failed with rate limiting for 5 / 2 / 1 / 0.5 / 0.1 SOL, and alternative public RPC paths were either paid-tier only, API-key-gated, or rate-limited as well. +- Until a devnet payer is funded, the remaining `anchor deploy` and on-chain smoke steps (`init`, `reserve`, `mint`, plus any admin instruction checks) cannot be executed. diff --git a/Anchor.toml b/Anchor.toml index e11f494..669ed69 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,19 +1,23 @@ [toolchain] -package_manager = "bun" +package_manager = "npm" +anchor_version = "0.31.1" [features] resolution = true skip-lint = false [programs.localnet] -doom_nft_program = "AavECgzCbVhHeBGAfcUgT1tYEC4N4B96E8XtF9H1fMGt" +doom_nft_program = "u929SRVcCFcGM2iyYkMykDRq7xW4N9ozEMU3Vo1hgfP" + +[programs.devnet] +doom_nft_program = "u929SRVcCFcGM2iyYkMykDRq7xW4N9ozEMU3Vo1hgfP" [registry] url = "https://api.apr.dev" [provider] cluster = "localnet" -wallet = "~/.config/solana/id.json" +wallet = "target/devnet/deployer.json" [scripts] test = "cargo test" diff --git a/README.md b/README.md index 43164ef..6dfd69c 100644 --- a/README.md +++ b/README.md @@ -1,126 +1,127 @@ # Doom NFT Program -Solanaブロックチェーン上で動作するNFT(Non-Fungible Token)プログラムです。Anchorフレームワークを使用して実装されています。 +This repository contains the Solana program for minting DOOM INDEX artworks as NFTs. It is built with Anchor and currently targets a Metaplex Core based mint flow with deterministic metadata URIs. -## 特徴 +## Features -- **NFTミント**: 新しいNFTを作成・発行 -- **トークン転送**: NFTの所有権移転 -- **SPLトークン互換**: Solanaの標準トークン規格に対応 -- **セキュリティ**: Anchorフレームワークのセキュリティ機能を使用 +- **Collection setup**: Initialize the DOOM INDEX collection on Metaplex Core +- **Token reservation**: Reserve a sequential `tokenId` before minting +- **NFT minting**: Mint a DOOM INDEX NFT from a valid reservation +- **Admin controls**: Manage base metadata URL, pause state, admin authority, and upgrade authority +- **Contract tests**: Run Rust integration tests against the real Core CPI path -## 技術スタック +## Tech Stack - **Blockchain**: Solana - **Framework**: Anchor - **Language**: Rust -- **Testing**: TypeScript, Mocha, Chai +- **NFT Standard**: Metaplex Core +- **Testing**: Rust `solana-program-test`, Bun - **Package Manager**: Bun -## 前提条件 +## Prerequisites - [Rust](https://rustup.rs/) -- [Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools) +- [Solana CLI](https://docs.anza.xyz/cli/install) - [Anchor](https://www.anchor-lang.com/) -- [Bun](https://bun.sh/) または [Node.js](https://nodejs.org/) +- [Bun](https://bun.sh/) -## インストール +## Installation ```bash -# リポジトリをクローン -git clone https://github.com/posaune0423/doom-nft-program.git +# Clone the repository +git clone https://github.com/doom-protocol/doom-nft-program.git cd doom-nft-program -# 依存関係のインストール +# Install dependencies bun install -# Solana CLIの設定(devnetを使用する場合) +# Install git hooks +bun run prepare + +# Optional: point Solana CLI at devnet solana config set --url https://api.devnet.solana.com -# キーペアの生成(初回のみ) +# Optional: create a local keypair if you do not have one yet solana-keygen new ``` -## ビルド +## Build ```bash -# プログラムのビルド -anchor build +# Build the Rust workspace +cargo build --workspace -# IDLファイルの生成 -anchor idl parse -f programs/doom-nft-program/src/lib.rs -o target/idl/doom_nft_program.json +# Build the contract test SBF dependency artifact +bun run build:sbf:test ``` -## テスト +## Test ```bash -# テスト実行 -anchor test - -# ローカルバリデーターでのテスト -anchor localnet -``` +# Run Rust unit tests plus contract tests +bun run test -## デプロイ +# Run only the contract test suite +bun run test:contract -```bash -# プログラムのデプロイ -anchor deploy - -# プログラムIDの確認 -solana program show --programs +# Run the full local quality gate +bun run check ``` -## 使用方法 - -### NFTミント +## Development Scripts -```rust -// プログラム内でNFTを作成 -create_mint(ctx)?; - -// トークンをミント -mint_token(ctx)?; -``` +```bash +# Initialize global config on devnet +bun run devnet:init -### NFT転送 +# Reserve the next token id on devnet +bun run devnet:reserve -```rust -// NFTを転送 -transfer_token(ctx)?; +# Mint a DOOM INDEX NFT on devnet +bun run devnet:mint ``` -## プログラム構造 +## Repository Structure -``` +```text programs/doom-nft-program/ -├── src/lib.rs # メインのプログラムロジック -├── Cargo.toml # Rust依存関係 -└── Xargo.toml # クロスコンパイル設定 - -tests/ -├── src/lib.rs # テストファイル -└── Cargo.toml # テスト依存関係 - -migrations/ -├── src/main.rs # マイグレーションスクリプト -└── Cargo.toml # マイグレーション依存関係 +├── src/ +│ ├── instructions/ # Instruction handlers +│ ├── state/ # On-chain account state +│ ├── constants.rs # Program constants +│ ├── error.rs # Custom errors +│ ├── events.rs # Program events +│ ├── lib.rs # Program entrypoint +│ └── utils.rs # Shared helpers +├── Cargo.toml +└── Xargo.toml + +tests/src/ +├── instructions/ # Source-aligned contract tests +├── lib.rs # Test module entrypoint +└── test_context.rs # Shared test fixtures and helpers + +scripts/ +├── build-test-sbf.sh # Fetches the Core program artifact for tests +├── test-contract-v1.sh # Runs the contract suite +└── devnet/ # Devnet helper scripts ``` -## コントリビューション +## Contributing -1. Forkしてください -2. Featureブランチを作成 (`git checkout -b feature/amazing-feature`) -3. 変更をコミット (`git commit -m 'Add amazing feature'`) -4. ブランチにPush (`git push origin feature/amazing-feature`) -5. Pull Requestを作成してください +1. Fork the repository. +2. Create a feature branch: `git checkout -b feature/amazing-feature` +3. Commit your changes: `git commit -m 'Add amazing feature'` +4. Push the branch: `git push origin feature/amazing-feature` +5. Open a pull request. -## ライセンス +## License -このプロジェクトはMITライセンスの下で公開されています。 +This project is released under the MIT License. -## 注意事項 +## Notes -- このプログラムは開発中です -- テストネットでのみ動作確認を行っています -- 本番環境での使用は自己責任でお願いします +- The program is still under active development. +- The contract test flow avoids a local SBF build of this program and runs it as a host builtin, while loading the official `mpl_core` program binary. +- Devnet deployment may still depend on Solana faucet availability. diff --git a/docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md b/docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md index 00e54b0..6035cf9 100644 --- a/docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md +++ b/docs/DOOM_INDEX_NFT_MINT_REQUIREMENTS.md @@ -1,93 +1,93 @@ -# DOOM INDEX NFT Mint Contract V1 要件定義書 +# DOOM INDEX NFT Mint Contract V1 Requirements -## 1. 目的 +## 1. Purpose -DOOM INDEX において一定時間ごとに生成される作品を、Solana 上の NFT として mint 可能にする。 -初期段階では仕様変更の可能性が高いため、本仕様の対象 contract は **`DOOM INDEX NFT Mint Contract V1`** として定義する。 -Contract V1 は **upgradeable program** を前提とし、変化しやすい metadata 生成と配信は off-chain に寄せる。 +Enable artworks generated by DOOM INDEX at fixed intervals to be minted as NFTs on Solana. +Because the specification is still likely to change in the early phase, the target contract for this spec is defined as **`DOOM INDEX NFT Mint Contract V1`**. +Contract V1 assumes an **upgradeable program**, while metadata generation and delivery, which are expected to change frequently, remain off-chain. -本機能の主目的は以下とする。 +The primary goals of this feature are: -- ユーザーが DOOM INDEX の作品を Solana NFT として取得できること -- NFT ごとにアプリケーション独自の `tokenId` を連番で払い出せること -- Solana の標準的な NFT metadata 仕様に沿って、thumbnail image に加えて 3D model を扱えること -- localnet に加えて devnet 上でも一通りの動作を確認できること -- 将来的な仕様変更に耐えられること +- Users can acquire DOOM INDEX artworks as Solana NFTs. +- The application can issue a sequential custom `tokenId` for each NFT. +- The NFT metadata follows standard Solana NFT conventions and supports a 3D model in addition to a thumbnail image. +- The full flow can be validated on `devnet` as well as `localnet`. +- The design can accommodate future specification changes. -## 2. 採用方針 +## 2. Design Principles -### 2.1 標準とチェーン +### 2.1 Standards and Chain -- NFT 標準は Metaplex Core を採用する -- 実体識別子は Solana / Metaplex Core 上の `Asset Address` とする -- `tokenId` はアプリケーション独自の連番識別子として on-chain で管理する -- 開発時の動作確認環境は `localnet` と `devnet` を対象とする -- Contract V1 は upgradeable program として deploy する -- 将来の Contract V2 以降に移行可能な前提で、state と instruction を最小構成に保つ +- Use Metaplex Core as the NFT standard. +- Use the Solana / Metaplex Core `Asset Address` as the canonical on-chain asset identifier. +- Manage `tokenId` on-chain as an application-specific sequential identifier. +- Support both `localnet` and `devnet` as development and validation environments. +- Deploy Contract V1 as an upgradeable program. +- Keep state and instructions minimal so migration to Contract V2 and beyond remains straightforward. -### 2.2 v1 の基本設計 +### 2.2 Core V1 Design -- mint 時の metadata URI は backend が都度承認するのではなく、program が `tokenId` から deterministic に決定する -- metadata URI の標準ルールは `"{base_metadata_url}/{tokenId}.json"` とする -- metadata JSON を mint 前に配置するため、mint 前に `tokenId` を確定する reservation フローを採用する -- 同一生成期間内に metadata の内容が同一であってもよい -- ただし URI 自体は tokenId ごとに一意とする -- on-chain に generation state は持たない -- user は任意の URI を指定できない -- business admin と program upgrade authority は分離する -- collection authority は EOA ではなく program 管理 PDA を第一候補とする +- The metadata URI at mint time is determined deterministically by the program from `tokenId`, rather than being approved by the backend each time. +- The standard metadata URI format is `"{base_metadata_url}/{tokenId}.json"`. +- Adopt a reservation flow that finalizes `tokenId` before mint so metadata JSON can be uploaded in advance. +- Metadata content may be identical across assets generated within the same generation window. +- The URI itself must still be unique per `tokenId`. +- Do not store generation state on-chain. +- Users must not be allowed to provide arbitrary URIs. +- Separate business admin authority from program upgrade authority. +- Prefer a program-managed PDA over an EOA for collection authority. -## 3. スコープ +## 3. Scope -### 3.1 対象 +### 3.1 In Scope -- Metaplex Core Collection の初期化 -- DOOM INDEX NFT の mint -- `tokenId` の連番採番 -- deterministic metadata URI の付与 -- 管理者による運用機能 -- devnet E2E 検証フロー -- standard metadata + 3D model 対応 +- Metaplex Core collection initialization +- DOOM INDEX NFT minting +- Sequential `tokenId` issuance +- Deterministic metadata URI assignment +- Admin operating functions +- Devnet end-to-end validation flow +- Standard metadata plus 3D model support -### 3.2 対象外 +### 3.2 Out of Scope -以下は v1 の対象外とする。 +The following items are out of scope for V1: -- on-chain generation boundary の厳密管理 +- Strict on-chain management of generation boundaries - cNFT / compressed NFT -- allowlist / WL mint -- per-wallet mint limit -- mint price / treasury -- 二次流通戦略 -- royalties の厳密設計 -- permissionless 以外の承認付き mint -- per-token metadata の on-chain 保持 -- 既存 NFT の自動 batch metadata 差し替え +- Allowlist / WL mint +- Per-wallet mint limits +- Mint price / treasury design +- Secondary market strategy +- Full royalty design +- Approval-gated minting beyond a permissionless flow +- On-chain storage of per-token metadata +- Automatic batch replacement of metadata for existing NFTs -## 4. システム概要 +## 4. System Overview -### 4.1 全体構成 +### 4.1 Overall Architecture -本システムは、作品 metadata を off-chain で管理し、mint 時点で program が算出した URI を使って Metaplex Core Asset を発行する構成とする。 +This system manages artwork metadata off-chain and issues Metaplex Core assets using a URI computed by the program at mint time. -- on-chain program は `tokenId` 採番、URI 決定、Collection 配下の Asset 作成を担当する -- off-chain backend は thumbnail / 3D model / metadata JSON の生成と配置を担当する -- frontend は user wallet 署名による mint UI を提供する +- The on-chain program is responsible for `tokenId` issuance, URI selection, and asset creation under the collection. +- The off-chain backend is responsible for generating and hosting the thumbnail, 3D model, and metadata JSON. +- The frontend provides the mint UI and wallet-signing flow for users. -### 4.2 識別子 +### 4.2 Identifiers -- `Asset Address`: Solana / Metaplex Core 上の NFT 実体識別子 -- `tokenId`: DOOM INDEX アプリケーション独自の連番 ID -- `Collection Address`: DOOM INDEX Collection の識別子 +- `Asset Address`: The canonical NFT identifier on Solana / Metaplex Core +- `tokenId`: The DOOM INDEX application-specific sequential ID +- `Collection Address`: The identifier for the DOOM INDEX collection -## 5. On-chain 設計 +## 5. On-Chain Design ### 5.1 GlobalConfig -v1 の永続 state は `GlobalConfig` と `MintReservation` とする。 -`GlobalConfig` の PDA seed は `["global_config"]` で固定する。 +V1 persistent state consists of `GlobalConfig` and `MintReservation`. +The PDA seed for `GlobalConfig` is fixed as `["global_config"]`. -推奨フィールドは以下とする。 +Recommended fields: - `admin: Pubkey` - `upgrade_authority: Pubkey` @@ -98,135 +98,135 @@ v1 の永続 state は `GlobalConfig` と `MintReservation` とする。 - `collection_update_authority: Pubkey` - `bump: u8` -`next_token_id` は次に払い出す値を保持し、初期値は `1` とする。 -Contract V1 は単一カウンタで採番するため、高並列 mint よりも実装単純性を優先する。 +`next_token_id` stores the next value to issue and starts at `1`. +Contract V1 uses a single counter and prioritizes implementation simplicity over highly parallel mint throughput. ### 5.2 MintReservation -`MintReservation` は mint 前に `tokenId` を確定し、metadata 事前配置と競合しないようにするための一時 state とする。 -PDA seed は `["reservation", token_id_le_bytes]` を標準とする。 +`MintReservation` is temporary state used to finalize `tokenId` before minting so the metadata can be uploaded ahead of time without conflicts. +The standard PDA seed is `["reservation", token_id_le_bytes]`. -推奨フィールドは以下とする。 +Recommended fields: - `token_id: u64` - `reserver: Pubkey` - `minted: bool` - `bump: u8` -### 5.3 Anchor instructions +### 5.3 Anchor Instructions #### `initialize_global_config` -- 実行者は `admin` -- `GlobalConfig` を初期化する -- `admin` と `upgrade_authority` を設定する -- `next_token_id = 1` -- `mint_paused = false` -- `base_metadata_url` を設定する +- Executed by `admin` +- Initializes `GlobalConfig` +- Sets `admin` and `upgrade_authority` +- Sets `next_token_id = 1` +- Sets `mint_paused = false` +- Sets `base_metadata_url` #### `initialize_collection` -- 実行者は `admin` -- Metaplex Core Collection を作成する -- 作成した Collection Address を `GlobalConfig.collection` に保存する -- Collection の update authority は program 管理 PDA を第一候補とする +- Executed by `admin` +- Creates the Metaplex Core collection +- Stores the created collection address in `GlobalConfig.collection` +- Prefers a program-managed PDA for the collection update authority #### `reserve_token_id` -- 実行者は user -- `mint_paused == false` を必須とする -- `tokenId = next_token_id` -- `next_token_id += 1` -- `MintReservation` を作成する -- reservation 作成後、off-chain backend は `"{base_metadata_url}/{tokenId}.json"` を配置する -- v1 では reservation expiry は持たず、未使用 reservation は将来運用または migration で整理可能とする +- Executed by the user +- Requires `mint_paused == false` +- Assigns `tokenId = next_token_id` +- Increments `next_token_id += 1` +- Creates `MintReservation` +- After reservation, the off-chain backend uploads `"{base_metadata_url}/{tokenId}.json"` +- V1 does not include reservation expiry; unused reservations may be handled later operationally or via migration #### `mint_doom_index_nft` -- 実行者は user -- `mint_paused == false` を必須とする -- user 自身の `MintReservation` を必須とする -- `MintReservation.minted == false` を必須とする -- name は `DOOM INDEX #` とする -- uri は `"{base_metadata_url}/{tokenId}.json"` とする -- user を owner とする Metaplex Core Asset を Collection 配下で作成する -- mint 成功時に `MintReservation.minted = true` とする +- Executed by the user +- Requires `mint_paused == false` +- Requires the user's own `MintReservation` +- Requires `MintReservation.minted == false` +- Sets the asset name to `DOOM INDEX #` +- Sets the URI to `"{base_metadata_url}/{tokenId}.json"` +- Creates a Metaplex Core asset under the collection with the user as owner +- Sets `MintReservation.minted = true` on successful mint #### `update_base_metadata_url` -- 実行者は `admin` -- `base_metadata_url` を更新する -- 変更は future mint にのみ反映する -- 既存 NFT の on-chain URI は変更しない +- Executed by `admin` +- Updates `base_metadata_url` +- Applies only to future mints +- Does not change the on-chain URI of existing NFTs #### `set_mint_paused` -- 実行者は `admin` -- mint の pause / unpause を切り替える +- Executed by `admin` +- Toggles mint pause / unpause #### `transfer_admin` -- 実行者は現 `admin` -- `admin` を更新する +- Executed by the current `admin` +- Updates `admin` #### `set_upgrade_authority` -- 実行者は現 `upgrade_authority` -- `upgrade_authority` を更新する -- business admin の操作権とは独立に扱う +- Executed by the current `upgrade_authority` +- Updates `upgrade_authority` +- Remains independent from business admin permissions ## 6. Functional Requirements ### 6.1 Mint -- FR-01: user は `reserve_token_id` により mint 前に `tokenId` を確保できること -- FR-02: user は予約済み `tokenId` を使って DOOM INDEX NFT を mint できること -- FR-03: 表示名は `DOOM INDEX #` とすること -- FR-04: mint 時に deterministic な metadata URI を設定すること -- FR-05: user は任意 URI を指定できないこと -- FR-06: NFT は Metaplex Core Asset として発行されること -- FR-07: mint された Asset は DOOM INDEX Collection に属すること -- FR-08: reservation 済みでない `tokenId` では mint できないこと -- FR-09: 同一 reservation を二重使用できないこと +- FR-01: The user can reserve a `tokenId` before minting via `reserve_token_id`. +- FR-02: The user can mint a DOOM INDEX NFT using a reserved `tokenId`. +- FR-03: The display name must be `DOOM INDEX #`. +- FR-04: Minting must assign a deterministic metadata URI. +- FR-05: The user must not be able to specify an arbitrary URI. +- FR-06: The NFT must be issued as a Metaplex Core asset. +- FR-07: The minted asset must belong to the DOOM INDEX collection. +- FR-08: Minting must fail for a `tokenId` without a reservation. +- FR-09: A reservation must not be reusable. -### 6.2 管理機能 +### 6.2 Admin Functions -- FR-10: 管理者は `GlobalConfig` を初期化できること -- FR-11: 管理者は Collection を初期化できること -- FR-12: 管理者は `base_metadata_url` を更新できること -- FR-13: 管理者は mint を停止 / 再開できること -- FR-14: 管理者は admin 権限を移譲できること -- FR-15: upgrade authority は admin と分離されること +- FR-10: The admin can initialize `GlobalConfig`. +- FR-11: The admin can initialize the collection. +- FR-12: The admin can update `base_metadata_url`. +- FR-13: The admin can pause and resume minting. +- FR-14: The admin can transfer admin authority. +- FR-15: Upgrade authority must be separated from admin authority. -### 6.3 Devnet 検証 +### 6.3 Devnet Validation -- FR-16: program は devnet に deploy 可能であること -- FR-17: devnet 上で Collection 初期化、reservation、metadata 配置、mint、状態確認まで一通り実行できること -- FR-18: devnet 上で発行された NFT は public metadata URI を参照できること -- FR-19: devnet 上で `image` と `animation_url` の両方が取得できること +- FR-16: The program can be deployed to devnet. +- FR-17: The full devnet flow covers collection initialization, reservation, metadata upload, minting, and state inspection. +- FR-18: NFTs issued on devnet reference a publicly accessible metadata URI. +- FR-19: Both `image` and `animation_url` can be fetched on devnet. -## 7. Metadata 仕様 +## 7. Metadata Specification -### 7.1 基本方針 +### 7.1 Basic Policy -Metaplex Core の off-chain JSON Schema に合わせる。 -Core の JSON metadata は Token Metadata 系の標準に近く、`name` `description` `image` `category` を必須とし、`animation_url` `external_url` `attributes` `properties.files` を任意で持てる。 +Follow the off-chain JSON schema used by Metaplex Core. +Core JSON metadata is close to the Token Metadata standard and requires `name`, `description`, `image`, and `category`, while allowing optional fields such as `animation_url`, `external_url`, `attributes`, and `properties.files`. -DOOM INDEX では以下を v1 の標準とする。 +For DOOM INDEX, V1 standardizes the following: -- `image`: thumbnail image の公開 URI -- `animation_url`: 3D model の公開 URI -- `properties.files`: image と 3D model を必ず列挙 -- `external_url`: DOOM INDEX の作品詳細ページ -- `attributes`: 作品生成に使った最低限の識別情報、visual parameter、prompt 情報を格納 -- `doom_index`: DOOM INDEX 独自の補助フィールド。長文 prompt や生値を機械可読に保持したい場合に使う +- `image`: Public URI for the thumbnail image +- `animation_url`: Public URI for the 3D model +- `properties.files`: Must list both the image and the 3D model +- `external_url`: Public DOOM INDEX artwork detail page +- `attributes`: Minimum identifying information, visual parameters, and prompt data used for the artwork +- `doom_index`: DOOM INDEX-specific auxiliary field for machine-readable storage of long prompts or raw values -### 7.2 Attributes 最低要件 +### 7.2 Minimum Attribute Requirements -v1 では、各 token の metadata に少なくとも以下の情報を含める。 -viewer 互換性のため、表示したい値は `attributes` に入れる。 +In V1, each token metadata document should include at least the following information. +For viewer compatibility, values meant to be displayed should be placed in `attributes`. -#### Basic information +#### Basic Information - `Generated` - `ID` @@ -234,7 +234,7 @@ viewer 互換性のため、表示したい値は `attributes` に入れる。 - `Params Hash` - `File Size` -#### Visual parameters +#### Visual Parameters - `fogDensity` - `skyTint` @@ -253,18 +253,18 @@ viewer 互換性のため、表示したい値は `attributes` に入れる。 - `lightIntensity` - `warmHue` -#### Prompt information +#### Prompt Information - `Prompt` - `Negative Prompt` -数値は小数のまま保持してよい。 -`Generated` は ISO 8601 UTC 文字列を標準とする。 -`File Size` は viewer 表示を優先し、`"472.62 KB"` のような文字列で保持してよい。 +Numeric values may remain as decimals. +`Generated` should use an ISO 8601 UTC timestamp. +`File Size` may be stored as a display-first string such as `"472.62 KB"`. -長文である `Prompt` と `Negative Prompt` は、`attributes` に加えて top-level custom field にも同値を保持してよい。 +Because `Prompt` and `Negative Prompt` can be long, the same values may also be duplicated in a top-level custom field in addition to `attributes`. -### 7.3 推奨 JSON 形式 +### 7.3 Recommended JSON Shape ```json { @@ -410,18 +410,18 @@ viewer 互換性のため、表示したい値は `attributes` に入れる。 } ``` -### 7.4 Media 要件 +### 7.4 Media Requirements -- thumbnail は PNG / JPEG のいずれかを標準とする -- 3D model は GLB を標準とする -- 将来的に glTF を扱う場合は MIME type を `model/gltf+json` とする -- metadata URI、image URI、animation_url URI は public HTTPS で参照可能であること -- devnet 検証時も mainnet を意識した URI 形式を用いること +- Use PNG or JPEG as the standard format for thumbnails. +- Use GLB as the standard format for 3D models. +- If glTF is added later, use the MIME type `model/gltf+json`. +- The metadata URI, image URI, and `animation_url` URI must all be publicly accessible over HTTPS. +- Even during devnet validation, use URI formats that are appropriate for eventual mainnet use. -### 7.5 Collection metadata +### 7.5 Collection Metadata -Collection 側の metadata も standard JSON 形式に合わせる。 -最低限以下を持つこと。 +Collection metadata should also follow the standard JSON shape. +At minimum, it should include: - `name` - `description` @@ -429,81 +429,81 @@ Collection 側の metadata も standard JSON 形式に合わせる。 - `external_url` - `category` -## 8. Mint フロー +## 8. Mint Flow -1. 作品生成ワーカーが一定期間ごとに作品を生成する -2. user が `reserve_token_id` transaction を送信する -3. on-chain program が `next_token_id` を読み、`tokenId` を採番し、`MintReservation` を作成する -4. backend が reservation 済み `tokenId` に対して thumbnail、3D model、metadata JSON を生成し、`{base_metadata_url}/{tokenId}.json` に配置する -5. user が `mint_doom_index_nft` transaction を送信する -6. program が reservation を検証し、name と URI を決定する -7. program が Collection 配下で Metaplex Core Asset を mint する -8. program が reservation を使用済みに更新する -9. user は Asset Address を受け取り、wallet / explorer / Metaplex Core viewer で確認できる +1. An artwork generation worker produces a new artwork at a fixed interval. +2. The user sends a `reserve_token_id` transaction. +3. The on-chain program reads `next_token_id`, assigns a `tokenId`, and creates `MintReservation`. +4. The backend generates the thumbnail, 3D model, and metadata JSON for the reserved `tokenId` and uploads them to `{base_metadata_url}/{tokenId}.json`. +5. The user sends a `mint_doom_index_nft` transaction. +6. The program validates the reservation and determines the final name and URI. +7. The program mints a Metaplex Core asset under the collection. +8. The program marks the reservation as used. +9. The user receives the asset address and can confirm it in a wallet, explorer, or Metaplex Core viewer. -## 9. Devnet 動作確認要件 +## 9. Devnet Validation Requirements -### 9.1 必須確認項目 +### 9.1 Required Checks -- upgradeable program を devnet に deploy できること -- `initialize_global_config` を devnet で実行できること -- `initialize_collection` を devnet で実行できること -- `reserve_token_id` を devnet で実行できること -- user wallet から `mint_doom_index_nft` を devnet で実行できること -- 発行後に Asset Address, owner, collection, name, uri を取得できること -- metadata JSON が取得できること -- thumbnail と 3D model の両方が取得できること -- 同じ reservation で 2 回 mint できないこと +- The upgradeable program can be deployed to devnet. +- `initialize_global_config` can be executed on devnet. +- `initialize_collection` can be executed on devnet. +- `reserve_token_id` can be executed on devnet. +- `mint_doom_index_nft` can be executed on devnet from a user wallet. +- After minting, the asset address, owner, collection, name, and URI can be retrieved. +- The metadata JSON can be fetched. +- Both the thumbnail and 3D model can be fetched. +- The same reservation cannot be used to mint twice. -### 9.2 推奨検証手段 +### 9.2 Recommended Validation Tools -- Solana Explorer の devnet +- Solana Explorer on devnet - Metaplex Core viewer -- script / frontend からの fetch 検証 +- Fetch-based validation from scripts or the frontend -### 9.3 実装に含めるべき補助物 +### 9.3 Supporting Deliverables to Include in Implementation -devnet での確認容易性のため、実装時には以下を同梱する。 +To make devnet validation easier, the implementation should include: -- devnet 用の初期化スクリプト -- devnet 用の mint スクリプトまたは UI -- Asset / Collection / metadata を検証する確認手順 +- An initialization script for devnet +- A devnet mint script or UI +- A verification procedure for asset, collection, and metadata checks -## 10. 非機能要件 +## 10. Non-Functional Requirements -### 10.1 拡張性 +### 10.1 Extensibility -- 生成期間は将来的に 10 分以外へ変更できること -- pricing, allowlist, per-wallet limit を将来的に追加できること -- metadata を per-token 固有化したまま運用拡張できること -- 将来的に asset immutability policy を強化できること -- Contract V1 から Contract V2 以降へ段階的に移行できること +- The generation interval can be changed from ten minutes in the future. +- Pricing, allowlists, and per-wallet limits can be added later. +- The metadata system can evolve while keeping per-token metadata distinct. +- Asset immutability policy can be tightened later. +- Migration from Contract V1 to Contract V2 and beyond remains possible. -### 10.2 保守性 +### 10.2 Maintainability -- on-chain state は最小限に保つこと -- metadata 生成ロジックは off-chain に寄せること -- program upgrade で機能追加しやすいこと -- 単一 `next_token_id` カウンタによる直列化を許容し、Contract V1 は高頻度大量 mint を最適化対象としない +- Keep on-chain state minimal. +- Keep metadata generation logic off-chain. +- Make future feature additions easy through program upgrades. +- Accept serialization through a single `next_token_id` counter; Contract V1 is not optimized for very high-frequency bulk minting. -### 10.3 互換性 +### 10.3 Compatibility -- Solana wallet と標準的な NFT viewer が理解できる metadata 形式を使うこと -- `image` を primary preview としつつ、`animation_url` で 3D model を提供すること +- Use a metadata format that Solana wallets and standard NFT viewers can understand. +- Provide `image` as the primary preview while using `animation_url` for the 3D model. ## 11. Open Items -以下は Contract V1 定義時点では未確定とする。 +The following items remain undecided at the time Contract V1 is defined: -1. 生成期間の境界を UTC 基準にするか JST 基準にするか -2. storage を IPFS / Arweave / Irys / R2+CDN のどれにするか -3. metadata URI の immutability をどこまで強制するか -4. Asset / Collection の update authority を将来 `None` に落とすか -5. marketplace 表示上で `tokenId` をどこまで明示したいか -6. 未使用 reservation を将来 reclaim / cleanup するか +1. Whether generation boundaries should follow UTC or JST +2. Which storage layer to use: IPFS, Arweave, Irys, or R2 + CDN +3. How strictly metadata URI immutability should be enforced +4. Whether the asset / collection update authority should eventually be set to `None` +5. How prominently `tokenId` should be shown in marketplace displays +6. Whether unused reservations should later be reclaimable or cleanable -## 12. 参考 +## 12. References -- Metaplex Core は off-chain metadata を参照する `name` + `uri` ベースで Asset を作成する -- Metaplex Core JSON Schema では `animation_url` に GLB を持てる -- `properties.files` には image と 3D model の両方を MIME type 付きで列挙できる +- Metaplex Core creates assets using a `name` + `uri` model that references off-chain metadata. +- The Metaplex Core JSON schema supports GLB via `animation_url`. +- `properties.files` can list both the image and the 3D model with MIME types. diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index 6debaab..3d5e09f 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -1,7 +1,7 @@ # PRODUCT -DOOM INDEX は、CoinGecko から取得したトレンドトークンの市場データを基に、10分ごとにユニークなジェネラティブアートを生成・提示するビジュアライゼーションプロダクトです。AI がトークンの市場心理を芸術として可視化し、生成画像は Cloudflare R2 に保存され、Web UI と OGP に活用されます。 +DOOM INDEX is a visualization product that generates and presents unique generative artwork every ten minutes based on trending token market data from CoinGecko. AI turns token market sentiment into artwork, and the generated images are stored in Cloudflare R2 for use in the web UI and OGP assets. -このプロジェクトは doomindex.fun (https://github.com/doom-protocol/doom-index)でAI生成された絵画をNFT化するためのsolanaのprogramです +This repository contains the Solana program that turns AI-generated DOOM INDEX artworks from [doomindex.fun](https://github.com/doom-protocol/doom-index) into NFTs. -DOOM INDEXで生成したAI生成絵画をipfsなどの分散がストレージにuploadしそのmetadataを使いsolanaの規格に準拠したNFTをmintできるようにします。 \ No newline at end of file +The goal is to upload DOOM INDEX artworks to decentralized storage such as IPFS, use that metadata, and mint NFTs that comply with Solana ecosystem standards. diff --git a/programs/doom-nft-program/src/lib.rs b/programs/doom-nft-program/src/lib.rs index cbf34bb..21732e8 100644 --- a/programs/doom-nft-program/src/lib.rs +++ b/programs/doom-nft-program/src/lib.rs @@ -15,7 +15,7 @@ pub use events::*; pub use instructions::*; pub use state::*; -declare_id!("AavECgzCbVhHeBGAfcUgT1tYEC4N4B96E8XtF9H1fMGt"); +declare_id!("u929SRVcCFcGM2iyYkMykDRq7xW4N9ozEMU3Vo1hgfP"); #[program] pub mod doom_nft_program { diff --git a/scripts/devnet/common.ts b/scripts/devnet/common.ts index f6a9bbc..7f525a6 100644 --- a/scripts/devnet/common.ts +++ b/scripts/devnet/common.ts @@ -17,7 +17,7 @@ const { export { Keypair, PublicKey, SystemProgram, TransactionInstruction }; export const DEFAULT_PROGRAM_ID = new PublicKey( - process.env.PROGRAM_ID ?? "AavECgzCbVhHeBGAfcUgT1tYEC4N4B96E8XtF9H1fMGt", + process.env.PROGRAM_ID ?? "u929SRVcCFcGM2iyYkMykDRq7xW4N9ozEMU3Vo1hgfP", ); export const MPL_CORE_PROGRAM_ID = new PublicKey("CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d"); diff --git a/tests/src/instructions/initialize_collection.rs b/tests/src/instructions/initialize_collection.rs new file mode 100644 index 0000000..7305548 --- /dev/null +++ b/tests/src/instructions/initialize_collection.rs @@ -0,0 +1,42 @@ +use solana_sdk::{signature::Keypair, signer::Signer}; + +use crate::test_context::{ + collection_authority_pda, fetch_collection, fetch_global_config, global_config_pda, + initialize_collection_ix, initialize_global_config_ix, process_instruction, start_context, +}; + +#[tokio::test] +async fn initialize_collection_persists_collection_and_authority() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + let config = fetch_global_config(&mut context).await; + let (collection_authority, _) = collection_authority_pda(global_config_pda().0); + assert_eq!(config.collection, collection.pubkey()); + assert_eq!(config.collection_update_authority, collection_authority); + + let collection = fetch_collection(&mut context, collection.pubkey()).await; + assert_eq!(collection.base.name, "DOOM INDEX"); + assert_eq!(collection.base.update_authority, collection_authority); +} diff --git a/tests/src/instructions/initialize_global_config.rs b/tests/src/instructions/initialize_global_config.rs new file mode 100644 index 0000000..fbaa5e8 --- /dev/null +++ b/tests/src/instructions/initialize_global_config.rs @@ -0,0 +1,30 @@ +use solana_sdk::{signature::Keypair, signature::Signature, signer::Signer}; + +use crate::test_context::{ + fetch_global_config, initialize_global_config_ix, process_instruction, start_context, +}; + +#[tokio::test] +async fn initialize_global_config_sets_defaults() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + let base_metadata_url = "https://example.com/doom-index"; + + let signature = process_instruction( + &mut context, + initialize_global_config_ix(payer, upgrade_authority.pubkey(), base_metadata_url), + &[], + ) + .await + .expect("initialize global config"); + + assert_ne!(signature, Signature::default()); + + let config = fetch_global_config(&mut context).await; + assert_eq!(config.admin, context.payer.pubkey()); + assert_eq!(config.upgrade_authority, upgrade_authority.pubkey()); + assert_eq!(config.next_token_id, 1); + assert!(!config.mint_paused); + assert_eq!(config.base_metadata_url, base_metadata_url); +} diff --git a/tests/src/instructions/mint_doom_index_nft.rs b/tests/src/instructions/mint_doom_index_nft.rs new file mode 100644 index 0000000..34b616b --- /dev/null +++ b/tests/src/instructions/mint_doom_index_nft.rs @@ -0,0 +1,93 @@ +use solana_program_test::BanksClientError; +use solana_sdk::{signature::Keypair, signer::Signer}; + +use crate::test_context::{ + fetch_asset, fetch_reservation, initialize_collection_ix, initialize_global_config_ix, + mint_doom_index_nft_ix, process_instruction, reserve_token_id_ix, start_context, +}; + +#[tokio::test] +async fn mint_with_valid_reservation_creates_core_asset() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect("reserve token id"); + + let asset = Keypair::new(); + process_instruction( + &mut context, + mint_doom_index_nft_ix(payer, 1, asset.pubkey(), collection.pubkey()), + &[&asset], + ) + .await + .expect("mint doom index nft"); + + let reservation = fetch_reservation(&mut context, 1).await; + assert!(reservation.minted); + + let asset = fetch_asset(&mut context, asset.pubkey()).await; + assert_eq!(asset.base.name, "DOOM INDEX #1"); + assert_eq!(asset.base.uri, "https://example.com/base/1.json"); + assert_eq!(asset.base.owner, context.payer.pubkey()); +} + +#[tokio::test] +async fn mint_without_reservation_fails() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + let asset = Keypair::new(); + let error = process_instruction( + &mut context, + mint_doom_index_nft_ix(payer, 1, asset.pubkey(), collection.pubkey()), + &[&asset], + ) + .await + .expect_err("mint should fail without reservation"); + + assert!(matches!(error, BanksClientError::TransactionError(_))); +} diff --git a/tests/src/instructions/mod.rs b/tests/src/instructions/mod.rs new file mode 100644 index 0000000..0102f97 --- /dev/null +++ b/tests/src/instructions/mod.rs @@ -0,0 +1,8 @@ +mod initialize_collection; +mod initialize_global_config; +mod mint_doom_index_nft; +mod reserve_token_id; +mod set_mint_paused; +mod set_upgrade_authority; +mod transfer_admin; +mod update_base_metadata_url; diff --git a/tests/src/instructions/reserve_token_id.rs b/tests/src/instructions/reserve_token_id.rs new file mode 100644 index 0000000..d6acf71 --- /dev/null +++ b/tests/src/instructions/reserve_token_id.rs @@ -0,0 +1,36 @@ +use solana_sdk::{signature::Keypair, signer::Signer}; + +use crate::test_context::{ + fetch_global_config, fetch_reservation, initialize_global_config_ix, process_instruction, + reserve_token_id_ix, start_context, +}; + +#[tokio::test] +async fn reserve_token_id_creates_reservation_and_increments_counter() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect("reserve token id"); + + let reservation = fetch_reservation(&mut context, 1).await; + assert_eq!(reservation.token_id, 1); + assert_eq!(reservation.reserver, context.payer.pubkey()); + assert!(!reservation.minted); + + let config = fetch_global_config(&mut context).await; + assert_eq!(config.next_token_id, 2); +} diff --git a/tests/src/instructions/set_mint_paused.rs b/tests/src/instructions/set_mint_paused.rs new file mode 100644 index 0000000..24e6263 --- /dev/null +++ b/tests/src/instructions/set_mint_paused.rs @@ -0,0 +1,37 @@ +use solana_program_test::BanksClientError; +use solana_sdk::{signature::Keypair, signer::Signer}; + +use crate::test_context::{ + initialize_global_config_ix, process_instruction, reserve_token_id_ix, set_mint_paused_ix, + start_context, +}; + +#[tokio::test] +async fn pause_blocks_reserve_and_mint() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + process_instruction(&mut context, set_mint_paused_ix(payer, true), &[]) + .await + .expect("pause mint"); + + let reserve_error = process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect_err("reserve should fail when paused"); + assert!(matches!( + reserve_error, + BanksClientError::TransactionError(_) + )); +} diff --git a/tests/src/instructions/set_upgrade_authority.rs b/tests/src/instructions/set_upgrade_authority.rs new file mode 100644 index 0000000..7d625d6 --- /dev/null +++ b/tests/src/instructions/set_upgrade_authority.rs @@ -0,0 +1,37 @@ +use solana_sdk::{signature::Keypair, signer::Signer}; + +use crate::test_context::{ + fetch_global_config, initialize_global_config_ix, process_instruction, + set_upgrade_authority_ix, start_context, +}; + +#[tokio::test] +async fn upgrade_authority_is_independent_from_admin() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let next_upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + set_upgrade_authority_ix(upgrade_authority.pubkey(), next_upgrade_authority.pubkey()), + &[&upgrade_authority], + ) + .await + .expect("set upgrade authority"); + + let config = fetch_global_config(&mut context).await; + assert_eq!(config.admin, payer); + assert_eq!(config.upgrade_authority, next_upgrade_authority.pubkey()); +} diff --git a/tests/src/instructions/transfer_admin.rs b/tests/src/instructions/transfer_admin.rs new file mode 100644 index 0000000..a7b46cc --- /dev/null +++ b/tests/src/instructions/transfer_admin.rs @@ -0,0 +1,38 @@ +use solana_sdk::{signature::Keypair, signer::Signer}; + +use crate::test_context::{ + fetch_global_config, initialize_global_config_ix, process_instruction, start_context, + transfer_admin_ix, +}; + +#[tokio::test] +async fn transfer_admin_updates_admin_authority() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let next_admin = Keypair::new(); + process_instruction( + &mut context, + transfer_admin_ix(payer, next_admin.pubkey()), + &[], + ) + .await + .expect("transfer admin"); + + assert_eq!( + fetch_global_config(&mut context).await.admin, + next_admin.pubkey() + ); +} diff --git a/tests/src/instructions/update_base_metadata_url.rs b/tests/src/instructions/update_base_metadata_url.rs new file mode 100644 index 0000000..678de55 --- /dev/null +++ b/tests/src/instructions/update_base_metadata_url.rs @@ -0,0 +1,37 @@ +use solana_sdk::{signature::Keypair, signer::Signer}; + +use crate::test_context::{ + fetch_global_config, initialize_global_config_ix, process_instruction, start_context, + update_base_metadata_url_ix, +}; + +#[tokio::test] +async fn update_base_metadata_url_updates_config() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + process_instruction( + &mut context, + update_base_metadata_url_ix(payer, "https://example.com/next"), + &[], + ) + .await + .expect("update base metadata url"); + + assert_eq!( + fetch_global_config(&mut context).await.base_metadata_url, + "https://example.com/next" + ); +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 61a55f8..5711ec3 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,527 +1,4 @@ #![cfg(test)] -use anchor_lang::{ - prelude::AccountDeserialize, - solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}, - system_program::ID as SYSTEM_PROGRAM_ID, - InstructionData, ToAccountMetas, -}; -use doom_nft_program::{ - accounts::{ - InitializeCollection, InitializeGlobalConfig, MintDoomIndexNft, ReserveTokenId, - SetMintPaused, SetUpgradeAuthority, TransferAdmin, UpdateBaseMetadataUrl, - }, - instruction, GlobalConfig, MintReservation, -}; -use mpl_core::{Asset, Collection}; -use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; -use solana_sdk::{ - instruction::Instruction, - pubkey::Pubkey, - signature::{Keypair, Signature}, - signer::Signer, - transaction::Transaction, -}; - -fn program_test() -> ProgramTest { - let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let bpf_out_dir = manifest_dir.join("..").join("target").join("test-sbf"); - std::env::set_var("BPF_OUT_DIR", &bpf_out_dir); - - let mut test = ProgramTest::default(); - test.prefer_bpf(false); - test.add_program( - "doom_nft_program", - doom_nft_program::id(), - processor!(doom_nft_program_test_processor), - ); - test.add_upgradeable_program_to_genesis("mpl_core_program", &mpl_core::ID); - test -} - -fn doom_nft_program_test_processor<'a, 'b, 'c, 'd>( - program_id: &'a Pubkey, - accounts: &'b [AccountInfo<'c>], - instruction_data: &'d [u8], -) -> ProgramResult { - // Anchor's generated entrypoint ties the slice lifetime to the inner AccountInfo lifetime. - // ProgramTest uses the looser builtin processor signature, so the wrapper narrows it for this call. - let accounts: &'c [AccountInfo<'c>] = unsafe { std::mem::transmute(accounts) }; - doom_nft_program::entry(program_id, accounts, instruction_data) -} - -fn global_config_pda() -> (Pubkey, u8) { - Pubkey::find_program_address(&[b"global_config"], &doom_nft_program::id()) -} - -fn collection_authority_pda(global_config: Pubkey) -> (Pubkey, u8) { - Pubkey::find_program_address( - &[b"collection_authority", global_config.as_ref()], - &doom_nft_program::id(), - ) -} - -fn reservation_pda(token_id: u64) -> (Pubkey, u8) { - Pubkey::find_program_address( - &[b"reservation", &token_id.to_le_bytes()], - &doom_nft_program::id(), - ) -} - -async fn process_instruction( - context: &mut ProgramTestContext, - instruction: Instruction, - extra_signers: &[&Keypair], -) -> Result { - let mut signers = vec![&context.payer]; - signers.extend_from_slice(extra_signers); - - let tx = Transaction::new_signed_with_payer( - &[instruction], - Some(&context.payer.pubkey()), - &signers, - context.last_blockhash, - ); - let signature = tx.signatures[0]; - context.banks_client.process_transaction(tx).await?; - Ok(signature) -} - -async fn fetch_global_config(context: &mut ProgramTestContext) -> GlobalConfig { - let (config, _) = global_config_pda(); - let account = context - .banks_client - .get_account(config) - .await - .expect("get config account") - .expect("config account exists"); - - let mut bytes = account.data.as_slice(); - GlobalConfig::try_deserialize(&mut bytes).expect("deserialize global config") -} - -async fn fetch_reservation(context: &mut ProgramTestContext, token_id: u64) -> MintReservation { - let (reservation, _) = reservation_pda(token_id); - let account = context - .banks_client - .get_account(reservation) - .await - .expect("get reservation account") - .expect("reservation account exists"); - - let mut bytes = account.data.as_slice(); - MintReservation::try_deserialize(&mut bytes).expect("deserialize reservation") -} - -fn initialize_global_config_ix( - admin: Pubkey, - upgrade_authority: Pubkey, - base_metadata_url: &str, -) -> Instruction { - let (global_config, _) = global_config_pda(); - - Instruction { - program_id: doom_nft_program::id(), - accounts: InitializeGlobalConfig { - global_config, - admin, - system_program: SYSTEM_PROGRAM_ID, - } - .to_account_metas(None), - data: instruction::InitializeGlobalConfig { - base_metadata_url: base_metadata_url.to_owned(), - upgrade_authority, - } - .data(), - } -} - -fn initialize_collection_ix(admin: Pubkey, collection: Pubkey) -> Instruction { - let (global_config, _) = global_config_pda(); - let (collection_update_authority, _) = collection_authority_pda(global_config); - - Instruction { - program_id: doom_nft_program::id(), - accounts: InitializeCollection { - global_config, - admin, - collection, - collection_update_authority, - mpl_core_program: mpl_core::ID, - system_program: SYSTEM_PROGRAM_ID, - } - .to_account_metas(None), - data: instruction::InitializeCollection {}.data(), - } -} - -fn reserve_token_id_ix(user: Pubkey, token_id: u64) -> Instruction { - let (global_config, _) = global_config_pda(); - let (reservation, _) = reservation_pda(token_id); - - Instruction { - program_id: doom_nft_program::id(), - accounts: ReserveTokenId { - global_config, - reservation, - user, - system_program: SYSTEM_PROGRAM_ID, - } - .to_account_metas(None), - data: instruction::ReserveTokenId {}.data(), - } -} - -fn mint_doom_index_nft_ix( - user: Pubkey, - token_id: u64, - asset: Pubkey, - collection: Pubkey, -) -> Instruction { - let (global_config, _) = global_config_pda(); - let (reservation, _) = reservation_pda(token_id); - let (collection_update_authority, _) = collection_authority_pda(global_config); - let config = global_config; - - Instruction { - program_id: doom_nft_program::id(), - accounts: MintDoomIndexNft { - global_config: config, - reservation, - user, - asset, - collection_update_authority, - collection, - mpl_core_program: mpl_core::ID, - system_program: SYSTEM_PROGRAM_ID, - } - .to_account_metas(None), - data: instruction::MintDoomIndexNft { token_id }.data(), - } -} - -#[tokio::test] -async fn initialize_global_config_sets_defaults() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - let base_metadata_url = "https://example.com/doom-index"; - - let signature = process_instruction( - &mut context, - initialize_global_config_ix(payer, upgrade_authority.pubkey(), base_metadata_url), - &[], - ) - .await - .expect("initialize global config"); - - assert_ne!(signature, Signature::default()); - - let config = fetch_global_config(&mut context).await; - assert_eq!(config.admin, context.payer.pubkey()); - assert_eq!(config.upgrade_authority, upgrade_authority.pubkey()); - assert_eq!(config.next_token_id, 1); - assert!(!config.mint_paused); - assert_eq!(config.base_metadata_url, base_metadata_url); -} - -#[tokio::test] -async fn initialize_collection_persists_collection_and_authority() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - process_instruction( - &mut context, - initialize_global_config_ix( - payer, - upgrade_authority.pubkey(), - "https://example.com/base", - ), - &[], - ) - .await - .expect("initialize global config"); - - let collection = Keypair::new(); - process_instruction( - &mut context, - initialize_collection_ix(payer, collection.pubkey()), - &[&collection], - ) - .await - .expect("initialize collection"); - - let config = fetch_global_config(&mut context).await; - let (collection_authority, _) = collection_authority_pda(global_config_pda().0); - assert_eq!(config.collection, collection.pubkey()); - assert_eq!(config.collection_update_authority, collection_authority); - - let collection_account = context - .banks_client - .get_account(collection.pubkey()) - .await - .expect("get collection account") - .expect("collection account exists"); - let collection = Collection::from_bytes(&collection_account.data).expect("decode collection"); - assert_eq!(collection.base.name, "DOOM INDEX"); - assert_eq!(collection.base.update_authority, collection_authority); -} - -#[tokio::test] -async fn reserve_token_id_creates_reservation_and_increments_counter() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - process_instruction( - &mut context, - initialize_global_config_ix( - payer, - upgrade_authority.pubkey(), - "https://example.com/base", - ), - &[], - ) - .await - .expect("initialize global config"); - - process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) - .await - .expect("reserve token id"); - - let reservation = fetch_reservation(&mut context, 1).await; - assert_eq!(reservation.token_id, 1); - assert_eq!(reservation.reserver, context.payer.pubkey()); - assert!(!reservation.minted); - - let config = fetch_global_config(&mut context).await; - assert_eq!(config.next_token_id, 2); -} - -#[tokio::test] -async fn mint_with_valid_reservation_creates_core_asset() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - process_instruction( - &mut context, - initialize_global_config_ix( - payer, - upgrade_authority.pubkey(), - "https://example.com/base", - ), - &[], - ) - .await - .expect("initialize global config"); - - let collection = Keypair::new(); - process_instruction( - &mut context, - initialize_collection_ix(payer, collection.pubkey()), - &[&collection], - ) - .await - .expect("initialize collection"); - - process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) - .await - .expect("reserve token id"); - - let asset = Keypair::new(); - process_instruction( - &mut context, - mint_doom_index_nft_ix(payer, 1, asset.pubkey(), collection.pubkey()), - &[&asset], - ) - .await - .expect("mint doom index nft"); - - let reservation = fetch_reservation(&mut context, 1).await; - assert!(reservation.minted); - - let asset_account = context - .banks_client - .get_account(asset.pubkey()) - .await - .expect("get asset account") - .expect("asset account exists"); - let asset = Asset::from_bytes(&asset_account.data).expect("decode asset"); - assert_eq!(asset.base.name, "DOOM INDEX #1"); - assert_eq!(asset.base.uri, "https://example.com/base/1.json"); - assert_eq!(asset.base.owner, context.payer.pubkey()); -} - -#[tokio::test] -async fn mint_without_reservation_fails() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - process_instruction( - &mut context, - initialize_global_config_ix( - payer, - upgrade_authority.pubkey(), - "https://example.com/base", - ), - &[], - ) - .await - .expect("initialize global config"); - - let collection = Keypair::new(); - process_instruction( - &mut context, - initialize_collection_ix(payer, collection.pubkey()), - &[&collection], - ) - .await - .expect("initialize collection"); - - let asset = Keypair::new(); - let error = process_instruction( - &mut context, - mint_doom_index_nft_ix(payer, 1, asset.pubkey(), collection.pubkey()), - &[&asset], - ) - .await - .expect_err("mint should fail without reservation"); - - assert!(matches!(error, BanksClientError::TransactionError(_))); -} - -#[tokio::test] -async fn pause_blocks_reserve_and_mint() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - process_instruction( - &mut context, - initialize_global_config_ix( - payer, - upgrade_authority.pubkey(), - "https://example.com/base", - ), - &[], - ) - .await - .expect("initialize global config"); - - let set_paused = Instruction { - program_id: doom_nft_program::id(), - accounts: SetMintPaused { - global_config: global_config_pda().0, - admin: context.payer.pubkey(), - } - .to_account_metas(None), - data: instruction::SetMintPaused { paused: true }.data(), - }; - process_instruction(&mut context, set_paused, &[]) - .await - .expect("pause mint"); - - let reserve_error = process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) - .await - .expect_err("reserve should fail when paused"); - assert!(matches!( - reserve_error, - BanksClientError::TransactionError(_) - )); -} - -#[tokio::test] -async fn admin_controls_update_base_url_and_transfer_admin() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - process_instruction( - &mut context, - initialize_global_config_ix( - payer, - upgrade_authority.pubkey(), - "https://example.com/base", - ), - &[], - ) - .await - .expect("initialize global config"); - - let update_url = Instruction { - program_id: doom_nft_program::id(), - accounts: UpdateBaseMetadataUrl { - global_config: global_config_pda().0, - admin: context.payer.pubkey(), - } - .to_account_metas(None), - data: instruction::UpdateBaseMetadataUrl { - base_metadata_url: "https://example.com/next".to_owned(), - } - .data(), - }; - process_instruction(&mut context, update_url, &[]) - .await - .expect("update base metadata url"); - assert_eq!( - fetch_global_config(&mut context).await.base_metadata_url, - "https://example.com/next" - ); - - let next_admin = Keypair::new(); - let transfer_admin = Instruction { - program_id: doom_nft_program::id(), - accounts: TransferAdmin { - global_config: global_config_pda().0, - admin: context.payer.pubkey(), - } - .to_account_metas(None), - data: instruction::TransferAdmin { - new_admin: next_admin.pubkey(), - } - .data(), - }; - process_instruction(&mut context, transfer_admin, &[]) - .await - .expect("transfer admin"); - assert_eq!( - fetch_global_config(&mut context).await.admin, - next_admin.pubkey() - ); -} - -#[tokio::test] -async fn upgrade_authority_is_independent_from_admin() { - let mut context = program_test().start_with_context().await; - let payer = context.payer.pubkey(); - let upgrade_authority = Keypair::new(); - process_instruction( - &mut context, - initialize_global_config_ix( - payer, - upgrade_authority.pubkey(), - "https://example.com/base", - ), - &[], - ) - .await - .expect("initialize global config"); - - let next_upgrade_authority = Keypair::new(); - let set_upgrade_authority = Instruction { - program_id: doom_nft_program::id(), - accounts: SetUpgradeAuthority { - global_config: global_config_pda().0, - upgrade_authority: upgrade_authority.pubkey(), - } - .to_account_metas(None), - data: instruction::SetUpgradeAuthority { - new_upgrade_authority: next_upgrade_authority.pubkey(), - } - .data(), - }; - process_instruction(&mut context, set_upgrade_authority, &[&upgrade_authority]) - .await - .expect("set upgrade authority"); - - let config = fetch_global_config(&mut context).await; - assert_eq!(config.admin, payer); - assert_eq!(config.upgrade_authority, next_upgrade_authority.pubkey()); -} +mod instructions; +mod test_context; diff --git a/tests/src/test_context.rs b/tests/src/test_context.rs new file mode 100644 index 0000000..b271850 --- /dev/null +++ b/tests/src/test_context.rs @@ -0,0 +1,281 @@ +use anchor_lang::{ + prelude::AccountDeserialize, + solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}, + system_program::ID as SYSTEM_PROGRAM_ID, + InstructionData, ToAccountMetas, +}; +use doom_nft_program::{ + accounts::{ + InitializeCollection, InitializeGlobalConfig, MintDoomIndexNft, ReserveTokenId, + SetMintPaused, SetUpgradeAuthority, TransferAdmin, UpdateBaseMetadataUrl, + }, + instruction, GlobalConfig, MintReservation, +}; +use mpl_core::{Asset, Collection}; +use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; +use solana_sdk::{ + instruction::Instruction, + pubkey::Pubkey, + signature::{Keypair, Signature}, + signer::Signer, + transaction::Transaction, +}; + +pub fn program_test() -> ProgramTest { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let bpf_out_dir = manifest_dir.join("..").join("target").join("test-sbf"); + std::env::set_var("BPF_OUT_DIR", &bpf_out_dir); + + let mut test = ProgramTest::default(); + test.prefer_bpf(false); + test.add_program( + "doom_nft_program", + doom_nft_program::id(), + processor!(doom_nft_program_test_processor), + ); + test.add_upgradeable_program_to_genesis("mpl_core_program", &mpl_core::ID); + test +} + +pub async fn start_context() -> ProgramTestContext { + program_test().start_with_context().await +} + +fn doom_nft_program_test_processor<'a, 'b, 'c, 'd>( + program_id: &'a Pubkey, + accounts: &'b [AccountInfo<'c>], + instruction_data: &'d [u8], +) -> ProgramResult { + // Anchor's generated entrypoint ties the slice lifetime to the inner AccountInfo lifetime. + // ProgramTest uses the looser builtin processor signature, so the wrapper narrows it for this call. + let accounts: &'c [AccountInfo<'c>] = unsafe { std::mem::transmute(accounts) }; + doom_nft_program::entry(program_id, accounts, instruction_data) +} + +pub fn global_config_pda() -> (Pubkey, u8) { + Pubkey::find_program_address(&[b"global_config"], &doom_nft_program::id()) +} + +pub fn collection_authority_pda(global_config: Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[b"collection_authority", global_config.as_ref()], + &doom_nft_program::id(), + ) +} + +pub fn reservation_pda(token_id: u64) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[b"reservation", &token_id.to_le_bytes()], + &doom_nft_program::id(), + ) +} + +pub async fn process_instruction( + context: &mut ProgramTestContext, + instruction: Instruction, + extra_signers: &[&Keypair], +) -> Result { + let mut signers = vec![&context.payer]; + signers.extend_from_slice(extra_signers); + + let tx = Transaction::new_signed_with_payer( + &[instruction], + Some(&context.payer.pubkey()), + &signers, + context.last_blockhash, + ); + let signature = tx.signatures[0]; + context.banks_client.process_transaction(tx).await?; + Ok(signature) +} + +pub async fn fetch_global_config(context: &mut ProgramTestContext) -> GlobalConfig { + let (config, _) = global_config_pda(); + let account = context + .banks_client + .get_account(config) + .await + .expect("get config account") + .expect("config account exists"); + + let mut bytes = account.data.as_slice(); + GlobalConfig::try_deserialize(&mut bytes).expect("deserialize global config") +} + +pub async fn fetch_reservation(context: &mut ProgramTestContext, token_id: u64) -> MintReservation { + let (reservation, _) = reservation_pda(token_id); + let account = context + .banks_client + .get_account(reservation) + .await + .expect("get reservation account") + .expect("reservation account exists"); + + let mut bytes = account.data.as_slice(); + MintReservation::try_deserialize(&mut bytes).expect("deserialize reservation") +} + +pub async fn fetch_collection(context: &mut ProgramTestContext, collection: Pubkey) -> Collection { + let account = context + .banks_client + .get_account(collection) + .await + .expect("get collection account") + .expect("collection account exists"); + + *Collection::from_bytes(&account.data).expect("decode collection") +} + +pub async fn fetch_asset(context: &mut ProgramTestContext, asset: Pubkey) -> Asset { + let account = context + .banks_client + .get_account(asset) + .await + .expect("get asset account") + .expect("asset account exists"); + + *Asset::from_bytes(&account.data).expect("decode asset") +} + +pub fn initialize_global_config_ix( + admin: Pubkey, + upgrade_authority: Pubkey, + base_metadata_url: &str, +) -> Instruction { + let (global_config, _) = global_config_pda(); + + Instruction { + program_id: doom_nft_program::id(), + accounts: InitializeGlobalConfig { + global_config, + admin, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::InitializeGlobalConfig { + base_metadata_url: base_metadata_url.to_owned(), + upgrade_authority, + } + .data(), + } +} + +pub fn initialize_collection_ix(admin: Pubkey, collection: Pubkey) -> Instruction { + let (global_config, _) = global_config_pda(); + let (collection_update_authority, _) = collection_authority_pda(global_config); + + Instruction { + program_id: doom_nft_program::id(), + accounts: InitializeCollection { + global_config, + admin, + collection, + collection_update_authority, + mpl_core_program: mpl_core::ID, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::InitializeCollection {}.data(), + } +} + +pub fn reserve_token_id_ix(user: Pubkey, token_id: u64) -> Instruction { + let (global_config, _) = global_config_pda(); + let (reservation, _) = reservation_pda(token_id); + + Instruction { + program_id: doom_nft_program::id(), + accounts: ReserveTokenId { + global_config, + reservation, + user, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::ReserveTokenId {}.data(), + } +} + +pub fn mint_doom_index_nft_ix( + user: Pubkey, + token_id: u64, + asset: Pubkey, + collection: Pubkey, +) -> Instruction { + let (global_config, _) = global_config_pda(); + let (reservation, _) = reservation_pda(token_id); + let (collection_update_authority, _) = collection_authority_pda(global_config); + + Instruction { + program_id: doom_nft_program::id(), + accounts: MintDoomIndexNft { + global_config, + reservation, + user, + asset, + collection_update_authority, + collection, + mpl_core_program: mpl_core::ID, + system_program: SYSTEM_PROGRAM_ID, + } + .to_account_metas(None), + data: instruction::MintDoomIndexNft { token_id }.data(), + } +} + +pub fn set_mint_paused_ix(admin: Pubkey, paused: bool) -> Instruction { + Instruction { + program_id: doom_nft_program::id(), + accounts: SetMintPaused { + global_config: global_config_pda().0, + admin, + } + .to_account_metas(None), + data: instruction::SetMintPaused { paused }.data(), + } +} + +pub fn update_base_metadata_url_ix(admin: Pubkey, base_metadata_url: &str) -> Instruction { + Instruction { + program_id: doom_nft_program::id(), + accounts: UpdateBaseMetadataUrl { + global_config: global_config_pda().0, + admin, + } + .to_account_metas(None), + data: instruction::UpdateBaseMetadataUrl { + base_metadata_url: base_metadata_url.to_owned(), + } + .data(), + } +} + +pub fn transfer_admin_ix(admin: Pubkey, new_admin: Pubkey) -> Instruction { + Instruction { + program_id: doom_nft_program::id(), + accounts: TransferAdmin { + global_config: global_config_pda().0, + admin, + } + .to_account_metas(None), + data: instruction::TransferAdmin { new_admin }.data(), + } +} + +pub fn set_upgrade_authority_ix( + upgrade_authority: Pubkey, + new_upgrade_authority: Pubkey, +) -> Instruction { + Instruction { + program_id: doom_nft_program::id(), + accounts: SetUpgradeAuthority { + global_config: global_config_pda().0, + upgrade_authority, + } + .to_account_metas(None), + data: instruction::SetUpgradeAuthority { + new_upgrade_authority, + } + .data(), + } +} From c6e5f2e60846bcc1e51cf34ceb45dd5acba417fc Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 09:03:13 +0900 Subject: [PATCH 7/8] chore: add devnet common tests, test fixtures, and config updates - Add scripts/devnet/common.test.ts - Add tests/fixtures (mpl_core_program.so, README) - Update Anchor.toml, README, build-test-sbf.sh, common.ts - Update .agents/memory/todo.md Made-with: Cursor --- .agents/memory/todo.md | 47 +++++++++++++++ Anchor.toml | 2 +- README.md | 4 +- scripts/build-test-sbf.sh | 18 +++++- scripts/devnet/common.test.ts | 67 ++++++++++++++++++++++ scripts/devnet/common.ts | 54 +++++++++++++++-- tests/fixtures/README.md | 8 +++ tests/fixtures/mpl_core_program.so | Bin 0 -> 716528 bytes tests/fixtures/mpl_core_program.so.sha256 | 1 + 9 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 scripts/devnet/common.test.ts create mode 100644 tests/fixtures/README.md create mode 100755 tests/fixtures/mpl_core_program.so create mode 100644 tests/fixtures/mpl_core_program.so.sha256 diff --git a/.agents/memory/todo.md b/.agents/memory/todo.md index ed2c71b..aa1936f 100644 --- a/.agents/memory/todo.md +++ b/.agents/memory/todo.md @@ -173,3 +173,50 @@ - Generated a dedicated devnet payer at `target/devnet/deployer.json` (`HmFV8YND3fAqhu1eP2Tii45sCUQu2FMUcaZdgmf1hmd9`) to avoid mutating the user's default Solana wallet. - Deployment is currently blocked because every attempted faucet path left the payer at `0 SOL`: `solana airdrop` against `api.devnet.solana.com` failed with rate limiting for 5 / 2 / 1 / 0.5 / 0.1 SOL, and alternative public RPC paths were either paid-tier only, API-key-gated, or rate-limited as well. - Until a devnet payer is funded, the remaining `anchor deploy` and on-chain smoke steps (`init`, `reserve`, `mint`, plus any admin instruction checks) cannot be executed. + +# Task Plan: Local Fee Measurement (2026-03-12) + +- [ ] Check the standard fee-reporting capabilities available in Anchor CLI and Solana CLI/RPC. +- [ ] Start a fresh local validator and fund the local deploy/test wallet. +- [ ] Deploy the program to localnet and record the deployment spend from balance deltas and transaction metadata. +- [ ] Execute local `initialize`, `reserve`, and `mint` flows with reachable metadata fixtures. +- [ ] Measure each instruction's network fee and rent-bearing account creations from balances and transaction details. +- [ ] Record the findings and the recommended standard tooling in the review section. + +# Review: Local Fee Measurement (2026-03-12) + +- Anchor CLI 0.32.1 の `deploy` / `test` help と公式 CLI docs を確認したが、Anchor 自体に標準の fee estimator / fee reporter はなかった。実測と見積りは Solana 側の `solana rent`, `getFeeForMessage`, `simulateTransaction`, `getTransaction.meta`, `solana confirm -v` を使うのが標準経路。 +- `solana-test-validator` を `target/localnet-ledger` で起動し、`mpl-core` を `--upgradeable-program CoREEN... target/test-sbf/mpl_core_program.so none` で genesis にロードした。 +- `python3 -m http.server 8123 --directory target/local-fixtures` で local metadata fixture を配信し、`BASE_METADATA_URL=http://127.0.0.1:8123` のまま `init -> reserve -> mint` を localnet で実行できた。 +- `anchor deploy --provider.cluster http://127.0.0.1:8899 --provider.wallet target/devnet/deployer.json` は成功し、program id `u929...` / data length `331336 bytes` / signature `4K2N3oif...` を確認した。 +- local deploy の総支出は `2.31019408 SOL` (`2310194080` lamports) で、内訳は program-data rent `2.30730264 SOL` + executable program account rent `0.00114144 SOL` + deploy transaction fees `0.00175 SOL` だった。 +- `initialize_global_config` (`4uMhwnyi...`) は `3721640` lamports (`0.00372164 SOL`) で、内訳は fee `5000` + `GlobalConfig` rent `3716640`。`computeUnitsConsumed = 10633`。 +- `initialize_collection` (`qVj2Rgi...`) は `1569040` lamports (`0.00156904 SOL`) で、内訳は fee `10000` + collection asset rent `1559040`。`computeUnitsConsumed = 15302`。 +- `reserve_token_id` (`5rJadHXM...`) は `1243880` lamports (`0.00124388 SOL`) で、内訳は fee `5000` + reservation rent `1238880`。`computeUnitsConsumed = 10190`。 +- `mint_doom_index_nft` (`2cevXVJF...`) は `3208240` lamports (`0.00320824 SOL`) で、内訳は fee `10000` + Core asset rent `3198240`。`computeUnitsConsumed = 25713`。 +- localnet で作られた account sizes は `GlobalConfig = 406 bytes`, collection asset `96 bytes`, reservation `50 bytes`, minted Core asset `116 bytes`。対応する rent は `solana rent` の実測と一致した。 + +# Task Plan: Code Review Against main (2026-03-12) + +- [ ] Inspect the diff against merge base `c1efa75aca4c3898e5ab0f6deeed665d7f9989df`. +- [ ] Validate changed code paths for behavioral regressions and test coverage gaps. +- [ ] Summarize prioritized, actionable findings with an overall correctness verdict. + +# Task Plan: Review Fixes For Test Fixture + Wallet Defaults (2026-03-12) + +- [x] Inspect the reported regressions in `scripts/build-test-sbf.sh`, `scripts/devnet/common.ts`, and `Anchor.toml`. +- [x] Replace the live Core program dump path with a version-pinned test fixture source. +- [x] Add or update coverage for devnet wallet resolution so the default path is exercised. +- [x] Align Anchor and devnet helper wallet defaults without depending on an untracked `target/` file. +- [x] Update docs that describe the contract-test fixture workflow if they no longer match behavior. +- [x] Run the relevant verification commands and record the exact outcomes. + +# Review: Review Fixes For Test Fixture + Wallet Defaults (2026-03-12) + +- Reverted `Anchor.toml` to the stable default wallet `~/.config/solana/id.json` so direct `anchor` commands no longer depend on the untracked `target/devnet/deployer.json`. +- Updated `scripts/devnet/common.ts` so wallet resolution now uses `ANCHOR_WALLET` first, then `provider.wallet` from `Anchor.toml`, then the stable default. This keeps the devnet helper scripts aligned with Anchor's configured wallet. +- Added `scripts/devnet/common.test.ts` to cover the wallet-resolution precedence and fallback behavior. +- Replaced the live `solana program dump` path in `scripts/build-test-sbf.sh` with a copy of a checked-in fixture. +- Added `tests/fixtures/mpl_core_program.so` and `tests/fixtures/mpl_core_program.so.sha256`, pinned to the official Metaplex Core release asset `release/core@0.9.10` with SHA-256 `604d401ea0c6c7b530c42274deeb903c953c1ef930bc5468497f60f3128493cc`. +- Updated `README.md` and `tests/fixtures/README.md` so the contract-test fixture workflow and provenance match the implementation. +- Verified with `bun test scripts/devnet/common.test.ts`, `bun x tsc --noEmit`, `bun run format:check`, `bun run lint`, and `bun run test:contract`. diff --git a/Anchor.toml b/Anchor.toml index 669ed69..0a263d0 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -17,7 +17,7 @@ url = "https://api.apr.dev" [provider] cluster = "localnet" -wallet = "target/devnet/deployer.json" +wallet = "~/.config/solana/id.json" [scripts] test = "cargo test" diff --git a/README.md b/README.md index 6dfd69c..5296b6c 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ tests/src/ └── test_context.rs # Shared test fixtures and helpers scripts/ -├── build-test-sbf.sh # Fetches the Core program artifact for tests +├── build-test-sbf.sh # Copies the pinned Core test fixture into target/test-sbf ├── test-contract-v1.sh # Runs the contract suite └── devnet/ # Devnet helper scripts ``` @@ -123,5 +123,5 @@ This project is released under the MIT License. ## Notes - The program is still under active development. -- The contract test flow avoids a local SBF build of this program and runs it as a host builtin, while loading the official `mpl_core` program binary. +- The contract test flow avoids a local SBF build of this program and runs it as a host builtin, while loading the pinned official Metaplex Core `release/core@0.9.10` fixture for the Core CPI path. - Devnet deployment may still depend on Solana faucet availability. diff --git a/scripts/build-test-sbf.sh b/scripts/build-test-sbf.sh index 38a8f86..3f0743b 100755 --- a/scripts/build-test-sbf.sh +++ b/scripts/build-test-sbf.sh @@ -3,10 +3,22 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" OUT_DIR="$ROOT_DIR/target/test-sbf" -MPL_CORE_PROGRAM_ID="CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" +FIXTURE_DIR="$ROOT_DIR/tests/fixtures" +PINNED_MPL_CORE_SO="$FIXTURE_DIR/mpl_core_program.so" +PINNED_MPL_CORE_SHA="$FIXTURE_DIR/mpl_core_program.so.sha256" mkdir -p "$OUT_DIR" -if [[ ! -f "$OUT_DIR/mpl_core_program.so" ]]; then - solana program dump -u m "$MPL_CORE_PROGRAM_ID" "$OUT_DIR/mpl_core_program.so" +if [[ ! -f "$PINNED_MPL_CORE_SO" ]]; then + echo "Missing pinned Metaplex Core fixture at $PINNED_MPL_CORE_SO" >&2 + exit 1 fi + +if [[ -f "$PINNED_MPL_CORE_SHA" ]]; then + ( + cd "$FIXTURE_DIR" + shasum -a 256 -c "$(basename "$PINNED_MPL_CORE_SHA")" + ) +fi + +cp "$PINNED_MPL_CORE_SO" "$OUT_DIR/mpl_core_program.so" diff --git a/scripts/devnet/common.test.ts b/scripts/devnet/common.test.ts new file mode 100644 index 0000000..49ed6b2 --- /dev/null +++ b/scripts/devnet/common.test.ts @@ -0,0 +1,67 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, test } from "node:test"; + +import { resolveWalletPath } from "./common"; + +const originalAnchorWallet = process.env.ANCHOR_WALLET; +const originalHome = process.env.HOME; + +afterEach(() => { + if (originalAnchorWallet === undefined) { + delete process.env.ANCHOR_WALLET; + } else { + process.env.ANCHOR_WALLET = originalAnchorWallet; + } + + if (originalHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = originalHome; + } +}); + +describe("resolveWalletPath", () => { + test("prefers ANCHOR_WALLET when it is set", () => { + process.env.ANCHOR_WALLET = "/tmp/custom-wallet.json"; + + assert.equal(resolveWalletPath("/tmp/unused/Anchor.toml"), "/tmp/custom-wallet.json"); + }); + + test("uses provider.wallet from Anchor.toml when ANCHOR_WALLET is unset", () => { + delete process.env.ANCHOR_WALLET; + + const tempDir = mkdtempSync(join(tmpdir(), "doom-anchor-wallet-")); + const anchorTomlPath = join(tempDir, "Anchor.toml"); + + try { + writeFileSync( + anchorTomlPath, + ["[provider]", 'cluster = "devnet"', 'wallet = "target/devnet/deployer.json"', ""].join("\n"), + "utf8", + ); + + assert.equal(resolveWalletPath(anchorTomlPath), join(tempDir, "target/devnet/deployer.json")); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); + + test("falls back to the stable default wallet when provider.wallet is missing", () => { + delete process.env.ANCHOR_WALLET; + process.env.HOME = "/tmp/home-dir"; + + const tempDir = mkdtempSync(join(tmpdir(), "doom-anchor-wallet-")); + const anchorTomlPath = join(tempDir, "Anchor.toml"); + + try { + writeFileSync(anchorTomlPath, ["[provider]", 'cluster = "localnet"', ""].join("\n"), "utf8"); + + assert.equal(resolveWalletPath(anchorTomlPath), "/tmp/home-dir/.config/solana/id.json"); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); +}); diff --git a/scripts/devnet/common.ts b/scripts/devnet/common.ts index 7f525a6..a9023f3 100644 --- a/scripts/devnet/common.ts +++ b/scripts/devnet/common.ts @@ -2,7 +2,7 @@ import { BN, web3 } from "@coral-xyz/anchor"; import * as borsh from "@coral-xyz/borsh"; import { createHash } from "node:crypto"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; +import { dirname, isAbsolute, resolve } from "node:path"; const { Connection, @@ -20,6 +20,9 @@ export const DEFAULT_PROGRAM_ID = new PublicKey( process.env.PROGRAM_ID ?? "u929SRVcCFcGM2iyYkMykDRq7xW4N9ozEMU3Vo1hgfP", ); export const MPL_CORE_PROGRAM_ID = new PublicKey("CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d"); +const DEFAULT_PROVIDER_WALLET = "~/.config/solana/id.json"; +const SCRIPT_DIR = __dirname; +const DEFAULT_ANCHOR_TOML_PATH = resolve(SCRIPT_DIR, "..", "..", "Anchor.toml"); const GLOBAL_CONFIG_SEED = Buffer.from("global_config"); const RESERVATION_SEED = Buffer.from("reservation"); @@ -53,11 +56,12 @@ export function getConnection(): web3.Connection { } export function loadWallet(): web3.Keypair { - const walletPath = resolve(process.env.ANCHOR_WALLET ?? "~/.config/solana/id.json").replace( - /^~(?=$|\/|\\)/, - process.env.HOME ?? "", - ); - return loadKeypair(walletPath); + return loadKeypair(resolveWalletPath()); +} + +export function resolveWalletPath(anchorTomlPath: string = DEFAULT_ANCHOR_TOML_PATH): string { + const walletPath = process.env.ANCHOR_WALLET ?? readProviderWalletPath(anchorTomlPath) ?? DEFAULT_PROVIDER_WALLET; + return resolveConfiguredPath(walletPath, anchorTomlPath); } export function loadKeypair(filePath: string): web3.Keypair { @@ -271,3 +275,41 @@ function instructionDiscriminator(name: string): Buffer { function accountDiscriminator(name: string): Buffer { return createHash("sha256").update(`account:${name}`).digest().subarray(0, 8); } + +function readProviderWalletPath(anchorTomlPath: string): string | null { + if (!existsSync(anchorTomlPath)) { + return null; + } + + const lines = readFileSync(anchorTomlPath, "utf8").split(/\r?\n/); + let inProviderSection = false; + + for (const line of lines) { + const trimmedLine = line.trim(); + + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + inProviderSection = trimmedLine === "[provider]"; + continue; + } + + if (!inProviderSection || trimmedLine.startsWith("#")) { + continue; + } + + const walletMatch = trimmedLine.match(/^wallet\s*=\s*"([^"]+)"$/); + if (walletMatch) { + return walletMatch[1]; + } + } + + return null; +} + +function resolveConfiguredPath(filePath: string, anchorTomlPath: string): string { + const expandedPath = filePath.replace(/^~(?=$|\/|\\)/, process.env.HOME ?? ""); + if (isAbsolute(expandedPath)) { + return expandedPath; + } + + return resolve(dirname(anchorTomlPath), expandedPath); +} diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..07fd854 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,8 @@ +# Test Fixtures + +`mpl_core_program.so` is the pinned Metaplex Core fixture used by the contract tests. + +- Source release: `release/core@0.9.10` +- Source asset: `https://github.com/metaplex-foundation/mpl-core/releases/download/release/core%400.9.10/mpl_core_program.so` +- Expected checksum: see [`mpl_core_program.so.sha256`](./mpl_core_program.so.sha256) +- Purpose: keep `ProgramTest` deterministic without dumping the live upgradeable Core program from the network diff --git a/tests/fixtures/mpl_core_program.so b/tests/fixtures/mpl_core_program.so new file mode 100755 index 0000000000000000000000000000000000000000..2bcf263206aac1a8886532a69ad58e834654dfd3 GIT binary patch literal 716528 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%0A8i5k5K7eHz!N6}$8jfs*TSK-}5y({Q5UNG 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%0D;*fBXH4e8h&9$Cr~@ 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 UU%CH(dDDKL;KUmbKIi%W521m|I{*Lx literal 0 HcmV?d00001 diff --git a/tests/fixtures/mpl_core_program.so.sha256 b/tests/fixtures/mpl_core_program.so.sha256 new file mode 100644 index 0000000..64be447 --- /dev/null +++ b/tests/fixtures/mpl_core_program.so.sha256 @@ -0,0 +1 @@ +604d401ea0c6c7b530c42274deeb903c953c1ef930bc5468497f60f3128493cc mpl_core_program.so From a0663ad7a6e975bfb0d60387e703129b362bae3c Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Thu, 12 Mar 2026 09:21:31 +0900 Subject: [PATCH 8/8] fix: mint/transfer_admin logic, devnet scripts, tests and config - Program: error codes, mint_doom_index_nft, transfer_admin, utils, lib - Devnet: common/init/mint scripts, common.test.ts, mint.test.ts - Tests: mint_doom_index_nft, set_mint_paused, transfer_admin, test_context - Config: Cargo.lock, package.json, Cargo.toml, tsconfig, test-contract-v1.sh - Update .agents/memory/todo.md Made-with: Cursor --- .agents/memory/todo.md | 21 ++++ Cargo.lock | 1 + package.json | 3 +- programs/doom-nft-program/Cargo.toml | 1 + programs/doom-nft-program/src/error.rs | 2 + .../src/instructions/mint_doom_index_nft.rs | 2 +- .../src/instructions/transfer_admin.rs | 9 +- programs/doom-nft-program/src/lib.rs | 100 ++++++++-------- programs/doom-nft-program/src/utils.rs | 43 ++++++- scripts/devnet/common.test.ts | 66 ++++++++++- scripts/devnet/common.ts | 108 ++++++++++++++---- scripts/devnet/init.ts | 17 +++ scripts/devnet/mint.test.ts | 61 ++++++++++ scripts/devnet/mint.ts | 76 +++++++++--- scripts/test-contract-v1.sh | 2 +- tests/src/instructions/mint_doom_index_nft.rs | 102 +++++++++++++++++ tests/src/instructions/set_mint_paused.rs | 29 ++++- tests/src/instructions/transfer_admin.rs | 4 +- tests/src/test_context.rs | 18 ++- tsconfig.json | 4 +- 20 files changed, 560 insertions(+), 109 deletions(-) create mode 100644 scripts/devnet/mint.test.ts diff --git a/.agents/memory/todo.md b/.agents/memory/todo.md index aa1936f..a35e185 100644 --- a/.agents/memory/todo.md +++ b/.agents/memory/todo.md @@ -220,3 +220,24 @@ - Added `tests/fixtures/mpl_core_program.so` and `tests/fixtures/mpl_core_program.so.sha256`, pinned to the official Metaplex Core release asset `release/core@0.9.10` with SHA-256 `604d401ea0c6c7b530c42274deeb903c953c1ef930bc5468497f60f3128493cc`. - Updated `README.md` and `tests/fixtures/README.md` so the contract-test fixture workflow and provenance match the implementation. - Verified with `bun test scripts/devnet/common.test.ts`, `bun x tsc --noEmit`, `bun run format:check`, `bun run lint`, and `bun run test:contract`. + +# Task Plan: Verify And Fix Review Findings (2026-03-12) + +- [x] Verify each reported finding against the current codebase and skip any already-fixed items. +- [x] Add or extend tests first for the confirmed behavior gaps in Rust and TypeScript helpers. +- [x] Implement the minimum code changes needed in the program, scripts, and package tooling. +- [x] Run focused and full verification commands. +- [x] Record which findings were fixed versus already resolved in the review section. + +# Review: Verify And Fix Review Findings (2026-03-12) + +- Added an explicit `typecheck` script and wired it into `check`, with `tsconfig.json` scoped to `scripts/**/*.ts` so the devnet scripts are covered by `tsc --noEmit`. +- Tightened program-side authority handling by requiring `transfer_admin` to be co-signed by the current `admin`, the current `upgrade_authority`, and the `new_admin` signer, which gives `GlobalConfig.upgrade_authority` a real privilege gate instead of leaving it unused. +- Removed the crate-wide deprecated lint suppression and replaced it with a wrapper module that localizes the Anchor 0.31 `#[program]` macro deprecation allowance while keeping `clippy -D warnings` green. +- Hardened `validate_base_metadata_url` to reject whitespace, non-HTTPS URLs, trailing slashes, and values over 256 bytes; aligned `scripts/devnet/init.ts` with the same constraints. +- Replaced the hard-coded asset name literal with `COLLECTION_NAME` and introduced `CollectionMismatch` so collection-address failures are reported accurately. +- Made `loadOrCreateKeypair` atomic with exclusive create mode `0o600`, added reserve retry logic on reservation contention, and fixed `scripts/test-contract-v1.sh` to run from `ROOT_DIR`. +- Updated `scripts/devnet/mint.ts` to decode the URI from the minted on-chain Metaplex Core asset account and to fall back from `HEAD` to `GET` when validating `image` and `animation_url`. +- Extended Rust and TypeScript coverage for reservation misuse, paused minting, transfer-admin handshakes, keypair permissions, reserve retries, on-chain URI decoding, and HEAD-to-GET asset validation. +- Verified and intentionally skipped two stale comments because the current code already addressed them: `scripts/build-test-sbf.sh` no longer reuses a dumped `mpl_core_program.so`, and the redundant `let config = global_config;` alias in `tests/src/lib.rs` was already gone after the earlier test-module split. +- Verified with `bun test scripts/devnet/common.test.ts scripts/devnet/mint.test.ts`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `bun run check`, and the `bun run check` path’s `./scripts/test-contract-v1.sh` contract suite. diff --git a/Cargo.lock b/Cargo.lock index 4640585..00bf459 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1469,6 +1469,7 @@ dependencies = [ "anchor-lang", "mpl-core", "solana-program", + "url", ] [[package]] diff --git a/package.json b/package.json index 8e3999a..94d0f49 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,9 @@ "devnet:init": "bun run ./scripts/devnet/init.ts", "devnet:reserve": "bun run ./scripts/devnet/reserve.ts", "devnet:mint": "bun run ./scripts/devnet/mint.ts", + "typecheck": "tsc --noEmit -p tsconfig.json", "test": "cargo test --workspace --exclude tests && bun run test:contract", - "check": "bun run format:check && bun run lint && bun run test" + "check": "bun run format:check && bun run lint && bun run test && bun run typecheck" }, "dependencies": { "@coral-xyz/anchor": "^0.31.1" diff --git a/programs/doom-nft-program/Cargo.toml b/programs/doom-nft-program/Cargo.toml index f24a3db..b7edcc2 100644 --- a/programs/doom-nft-program/Cargo.toml +++ b/programs/doom-nft-program/Cargo.toml @@ -26,3 +26,4 @@ unexpected_cfgs = { level = "allow", check-cfg = ["cfg(target_os, values(\"solan anchor-lang = "0.31.1" mpl-core = { version = "0.11.1", features = ["anchor"] } solana-program = "2.2.1" +url = "2.5.4" diff --git a/programs/doom-nft-program/src/error.rs b/programs/doom-nft-program/src/error.rs index 527276d..5a3cce0 100644 --- a/programs/doom-nft-program/src/error.rs +++ b/programs/doom-nft-program/src/error.rs @@ -8,6 +8,8 @@ pub enum DoomNftProgramError { MintPaused, #[msg("The collection has not been initialized.")] CollectionNotInitialized, + #[msg("The provided collection account does not match the configured collection.")] + CollectionMismatch, #[msg("The collection has already been initialized.")] CollectionAlreadyInitialized, #[msg("The reservation has already been used to mint an asset.")] diff --git a/programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs b/programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs index 7be0e17..ab295e1 100644 --- a/programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs +++ b/programs/doom-nft-program/src/instructions/mint_doom_index_nft.rs @@ -43,7 +43,7 @@ pub struct MintDoomIndexNft<'info> { /// CHECK: Existing Core collection account. Address checked against config. #[account( mut, - address = global_config.collection @ DoomNftProgramError::CollectionNotInitialized + address = global_config.collection @ DoomNftProgramError::CollectionMismatch )] pub collection: UncheckedAccount<'info>, diff --git a/programs/doom-nft-program/src/instructions/transfer_admin.rs b/programs/doom-nft-program/src/instructions/transfer_admin.rs index 3f1187a..6cc495c 100644 --- a/programs/doom-nft-program/src/instructions/transfer_admin.rs +++ b/programs/doom-nft-program/src/instructions/transfer_admin.rs @@ -8,14 +8,17 @@ pub struct TransferAdmin<'info> { mut, seeds = [GLOBAL_CONFIG_SEED], bump = global_config.bump, - has_one = admin @ DoomNftProgramError::Unauthorized + has_one = admin @ DoomNftProgramError::Unauthorized, + has_one = upgrade_authority @ DoomNftProgramError::Unauthorized )] pub global_config: Account<'info, GlobalConfig>, pub admin: Signer<'info>, + pub upgrade_authority: Signer<'info>, + pub new_admin: Signer<'info>, } -pub fn process_transfer_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { - ctx.accounts.global_config.admin = new_admin; +pub fn process_transfer_admin(ctx: Context) -> Result<()> { + ctx.accounts.global_config.admin = ctx.accounts.new_admin.key(); Ok(()) } diff --git a/programs/doom-nft-program/src/lib.rs b/programs/doom-nft-program/src/lib.rs index 21732e8..1f07907 100644 --- a/programs/doom-nft-program/src/lib.rs +++ b/programs/doom-nft-program/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(deprecated)] - pub mod constants; pub mod error; pub mod events; @@ -17,59 +15,67 @@ pub use state::*; declare_id!("u929SRVcCFcGM2iyYkMykDRq7xW4N9ozEMU3Vo1hgfP"); -#[program] -pub mod doom_nft_program { +mod anchor_api { + #![allow(deprecated)] // Anchor 0.31 expands #[program] through deprecated AccountInfo::realloc generated code. + use super::*; - pub fn initialize_global_config( - ctx: Context, - base_metadata_url: String, - upgrade_authority: Pubkey, - ) -> Result<()> { - instructions::initialize_global_config::process_initialize_global_config( - ctx, - base_metadata_url, - upgrade_authority, - ) - } + #[program] + pub mod doom_nft_program { + use super::*; - pub fn initialize_collection(ctx: Context) -> Result<()> { - instructions::initialize_collection::process_initialize_collection(ctx) - } + pub fn initialize_global_config( + ctx: Context, + base_metadata_url: String, + upgrade_authority: Pubkey, + ) -> Result<()> { + instructions::initialize_global_config::process_initialize_global_config( + ctx, + base_metadata_url, + upgrade_authority, + ) + } - pub fn reserve_token_id(ctx: Context) -> Result<()> { - instructions::reserve_token_id::process_reserve_token_id(ctx) - } + pub fn initialize_collection(ctx: Context) -> Result<()> { + instructions::initialize_collection::process_initialize_collection(ctx) + } - pub fn mint_doom_index_nft(ctx: Context, token_id: u64) -> Result<()> { - instructions::mint_doom_index_nft::process_mint_doom_index_nft(ctx, token_id) - } + pub fn reserve_token_id(ctx: Context) -> Result<()> { + instructions::reserve_token_id::process_reserve_token_id(ctx) + } - pub fn update_base_metadata_url( - ctx: Context, - base_metadata_url: String, - ) -> Result<()> { - instructions::update_base_metadata_url::process_update_base_metadata_url( - ctx, - base_metadata_url, - ) - } + pub fn mint_doom_index_nft(ctx: Context, token_id: u64) -> Result<()> { + instructions::mint_doom_index_nft::process_mint_doom_index_nft(ctx, token_id) + } - pub fn set_mint_paused(ctx: Context, paused: bool) -> Result<()> { - instructions::set_mint_paused::process_set_mint_paused(ctx, paused) - } + pub fn update_base_metadata_url( + ctx: Context, + base_metadata_url: String, + ) -> Result<()> { + instructions::update_base_metadata_url::process_update_base_metadata_url( + ctx, + base_metadata_url, + ) + } - pub fn transfer_admin(ctx: Context, new_admin: Pubkey) -> Result<()> { - instructions::transfer_admin::process_transfer_admin(ctx, new_admin) - } + pub fn set_mint_paused(ctx: Context, paused: bool) -> Result<()> { + instructions::set_mint_paused::process_set_mint_paused(ctx, paused) + } - pub fn set_upgrade_authority( - ctx: Context, - new_upgrade_authority: Pubkey, - ) -> Result<()> { - instructions::set_upgrade_authority::process_set_upgrade_authority( - ctx, - new_upgrade_authority, - ) + pub fn transfer_admin(ctx: Context) -> Result<()> { + instructions::transfer_admin::process_transfer_admin(ctx) + } + + pub fn set_upgrade_authority( + ctx: Context, + new_upgrade_authority: Pubkey, + ) -> Result<()> { + instructions::set_upgrade_authority::process_set_upgrade_authority( + ctx, + new_upgrade_authority, + ) + } } } + +pub use anchor_api::*; diff --git a/programs/doom-nft-program/src/utils.rs b/programs/doom-nft-program/src/utils.rs index d90ad6c..a43c25e 100644 --- a/programs/doom-nft-program/src/utils.rs +++ b/programs/doom-nft-program/src/utils.rs @@ -1,10 +1,16 @@ use anchor_lang::prelude::*; +use url::Url; -use crate::error::DoomNftProgramError; +use crate::{constants::COLLECTION_NAME, error::DoomNftProgramError}; pub fn validate_base_metadata_url(base_metadata_url: &str) -> Result<()> { + let trimmed = base_metadata_url.trim(); require!( - !base_metadata_url.is_empty() && !base_metadata_url.ends_with('/'), + !trimmed.is_empty() + && trimmed == base_metadata_url + && trimmed.len() <= 256 + && !trimmed.ends_with('/') + && matches!(Url::parse(trimmed), Ok(url) if url.scheme() == "https"), DoomNftProgramError::BaseMetadataUrlInvalid ); @@ -16,9 +22,40 @@ pub fn build_collection_uri(base_metadata_url: &str) -> String { } pub fn build_asset_name(token_id: u64) -> String { - format!("DOOM INDEX #{token_id}") + format!("{COLLECTION_NAME} #{token_id}") } pub fn build_asset_uri(base_metadata_url: &str, token_id: u64) -> String { format!("{base_metadata_url}/{token_id}.json") } + +#[cfg(test)] +mod tests { + use super::validate_base_metadata_url; + + #[test] + fn accepts_https_url_without_trailing_slash() { + assert!(validate_base_metadata_url("https://example.com/base").is_ok()); + } + + #[test] + fn rejects_non_https_url() { + assert!(validate_base_metadata_url("http://example.com/base").is_err()); + } + + #[test] + fn rejects_whitespace_padded_url() { + assert!(validate_base_metadata_url(" https://example.com/base ").is_err()); + } + + #[test] + fn rejects_trailing_slash() { + assert!(validate_base_metadata_url("https://example.com/base/").is_err()); + } + + #[test] + fn rejects_overlong_url() { + let overlong = format!("https://example.com/{}", "a".repeat(300)); + assert!(validate_base_metadata_url(&overlong).is_err()); + } +} diff --git a/scripts/devnet/common.test.ts b/scripts/devnet/common.test.ts index 49ed6b2..75edee3 100644 --- a/scripts/devnet/common.test.ts +++ b/scripts/devnet/common.test.ts @@ -1,10 +1,12 @@ import assert from "node:assert/strict"; -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, test } from "node:test"; -import { resolveWalletPath } from "./common"; +import { BN, web3 } from "@coral-xyz/anchor"; + +import { loadOrCreateKeypair, resolveWalletPath, reserveTokenId } from "./common"; const originalAnchorWallet = process.env.ANCHOR_WALLET; const originalHome = process.env.HOME; @@ -65,3 +67,63 @@ describe("resolveWalletPath", () => { } }); }); + +describe("loadOrCreateKeypair", () => { + test("creates a new keypair file with owner-only permissions", () => { + const tempDir = mkdtempSync(join(tmpdir(), "doom-keypair-")); + const keypairPath = join(tempDir, "keypair.json"); + + try { + const keypair = loadOrCreateKeypair(keypairPath); + const storedSecret = JSON.parse(readFileSync(keypairPath, "utf8")) as number[]; + + assert.deepEqual(storedSecret, Array.from(keypair.secretKey)); + assert.equal(statSync(keypairPath).mode & 0o777, 0o600); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); +}); + +describe("reserveTokenId", () => { + test("retries after a reservation contention error and recomputes the reservation PDA", async () => { + const payer = web3.Keypair.generate(); + const seenReservations: string[] = []; + let fetchCount = 0; + let sendCount = 0; + + const result = await reserveTokenId({} as web3.Connection, payer, undefined, { + fetchGlobalConfig: async () => { + fetchCount += 1; + const nextTokenId = fetchCount === 1 ? new BN(1) : new BN(2); + return { + admin: payer.publicKey, + upgradeAuthority: payer.publicKey, + nextTokenId, + mintPaused: false, + baseMetadataUrl: "https://example.com/base", + collection: payer.publicKey, + collectionUpdateAuthority: payer.publicKey, + bump: 255, + }; + }, + sendInstructions: async (_connection, _payer, instructions) => { + sendCount += 1; + seenReservations.push(instructions[0].keys[1].pubkey.toBase58()); + if (sendCount === 1) { + throw new Error("Allocate: account Address already in use"); + } + + return "retry-signature"; + }, + backoffMs: 0, + }); + + assert.equal(result.signature, "retry-signature"); + assert.equal(result.tokenId.toString(), "2"); + assert.equal(seenReservations.length, 2); + assert.notEqual(seenReservations[0], seenReservations[1]); + assert.equal(result.reservation.toBase58(), seenReservations[1]); + assert.equal(result.globalConfig.nextTokenId.toString(), "2"); + }); +}); diff --git a/scripts/devnet/common.ts b/scripts/devnet/common.ts index a9023f3..4ef0a6b 100644 --- a/scripts/devnet/common.ts +++ b/scripts/devnet/common.ts @@ -51,6 +51,13 @@ export type GlobalConfigAccount = { bump: number; }; +type ReserveTokenIdOptions = { + fetchGlobalConfig?: typeof fetchGlobalConfig; + sendInstructions?: typeof sendInstructions; + maxAttempts?: number; + backoffMs?: number; +}; + export function getConnection(): web3.Connection { return new Connection(process.env.ANCHOR_PROVIDER_URL ?? "https://api.devnet.solana.com", "confirmed"); } @@ -71,14 +78,30 @@ export function loadKeypair(filePath: string): web3.Keypair { export function loadOrCreateKeypair(filePath: string): web3.Keypair { const absolutePath = resolve(filePath); - if (existsSync(absolutePath)) { + try { return loadKeypair(absolutePath); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + throw error; + } } mkdirSync(dirname(absolutePath), { recursive: true }); const keypair = Keypair.generate(); - writeFileSync(absolutePath, JSON.stringify(Array.from(keypair.secretKey)), "utf8"); - return keypair; + try { + writeFileSync(absolutePath, JSON.stringify(Array.from(keypair.secretKey)), { + encoding: "utf8", + flag: "wx", + mode: 0o600, + }); + return keypair; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "EEXIST") { + return loadKeypair(absolutePath); + } + + throw error; + } } export function writeJson(filePath: string, payload: unknown): void { @@ -209,34 +232,53 @@ export async function reserveTokenId( connection: web3.Connection, payer: web3.Keypair, programId: web3.PublicKey = DEFAULT_PROGRAM_ID, + options: ReserveTokenIdOptions = {}, ): Promise<{ signature: string; tokenId: bigint; reservation: web3.PublicKey; globalConfig: GlobalConfigAccount; }> { - const before = await fetchGlobalConfig(connection, programId); - const tokenId = BigInt(before.nextTokenId.toString()); - const [globalConfig] = globalConfigPda(programId); - const [reservation] = reservationPda(tokenId, programId); - - const instruction = createInstruction( - "reserve_token_id", - borsh.struct([]), - {}, - [ - { pubkey: globalConfig, isSigner: false, isWritable: true }, - { pubkey: reservation, isSigner: false, isWritable: true }, - { pubkey: payer.publicKey, isSigner: true, isWritable: true }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - ], - programId, - ); - - const signature = await sendInstructions(connection, payer, [instruction]); - const after = await fetchGlobalConfig(connection, programId); + const fetchGlobalConfigImpl = options.fetchGlobalConfig ?? fetchGlobalConfig; + const sendInstructionsImpl = options.sendInstructions ?? sendInstructions; + const maxAttempts = options.maxAttempts ?? 4; + const backoffMs = options.backoffMs ?? 200; + + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + const before = await fetchGlobalConfigImpl(connection, programId); + const tokenId = BigInt(before.nextTokenId.toString()); + const [globalConfig] = globalConfigPda(programId); + const [reservation] = reservationPda(tokenId, programId); + + const instruction = createInstruction( + "reserve_token_id", + borsh.struct([]), + {}, + [ + { pubkey: globalConfig, isSigner: false, isWritable: true }, + { pubkey: reservation, isSigner: false, isWritable: true }, + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + ], + programId, + ); + + try { + const signature = await sendInstructionsImpl(connection, payer, [instruction]); + const after = await fetchGlobalConfigImpl(connection, programId); + return { signature, tokenId, reservation, globalConfig: after }; + } catch (error) { + if (attempt === maxAttempts || !isReservationContentionError(error)) { + throw error; + } + + if (backoffMs > 0) { + await sleep(backoffMs); + } + } + } - return { signature, tokenId, reservation, globalConfig: after }; + throw new Error("reserveTokenId exhausted retries without a terminal error"); } export function mintDoomIndexNftInstruction( @@ -272,6 +314,24 @@ function instructionDiscriminator(name: string): Buffer { return createHash("sha256").update(`global:${name}`).digest().subarray(0, 8); } +function isReservationContentionError(error: unknown): boolean { + const text = [ + error instanceof Error ? error.message : "", + error instanceof Error ? error.toString() : String(error), + typeof error === "object" && error !== null && "logs" in error && Array.isArray((error as { logs?: unknown }).logs) + ? ((error as { logs: string[] }).logs ?? []).join("\n") + : "", + ] + .join("\n") + .toLowerCase(); + + return text.includes("already in use"); +} + +async function sleep(milliseconds: number): Promise { + await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds)); +} + function accountDiscriminator(name: string): Buffer { return createHash("sha256").update(`account:${name}`).digest().subarray(0, 8); } diff --git a/scripts/devnet/init.ts b/scripts/devnet/init.ts index ec90880..016ecae 100644 --- a/scripts/devnet/init.ts +++ b/scripts/devnet/init.ts @@ -15,6 +15,23 @@ async function main(): Promise { if (!baseMetadataUrl) { throw new Error("BASE_METADATA_URL is required"); } + if (baseMetadataUrl.trim() !== baseMetadataUrl) { + throw new Error("BASE_METADATA_URL must not contain leading or trailing whitespace"); + } + if (!baseMetadataUrl.startsWith("https://")) { + throw new Error("BASE_METADATA_URL must use https"); + } + try { + new URL(baseMetadataUrl); + } catch { + throw new Error("BASE_METADATA_URL must be a valid URL"); + } + if (baseMetadataUrl.endsWith("/")) { + throw new Error("BASE_METADATA_URL must not end with a trailing slash"); + } + if (Buffer.byteLength(baseMetadataUrl, "utf8") > 256) { + throw new Error("BASE_METADATA_URL must be 256 bytes or fewer"); + } const connection = getConnection(); const payer = loadWallet(); diff --git a/scripts/devnet/mint.test.ts b/scripts/devnet/mint.test.ts new file mode 100644 index 0000000..b4befe2 --- /dev/null +++ b/scripts/devnet/mint.test.ts @@ -0,0 +1,61 @@ +import assert from "node:assert/strict"; +import { describe, test } from "node:test"; + +import { assertUrlReachable, decodeAssetUri } from "./mint"; + +describe("decodeAssetUri", () => { + test("returns the URI stored in the on-chain asset data", () => { + const accountData = Buffer.concat([ + Buffer.from([1]), + Buffer.alloc(32, 7), + Buffer.from([2]), + Buffer.alloc(32, 8), + encodeBorshString("DOOM INDEX #1"), + encodeBorshString("https://example.com/base/1.json"), + Buffer.from([0]), + Buffer.from([9, 9, 9]), + ]); + + assert.equal(decodeAssetUri(accountData), "https://example.com/base/1.json"); + }); +}); + +describe("assertUrlReachable", () => { + test("falls back to GET when HEAD is not supported", async () => { + const calls: Array<{ url: string; method: string }> = []; + await assertUrlReachable("https://example.com/asset.png", "Image", async (url, init) => { + calls.push({ url: String(url), method: init?.method ?? "GET" }); + if (init?.method === "HEAD") { + return new Response(null, { status: 405, statusText: "Method Not Allowed" }); + } + + return new Response("ok", { status: 200, statusText: "OK" }); + }); + + assert.deepEqual(calls, [ + { url: "https://example.com/asset.png", method: "HEAD" }, + { url: "https://example.com/asset.png", method: "GET" }, + ]); + }); + + test("throws when both HEAD and GET fail", async () => { + await assert.rejects( + () => + assertUrlReachable("https://example.com/asset.png", "Image", async (_url, init) => { + if (init?.method === "HEAD") { + return new Response(null, { status: 403, statusText: "Forbidden" }); + } + + return new Response(null, { status: 404, statusText: "Not Found" }); + }), + /Image fetch failed: 404 Not Found/, + ); + }); +}); + +function encodeBorshString(value: string): Buffer { + const text = Buffer.from(value, "utf8"); + const length = Buffer.alloc(4); + length.writeUInt32LE(text.length, 0); + return Buffer.concat([length, text]); +} diff --git a/scripts/devnet/mint.ts b/scripts/devnet/mint.ts index 88d0118..42e7fd5 100644 --- a/scripts/devnet/mint.ts +++ b/scripts/devnet/mint.ts @@ -1,8 +1,8 @@ import { - buildMetadataUri, fetchGlobalConfig, getConnection, Keypair, + MPL_CORE_PROGRAM_ID, loadWallet, mintDoomIndexNftInstruction, readJson, @@ -14,6 +14,45 @@ type ReservationOutput = { tokenId: string; }; +type FetchLike = typeof fetch; + +export function decodeAssetUri(accountData: Buffer | Uint8Array): string { + const data = Buffer.from(accountData); + let offset = 0; + + const key = data.readUInt8(offset); + offset += 1; + if (key !== 1) { + throw new Error(`Expected Metaplex Core AssetV1 account data, got key ${key}`); + } + + offset += 32; + + const updateAuthorityKind = data.readUInt8(offset); + offset += 1; + if (updateAuthorityKind === 1 || updateAuthorityKind === 2) { + offset += 32; + } else if (updateAuthorityKind !== 0) { + throw new Error(`Unknown Metaplex Core update authority kind ${updateAuthorityKind}`); + } + + const [, afterName] = readBorshString(data, offset); + const [uri] = readBorshString(data, afterName); + return uri; +} + +export async function assertUrlReachable(url: string, label: string, fetchImpl: FetchLike = fetch): Promise { + const headResponse = await fetchImpl(url, { method: "HEAD" }); + if (headResponse.ok) { + return; + } + + const getResponse = await fetchImpl(url, { method: "GET" }); + if (!getResponse.ok) { + throw new Error(`${label} fetch failed: ${getResponse.status} ${getResponse.statusText}`); + } +} + async function main(): Promise { const connection = getConnection(); const payer = loadWallet(); @@ -37,8 +76,11 @@ async function main(): Promise { if (!assetAccount) { throw new Error(`Asset account not found at ${asset.publicKey.toBase58()}`); } + if (!assetAccount.owner.equals(MPL_CORE_PROGRAM_ID)) { + throw new Error(`Asset account ${asset.publicKey.toBase58()} is not owned by Metaplex Core`); + } - const metadataUri = buildMetadataUri(globalConfig.baseMetadataUrl, tokenId); + const metadataUri = decodeAssetUri(assetAccount.data); const metadataResponse = await fetch(metadataUri); if (!metadataResponse.ok) { throw new Error(`Metadata fetch failed: ${metadataResponse.status} ${metadataResponse.statusText}`); @@ -51,17 +93,8 @@ async function main(): Promise { throw new Error("Metadata must include both image and animation_url"); } - const imageResponse = await fetch(metadata.image, { method: "HEAD" }); - if (!imageResponse.ok) { - throw new Error(`Image fetch failed: ${imageResponse.status} ${imageResponse.statusText}`); - } - - const animationResponse = await fetch(metadata.animation_url, { - method: "HEAD", - }); - if (!animationResponse.ok) { - throw new Error(`animation_url fetch failed: ${animationResponse.status} ${animationResponse.statusText}`); - } + await assertUrlReachable(metadata.image, "Image"); + await assertUrlReachable(metadata.animation_url, "animation_url"); const output = { signature, @@ -76,7 +109,16 @@ async function main(): Promise { console.log(JSON.stringify(output, null, 2)); } -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); +function readBorshString(data: Buffer, offset: number): [string, number] { + const length = data.readUInt32LE(offset); + const start = offset + 4; + const end = start + length; + return [data.subarray(start, end).toString("utf8"), end]; +} + +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exitCode = 1; + }); +} diff --git a/scripts/test-contract-v1.sh b/scripts/test-contract-v1.sh index 35c0644..ba9e342 100755 --- a/scripts/test-contract-v1.sh +++ b/scripts/test-contract-v1.sh @@ -4,4 +4,4 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" "$ROOT_DIR/scripts/build-test-sbf.sh" -BPF_OUT_DIR="$ROOT_DIR/target/test-sbf" cargo test -p tests --lib -- --test-threads=1 +(cd "$ROOT_DIR" && BPF_OUT_DIR="$ROOT_DIR/target/test-sbf" cargo test -p tests --lib -- --test-threads=1) diff --git a/tests/src/instructions/mint_doom_index_nft.rs b/tests/src/instructions/mint_doom_index_nft.rs index 34b616b..fcee00b 100644 --- a/tests/src/instructions/mint_doom_index_nft.rs +++ b/tests/src/instructions/mint_doom_index_nft.rs @@ -91,3 +91,105 @@ async fn mint_without_reservation_fails() { assert!(matches!(error, BanksClientError::TransactionError(_))); } + +#[tokio::test] +async fn mint_with_other_users_reservation_fails() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let other_user = Keypair::new(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect("reserve token id"); + + process_instruction( + &mut context, + solana_sdk::system_instruction::transfer(&payer, &other_user.pubkey(), 1_000_000_000), + &[], + ) + .await + .expect("fund other user"); + + let asset = Keypair::new(); + let error = process_instruction( + &mut context, + mint_doom_index_nft_ix(other_user.pubkey(), 1, asset.pubkey(), collection.pubkey()), + &[&other_user, &asset], + ) + .await + .expect_err("mint should fail for a different reservation owner"); + + assert!(matches!(error, BanksClientError::TransactionError(_))); +} + +#[tokio::test] +async fn mint_cannot_reuse_reservation_after_success() { + let mut context = start_context().await; + let payer = context.payer.pubkey(); + let upgrade_authority = Keypair::new(); + process_instruction( + &mut context, + initialize_global_config_ix( + payer, + upgrade_authority.pubkey(), + "https://example.com/base", + ), + &[], + ) + .await + .expect("initialize global config"); + + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect("reserve token id"); + + let first_asset = Keypair::new(); + process_instruction( + &mut context, + mint_doom_index_nft_ix(payer, 1, first_asset.pubkey(), collection.pubkey()), + &[&first_asset], + ) + .await + .expect("first mint succeeds"); + + let second_asset = Keypair::new(); + let error = process_instruction( + &mut context, + mint_doom_index_nft_ix(payer, 1, second_asset.pubkey(), collection.pubkey()), + &[&second_asset], + ) + .await + .expect_err("second mint should fail"); + + assert!(matches!(error, BanksClientError::TransactionError(_))); +} diff --git a/tests/src/instructions/set_mint_paused.rs b/tests/src/instructions/set_mint_paused.rs index 24e6263..9bb8524 100644 --- a/tests/src/instructions/set_mint_paused.rs +++ b/tests/src/instructions/set_mint_paused.rs @@ -2,8 +2,8 @@ use solana_program_test::BanksClientError; use solana_sdk::{signature::Keypair, signer::Signer}; use crate::test_context::{ - initialize_global_config_ix, process_instruction, reserve_token_id_ix, set_mint_paused_ix, - start_context, + initialize_collection_ix, initialize_global_config_ix, mint_doom_index_nft_ix, + process_instruction, reserve_token_id_ix, set_mint_paused_ix, start_context, }; #[tokio::test] @@ -23,15 +23,38 @@ async fn pause_blocks_reserve_and_mint() { .await .expect("initialize global config"); + let collection = Keypair::new(); + process_instruction( + &mut context, + initialize_collection_ix(payer, collection.pubkey()), + &[&collection], + ) + .await + .expect("initialize collection"); + + process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + .await + .expect("reserve token id"); + process_instruction(&mut context, set_mint_paused_ix(payer, true), &[]) .await .expect("pause mint"); - let reserve_error = process_instruction(&mut context, reserve_token_id_ix(payer, 1), &[]) + let reserve_error = process_instruction(&mut context, reserve_token_id_ix(payer, 2), &[]) .await .expect_err("reserve should fail when paused"); assert!(matches!( reserve_error, BanksClientError::TransactionError(_) )); + + let asset = Keypair::new(); + let mint_error = process_instruction( + &mut context, + mint_doom_index_nft_ix(payer, 1, asset.pubkey(), collection.pubkey()), + &[&asset], + ) + .await + .expect_err("mint should fail when paused"); + assert!(matches!(mint_error, BanksClientError::TransactionError(_))); } diff --git a/tests/src/instructions/transfer_admin.rs b/tests/src/instructions/transfer_admin.rs index a7b46cc..03d5031 100644 --- a/tests/src/instructions/transfer_admin.rs +++ b/tests/src/instructions/transfer_admin.rs @@ -25,8 +25,8 @@ async fn transfer_admin_updates_admin_authority() { let next_admin = Keypair::new(); process_instruction( &mut context, - transfer_admin_ix(payer, next_admin.pubkey()), - &[], + transfer_admin_ix(payer, upgrade_authority.pubkey(), next_admin.pubkey()), + &[&upgrade_authority, &next_admin], ) .await .expect("transfer admin"); diff --git a/tests/src/test_context.rs b/tests/src/test_context.rs index b271850..26a3e70 100644 --- a/tests/src/test_context.rs +++ b/tests/src/test_context.rs @@ -46,8 +46,12 @@ fn doom_nft_program_test_processor<'a, 'b, 'c, 'd>( accounts: &'b [AccountInfo<'c>], instruction_data: &'d [u8], ) -> ProgramResult { - // Anchor's generated entrypoint ties the slice lifetime to the inner AccountInfo lifetime. - // ProgramTest uses the looser builtin processor signature, so the wrapper narrows it for this call. + // SAFETY: This only narrows the outer slice lifetime from `&'b [AccountInfo<'c>]` to + // `&'c [AccountInfo<'c>]` without changing the slice layout or the contained `AccountInfo` + // values. `accounts` already points to `AccountInfo<'c>` elements, and callers uphold that + // the slice lives for the full duration of `doom_nft_program::entry(program_id, accounts, + // instruction_data)`, so this cast does not extend any inner borrow past its original + // lifetime. let accounts: &'c [AccountInfo<'c>] = unsafe { std::mem::transmute(accounts) }; doom_nft_program::entry(program_id, accounts, instruction_data) } @@ -250,15 +254,21 @@ pub fn update_base_metadata_url_ix(admin: Pubkey, base_metadata_url: &str) -> In } } -pub fn transfer_admin_ix(admin: Pubkey, new_admin: Pubkey) -> Instruction { +pub fn transfer_admin_ix( + admin: Pubkey, + upgrade_authority: Pubkey, + new_admin: Pubkey, +) -> Instruction { Instruction { program_id: doom_nft_program::id(), accounts: TransferAdmin { global_config: global_config_pda().0, admin, + upgrade_authority, + new_admin, } .to_account_metas(None), - data: instruction::TransferAdmin { new_admin }.data(), + data: instruction::TransferAdmin {}.data(), } } diff --git a/tsconfig.json b/tsconfig.json index cd5d2e3..f740e10 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,7 @@ "module": "commonjs", "target": "es6", "esModuleInterop": true - } + }, + "include": ["scripts/**/*.ts"], + "exclude": ["node_modules", "target"] }