diff --git a/.github/actions/private-tangle/setup/action.yml b/.github/actions/private-tangle/setup/action.yml index 31d5797112..f0b7c4b08d 100644 --- a/.github/actions/private-tangle/setup/action.yml +++ b/.github/actions/private-tangle/setup/action.yml @@ -18,13 +18,13 @@ runs: # remove the last 5 lines and add docker compose with `-d` to run it in the background sed -i -n -e :a -e '1,5!{P;N;D;};N;ba' run.sh - echo "docker compose -f \$DOCKER_COMPOSE_FILE up -d" >> run.sh + echo "docker compose -f docker-compose.yml up -d" >> run.sh working-directory: iota-core/tools/docker-network # Copied from https://github.com/iotaledger/iota-core/blob/926bf11b5fb6dc46b25482770e0ccf1e048b97c9/.github/workflows/unit-test.yml#L16-L19 - uses: actions/setup-go@v4 with: - go-version-file: 'iota-core/go.mod' + go-version-file: "iota-core/go.mod" cache: false - name: Setup private tangle @@ -40,5 +40,5 @@ runs: run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.4/wait-for | sh -s -- -t 120 http://localhost:8080/health -- echo "Tangle is up" # TODO enable, maybe need another URL # - name: Wait for faucet to start - # shell: bash - # run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.4/wait-for | sh -s -- -t 120 http://localhost:8081/api/info -- echo "Faucet is up" + # shell: bash + # run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.4/wait-for | sh -s -- -t 120 http://localhost:8081/api/info -- echo "Faucet is up" diff --git a/.github/workflows/bindings-python.yml b/.github/workflows/bindings-python.yml index 32543d9794..48c0f60c93 100644 --- a/.github/workflows/bindings-python.yml +++ b/.github/workflows/bindings-python.yml @@ -77,7 +77,7 @@ jobs: matrix: # os: [windows-latest, macos-latest, ubuntu-latest] os: [windows-latest, ubuntu-latest] - python-version: ["3.10"] + python-version: ["3.10", "3.11", "3.12"] steps: - name: Checkout the Source Code diff --git a/Cargo.lock b/Cargo.lock index 5ec1f91fb8..5ad22abad6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "once_cell", @@ -159,7 +159,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -274,7 +274,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -382,11 +382,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" dependencies = [ - "jobserver", "libc", ] @@ -462,9 +461,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", @@ -472,9 +471,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", @@ -484,21 +483,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "cli-wallet" @@ -574,16 +573,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-oid" version = "0.9.5" @@ -602,6 +591,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -620,9 +618,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -644,9 +642,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -664,6 +662,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +dependencies = [ + "quote", + "syn 2.0.39", +] + [[package]] name = "ctr" version = "0.9.2" @@ -705,13 +713,13 @@ dependencies = [ [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -735,7 +743,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -746,7 +754,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -807,7 +815,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1013,9 +1021,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -1077,9 +1085,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" +checksum = "f69037fe1b785e84986b4f2cbcf647381876a00671d25ceef715d7812dd7e1dd" [[package]] name = "field-offset" @@ -1111,7 +1119,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -1137,9 +1145,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -1152,9 +1160,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -1162,15 +1170,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -1180,38 +1188,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -1238,9 +1246,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", @@ -1429,9 +1437,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1487,9 +1495,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -1556,20 +1564,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.2", ] -[[package]] -name = "indoc" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" - [[package]] name = "inout" version = "0.1.3" @@ -1724,43 +1726,18 @@ dependencies = [ name = "iota-sdk-nodejs" version = "0.1.0" dependencies = [ + "async-trait", "iota-sdk-bindings-core", "log", - "neon", - "once_cell", - "serde_json", - "tokio", -] - -[[package]] -name = "iota-sdk-python" -version = "1.1.0" -dependencies = [ - "futures", - "iota-sdk-bindings-core", + "napi", + "napi-build", + "napi-derive", "once_cell", - "pyo3", "serde_json", + "thiserror", "tokio", ] -[[package]] -name = "iota-sdk-wasm" -version = "0.1.0" -dependencies = [ - "console_error_panic_hook", - "getrandom", - "instant", - "iota-sdk-bindings-core", - "js-sys", - "log", - "serde_json", - "tokio", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-logger", -] - [[package]] name = "iota_stronghold" version = "2.0.0" @@ -1808,20 +1785,11 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -1891,15 +1859,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" -version = "0.6.7" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -1907,12 +1875,23 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "winapi", + "windows-sys 0.48.0", +] + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", ] [[package]] @@ -1955,9 +1934,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" @@ -2045,44 +2024,58 @@ dependencies = [ ] [[package]] -name = "neon" -version = "0.10.1" +name = "napi" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" +checksum = "f9d90182620f32fe34b6ac9b52cba898af26e94c7f5abc01eb4094c417ae2e6c" dependencies = [ - "neon-build", - "neon-macros", - "neon-runtime", - "semver 0.9.0", - "smallvec", + "bitflags 2.4.1", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", + "tokio", ] [[package]] -name = "neon-build" -version = "0.10.1" +name = "napi-build" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" +checksum = "d4b4532cf86bfef556348ac65e561e3123879f0e7566cca6d43a6ff5326f13df" [[package]] -name = "neon-macros" -version = "0.10.1" +name = "napi-derive" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" +checksum = "3619fa472d23cd5af94d63a2bae454a77a8863251f40230fbf59ce20eafa8a86" dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", "quote", "syn 1.0.109", - "syn-mid", ] [[package]] -name = "neon-runtime" -version = "0.10.1" +name = "napi-derive-backend" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" +checksum = "ecd3ea4b54020c73d591a49cd192f6334c5f37f71a63ead54dbc851fa991ef00" dependencies = [ - "cfg-if", - "libloading 0.6.7", - "smallvec", + "convert_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "napi-sys" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b" +dependencies = [ + "libloading 0.8.1", ] [[package]] @@ -2190,9 +2183,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "packable" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11259b086696fc9256f790485d8f14f11f0fa60a60351af9693e3d49fd24fdb6" +checksum = "eee4180de5473e336f075507fea07003183e059bc0d35398b0b0039419747e76" dependencies = [ "autocfg", "packable-derive", @@ -2202,9 +2195,9 @@ dependencies = [ [[package]] name = "packable-derive" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567693dd2f9a4339cb0a54adfcc0cb431c0ac88b2e46c6ddfb5f5d11a1cc4f" +checksum = "26a5ee3cb4c60754a0b49a3b7a6f5067a191ff5ec9d90414f6c4cfd21ce9acff" dependencies = [ "proc-macro-crate", "proc-macro-error", @@ -2239,29 +2232,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "smallvec", - "windows-targets 0.48.5", -] - [[package]] name = "paste" version = "1.0.14" @@ -2339,9 +2309,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "platforms" -version = "3.1.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" [[package]] name = "poly1305" @@ -2406,7 +2376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2464,66 +2434,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "pyo3" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" -dependencies = [ - "cfg-if", - "indoc", - "libc", - "memoffset 0.9.0", - "parking_lot", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", - "unindent", -] - -[[package]] -name = "pyo3-build-config" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" -dependencies = [ - "once_cell", - "target-lexicon", -] - -[[package]] -name = "pyo3-ffi" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" -dependencies = [ - "libc", - "pyo3-build-config", -] - -[[package]] -name = "pyo3-macros" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" -dependencies = [ - "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "quote" version = "1.0.33" @@ -2585,15 +2495,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -2605,12 +2506,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] @@ -2693,21 +2594,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.5" @@ -2717,8 +2603,8 @@ dependencies = [ "cc", "getrandom", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.48.0", ] @@ -2789,14 +2675,14 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.20", + "semver", ] [[package]] name = "rustix" -version = "0.38.20" +version = "0.38.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +checksum = "80109a168d9bc0c7f483083244543a6eb0dba02295d33ca268145e6190d6df0c" dependencies = [ "bitflags 2.4.1", "errno", @@ -2812,7 +2698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", - "ring 0.17.5", + "ring", "rustls-webpki", "sct", ] @@ -2831,9 +2717,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64 0.21.5", ] @@ -2844,8 +2730,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -2880,7 +2766,7 @@ checksum = "5a32af5427251d2e4be14fc151eabe18abb4a7aad5efee7044da9f096c906a43" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2935,12 +2821,12 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring", + "untrusted", ] [[package]] @@ -2981,52 +2867,37 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" -version = "1.0.189" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -3035,13 +2906,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3121,9 +2992,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "snafu" @@ -3167,12 +3038,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -3287,26 +3152,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn-mid" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea305d57546cc8cd04feb14b62ec84bf17f50e3f7b12560d7bfa9265f39d9ed" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "system-configuration" version = "0.5.1" @@ -3334,12 +3188,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "target-lexicon" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" - [[package]] name = "thiserror" version = "1.0.50" @@ -3357,7 +3205,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3426,9 +3274,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -3443,13 +3291,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3464,9 +3312,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -3488,7 +3336,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "toml_datetime", "winnow", ] @@ -3518,7 +3366,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3618,12 +3466,6 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" -[[package]] -name = "unindent" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" - [[package]] name = "universal-hash" version = "0.5.1" @@ -3634,12 +3476,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -3709,36 +3545,34 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", - "serde", - "serde_json", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -3748,9 +3582,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3758,39 +3592,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - -[[package]] -name = "wasm-logger" -version = "0.2.0" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" -dependencies = [ - "log", - "wasm-bindgen", - "web-sys", -] +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -4028,9 +3851,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.17" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -4093,22 +3916,22 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.11" +version = "0.7.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c19fae0c8a9efc6a8281f2e623db8af1db9e57852e04cde3e754dd2dc29340f" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.11" +version = "0.7.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc56589e9ddd1f1c28d4b4b5c773ce232910a6bb67a70133d61c9e347585efe9" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -4129,5 +3952,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] diff --git a/Cargo.toml b/Cargo.toml index 99647d0d92..527f5a4068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,10 @@ resolver = "2" members = [ "bindings/core", "bindings/nodejs", - "bindings/python", - "bindings/wasm", + # TODO: issue #1423 + #"bindings/python", + # TODO: issue #1425 + #"bindings/wasm", "cli", "sdk", ] diff --git a/bindings/core/Cargo.toml b/bindings/core/Cargo.toml index 761e579065..aaff827018 100644 --- a/bindings/core/Cargo.toml +++ b/bindings/core/Cargo.toml @@ -23,20 +23,20 @@ iota-crypto = { version = "0.23.0", default-features = false, features = [ "bip44", ] } log = { version = "0.4.20", default-features = false } -packable = { version = "0.8.3", default-features = false } +packable = { version = "0.9.0", default-features = false } prefix-hex = { version = "0.7.1", default-features = false } primitive-types = { version = "0.12.2", default-features = false } serde = { version = "1.0.188", default-features = false } serde_json = { version = "1.0.107", default-features = false } thiserror = { version = "1.0.49", default-features = false } tokio = { version = "1.33.0", default-features = false } -url = { version = "2.4.1", default-features = false, features = [ - "serde", -] } +url = { version = "2.4.1", default-features = false, features = ["serde"] } zeroize = { version = "1.6.0", default-features = false } [dev-dependencies] -pretty_assertions = { version = "1.4.0", default-features = false, features = [ "alloc" ] } +pretty_assertions = { version = "1.4.0", default-features = false, features = [ + "alloc", +] } [features] events = ["iota-sdk/events"] diff --git a/bindings/core/src/error.rs b/bindings/core/src/error.rs index 08b54e8adf..441130e0a9 100644 --- a/bindings/core/src/error.rs +++ b/bindings/core/src/error.rs @@ -4,13 +4,12 @@ use packable::error::UnexpectedEOF; use serde::{ser::SerializeMap, Serialize, Serializer}; -pub use super::{method::AccountMethod, response::Response}; - /// Result type of the bindings core crate. pub type Result = std::result::Result; /// Error type for the bindings core crate. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// Block errors. #[error("{0}")] diff --git a/bindings/core/src/lib.rs b/bindings/core/src/lib.rs index 75ab8c4288..f4fbfce8bb 100644 --- a/bindings/core/src/lib.rs +++ b/bindings/core/src/lib.rs @@ -11,11 +11,14 @@ mod response; use std::fmt::{Formatter, Result as FmtResult}; +use crypto::keys::bip44::Bip44; use derivative::Derivative; use fern_logger::{logger_init, LoggerConfig, LoggerOutputConfigBuilder}; pub use iota_sdk; use iota_sdk::{ client::secret::{SecretManager, SecretManagerDto}, + types::block::address::Bech32Address, + utils::serde::bip44::option_bip44, wallet::{ClientOptions, Wallet}, }; use serde::Deserialize; @@ -26,7 +29,7 @@ pub use self::method_handler::listen_mqtt; pub use self::method_handler::CallMethod; pub use self::{ error::{Error, Result}, - method::{AccountMethod, ClientMethod, SecretManagerMethod, UtilsMethod, WalletMethod}, + method::{ClientMethod, SecretManagerMethod, UtilsMethod, WalletMethod}, method_handler::{call_client_method, call_secret_manager_method, call_utils_method, call_wallet_method}, response::Response, }; @@ -41,26 +44,34 @@ pub fn init_logger(config: String) -> std::result::Result<(), fern_logger::Error #[derivative(Debug)] #[serde(rename_all = "camelCase")] pub struct WalletOptions { - pub storage_path: Option, + pub address: Option, + pub alias: Option, + #[serde(with = "option_bip44", default)] + pub bip_path: Option, pub client_options: Option, - pub coin_type: Option, #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] pub secret_manager: Option, + pub storage_path: Option, } impl WalletOptions { - pub fn with_storage_path(mut self, storage_path: impl Into>) -> Self { - self.storage_path = storage_path.into(); + pub fn with_address(mut self, address: impl Into>) -> Self { + self.address = address.into(); self } - pub fn with_client_options(mut self, client_options: impl Into>) -> Self { - self.client_options = client_options.into(); + pub fn with_alias(mut self, alias: impl Into>) -> Self { + self.alias = alias.into(); + self + } + + pub fn with_bip_path(mut self, bip_path: impl Into>) -> Self { + self.bip_path = bip_path.into(); self } - pub fn with_coin_type(mut self, coin_type: impl Into>) -> Self { - self.coin_type = coin_type.into(); + pub fn with_client_options(mut self, client_options: impl Into>) -> Self { + self.client_options = client_options.into(); self } @@ -69,11 +80,18 @@ impl WalletOptions { self } + pub fn with_storage_path(mut self, storage_path: impl Into>) -> Self { + self.storage_path = storage_path.into(); + self + } + pub async fn build(self) -> iota_sdk::wallet::Result { log::debug!("wallet options: {self:?}"); let mut builder = Wallet::builder() - .with_client_options(self.client_options) - .with_coin_type(self.coin_type); + .with_address(self.address) + .with_alias(self.alias) + .with_bip_path(self.bip_path) + .with_client_options(self.client_options); #[cfg(feature = "storage")] if let Some(storage_path) = &self.storage_path { diff --git a/bindings/core/src/method/account.rs b/bindings/core/src/method/account.rs deleted file mode 100644 index 20e10a1592..0000000000 --- a/bindings/core/src/method/account.rs +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "participation")] -use iota_sdk::{ - client::node_manager::node::Node, - types::api::plugins::participation::types::{ParticipationEventId, ParticipationEventType}, - wallet::account::types::participation::ParticipationEventRegistrationOptions, -}; -use iota_sdk::{ - client::{ - api::{input_selection::Burn, PreparedTransactionDataDto, SignedTransactionDataDto}, - secret::GenerateAddressOptions, - }, - types::block::{ - address::Bech32Address, - output::{dto::OutputDto, OutputId, TokenId}, - payload::signed_transaction::TransactionId, - }, - wallet::{ - account::{ - ConsolidationParams, CreateAccountParams, CreateNativeTokenParams, FilterOptions, MintNftParams, - OutputParams, OutputsToClaim, SyncOptions, TransactionOptions, - }, - SendNativeTokenParams, SendNftParams, SendParams, - }, - U256, -}; -use serde::{Deserialize, Serialize}; - -/// Each public account method. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(tag = "name", content = "data", rename_all = "camelCase")] -#[non_exhaustive] -pub enum AccountMethod { - /// List addresses. - /// Expected response: [`Addresses`](crate::Response::Addresses) - Addresses, - /// Returns only addresses of the account with unspent outputs - /// Expected response: - /// [`AddressesWithUnspentOutputs`](crate::Response::AddressesWithUnspentOutputs) - AddressesWithUnspentOutputs, - /// Get outputs with additional unlock conditions - /// Expected response: [`OutputIds`](crate::Response::OutputIds) - #[serde(rename_all = "camelCase")] - ClaimableOutputs { outputs_to_claim: OutputsToClaim }, - /// Claim outputs. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - #[serde(rename_all = "camelCase")] - ClaimOutputs { output_ids_to_claim: Vec }, - /// Removes a previously registered participation event from local storage. - /// Expected response: [`Ok`](crate::Response::Ok) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - DeregisterParticipationEvent { event_id: ParticipationEventId }, - /// Generate new Ed25519 addresses. - /// Expected response: [`GeneratedEd25519Addresses`](crate::Response::GeneratedEd25519Addresses) - GenerateEd25519Addresses { - amount: u32, - options: Option, - }, - /// Get account balance information. - /// Expected response: [`Balance`](crate::Response::Balance) - GetBalance, - /// Get the [`Output`](iota_sdk::types::block::output::Output) that minted a native token by its TokenId - /// Expected response: [`Output`](crate::Response::Output) - #[serde(rename_all = "camelCase")] - GetFoundryOutput { token_id: TokenId }, - /// Get the transaction with inputs of an incoming transaction stored in the account - /// List might not be complete, if the node pruned the data already - /// Expected response: [`Transaction`](crate::Response::Transaction) - #[serde(rename_all = "camelCase")] - GetIncomingTransaction { transaction_id: TransactionId }, - /// Get the [`OutputData`](iota_sdk::wallet::account::types::OutputData) of an output stored in the account - /// Expected response: [`OutputData`](crate::Response::OutputData) - #[serde(rename_all = "camelCase")] - GetOutput { output_id: OutputId }, - /// Expected response: [`ParticipationEvent`](crate::Response::ParticipationEvent) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationEvent { event_id: ParticipationEventId }, - /// Expected response: [`ParticipationEventIds`](crate::Response::ParticipationEventIds) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationEventIds { - node: Node, - event_type: Option, - }, - /// Expected response: - /// [`ParticipationEventStatus`](crate::Response::ParticipationEventStatus) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationEventStatus { event_id: ParticipationEventId }, - /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - GetParticipationEvents, - /// Calculates a participation overview for an account. If event_ids are provided, only return outputs and tracked - /// participations for them. - /// Expected response: - /// [`AccountParticipationOverview`](crate::Response::AccountParticipationOverview) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - GetParticipationOverview { - event_ids: Option>, - }, - /// Get the [`Transaction`](iota_sdk::wallet::account::types::Transaction) of a transaction stored in the account - /// Expected response: [`Transaction`](crate::Response::Transaction) - #[serde(rename_all = "camelCase")] - GetTransaction { transaction_id: TransactionId }, - /// Get the account's total voting power (voting or NOT voting). - /// Expected response: [`VotingPower`](crate::Response::VotingPower) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - GetVotingPower, - /// Returns all incoming transactions of the account - /// Expected response: - /// [`Transactions`](crate::Response::Transactions) - IncomingTransactions, - /// Returns all outputs of the account - /// Expected response: [`OutputsData`](crate::Response::OutputsData) - #[serde(rename_all = "camelCase")] - Outputs { filter_options: Option }, - /// Returns all pending transactions of the account - /// Expected response: [`Transactions`](crate::Response::Transactions) - PendingTransactions, - /// A generic function that can be used to burn native tokens, nfts, foundries and accounts. - /// - /// Note that burning **native tokens** doesn't require the foundry output which minted them, but will not - /// increase the foundries `melted_tokens` field, which makes it impossible to destroy the foundry output. - /// Therefore it's recommended to use melting, if the foundry output is available. - /// - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareBurn { - burn: Burn, - options: Option, - }, - /// Consolidate outputs. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareConsolidateOutputs { params: ConsolidationParams }, - /// Create an alias output. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareCreateAccountOutput { - params: Option, - options: Option, - }, - /// Prepare to create a native token. - /// Expected response: - /// [`PreparedCreateNativeTokenTransaction`](crate::Response::PreparedCreateNativeTokenTransaction) - PrepareCreateNativeToken { - params: CreateNativeTokenParams, - options: Option, - }, - /// Reduces an account's "voting power" by a given amount. - /// This will stop voting, but the voting data isn't lost and calling `Vote` without parameters will revote. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - PrepareDecreaseVotingPower { - #[serde(with = "iota_sdk::utils::serde::string")] - amount: u64, - }, - /// Designates a given amount of tokens towards an account's "voting power" by creating a - /// special output, which is really a basic one with some metadata. - /// This will stop voting in most cases (if there is a remainder output), but the voting data isn't lost and - /// calling `Vote` without parameters will revote. Expected response: - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - PrepareIncreaseVotingPower { - #[serde(with = "iota_sdk::utils::serde::string")] - amount: u64, - }, - /// Prepare to melt native tokens. This happens with the foundry output which minted them, by increasing it's - /// `melted_tokens` field. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[serde(rename_all = "camelCase")] - PrepareMeltNativeToken { - /// Native token id - token_id: TokenId, - /// To be melted amount - melt_amount: U256, - options: Option, - }, - /// Prepare to mint additional native tokens. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[serde(rename_all = "camelCase")] - PrepareMintNativeToken { - /// Native token id - token_id: TokenId, - /// To be minted amount - mint_amount: U256, - options: Option, - }, - /// Prepare to mint NFTs. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareMintNfts { - params: Vec, - options: Option, - }, - /// Prepare an output. - /// Expected response: [`Output`](crate::Response::Output) - #[serde(rename_all = "camelCase")] - PrepareOutput { - params: Box, - transaction_options: Option, - }, - /// Prepare to send base coins. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareSend { - params: Vec, - options: Option, - }, - /// Prepare to send a native token. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareSendNativeToken { - params: Vec, - options: Option, - }, - /// Prepare to Send nft. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareSendNft { - params: Vec, - options: Option, - }, - /// Stop participating for an event. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - PrepareStopParticipating { event_id: ParticipationEventId }, - /// Prepare transaction. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareTransaction { - outputs: Vec, - options: Option, - }, - /// Vote for a participation event. - /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[serde(rename_all = "camelCase")] - PrepareVote { - event_id: Option, - answers: Option>, - }, - /// Stores participation information locally and returns the event. - /// - /// This will NOT store the node url and auth inside the client options. - /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - RegisterParticipationEvents { - options: ParticipationEventRegistrationOptions, - }, - /// Reissues a transaction sent from the account for a provided transaction id until it's - /// included (referenced by a milestone). Returns the included block id. - /// Expected response: [`BlockId`](crate::Response::BlockId) - #[serde(rename_all = "camelCase")] - ReissueTransactionUntilIncluded { - /// Transaction id - transaction_id: TransactionId, - /// Interval - interval: Option, - /// Maximum attempts - max_attempts: Option, - }, - /// Send base coins. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - Send { - #[serde(with = "iota_sdk::utils::serde::string")] - amount: u64, - address: Bech32Address, - options: Option, - }, - /// Send base coins to multiple addresses, or with additional parameters. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - SendWithParams { - params: Vec, - options: Option, - }, - /// Send outputs in a transaction. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - SendOutputs { - outputs: Vec, - options: Option, - }, - /// Set the alias of the account. - /// Expected response: [`Ok`](crate::Response::Ok) - SetAlias { alias: String }, - /// Set the fallback SyncOptions for account syncing. - /// If storage is enabled, will persist during restarts. - /// Expected response: [`Ok`](crate::Response::Ok) - SetDefaultSyncOptions { options: SyncOptions }, - /// Validate the transaction, sign it, submit it to a node and store it in the account. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - #[serde(rename_all = "camelCase")] - SignAndSubmitTransaction { - prepared_transaction_data: PreparedTransactionDataDto, - }, - /// Sign a prepared transaction. - /// Expected response: [`SignedTransactionData`](crate::Response::SignedTransactionData) - #[serde(rename_all = "camelCase")] - SignTransaction { - prepared_transaction_data: PreparedTransactionDataDto, - }, - /// Validate the transaction, submit it to a node and store it in the account. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - #[serde(rename_all = "camelCase")] - SubmitAndStoreTransaction { - signed_transaction_data: SignedTransactionDataDto, - }, - /// Sync the account by fetching new information from the nodes. Will also reissue pending transactions - /// if necessary. A custom default can be set using SetDefaultSyncOptions. - /// Expected response: [`Balance`](crate::Response::Balance) - Sync { - /// Sync options - options: Option, - }, - /// Returns all transaction of the account - /// Expected response: [`Transactions`](crate::Response::Transactions) - Transactions, - /// Returns all unspent outputs of the account - /// Expected response: [`OutputsData`](crate::Response::OutputsData) - #[serde(rename_all = "camelCase")] - UnspentOutputs { filter_options: Option }, -} diff --git a/bindings/core/src/method/client.rs b/bindings/core/src/method/client.rs index e2f1301571..cb62fe85db 100644 --- a/bindings/core/src/method/client.rs +++ b/bindings/core/src/method/client.rs @@ -7,16 +7,17 @@ use iota_sdk::client::mqtt::Topic; use iota_sdk::{ client::{ node_api::indexer::query_parameters::{ - AccountOutputQueryParameters, BasicOutputQueryParameters, FoundryOutputQueryParameters, - NftOutputQueryParameters, OutputQueryParameters, + AccountOutputQueryParameters, AnchorOutputQueryParameters, BasicOutputQueryParameters, + DelegationOutputQueryParameters, FoundryOutputQueryParameters, NftOutputQueryParameters, + OutputQueryParameters, }, node_manager::node::NodeAuth, }, types::block::{ address::{Bech32Address, Hrp}, output::{ - dto::OutputDto, feature::Feature, unlock_condition::dto::UnlockConditionDto, AccountId, FoundryId, - NativeToken, NftId, OutputId, TokenScheme, + dto::OutputDto, feature::Feature, unlock_condition::dto::UnlockConditionDto, AccountId, AnchorId, + DelegationId, FoundryId, NativeToken, NftId, OutputId, TokenScheme, }, payload::{dto::PayloadDto, signed_transaction::TransactionId}, BlockId, IssuerId, SignedBlockDto, @@ -36,7 +37,7 @@ pub enum ClientMethod { #[allow(missing_docs)] #[serde(rename_all = "camelCase")] BuildAccountOutput { - // If not provided, minimum storage deposit will be used + // If not provided, minimum amount will be used #[serde(default, with = "option_string")] amount: Option, // TODO: Determine if `default` is wanted here @@ -53,7 +54,7 @@ pub enum ClientMethod { #[allow(missing_docs)] #[serde(rename_all = "camelCase")] BuildBasicOutput { - // If not provided, minimum storage deposit will be used + // If not provided, minimum amount will be used #[serde(default, with = "option_string")] amount: Option, // TODO: Determine if `default` is wanted here @@ -68,7 +69,7 @@ pub enum ClientMethod { #[allow(missing_docs)] #[serde(rename_all = "camelCase")] BuildFoundryOutput { - // If not provided, minimum storage deposit will be used + // If not provided, minimum amount will be used #[serde(default, with = "option_string")] amount: Option, native_tokens: Option>, @@ -83,7 +84,7 @@ pub enum ClientMethod { #[allow(missing_docs)] #[serde(rename_all = "camelCase")] BuildNftOutput { - // If not provided, minimum storage deposit will be used + // If not provided, minimum amount will be used #[serde(default, with = "option_string")] amount: Option, // TODO: Determine if `default` is wanted here @@ -234,17 +235,29 @@ pub enum ClientMethod { /// Account id account_id: AccountId, }, - /// Fetch NFT output IDs + /// Fetch anchor output IDs #[serde(rename_all = "camelCase")] - NftOutputIds { + AnchorOutputIds { /// Query parameters for output requests - query_parameters: NftOutputQueryParameters, + query_parameters: AnchorOutputQueryParameters, }, - /// Fetch NFT output ID + /// Fetch anchor output ID #[serde(rename_all = "camelCase")] - NftOutputId { - /// NFT ID - nft_id: NftId, + AnchorOutputId { + /// Anchor id + anchor_id: AnchorId, + }, + /// Fetch delegation output IDs + #[serde(rename_all = "camelCase")] + DelegationOutputIds { + /// Query parameters for output requests + query_parameters: DelegationOutputQueryParameters, + }, + /// Fetch delegation output ID + #[serde(rename_all = "camelCase")] + DelegationOutputId { + /// Delegation id + delegation_id: DelegationId, }, /// Fetch foundry Output IDs #[serde(rename_all = "camelCase")] @@ -258,6 +271,18 @@ pub enum ClientMethod { /// Foundry ID foundry_id: FoundryId, }, + /// Fetch NFT output IDs + #[serde(rename_all = "camelCase")] + NftOutputIds { + /// Query parameters for output requests + query_parameters: NftOutputQueryParameters, + }, + /// Fetch NFT output ID + #[serde(rename_all = "camelCase")] + NftOutputId { + /// NFT ID + nft_id: NftId, + }, ////////////////////////////////////////////////////////////////////// // High level API @@ -324,10 +349,10 @@ pub enum ClientMethod { /// Human readable part bech32_hrp: Option, }, - /// Calculate the minimum required storage deposit for an output. + /// Calculate the minimum required amount for an output. /// Expected response: - /// [`MinimumRequiredStorageDeposit`](crate::Response::MinimumRequiredStorageDeposit) - MinimumRequiredStorageDeposit { output: OutputDto }, + /// [`OutputAmount`](crate::Response::OutputAmount) + ComputeMinimumOutputAmount { output: OutputDto }, /// Requests funds for a given address from the faucet, for example `https://faucet.testnet.shimmer.network/api/enqueue` or `http://localhost:8091/api/enqueue`. RequestFundsFromFaucet { /// Faucet URL diff --git a/bindings/core/src/method/mod.rs b/bindings/core/src/method/mod.rs index 966b583013..6e47c80767 100644 --- a/bindings/core/src/method/mod.rs +++ b/bindings/core/src/method/mod.rs @@ -1,13 +1,9 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod account; mod client; mod secret_manager; mod utils; mod wallet; -pub use self::{ - account::AccountMethod, client::ClientMethod, secret_manager::SecretManagerMethod, utils::UtilsMethod, - wallet::WalletMethod, -}; +pub use self::{client::ClientMethod, secret_manager::SecretManagerMethod, utils::UtilsMethod, wallet::WalletMethod}; diff --git a/bindings/core/src/method/secret_manager.rs b/bindings/core/src/method/secret_manager.rs index c538f3aec2..b70c10db53 100644 --- a/bindings/core/src/method/secret_manager.rs +++ b/bindings/core/src/method/secret_manager.rs @@ -36,7 +36,7 @@ pub enum SecretManagerMethod { SignatureUnlock { /// Transaction signing hash transaction_signing_hash: String, - /// Chain to sign the hash with + /// Chain used to sign the hash #[serde(with = "Bip44Def")] chain: Bip44, }, @@ -44,7 +44,7 @@ pub enum SecretManagerMethod { SignEd25519 { /// The message to sign, hex encoded String message: String, - /// Chain to sign the message with + /// Chain used to sign the message #[serde(with = "Bip44Def")] chain: Bip44, }, @@ -52,7 +52,7 @@ pub enum SecretManagerMethod { SignSecp256k1Ecdsa { /// The message to sign, hex encoded String message: String, - /// Chain to sign the message with + /// Chain used to sign the message #[serde(with = "Bip44Def")] chain: Bip44, }, @@ -66,7 +66,7 @@ pub enum SecretManagerMethod { #[serde(rename_all = "camelCase")] SignBlock { unsigned_block: UnsignedBlockDto, - /// Chain to sign the essence hash with + /// Chain used to sign the block #[serde(with = "Bip44Def")] chain: Bip44, }, diff --git a/bindings/core/src/method/utils.rs b/bindings/core/src/method/utils.rs index 71047d504d..e3e07d06af 100644 --- a/bindings/core/src/method/utils.rs +++ b/bindings/core/src/method/utils.rs @@ -4,7 +4,7 @@ use derivative::Derivative; use iota_sdk::types::block::{ address::{Bech32Address, Hrp}, - output::{dto::OutputDto, AccountId, NftId, OutputId, RentStructure}, + output::{dto::OutputDto, AccountId, NftId, OutputId, StorageScoreParameters}, payload::signed_transaction::{ dto::{SignedTransactionPayloadDto, TransactionDto}, TransactionId, @@ -128,8 +128,12 @@ pub enum UtilsMethod { /// The transaction. transaction: TransactionDto, }, - /// Computes the required storage deposit of an output. - ComputeStorageDeposit { output: OutputDto, rent: RentStructure }, + /// Computes the minimum required amount of an output. + #[serde(rename_all = "camelCase")] + ComputeMinimumOutputAmount { + output: OutputDto, + storage_score_parameters: StorageScoreParameters, + }, /// Checks if the given mnemonic is valid. /// Expected response: [`Ok`](crate::Response::Ok) VerifyMnemonic { diff --git a/bindings/core/src/method/wallet.rs b/bindings/core/src/method/wallet.rs index 9c48819fd4..f65467ae52 100644 --- a/bindings/core/src/method/wallet.rs +++ b/bindings/core/src/method/wallet.rs @@ -7,21 +7,33 @@ use std::path::PathBuf; use derivative::Derivative; #[cfg(feature = "events")] use iota_sdk::wallet::events::types::{WalletEvent, WalletEventType}; +#[cfg(feature = "participation")] use iota_sdk::{ - client::{node_manager::node::NodeAuth, secret::GenerateAddressOptions}, - types::block::address::Hrp, + client::node_manager::node::Node, + types::api::plugins::participation::types::{ParticipationEventId, ParticipationEventType}, + wallet::types::participation::ParticipationEventRegistrationOptions, +}; +use iota_sdk::{ + client::{ + api::{input_selection::Burn, PreparedTransactionDataDto, SignedTransactionDataDto}, + node_manager::node::NodeAuth, + secret::GenerateAddressOptions, + }, + types::block::{ + address::{Bech32Address, Hrp}, + output::{dto::OutputDto, OutputId, TokenId}, + payload::signed_transaction::TransactionId, + }, wallet::{ - account::{ - types::{AccountIdentifier, Bip44Address}, - SyncOptions, - }, - ClientOptions, + ClientOptions, ConsolidationParams, CreateAccountParams, CreateNativeTokenParams, FilterOptions, MintNftParams, + OutputParams, OutputsToClaim, SendNativeTokensParams, SendNftParams, SendParams, SyncOptions, + TransactionOptions, }, + U256, }; use serde::{Deserialize, Serialize}; use url::Url; -use crate::method::account::AccountMethod; #[cfg(feature = "stronghold")] use crate::OmittedDebug; @@ -31,36 +43,9 @@ use crate::OmittedDebug; #[serde(tag = "name", content = "data", rename_all = "camelCase")] #[non_exhaustive] pub enum WalletMethod { - /// Creates an account. - /// Expected response: [`Account`](crate::Response::Account) - #[serde(rename_all = "camelCase")] - CreateAccount { - /// The account alias. - alias: Option, - /// The bech32 HRP. - bech32_hrp: Option, - /// BIP44 addresses. - addresses: Option>, - }, - /// Read account. - /// Expected response: [`Account`](crate::Response::Account) - #[serde(rename_all = "camelCase")] - GetAccount { account_id: AccountIdentifier }, - /// Return the account indexes. - /// Expected response: [`AccountIndexes`](crate::Response::AccountIndexes) - GetAccountIndexes, - /// Read accounts. - /// Expected response: [`Accounts`](crate::Response::Accounts) - GetAccounts, - /// Consume an account method. - /// Returns [`Response`](crate::Response) - #[serde(rename_all = "camelCase")] - CallAccountMethod { - /// The account identifier. - account_id: AccountIdentifier, - /// The account method to call. - method: AccountMethod, - }, + /// Returns the accounts of the wallet. + /// Expected response: [`OutputsData`](crate::Response::OutputsData) + Accounts, /// Backup storage. Password must be the current one, when Stronghold is used as SecretManager. /// Expected response: [`Ok`](crate::Response::Ok) #[cfg(feature = "stronghold")] @@ -72,53 +57,15 @@ pub enum WalletMethod { #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] password: String, }, - /// Change the Stronghold password to another one and also re-encrypt the values in the loaded snapshot with it. - /// Expected response: [`Ok`](crate::Response::Ok) - #[cfg(feature = "stronghold")] - #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] - #[serde(rename_all = "camelCase")] - ChangeStrongholdPassword { - #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] - current_password: String, - #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] - new_password: String, - }, - /// Clears the Stronghold password from memory. - /// Expected response: [`Ok`](crate::Response::Ok) - #[cfg(feature = "stronghold")] - #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] - ClearStrongholdPassword, - /// Checks if the Stronghold password is available. - /// Expected response: - /// [`Bool`](crate::Response::Bool) - #[cfg(feature = "stronghold")] - #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] - IsStrongholdPasswordAvailable, - /// Find accounts with unspent outputs - /// Expected response: [`Accounts`](crate::Response::Accounts) - #[serde(rename_all = "camelCase")] - RecoverAccounts { - /// The index of the first account to search for. - account_start_index: u32, - /// The number of accounts to search for, after the last account with unspent outputs. - account_gap_limit: u32, - /// The number of addresses to search for, after the last address with unspent outputs, in - /// each account. - address_gap_limit: u32, - /// Optional parameter to specify the sync options. The `address_start_index` and `force_syncing` - /// fields will be overwritten to skip existing addresses. - sync_options: Option, - }, /// Restore a backup from a Stronghold file - /// Replaces client_options, coin_type, secret_manager and accounts. Returns an error if accounts were already + /// Replaces client_options, coin_type, secret_manager and wallet. Returns an error if the wallet was already /// created If Stronghold is used as secret_manager, the existing Stronghold file will be overwritten. If a /// mnemonic was stored, it will be gone. /// if ignore_if_coin_type_mismatch.is_some(), client options will not be restored - /// if ignore_if_coin_type_mismatch == Some(true), client options coin type and accounts will not be restored if + /// if ignore_if_coin_type_mismatch == Some(true), client options coin type and the wallet will not be restored if /// the cointype doesn't match - /// if ignore_if_bech32_hrp_mismatch == Some("rms"), but addresses have something different like "smr", no accounts - /// will be restored. - /// Expected response: [`Ok`](crate::Response::Ok) + /// If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current + /// address, the wallet will not be restored. Expected response: [`Ok`](crate::Response::Ok) #[cfg(feature = "stronghold")] #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] #[serde(rename_all = "camelCase")] @@ -129,20 +76,343 @@ pub enum WalletMethod { #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] password: String, /// If ignore_if_coin_type_mismatch.is_some(), client options will not be restored. - /// If ignore_if_coin_type_mismatch == Some(true), client options coin type and accounts will not be restored + /// If ignore_if_coin_type_mismatch == Some(true), client options coin type and wallet will not be restored /// if the cointype doesn't match. ignore_if_coin_type_mismatch: Option, - /// If ignore_if_bech32_hrp_mismatch == Some("rms"), but addresses have something different like "smr", no - /// accounts will be restored. + /// If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current + /// address, the wallet will not be restored. ignore_if_bech32_mismatch: Option, }, - /// Removes the latest account (account with the largest account index). - /// Expected response: [`Ok`](crate::Response::Ok) - RemoveLatestAccount, - /// Updates the client options for all accounts. + /// Updates the client options for the wallet. /// Expected response: [`Ok`](crate::Response::Ok) #[serde(rename_all = "camelCase")] SetClientOptions { client_options: Box }, + /// Start background syncing. + /// Expected response: [`Ok`](crate::Response::Ok) + #[serde(rename_all = "camelCase")] + StartBackgroundSync { + /// Sync options + options: Option, + /// Interval in milliseconds + interval_in_milliseconds: Option, + }, + /// Stop background syncing. + /// Expected response: [`Ok`](crate::Response::Ok) + StopBackgroundSync, + // Remove all listeners of this type. Empty vec clears all listeners + /// Expected response: [`Ok`](crate::Response::Ok) + #[cfg(feature = "events")] + #[cfg_attr(docsrs, doc(cfg(feature = "events")))] + #[serde(rename_all = "camelCase")] + ClearListeners { event_types: Vec }, + /// Update the authentication for the provided node. + /// Expected response: [`Ok`](crate::Response::Ok) + UpdateNodeAuth { + /// Node url + url: Url, + /// Authentication options + auth: Option, + }, + /// Get outputs with additional unlock conditions + /// Expected response: [`OutputIds`](crate::Response::OutputIds) + #[serde(rename_all = "camelCase")] + ClaimableOutputs { outputs_to_claim: OutputsToClaim }, + /// Claim outputs. + /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) + #[serde(rename_all = "camelCase")] + ClaimOutputs { output_ids_to_claim: Vec }, + /// Removes a previously registered participation event from local storage. + /// Expected response: [`Ok`](crate::Response::Ok) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[serde(rename_all = "camelCase")] + DeregisterParticipationEvent { event_id: ParticipationEventId }, + /// Get the wallet address. + /// Expected response: [`Address`](crate::Response::Address) + GetAddress, + /// Get wallet balance information. + /// Expected response: [`Balance`](crate::Response::Balance) + GetBalance, + /// Get the [`Output`](iota_sdk::types::block::output::Output) that minted a native token by its TokenId + /// Expected response: [`Output`](crate::Response::Output) + #[serde(rename_all = "camelCase")] + GetFoundryOutput { token_id: TokenId }, + /// Get the transaction with inputs of an incoming transaction stored in the wallet + /// List might not be complete, if the node pruned the data already + /// Expected response: [`Transaction`](crate::Response::Transaction) + #[serde(rename_all = "camelCase")] + GetIncomingTransaction { transaction_id: TransactionId }, + /// Get the [`OutputData`](iota_sdk::wallet::types::OutputData) of an output stored in the wallet + /// Expected response: [`OutputData`](crate::Response::OutputData) + #[serde(rename_all = "camelCase")] + GetOutput { output_id: OutputId }, + /// Expected response: [`ParticipationEvent`](crate::Response::ParticipationEvent) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[serde(rename_all = "camelCase")] + GetParticipationEvent { event_id: ParticipationEventId }, + /// Expected response: [`ParticipationEventIds`](crate::Response::ParticipationEventIds) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[serde(rename_all = "camelCase")] + GetParticipationEventIds { + node: Node, + event_type: Option, + }, + /// Expected response: + /// [`ParticipationEventStatus`](crate::Response::ParticipationEventStatus) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[serde(rename_all = "camelCase")] + GetParticipationEventStatus { event_id: ParticipationEventId }, + /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + GetParticipationEvents, + /// Calculates a participation overview for the wallet. If event_ids are provided, only return outputs and tracked + /// participations for them. + /// Expected response: + /// [`ParticipationOverview`](crate::Response::ParticipationOverview) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[serde(rename_all = "camelCase")] + GetParticipationOverview { + event_ids: Option>, + }, + /// Get the [`Transaction`](iota_sdk::wallet::types::Transaction) of a transaction stored in the wallet + /// Expected response: [`Transaction`](crate::Response::Transaction) + #[serde(rename_all = "camelCase")] + GetTransaction { transaction_id: TransactionId }, + /// Get the wallet's total voting power (voting or NOT voting). + /// Expected response: [`VotingPower`](crate::Response::VotingPower) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + GetVotingPower, + /// Returns the implicit account creation address of the wallet if it is Ed25519 based. + /// Expected response: [`Bech32Address`](crate::Response::Bech32Address) + ImplicitAccountCreationAddress, + /// Returns the implicit accounts of the wallet. + /// Expected response: [`OutputsData`](crate::Response::OutputsData) + ImplicitAccounts, + /// Returns all incoming transactions of the wallet + /// Expected response: + /// [`Transactions`](crate::Response::Transactions) + IncomingTransactions, + /// Returns all outputs of the wallet + /// Expected response: [`OutputsData`](crate::Response::OutputsData) + #[serde(rename_all = "camelCase")] + Outputs { filter_options: Option }, + /// Returns all pending transactions of the wallet + /// Expected response: [`Transactions`](crate::Response::Transactions) + PendingTransactions, + /// A generic function that can be used to burn native tokens, nfts, foundries and accounts. + /// + /// Note that burning **native tokens** doesn't require the foundry output which minted them, but will not + /// increase the foundries `melted_tokens` field, which makes it impossible to destroy the foundry output. + /// Therefore it's recommended to use melting, if the foundry output is available. + /// + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareBurn { + burn: Burn, + options: Option, + }, + /// Consolidate outputs. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareConsolidateOutputs { params: ConsolidationParams }, + /// Create an alias output. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareCreateAccountOutput { + params: Option, + options: Option, + }, + /// Prepare to create a native token. + /// Expected response: + /// [`PreparedCreateNativeTokenTransaction`](crate::Response::PreparedCreateNativeTokenTransaction) + PrepareCreateNativeToken { + params: CreateNativeTokenParams, + options: Option, + }, + /// Reduces a wallet's "voting power" by a given amount. + /// This will stop voting, but the voting data isn't lost and calling `Vote` without parameters will revote. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + PrepareDecreaseVotingPower { + #[serde(with = "iota_sdk::utils::serde::string")] + amount: u64, + }, + /// Designates a given amount of tokens towards a wallet's "voting power" by creating a + /// special output, which is really a basic one with some metadata. + /// This will stop voting in most cases (if there is a remainder output), but the voting data isn't lost and + /// calling `Vote` without parameters will revote. Expected response: + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + PrepareIncreaseVotingPower { + #[serde(with = "iota_sdk::utils::serde::string")] + amount: u64, + }, + /// Prepare to melt native tokens. This happens with the foundry output which minted them, by increasing it's + /// `melted_tokens` field. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + #[serde(rename_all = "camelCase")] + PrepareMeltNativeToken { + /// Native token id + token_id: TokenId, + /// To be melted amount + melt_amount: U256, + options: Option, + }, + /// Prepare to mint additional native tokens. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + #[serde(rename_all = "camelCase")] + PrepareMintNativeToken { + /// Native token id + token_id: TokenId, + /// To be minted amount + mint_amount: U256, + options: Option, + }, + /// Prepare to mint NFTs. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareMintNfts { + params: Vec, + options: Option, + }, + /// Prepare an output. + /// Expected response: [`Output`](crate::Response::Output) + #[serde(rename_all = "camelCase")] + PrepareOutput { + params: Box, + transaction_options: Option, + }, + /// Prepare to send base coins. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareSend { + params: Vec, + options: Option, + }, + /// Prepare to send native tokens. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareSendNativeTokens { + params: Vec, + options: Option, + }, + /// Prepare to Send nft. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareSendNft { + params: Vec, + options: Option, + }, + /// Stop participating for an event. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[serde(rename_all = "camelCase")] + PrepareStopParticipating { event_id: ParticipationEventId }, + /// Prepare transaction. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareTransaction { + outputs: Vec, + options: Option, + }, + /// Vote for a participation event. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[serde(rename_all = "camelCase")] + PrepareVote { + event_id: Option, + answers: Option>, + }, + /// Stores participation information locally and returns the event. + /// + /// This will NOT store the node url and auth inside the client options. + /// Expected response: [`ParticipationEvents`](crate::Response::ParticipationEvents) + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + RegisterParticipationEvents { + options: ParticipationEventRegistrationOptions, + }, + /// Reissues a transaction sent from the wallet for a provided transaction id until it's + /// included (referenced by a milestone). Returns the included block id. + /// Expected response: [`BlockId`](crate::Response::BlockId) + #[serde(rename_all = "camelCase")] + ReissueTransactionUntilIncluded { + /// Transaction id + transaction_id: TransactionId, + /// Interval + interval: Option, + /// Maximum attempts + max_attempts: Option, + }, + /// Send base coins. + /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) + Send { + #[serde(with = "iota_sdk::utils::serde::string")] + amount: u64, + address: Bech32Address, + options: Option, + }, + /// Send base coins to multiple addresses, or with additional parameters. + /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) + SendWithParams { + params: Vec, + options: Option, + }, + /// Send outputs in a transaction. + /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) + SendOutputs { + outputs: Vec, + options: Option, + }, + /// Set the alias of the wallet. + /// Expected response: [`Ok`](crate::Response::Ok) + SetAlias { alias: String }, + /// Set the fallback SyncOptions for wallet syncing. + /// If storage is enabled, will persist during restarts. + /// Expected response: [`Ok`](crate::Response::Ok) + SetDefaultSyncOptions { options: SyncOptions }, + /// Validate the transaction, sign it, submit it to a node and store it in the wallet. + /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) + #[serde(rename_all = "camelCase")] + SignAndSubmitTransaction { + prepared_transaction_data: PreparedTransactionDataDto, + }, + /// Sign a prepared transaction. + /// Expected response: [`SignedTransactionData`](crate::Response::SignedTransactionData) + #[serde(rename_all = "camelCase")] + SignTransaction { + prepared_transaction_data: PreparedTransactionDataDto, + }, + /// Validate the transaction, submit it to a node and store it in the wallet. + /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) + #[serde(rename_all = "camelCase")] + SubmitAndStoreTransaction { + signed_transaction_data: SignedTransactionDataDto, + }, + /// Sync the wallet by fetching new information from the nodes. Will also reissue pending transactions + /// if necessary. A custom default can be set using SetDefaultSyncOptions. + /// Expected response: [`Balance`](crate::Response::Balance) + Sync { + /// Sync options + options: Option, + }, + /// Returns all transactions of the wallet + /// Expected response: [`Transactions`](crate::Response::Transactions) + Transactions, + /// Returns all unspent outputs of the wallet + /// Expected response: [`OutputsData`](crate::Response::OutputsData) + #[serde(rename_all = "camelCase")] + UnspentOutputs { filter_options: Option }, + + /// Emits an event for testing if the event system is working + /// Expected response: [`Ok`](crate::Response::Ok) + #[cfg(feature = "events")] + #[cfg_attr(docsrs, doc(cfg(feature = "events")))] + EmitTestEvent { event: WalletEvent }, + + // TODO: reconsider whether to have the following methods on the wallet /// Generate an address without storing it /// Expected response: [`Bech32Address`](crate::Response::Bech32Address) #[serde(rename_all = "camelCase")] @@ -183,35 +453,26 @@ pub enum WalletMethod { #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] mnemonic: String, }, - /// Start background syncing. + /// Change the Stronghold password to another one and also re-encrypt the values in the loaded snapshot with it. /// Expected response: [`Ok`](crate::Response::Ok) + #[cfg(feature = "stronghold")] + #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] #[serde(rename_all = "camelCase")] - StartBackgroundSync { - /// Sync options - options: Option, - /// Interval in milliseconds - interval_in_milliseconds: Option, + ChangeStrongholdPassword { + #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] + current_password: String, + #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] + new_password: String, }, - /// Stop background syncing. - /// Expected response: [`Ok`](crate::Response::Ok) - StopBackgroundSync, - /// Emits an event for testing if the event system is working - /// Expected response: [`Ok`](crate::Response::Ok) - #[cfg(feature = "events")] - #[cfg_attr(docsrs, doc(cfg(feature = "events")))] - EmitTestEvent { event: WalletEvent }, - // Remove all listeners of this type. Empty vec clears all listeners - /// Expected response: [`Ok`](crate::Response::Ok) - #[cfg(feature = "events")] - #[cfg_attr(docsrs, doc(cfg(feature = "events")))] - #[serde(rename_all = "camelCase")] - ClearListeners { event_types: Vec }, - /// Update the authentication for the provided node. + /// Clears the Stronghold password from memory. /// Expected response: [`Ok`](crate::Response::Ok) - UpdateNodeAuth { - /// Node url - url: Url, - /// Authentication options - auth: Option, - }, + #[cfg(feature = "stronghold")] + #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] + ClearStrongholdPassword, + /// Checks if the Stronghold password is available. + /// Expected response: + /// [`Bool`](crate::Response::Bool) + #[cfg(feature = "stronghold")] + #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] + IsStrongholdPasswordAvailable, } diff --git a/bindings/core/src/method_handler/account.rs b/bindings/core/src/method_handler/account.rs deleted file mode 100644 index 6291dbfe8a..0000000000 --- a/bindings/core/src/method_handler/account.rs +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use iota_sdk::{ - client::api::{ - PreparedTransactionData, PreparedTransactionDataDto, SignedTransactionData, SignedTransactionDataDto, - }, - types::{ - block::output::{dto::OutputDto, Output}, - TryFromDto, - }, - wallet::account::{ - types::TransactionWithMetadataDto, Account, OutputDataDto, PreparedCreateNativeTokenTransactionDto, - }, -}; - -use crate::{method::AccountMethod, Response, Result}; - -pub(crate) async fn call_account_method_internal(account: &Account, method: AccountMethod) -> Result { - let response = match method { - AccountMethod::Addresses => { - let addresses = account.addresses().await; - Response::Addresses(addresses) - } - AccountMethod::AddressesWithUnspentOutputs => { - let addresses = account.addresses_with_unspent_outputs().await?; - Response::AddressesWithUnspentOutputs(addresses) - } - AccountMethod::ClaimableOutputs { outputs_to_claim } => { - let output_ids = account.claimable_outputs(outputs_to_claim).await?; - Response::OutputIds(output_ids) - } - AccountMethod::ClaimOutputs { output_ids_to_claim } => { - let transaction = account.claim_outputs(output_ids_to_claim.to_vec()).await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - #[cfg(feature = "participation")] - AccountMethod::DeregisterParticipationEvent { event_id } => { - account.deregister_participation_event(&event_id).await?; - Response::Ok - } - AccountMethod::GenerateEd25519Addresses { amount, options } => { - let address = account.generate_ed25519_addresses(amount, options).await?; - Response::GeneratedAccountAddresses(address) - } - AccountMethod::GetBalance => Response::Balance(account.balance().await?), - AccountMethod::GetFoundryOutput { token_id } => { - let output = account.get_foundry_output(token_id).await?; - Response::Output(OutputDto::from(&output)) - } - AccountMethod::GetIncomingTransaction { transaction_id } => { - let transaction = account.get_incoming_transaction(&transaction_id).await; - - transaction.map_or_else( - || Response::Transaction(None), - |transaction| Response::Transaction(Some(Box::new(TransactionWithMetadataDto::from(&transaction)))), - ) - } - AccountMethod::GetOutput { output_id } => { - let output_data = account.get_output(&output_id).await; - Response::OutputData(output_data.as_ref().map(OutputDataDto::from).map(Box::new)) - } - #[cfg(feature = "participation")] - AccountMethod::GetParticipationEvent { event_id } => { - let event_and_nodes = account.get_participation_event(event_id).await?; - Response::ParticipationEvent(event_and_nodes) - } - #[cfg(feature = "participation")] - AccountMethod::GetParticipationEventIds { node, event_type } => { - let event_ids = account.get_participation_event_ids(&node, event_type).await?; - Response::ParticipationEventIds(event_ids) - } - #[cfg(feature = "participation")] - AccountMethod::GetParticipationEventStatus { event_id } => { - let event_status = account.get_participation_event_status(&event_id).await?; - Response::ParticipationEventStatus(event_status) - } - #[cfg(feature = "participation")] - AccountMethod::GetParticipationEvents => { - let events = account.get_participation_events().await?; - Response::ParticipationEvents(events) - } - #[cfg(feature = "participation")] - AccountMethod::GetParticipationOverview { event_ids } => { - let overview = account.get_participation_overview(event_ids).await?; - Response::AccountParticipationOverview(overview) - } - AccountMethod::GetTransaction { transaction_id } => { - let transaction = account.get_transaction(&transaction_id).await; - Response::Transaction(transaction.as_ref().map(TransactionWithMetadataDto::from).map(Box::new)) - } - #[cfg(feature = "participation")] - AccountMethod::GetVotingPower => { - let voting_power = account.get_voting_power().await?; - Response::VotingPower(voting_power.to_string()) - } - AccountMethod::IncomingTransactions => { - let transactions = account.incoming_transactions().await; - Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) - } - AccountMethod::Outputs { filter_options } => { - let outputs = account.outputs(filter_options).await?; - Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect()) - } - AccountMethod::PendingTransactions => { - let transactions = account.pending_transactions().await; - Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) - } - AccountMethod::PrepareBurn { burn, options } => { - let data = account.prepare_burn(burn, options).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareConsolidateOutputs { params } => { - let data = account.prepare_consolidate_outputs(params).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareCreateAccountOutput { params, options } => { - let data = account.prepare_create_account_output(params, options).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareMeltNativeToken { - token_id, - melt_amount, - options, - } => { - let data = account - .prepare_melt_native_token(token_id, melt_amount, options) - .await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - #[cfg(feature = "participation")] - AccountMethod::PrepareDecreaseVotingPower { amount } => { - let data = account.prepare_decrease_voting_power(amount).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareMintNativeToken { - token_id, - mint_amount, - options, - } => { - let data = account - .prepare_mint_native_token(token_id, mint_amount, options) - .await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - #[cfg(feature = "participation")] - AccountMethod::PrepareIncreaseVotingPower { amount } => { - let data = account.prepare_increase_voting_power(amount).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareMintNfts { params, options } => { - let data = account.prepare_mint_nfts(params, options).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareCreateNativeToken { params, options } => { - let data = account.prepare_create_native_token(params, options).await?; - Response::PreparedCreateNativeTokenTransaction(PreparedCreateNativeTokenTransactionDto::from(&data)) - } - AccountMethod::PrepareOutput { - params, - transaction_options, - } => { - let output = account.prepare_output(*params, transaction_options).await?; - Response::Output(OutputDto::from(&output)) - } - AccountMethod::PrepareSend { params, options } => { - let data = account.prepare_send(params, options).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareSendNativeToken { params, options } => { - let data = account.prepare_send_native_token(params.clone(), options).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareSendNft { params, options } => { - let data = account.prepare_send_nft(params.clone(), options).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - #[cfg(feature = "participation")] - AccountMethod::PrepareStopParticipating { event_id } => { - let data = account.prepare_stop_participating(event_id).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - AccountMethod::PrepareTransaction { outputs, options } => { - let token_supply = account.client().get_token_supply().await?; - let data = account - .prepare_transaction( - outputs - .into_iter() - .map(|o| Ok(Output::try_from_dto_with_params(o, token_supply)?)) - .collect::>>()?, - options, - ) - .await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - #[cfg(feature = "participation")] - AccountMethod::PrepareVote { event_id, answers } => { - let data = account.prepare_vote(event_id, answers).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } - #[cfg(feature = "participation")] - AccountMethod::RegisterParticipationEvents { options } => { - let events = account.register_participation_events(&options).await?; - Response::ParticipationEvents(events) - } - AccountMethod::ReissueTransactionUntilIncluded { - transaction_id, - interval, - max_attempts, - } => { - let block_id = account - .reissue_transaction_until_included(&transaction_id, interval, max_attempts) - .await?; - Response::BlockId(block_id) - } - AccountMethod::Send { - amount, - address, - options, - } => { - let transaction = account.send(amount, address, options).await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - AccountMethod::SendWithParams { params, options } => { - let transaction = account.send_with_params(params, options).await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - AccountMethod::SendOutputs { outputs, options } => { - let token_supply = account.client().get_token_supply().await?; - let transaction = account - .send_outputs( - outputs - .into_iter() - .map(|o| Ok(Output::try_from_dto_with_params(o, token_supply)?)) - .collect::>>()?, - options, - ) - .await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - AccountMethod::SetAlias { alias } => { - account.set_alias(&alias).await?; - Response::Ok - } - AccountMethod::SetDefaultSyncOptions { options } => { - account.set_default_sync_options(options).await?; - Response::Ok - } - AccountMethod::SignAndSubmitTransaction { - prepared_transaction_data, - } => { - let transaction = account - .sign_and_submit_transaction( - PreparedTransactionData::try_from_dto_with_params( - prepared_transaction_data, - account.client().get_protocol_parameters().await?, - )?, - None, - ) - .await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - AccountMethod::SignTransaction { - prepared_transaction_data, - } => { - let signed_transaction_data = account - .sign_transaction(&PreparedTransactionData::try_from_dto(prepared_transaction_data)?) - .await?; - Response::SignedTransactionData(SignedTransactionDataDto::from(&signed_transaction_data)) - } - AccountMethod::SubmitAndStoreTransaction { - signed_transaction_data, - } => { - let signed_transaction_data = SignedTransactionData::try_from_dto_with_params( - signed_transaction_data, - account.client().get_protocol_parameters().await?, - )?; - let transaction = account - .submit_and_store_transaction(signed_transaction_data, None) - .await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - AccountMethod::Sync { options } => Response::Balance(account.sync(options).await?), - AccountMethod::Transactions => { - let transactions = account.transactions().await; - Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) - } - AccountMethod::UnspentOutputs { filter_options } => { - let outputs = account.unspent_outputs(filter_options).await?; - Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect()) - } - }; - Ok(response) -} diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index a22d93eccf..ca5566d420 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -9,7 +9,8 @@ use iota_sdk::{ api::core::OutputWithMetadataResponse, block::{ output::{ - dto::OutputDto, AccountOutput, BasicOutput, FoundryOutput, NftOutput, Output, OutputBuilderAmount, Rent, + dto::OutputDto, AccountOutput, BasicOutput, FoundryOutput, MinimumOutputAmount, NftOutput, Output, + OutputBuilderAmount, }, payload::Payload, SignedBlock, SignedBlockDto, UnsignedBlockDto, @@ -68,7 +69,7 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM if let Some(amount) = amount { OutputBuilderAmount::Amount(amount) } else { - OutputBuilderAmount::MinimumStorageDeposit(client.get_rent_structure().await?) + OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) }, mana, &account_id, @@ -76,7 +77,6 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM unlock_conditions, features, immutable_features, - client.get_token_supply().await?, )?); Response::Output(OutputDto::from(&output)) @@ -92,13 +92,12 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM if let Some(amount) = amount { OutputBuilderAmount::Amount(amount) } else { - OutputBuilderAmount::MinimumStorageDeposit(client.get_rent_structure().await?) + OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) }, mana, native_tokens, unlock_conditions, features, - client.get_token_supply().await?, )?); Response::Output(OutputDto::from(&output)) @@ -116,7 +115,7 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM if let Some(amount) = amount { OutputBuilderAmount::Amount(amount) } else { - OutputBuilderAmount::MinimumStorageDeposit(client.get_rent_structure().await?) + OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) }, native_tokens, serial_number, @@ -124,7 +123,6 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM unlock_conditions, features, immutable_features, - client.get_token_supply().await?, )?); Response::Output(OutputDto::from(&output)) @@ -141,14 +139,13 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM if let Some(amount) = amount { OutputBuilderAmount::Amount(amount) } else { - OutputBuilderAmount::MinimumStorageDeposit(client.get_rent_structure().await?) + OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) }, mana, &nft_id, unlock_conditions, features, immutable_features, - client.get_token_supply().await?, )?); Response::Output(OutputDto::from(&output)) @@ -231,14 +228,24 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM Response::OutputIdsResponse(client.account_output_ids(query_parameters).await?) } ClientMethod::AccountOutputId { account_id } => Response::OutputId(client.account_output_id(account_id).await?), - ClientMethod::NftOutputIds { query_parameters } => { - Response::OutputIdsResponse(client.nft_output_ids(query_parameters).await?) + ClientMethod::AnchorOutputIds { query_parameters } => { + Response::OutputIdsResponse(client.anchor_output_ids(query_parameters).await?) + } + ClientMethod::AnchorOutputId { anchor_id } => Response::OutputId(client.anchor_output_id(anchor_id).await?), + ClientMethod::DelegationOutputIds { query_parameters } => { + Response::OutputIdsResponse(client.delegation_output_ids(query_parameters).await?) + } + ClientMethod::DelegationOutputId { delegation_id } => { + Response::OutputId(client.delegation_output_id(delegation_id).await?) } - ClientMethod::NftOutputId { nft_id } => Response::OutputId(client.nft_output_id(nft_id).await?), ClientMethod::FoundryOutputIds { query_parameters } => { Response::OutputIdsResponse(client.foundry_output_ids(query_parameters).await?) } ClientMethod::FoundryOutputId { foundry_id } => Response::OutputId(client.foundry_output_id(foundry_id).await?), + ClientMethod::NftOutputIds { query_parameters } => { + Response::OutputIdsResponse(client.nft_output_ids(query_parameters).await?) + } + ClientMethod::NftOutputId { nft_id } => Response::OutputId(client.nft_output_id(nft_id).await?), ClientMethod::GetOutputs { output_ids } => { let outputs_response = client .get_outputs_with_metadata(&output_ids) @@ -280,13 +287,11 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM ClientMethod::HexPublicKeyToBech32Address { hex, bech32_hrp } => { Response::Bech32Address(client.hex_public_key_to_bech32_address(&hex, bech32_hrp).await?) } - ClientMethod::MinimumRequiredStorageDeposit { output } => { - let output = Output::try_from_dto_with_params(output, client.get_token_supply().await?)?; - let rent_structure = client.get_rent_structure().await?; - - let minimum_storage_deposit = output.rent_cost(rent_structure); + ClientMethod::ComputeMinimumOutputAmount { output } => { + let output = Output::try_from(output)?; + let storage_score_params = client.get_storage_score_parameters().await?; - Response::MinimumRequiredStorageDeposit(minimum_storage_deposit.to_string()) + Response::OutputAmount(output.minimum_amount(storage_score_params)) } ClientMethod::RequestFundsFromFaucet { url, address } => { Response::Faucet(request_funds_from_faucet(&url, &address).await?) diff --git a/bindings/core/src/method_handler/mod.rs b/bindings/core/src/method_handler/mod.rs index 49be54cf5e..5b39e211fa 100644 --- a/bindings/core/src/method_handler/mod.rs +++ b/bindings/core/src/method_handler/mod.rs @@ -1,7 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod account; mod call_method; mod client; mod secret_manager; diff --git a/bindings/core/src/method_handler/secret_manager.rs b/bindings/core/src/method_handler/secret_manager.rs index cfdde9882c..75d6ddbbfb 100644 --- a/bindings/core/src/method_handler/secret_manager.rs +++ b/bindings/core/src/method_handler/secret_manager.rs @@ -8,7 +8,7 @@ use iota_sdk::client::secret::stronghold::StrongholdSecretManager; use iota_sdk::{ client::{ api::{GetAddressesOptions, PreparedTransactionData}, - secret::{DowncastSecretManager, SecretManage, SignBlock}, + secret::{DowncastSecretManager, SecretManage, SecretManager, SignBlock}, }, types::{ block::{address::ToBech32Ext, core::UnsignedBlock, unlock::Unlock, SignedBlockDto}, @@ -124,6 +124,13 @@ where if let Some(secret_manager) = secret_manager.downcast::() { secret_manager.store_mnemonic(mnemonic).await?; Response::Ok + } else if let Some(secret_manager) = secret_manager.downcast::() { + if let SecretManager::Stronghold(secret_manager) = secret_manager { + secret_manager.store_mnemonic(mnemonic).await?; + Response::Ok + } else { + return Err(iota_sdk::client::Error::SecretManagerMismatch.into()); + } } else { return Err(iota_sdk::client::Error::SecretManagerMismatch.into()); } diff --git a/bindings/core/src/method_handler/utils.rs b/bindings/core/src/method_handler/utils.rs index 4a0432cfec..89a25e8424 100644 --- a/bindings/core/src/method_handler/utils.rs +++ b/bindings/core/src/method_handler/utils.rs @@ -8,7 +8,7 @@ use iota_sdk::{ block::{ address::{AccountAddress, Address, ToBech32Ext}, input::UtxoInput, - output::{AccountId, FoundryId, NftId, Output, OutputId, Rent, TokenId}, + output::{AccountId, FoundryId, MinimumOutputAmount, NftId, Output, OutputId, TokenId}, payload::{signed_transaction::Transaction, SignedTransactionPayload}, SignedBlock, }, @@ -72,9 +72,12 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result { Response::Hash(Transaction::try_from_dto(transaction)?.signing_hash().to_string()) } - UtilsMethod::ComputeStorageDeposit { output, rent } => { - let out = Output::try_from_dto(output)?; - Response::MinimumRequiredStorageDeposit(out.rent_cost(rent).to_string()) + UtilsMethod::ComputeMinimumOutputAmount { + output, + storage_score_parameters: storage_params, + } => { + let out = Output::try_from(output)?; + Response::OutputAmount(out.minimum_amount(storage_params)) } UtilsMethod::VerifyMnemonic { mnemonic } => { let mnemonic = Mnemonic::from(mnemonic); @@ -102,7 +105,7 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result Response::Input(UtxoInput::from(output_id)), UtilsMethod::ComputeSlotCommitmentId { slot_commitment } => Response::SlotCommitmentId(slot_commitment.id()), UtilsMethod::OutputHexBytes { output } => { - let output = Output::try_from_dto(output)?; + let output = Output::try_from(output)?; Response::HexBytes(prefix_hex::encode(output.pack_to_vec())) } }; diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index 01510cace4..6825dc72e0 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -4,68 +4,27 @@ use std::time::Duration; use iota_sdk::{ - types::block::address::ToBech32Ext, - wallet::{account::AccountDetailsDto, Wallet}, + client::api::{ + PreparedTransactionData, PreparedTransactionDataDto, SignedTransactionData, SignedTransactionDataDto, + }, + types::{ + block::{ + address::ToBech32Ext, + output::{dto::OutputDto, Output}, + }, + TryFromDto, + }, + wallet::{types::TransactionWithMetadataDto, OutputDataDto, PreparedCreateNativeTokenTransactionDto, Wallet}, }; -use super::account::call_account_method_internal; -use crate::{method::WalletMethod, response::Response, Result}; +use crate::{method::WalletMethod, response::Response}; /// Call a wallet method. -pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletMethod) -> Result { +pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletMethod) -> crate::Result { let response = match method { - WalletMethod::CreateAccount { - alias, - bech32_hrp, - addresses, - } => { - let mut builder = wallet.create_account(); - - if let Some(alias) = alias { - builder = builder.with_alias(alias); - } - - if let Some(bech32_hrp) = bech32_hrp { - builder = builder.with_bech32_hrp(bech32_hrp); - } - - if let Some(addresses) = addresses { - builder = builder.with_addresses(addresses); - } - - match builder.finish().await { - Ok(account) => { - let account = account.details().await; - Response::Account(AccountDetailsDto::from(&*account)) - } - Err(e) => return Err(e.into()), - } - } - WalletMethod::GetAccount { account_id } => { - let account = wallet.get_account(account_id.clone()).await?; - let account = account.details().await; - Response::Account(AccountDetailsDto::from(&*account)) - } - WalletMethod::GetAccountIndexes => { - let accounts = wallet.get_accounts().await?; - let mut account_indexes = Vec::with_capacity(accounts.len()); - for account in accounts.iter() { - account_indexes.push(*account.details().await.index()); - } - Response::AccountIndexes(account_indexes) - } - WalletMethod::GetAccounts => { - let accounts = wallet.get_accounts().await?; - let mut account_dtos = Vec::with_capacity(accounts.len()); - for account in accounts { - let account = account.details().await; - account_dtos.push(AccountDetailsDto::from(&*account)); - } - Response::Accounts(account_dtos) - } - WalletMethod::CallAccountMethod { account_id, method } => { - let account = wallet.get_account(account_id).await?; - call_account_method_internal(&account, method).await? + WalletMethod::Accounts => { + let accounts = wallet.accounts().await; + Response::OutputsData(accounts.iter().map(OutputDataDto::from).collect()) } #[cfg(feature = "stronghold")] WalletMethod::Backup { destination, password } => { @@ -92,26 +51,6 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let is_available = wallet.is_stronghold_password_available().await?; Response::Bool(is_available) } - WalletMethod::RecoverAccounts { - account_start_index, - account_gap_limit, - address_gap_limit, - sync_options, - } => { - let accounts = wallet - .recover_accounts(account_start_index, account_gap_limit, address_gap_limit, sync_options) - .await?; - let mut account_dtos = Vec::with_capacity(accounts.len()); - for account in accounts { - let account = account.details().await; - account_dtos.push(AccountDetailsDto::from(&*account)); - } - Response::Accounts(account_dtos) - } - WalletMethod::RemoveLatestAccount => { - wallet.remove_latest_account().await?; - Response::Ok - } #[cfg(feature = "stronghold")] WalletMethod::RestoreBackup { source, @@ -150,7 +89,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let bech32_hrp = match bech32_hrp { Some(bech32_hrp) => bech32_hrp, - None => wallet.get_bech32_hrp().await?, + None => *wallet.address().await.hrp(), }; Response::Bech32Address(address.to_bech32(bech32_hrp)) @@ -199,6 +138,271 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM wallet.update_node_auth(url, auth).await?; Response::Ok } + WalletMethod::ClaimableOutputs { outputs_to_claim } => { + let output_ids = wallet.claimable_outputs(outputs_to_claim).await?; + Response::OutputIds(output_ids) + } + WalletMethod::ClaimOutputs { output_ids_to_claim } => { + let transaction = wallet.claim_outputs(output_ids_to_claim.to_vec()).await?; + Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) + } + #[cfg(feature = "participation")] + WalletMethod::DeregisterParticipationEvent { event_id } => { + wallet.deregister_participation_event(&event_id).await?; + Response::Ok + } + WalletMethod::GetAddress => { + let address = wallet.address().await; + Response::Address(address) + } + WalletMethod::GetBalance => Response::Balance(wallet.balance().await?), + WalletMethod::GetFoundryOutput { token_id } => { + let output = wallet.get_foundry_output(token_id).await?; + Response::Output(OutputDto::from(&output)) + } + WalletMethod::GetIncomingTransaction { transaction_id } => { + let transaction = wallet.get_incoming_transaction(&transaction_id).await; + + transaction.map_or_else( + || Response::Transaction(None), + |transaction| Response::Transaction(Some(Box::new(TransactionWithMetadataDto::from(&transaction)))), + ) + } + WalletMethod::GetOutput { output_id } => { + let output_data = wallet.get_output(&output_id).await; + Response::OutputData(output_data.as_ref().map(OutputDataDto::from).map(Box::new)) + } + #[cfg(feature = "participation")] + WalletMethod::GetParticipationEvent { event_id } => { + let event_and_nodes = wallet.get_participation_event(event_id).await?; + Response::ParticipationEvent(event_and_nodes) + } + #[cfg(feature = "participation")] + WalletMethod::GetParticipationEventIds { node, event_type } => { + let event_ids = wallet.get_participation_event_ids(&node, event_type).await?; + Response::ParticipationEventIds(event_ids) + } + #[cfg(feature = "participation")] + WalletMethod::GetParticipationEventStatus { event_id } => { + let event_status = wallet.get_participation_event_status(&event_id).await?; + Response::ParticipationEventStatus(event_status) + } + #[cfg(feature = "participation")] + WalletMethod::GetParticipationEvents => { + let events = wallet.get_participation_events().await?; + Response::ParticipationEvents(events) + } + #[cfg(feature = "participation")] + WalletMethod::GetParticipationOverview { event_ids } => { + let overview = wallet.get_participation_overview(event_ids).await?; + Response::ParticipationOverview(overview) + } + WalletMethod::GetTransaction { transaction_id } => { + let transaction = wallet.get_transaction(&transaction_id).await; + Response::Transaction(transaction.as_ref().map(TransactionWithMetadataDto::from).map(Box::new)) + } + #[cfg(feature = "participation")] + WalletMethod::GetVotingPower => { + let voting_power = wallet.get_voting_power().await?; + Response::VotingPower(voting_power.to_string()) + } + WalletMethod::ImplicitAccountCreationAddress => { + let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; + Response::Bech32Address(implicit_account_creation_address) + } + WalletMethod::ImplicitAccounts => { + let implicit_accounts = wallet.implicit_accounts().await; + Response::OutputsData(implicit_accounts.iter().map(OutputDataDto::from).collect()) + } + WalletMethod::IncomingTransactions => { + let transactions = wallet.incoming_transactions().await; + Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) + } + WalletMethod::Outputs { filter_options } => { + let outputs = wallet.outputs(filter_options).await; + Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect()) + } + WalletMethod::PendingTransactions => { + let transactions = wallet.pending_transactions().await; + Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) + } + WalletMethod::PrepareBurn { burn, options } => { + let data = wallet.prepare_burn(burn, options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareConsolidateOutputs { params } => { + let data = wallet.prepare_consolidate_outputs(params).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareCreateAccountOutput { params, options } => { + let data = wallet.prepare_create_account_output(params, options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareMeltNativeToken { + token_id, + melt_amount, + options, + } => { + let data = wallet.prepare_melt_native_token(token_id, melt_amount, options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + #[cfg(feature = "participation")] + WalletMethod::PrepareDecreaseVotingPower { amount } => { + let data = wallet.prepare_decrease_voting_power(amount).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareMintNativeToken { + token_id, + mint_amount, + options, + } => { + let data = wallet.prepare_mint_native_token(token_id, mint_amount, options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + #[cfg(feature = "participation")] + WalletMethod::PrepareIncreaseVotingPower { amount } => { + let data = wallet.prepare_increase_voting_power(amount).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareMintNfts { params, options } => { + let data = wallet.prepare_mint_nfts(params, options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareCreateNativeToken { params, options } => { + let data = wallet.prepare_create_native_token(params, options).await?; + Response::PreparedCreateNativeTokenTransaction(PreparedCreateNativeTokenTransactionDto::from(&data)) + } + WalletMethod::PrepareOutput { + params, + transaction_options, + } => { + let output = wallet.prepare_output(*params, transaction_options).await?; + Response::Output(OutputDto::from(&output)) + } + WalletMethod::PrepareSend { params, options } => { + let data = wallet.prepare_send(params, options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareSendNativeTokens { params, options } => { + let data = wallet.prepare_send_native_tokens(params.clone(), options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareSendNft { params, options } => { + let data = wallet.prepare_send_nft(params.clone(), options).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + #[cfg(feature = "participation")] + WalletMethod::PrepareStopParticipating { event_id } => { + let data = wallet.prepare_stop_participating(event_id).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + WalletMethod::PrepareTransaction { outputs, options } => { + let data = wallet + .prepare_transaction( + outputs + .into_iter() + .map(Output::try_from) + .collect::, _>>()?, + options, + ) + .await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + #[cfg(feature = "participation")] + WalletMethod::PrepareVote { event_id, answers } => { + let data = wallet.prepare_vote(event_id, answers).await?; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } + #[cfg(feature = "participation")] + WalletMethod::RegisterParticipationEvents { options } => { + let events = wallet.register_participation_events(&options).await?; + Response::ParticipationEvents(events) + } + WalletMethod::ReissueTransactionUntilIncluded { + transaction_id, + interval, + max_attempts, + } => { + let block_id = wallet + .reissue_transaction_until_included(&transaction_id, interval, max_attempts) + .await?; + Response::BlockId(block_id) + } + WalletMethod::Send { + amount, + address, + options, + } => { + let transaction = wallet.send(amount, address, options).await?; + Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) + } + WalletMethod::SendWithParams { params, options } => { + let transaction = wallet.send_with_params(params, options).await?; + Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) + } + WalletMethod::SendOutputs { outputs, options } => { + let transaction = wallet + .send_outputs( + outputs + .into_iter() + .map(Output::try_from) + .collect::, _>>()?, + options, + ) + .await?; + Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) + } + WalletMethod::SetAlias { alias } => { + wallet.set_alias(&alias).await?; + Response::Ok + } + WalletMethod::SetDefaultSyncOptions { options } => { + wallet.set_default_sync_options(options).await?; + Response::Ok + } + WalletMethod::SignAndSubmitTransaction { + prepared_transaction_data, + } => { + let transaction = wallet + .sign_and_submit_transaction( + PreparedTransactionData::try_from_dto_with_params( + prepared_transaction_data, + wallet.client().get_protocol_parameters().await?, + )?, + None, + ) + .await?; + Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) + } + WalletMethod::SignTransaction { + prepared_transaction_data, + } => { + let signed_transaction_data = wallet + .sign_transaction(&PreparedTransactionData::try_from_dto(prepared_transaction_data)?) + .await?; + Response::SignedTransactionData(SignedTransactionDataDto::from(&signed_transaction_data)) + } + WalletMethod::SubmitAndStoreTransaction { + signed_transaction_data, + } => { + let signed_transaction_data = SignedTransactionData::try_from_dto_with_params( + signed_transaction_data, + wallet.client().get_protocol_parameters().await?, + )?; + let transaction = wallet + .submit_and_store_transaction(signed_transaction_data, None) + .await?; + Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) + } + WalletMethod::Sync { options } => Response::Balance(wallet.sync(options).await?), + WalletMethod::Transactions => { + let transactions = wallet.transactions().await; + Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) + } + WalletMethod::UnspentOutputs { filter_options } => { + let outputs = wallet.unspent_outputs(filter_options).await; + Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect()) + } }; Ok(response) } diff --git a/bindings/core/src/response.rs b/bindings/core/src/response.rs index 5ba2b88ef8..5db10c788e 100644 --- a/bindings/core/src/response.rs +++ b/bindings/core/src/response.rs @@ -33,16 +33,17 @@ use iota_sdk::{ BlockId, SignedBlockDto, UnsignedBlockDto, }, }, - wallet::account::{ - types::{AddressWithUnspentOutputs, Balance, Bip44Address, OutputDataDto, TransactionWithMetadataDto}, - AccountDetailsDto, PreparedCreateNativeTokenTransactionDto, + utils::serde::string, + wallet::{ + types::{Balance, OutputDataDto, TransactionWithMetadataDto}, + PreparedCreateNativeTokenTransactionDto, }, }; use serde::Serialize; #[cfg(feature = "participation")] use { iota_sdk::types::api::plugins::participation::types::{ParticipationEventId, ParticipationEventStatus}, - iota_sdk::wallet::account::{AccountParticipationOverview, ParticipationEventWithNodes}, + iota_sdk::wallet::{ParticipationEventWithNodes, ParticipationOverview}, std::collections::HashMap, }; @@ -130,15 +131,19 @@ pub enum Response { Outputs(Vec), /// Response for: /// - [`AccountOutputId`](crate::method::ClientMethod::AccountOutputId) + /// - [`AnchorOutputId`](crate::method::ClientMethod::AnchorOutputId) + /// - [`DelegationOutputId`](crate::method::ClientMethod::DelegationOutputId) /// - [`FoundryOutputId`](crate::method::ClientMethod::FoundryOutputId) /// - [`NftOutputId`](crate::method::ClientMethod::NftOutputId) OutputId(OutputId), /// Response for: - /// - [`AccountOutputIds`](crate::method::ClientMethod::AccountOutputIds) + /// - [`OutputIds`](crate::method::ClientMethod::OutputIds) /// - [`BasicOutputIds`](crate::method::ClientMethod::BasicOutputIds) + /// - [`AccountOutputIds`](crate::method::ClientMethod::AccountOutputIds) + /// - [`AnchorOutputIds`](crate::method::ClientMethod::AnchorOutputIds) + /// - [`DelegationOutputIds`](crate::method::ClientMethod::DelegationOutputIds) /// - [`FoundryOutputIds`](crate::method::ClientMethod::FoundryOutputIds) /// - [`NftOutputIds`](crate::method::ClientMethod::NftOutputIds) - /// - [`OutputIds`](crate::method::ClientMethod::OutputIds) OutputIdsResponse(OutputIdsResponse), /// Response for: /// - [`FindBlocks`](crate::method::ClientMethod::FindBlocks) @@ -194,14 +199,15 @@ pub enum Response { /// - [`BuildBasicOutput`](crate::method::ClientMethod::BuildBasicOutput) /// - [`BuildFoundryOutput`](crate::method::ClientMethod::BuildFoundryOutput) /// - [`BuildNftOutput`](crate::method::ClientMethod::BuildNftOutput) - /// - [`GetFoundryOutput`](crate::method::AccountMethod::GetFoundryOutput) - /// - [`PrepareOutput`](crate::method::AccountMethod::PrepareOutput) + /// - [`GetFoundryOutput`](crate::method::WalletMethod::GetFoundryOutput) + /// - [`PrepareOutput`](crate::method::WalletMethod::PrepareOutput) Output(OutputDto), /// Response for: /// - [`AccountIdToBech32`](crate::method::ClientMethod::AccountIdToBech32) /// - [`HexPublicKeyToBech32Address`](crate::method::ClientMethod::HexPublicKeyToBech32Address) /// - [`HexToBech32`](crate::method::ClientMethod::HexToBech32) /// - [`NftIdToBech32`](crate::method::ClientMethod::NftIdToBech32) + /// - [`ImplicitAccountCreationAddress`](crate::method::WalletMethod::ImplicitAccountCreationAddress) Bech32Address(Bech32Address), /// - [`Faucet`](crate::method::ClientMethod::RequestFundsFromFaucet) Faucet(String), @@ -217,7 +223,7 @@ pub enum Response { /// - [`BlockId`](crate::method::UtilsMethod::BlockId) /// - [`PostBlock`](crate::method::ClientMethod::PostBlock) /// - [`PostBlockRaw`](crate::method::ClientMethod::PostBlockRaw) - /// - [`ReissueTransactionUntilIncluded`](crate::method::AccountMethod::ReissueTransactionUntilIncluded) + /// - [`ReissueTransactionUntilIncluded`](crate::method::WalletMethod::ReissueTransactionUntilIncluded) BlockId(BlockId), /// Response for: /// - [`GetHealth`](crate::method::ClientMethod::GetHealth) @@ -229,12 +235,12 @@ pub enum Response { /// - [`Backup`](crate::method::WalletMethod::Backup), /// - [`ClearListeners`](crate::method::WalletMethod::ClearListeners) /// - [`ClearStrongholdPassword`](crate::method::WalletMethod::ClearStrongholdPassword), - /// - [`DeregisterParticipationEvent`](crate::method::AccountMethod::DeregisterParticipationEvent), + /// - [`DeregisterParticipationEvent`](crate::method::WalletMethod::DeregisterParticipationEvent), /// - [`EmitTestEvent`](crate::method::WalletMethod::EmitTestEvent), /// - [`RestoreBackup`](crate::method::WalletMethod::RestoreBackup), - /// - [`SetAlias`](crate::method::AccountMethod::SetAlias), + /// - [`SetAlias`](crate::method::WalletMethod::SetAlias), /// - [`SetClientOptions`](crate::method::WalletMethod::SetClientOptions), - /// - [`SetDefaultSyncOptions`](crate::method::AccountMethod::SetDefaultSyncOptions), + /// - [`SetDefaultSyncOptions`](crate::method::WalletMethod::SetDefaultSyncOptions), /// - [`SetStrongholdPassword`](crate::method::WalletMethod::SetStrongholdPassword), /// - [`SetStrongholdPasswordClearInterval`](crate::method::WalletMethod::SetStrongholdPasswordClearInterval), /// - [`StartBackgroundSync`](crate::method::WalletMethod::StartBackgroundSync), @@ -248,109 +254,93 @@ pub enum Response { // wallet responses /// Response for: - /// - [`CreateAccount`](crate::method::WalletMethod::CreateAccount), - /// - [`GetAccount`](crate::method::WalletMethod::GetAccount) - Account(AccountDetailsDto), - /// Response for: - /// - [`GetAccountIndexes`](crate::method::WalletMethod::GetAccountIndexes) - AccountIndexes(Vec), + /// - [`GetAddress`](crate::method::WalletMethod::GetAddress) + Address(Bech32Address), /// Response for: - /// - [`GetAccounts`](crate::method::WalletMethod::GetAccounts) - Accounts(Vec), + /// - [`ClientMethod::ComputeMinimumOutputAmount`](crate::method::ClientMethod::ComputeMinimumOutputAmount) + /// - [`UtilsMethod::ComputeMinimumOutputAmount`](crate::method::UtilsMethod::ComputeMinimumOutputAmount) + OutputAmount(#[serde(with = "string")] u64), /// Response for: - /// - [`Addresses`](crate::method::AccountMethod::Addresses) - Addresses(Vec), - /// Response for: - /// - [`AddressesWithUnspentOutputs`](crate::method::AccountMethod::AddressesWithUnspentOutputs) - AddressesWithUnspentOutputs(Vec), - /// Response for: - /// - [`MinimumRequiredStorageDeposit`](crate::method::ClientMethod::MinimumRequiredStorageDeposit) - /// - [`ComputeStorageDeposit`](crate::method::UtilsMethod::ComputeStorageDeposit) - MinimumRequiredStorageDeposit(String), - /// Response for: - /// - [`ClaimableOutputs`](crate::method::AccountMethod::ClaimableOutputs) + /// - [`ClaimableOutputs`](crate::method::WalletMethod::ClaimableOutputs) OutputIds(Vec), /// Response for: - /// - [`GetOutput`](crate::method::AccountMethod::GetOutput) + /// - [`GetOutput`](crate::method::WalletMethod::GetOutput) OutputData(Option>), /// Response for: - /// - [`Outputs`](crate::method::AccountMethod::Outputs), - /// - [`UnspentOutputs`](crate::method::AccountMethod::UnspentOutputs) + /// - [`Outputs`](crate::method::WalletMethod::Outputs), + /// - [`UnspentOutputs`](crate::method::WalletMethod::UnspentOutputs) OutputsData(Vec), /// Response for: - /// - [`PrepareBurn`](crate::method::AccountMethod::PrepareBurn), - /// - [`PrepareConsolidateOutputs`](crate::method::AccountMethod::PrepareConsolidateOutputs) - /// - [`PrepareCreateAccountOutput`](crate::method::AccountMethod::PrepareCreateAccountOutput) - /// - [`PrepareDecreaseVotingPower`](crate::method::AccountMethod::PrepareDecreaseVotingPower) - /// - [`PrepareIncreaseVotingPower`](crate::method::AccountMethod::PrepareIncreaseVotingPower) - /// - [`PrepareMeltNativeToken`](crate::method::AccountMethod::PrepareMeltNativeToken) - /// - [`PrepareMintNativeToken`](crate::method::AccountMethod::PrepareMintNativeToken), - /// - [`PrepareMintNfts`](crate::method::AccountMethod::PrepareMintNfts), - /// - [`PrepareSend`](crate::method::AccountMethod::PrepareSend), - /// - [`PrepareSendNativeTokens`](crate::method::AccountMethod::PrepareSendNativeTokens), - /// - [`PrepareSendNft`](crate::method::AccountMethod::PrepareSendNft), - /// - [`PrepareStopParticipating`](crate::method::AccountMethod::PrepareStopParticipating) - /// - [`PrepareTransaction`](crate::method::AccountMethod::PrepareTransaction) - /// - [`PrepareVote`](crate::method::AccountMethod::PrepareVote) + /// - [`PrepareBurn`](crate::method::WalletMethod::PrepareBurn), + /// - [`PrepareConsolidateOutputs`](crate::method::WalletMethod::PrepareConsolidateOutputs) + /// - [`PrepareCreateAccountOutput`](crate::method::WalletMethod::PrepareCreateAccountOutput) + /// - [`PrepareDecreaseVotingPower`](crate::method::WalletMethod::PrepareDecreaseVotingPower) + /// - [`PrepareIncreaseVotingPower`](crate::method::WalletMethod::PrepareIncreaseVotingPower) + /// - [`PrepareMeltNativeToken`](crate::method::WalletMethod::PrepareMeltNativeToken) + /// - [`PrepareMintNativeToken`](crate::method::WalletMethod::PrepareMintNativeToken), + /// - [`PrepareMintNfts`](crate::method::WalletMethod::PrepareMintNfts), + /// - [`PrepareSend`](crate::method::WalletMethod::PrepareSend), + /// - [`PrepareSendNativeTokens`](crate::method::WalletMethod::PrepareSendNativeTokens), + /// - [`PrepareSendNft`](crate::method::WalletMethod::PrepareSendNft), + /// - [`PrepareStopParticipating`](crate::method::WalletMethod::PrepareStopParticipating) + /// - [`PrepareTransaction`](crate::method::WalletMethod::PrepareTransaction) + /// - [`PrepareVote`](crate::method::WalletMethod::PrepareVote) PreparedTransaction(PreparedTransactionDataDto), /// Response for: - /// - [`PrepareCreateNativeToken`](crate::method::AccountMethod::PrepareCreateNativeToken), + /// - [`PrepareCreateNativeToken`](crate::method::WalletMethod::PrepareCreateNativeToken), PreparedCreateNativeTokenTransaction(PreparedCreateNativeTokenTransactionDto), /// Response for: - /// - [`GetIncomingTransaction`](crate::method::AccountMethod::GetIncomingTransaction) - /// - [`GetTransaction`](crate::method::AccountMethod::GetTransaction), + /// - [`GetIncomingTransaction`](crate::method::WalletMethod::GetIncomingTransaction) + /// - [`GetTransaction`](crate::method::WalletMethod::GetTransaction), Transaction(Option>), /// Response for: - /// - [`IncomingTransactions`](crate::method::AccountMethod::IncomingTransactions) - /// - [`PendingTransactions`](crate::method::AccountMethod::PendingTransactions), - /// - [`Transactions`](crate::method::AccountMethod::Transactions), + /// - [`IncomingTransactions`](crate::method::WalletMethod::IncomingTransactions) + /// - [`PendingTransactions`](crate::method::WalletMethod::PendingTransactions), + /// - [`Transactions`](crate::method::WalletMethod::Transactions), Transactions(Vec), /// Response for: - /// - [`SignTransaction`](crate::method::AccountMethod::SignTransaction) + /// - [`SignTransaction`](crate::method::WalletMethod::SignTransaction) SignedTransactionData(SignedTransactionDataDto), /// Response for: - /// - [`GenerateEd25519Addresses`](crate::method::AccountMethod::GenerateEd25519Addresses) - GeneratedAccountAddresses(Vec), - /// Response for: - /// - [`GetBalance`](crate::method::AccountMethod::GetBalance), - /// - [`Sync`](crate::method::AccountMethod::Sync) + /// - [`GetBalance`](crate::method::WalletMethod::GetBalance), + /// - [`Sync`](crate::method::WalletMethod::Sync) Balance(Balance), /// Response for: - /// - [`ClaimOutputs`](crate::method::AccountMethod::ClaimOutputs) - /// - [`Send`](crate::method::AccountMethod::Send) - /// - [`SendOutputs`](crate::method::AccountMethod::SendOutputs) - /// - [`SignAndSubmitTransaction`](crate::method::AccountMethod::SignAndSubmitTransaction) - /// - [`SubmitAndStoreTransaction`](crate::method::AccountMethod::SubmitAndStoreTransaction) + /// - [`ClaimOutputs`](crate::method::WalletMethod::ClaimOutputs) + /// - [`Send`](crate::method::WalletMethod::Send) + /// - [`SendOutputs`](crate::method::WalletMethod::SendOutputs) + /// - [`SignAndSubmitTransaction`](crate::method::WalletMethod::SignAndSubmitTransaction) + /// - [`SubmitAndStoreTransaction`](crate::method::WalletMethod::SubmitAndStoreTransaction) SentTransaction(TransactionWithMetadataDto), /// Response for: - /// - [`GetParticipationEvent`](crate::method::AccountMethod::GetParticipationEvent) + /// - [`GetParticipationEvent`](crate::method::WalletMethod::GetParticipationEvent) #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] ParticipationEvent(Option), /// Response for: - /// - [`GetParticipationEventIds`](crate::method::AccountMethod::GetParticipationEventIds) + /// - [`GetParticipationEventIds`](crate::method::WalletMethod::GetParticipationEventIds) #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] ParticipationEventIds(Vec), /// Response for: - /// - [`GetParticipationEventStatus`](crate::method::AccountMethod::GetParticipationEventStatus) + /// - [`GetParticipationEventStatus`](crate::method::WalletMethod::GetParticipationEventStatus) #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] ParticipationEventStatus(ParticipationEventStatus), /// Response for: - /// - [`GetParticipationEvents`](crate::method::AccountMethod::GetParticipationEvents) - /// - [`RegisterParticipationEvents`](crate::method::AccountMethod::RegisterParticipationEvents) + /// - [`GetParticipationEvents`](crate::method::WalletMethod::GetParticipationEvents) + /// - [`RegisterParticipationEvents`](crate::method::WalletMethod::RegisterParticipationEvents) #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] ParticipationEvents(HashMap), /// Response for: - /// - [`GetVotingPower`](crate::method::AccountMethod::GetVotingPower) + /// - [`GetVotingPower`](crate::method::WalletMethod::GetVotingPower) #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] VotingPower(String), /// Response for: - /// - [`GetParticipationOverview`](crate::method::AccountMethod::GetParticipationOverview) + /// - [`GetParticipationOverview`](crate::method::WalletMethod::GetParticipationOverview) #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - AccountParticipationOverview(AccountParticipationOverview), + ParticipationOverview(ParticipationOverview), } diff --git a/bindings/core/tests/combined.rs b/bindings/core/tests/combined.rs index 7ad2e68b48..79d838ed83 100644 --- a/bindings/core/tests/combined.rs +++ b/bindings/core/tests/combined.rs @@ -17,17 +17,16 @@ use iota_sdk::{ }, TryFromDto, }, - wallet::account::types::AccountIdentifier, }; use iota_sdk_bindings_core::{ - call_client_method, call_secret_manager_method, AccountMethod, CallMethod, ClientMethod, Response, Result, - SecretManagerMethod, WalletMethod, WalletOptions, + call_client_method, call_secret_manager_method, CallMethod, ClientMethod, Response, Result, SecretManagerMethod, + WalletMethod, WalletOptions, }; use pretty_assertions::assert_eq; #[tokio::test] -async fn create_account() -> Result<()> { - let storage_path = "test-storage/create_account"; +async fn create_wallet() -> Result<()> { + let storage_path = "test-storage/create_wallet"; std::fs::remove_dir_all(storage_path).ok(); let secret_manager = r#"{"Mnemonic":"about solution utility exist rail budget vacuum major survey clerk pave ankle wealth gym gossip still medal expect strong rely amazing inspire lazy lunar"}"#; @@ -44,34 +43,13 @@ async fn create_account() -> Result<()> { let wallet = WalletOptions::default() .with_storage_path(storage_path.to_string()) .with_client_options(ClientBuilder::new().from_json(client_options).unwrap()) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .with_secret_manager(serde_json::from_str::(secret_manager).unwrap()) .build() .await?; - // create an account let response = wallet - .call_method(WalletMethod::CreateAccount { - alias: None, - bech32_hrp: None, - addresses: None, - }) - .await; - - match response { - Response::Account(account) => { - assert_eq!(account.index, 0); - let id = account.index; - println!("Created account index: {id}") - } - _ => panic!("unexpected response {response:?}"), - } - - let response = wallet - .call_method(WalletMethod::CallAccountMethod { - account_id: AccountIdentifier::Index(0), - method: AccountMethod::UnspentOutputs { filter_options: None }, - }) + .call_method(WalletMethod::UnspentOutputs { filter_options: None }) .await; match response { @@ -83,114 +61,6 @@ async fn create_account() -> Result<()> { Ok(()) } -#[tokio::test] -async fn verify_accounts() -> Result<()> { - let storage_path = "test-storage/verify_accounts"; - std::fs::remove_dir_all(storage_path).ok(); - - let secret_manager = r#"{"Mnemonic":"about solution utility exist rail budget vacuum major survey clerk pave ankle wealth gym gossip still medal expect strong rely amazing inspire lazy lunar"}"#; - let client_options = r#"{ - "nodes":[ - { - "url":"http://localhost:14265", - "auth":null, - "disabled":false - } - ] - }"#; - - let wallet = WalletOptions::default() - .with_storage_path(storage_path.to_string()) - .with_client_options(ClientBuilder::new().from_json(client_options).unwrap()) - .with_coin_type(SHIMMER_COIN_TYPE) - .with_secret_manager(serde_json::from_str::(secret_manager).unwrap()) - .build() - .await?; - - let mut account_details = BTreeMap::new(); - let mut handle_response = |response| match response { - Response::Account(account) => { - account_details.insert(account.index, account); - } - _ => panic!("unexpected response {response:?}"), - }; - - // Create a few accounts - for alias in ["Alice", "Bob", "Roger", "Denise", "Farquad", "Pikachu"] { - handle_response( - wallet - .call_method(WalletMethod::CreateAccount { - alias: Some(alias.to_owned()), - bech32_hrp: None, - addresses: None, - }) - .await, - ); - } - - // Remove latest account - match wallet.call_method(WalletMethod::RemoveLatestAccount).await { - Response::Ok => {} - response => panic!("unexpected response {response:?}"), - } - - account_details.pop_last(); - - // Get individual account details - for account in account_details.values() { - // By Index - match wallet - .call_method(WalletMethod::GetAccount { - account_id: account.index.into(), - }) - .await - { - Response::Account(details) => { - assert_eq!(&account_details[&details.index], &details); - } - response => panic!("unexpected response {response:?}"), - } - - // By Name - match wallet - .call_method(WalletMethod::GetAccount { - account_id: account.alias.as_str().into(), - }) - .await - { - Response::Account(details) => { - assert_eq!(&account_details[&details.index], &details); - } - response => panic!("unexpected response {response:?}"), - } - } - - // Get account details - match wallet.call_method(WalletMethod::GetAccounts).await { - Response::Accounts(details) => { - assert_eq!(account_details.len(), details.len()); - for detail in details { - assert_eq!(&account_details[&detail.index], &detail); - } - } - response => panic!("unexpected response {response:?}"), - } - - // Get account indexes - match wallet.call_method(WalletMethod::GetAccountIndexes).await { - Response::AccountIndexes(indexes) => { - assert_eq!(account_details.len(), indexes.len()); - for index in indexes { - assert!(account_details.contains_key(&index)); - } - } - response => panic!("unexpected response {response:?}"), - } - - std::fs::remove_dir_all(storage_path).ok(); - Ok(()) -} - #[tokio::test] async fn client_from_wallet() -> Result<()> { let storage_path = "test-storage/client_from_wallet"; @@ -210,32 +80,14 @@ async fn client_from_wallet() -> Result<()> { let wallet = WalletOptions::default() .with_storage_path(storage_path.to_string()) .with_client_options(ClientBuilder::new().from_json(client_options).unwrap()) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .with_secret_manager(serde_json::from_str::(secret_manager).unwrap()) .build() .await?; - // create an account - let response = wallet - .call_method(WalletMethod::CreateAccount { - alias: None, - bech32_hrp: None, - addresses: None, - }) - .await; - - match response { - Response::Account(account) => { - assert_eq!(account.index, 0); - let id = account.index; - println!("Created account index: {id}") - } - _ => panic!("unexpected response {response:?}"), - } - // TODO reenable // // Send ClientMethod via the client from the wallet - // let response = wallet.get_accounts().await?[0] + // let response = wallet // .client() // .call_method(ClientMethod::GetHealth) // .await; diff --git a/bindings/core/tests/secrets_debug.rs b/bindings/core/tests/secrets_debug.rs index 79e35d6052..6f57d1eba5 100644 --- a/bindings/core/tests/secrets_debug.rs +++ b/bindings/core/tests/secrets_debug.rs @@ -26,6 +26,6 @@ fn method_interface_secrets_debug() { let wallet_options = WalletOptions::default().with_secret_manager(SecretManagerDto::Placeholder); assert_eq!( format!("{:?}", wallet_options), - "WalletOptions { storage_path: None, client_options: None, coin_type: None, secret_manager: Some() }" + "WalletOptions { address: None, alias: None, bip_path: None, client_options: None, secret_manager: Some(), storage_path: None }" ); } diff --git a/bindings/core/tests/serialize_error.rs b/bindings/core/tests/serialize_error.rs index 472044f86b..67c599b53f 100644 --- a/bindings/core/tests/serialize_error.rs +++ b/bindings/core/tests/serialize_error.rs @@ -1,20 +1,37 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::{client::Error as ClientError, wallet::Error as WalletError}; +use crypto::keys::bip44::Bip44; +use iota_sdk::{ + client::{constants::SHIMMER_COIN_TYPE, Error as ClientError}, + wallet::Error as WalletError, +}; use iota_sdk_bindings_core::Error; use pretty_assertions::assert_eq; #[test] fn custom_error_serialization() { + // testing a unit-type-like error let error = Error::Client(ClientError::HealthyNodePoolEmpty); assert_eq!( serde_json::to_string(&error).unwrap(), "{\"type\":\"client\",\"error\":\"no healthy node available\"}" ); - let error = Error::Wallet(WalletError::AccountNotFound("Alice".to_string())); + + // testing a tuple-like error + let error = Error::Wallet(WalletError::InvalidMnemonic("nilly willy".to_string())); + assert_eq!( + serde_json::to_string(&error).unwrap(), + "{\"type\":\"wallet\",\"error\":\"invalid mnemonic: nilly willy\"}" + ); + + // testing a struct-like error + let error = Error::Wallet(WalletError::BipPathMismatch { + old_bip_path: None, + new_bip_path: Some(Bip44::new(SHIMMER_COIN_TYPE)), + }); assert_eq!( serde_json::to_string(&error).unwrap(), - "{\"type\":\"wallet\",\"error\":\"account Alice not found\"}" + "{\"type\":\"wallet\",\"error\":\"BIP44 mismatch: Some(Bip44 { coin_type: 4219, account: 0, change: 0, address_index: 0 }), existing bip path is: None\"}" ); } diff --git a/bindings/nodejs/Cargo.toml b/bindings/nodejs/Cargo.toml index 1f3e6d8dd0..cf4f515cb1 100644 --- a/bindings/nodejs/Cargo.toml +++ b/bindings/nodejs/Cargo.toml @@ -18,6 +18,7 @@ crate-type = ["cdylib"] doc = false [dependencies] +async-trait = { version = "0.1.73", default-features = false } iota-sdk-bindings-core = { path = "../core", default-features = false, features = [ "events", "ledger_nano", @@ -28,17 +29,17 @@ iota-sdk-bindings-core = { path = "../core", default-features = false, features "mqtt", "private_key_secret_manager", ] } - log = { version = "0.4.20", default-features = false } -neon = { version = "0.10.1", default-features = false, features = [ - "napi-6", - "event-queue-api", - "promise-api", -] } +napi = { version = "2.13.3", default-features = false, features = ["async"] } +napi-derive = { version = "2.13.0", default-features = false } once_cell = { version = "1.18.0", default-features = false } serde_json = { version = "1.0.107", default-features = false } +thiserror = { version = "1.0.49", default-features = false } tokio = { version = "1.33.0", default-features = false } +[build-dependencies] +napi-build = { version = "2.0.1", default-features = false } + [profile.production] codegen-units = 1 inherits = "release" diff --git a/bindings/nodejs/README.md b/bindings/nodejs/README.md index d47c54c055..abac727e55 100644 --- a/bindings/nodejs/README.md +++ b/bindings/nodejs/README.md @@ -73,8 +73,7 @@ If you have already installed the project and only want to run the build, run th npm run build ``` -This command uses the [cargo-cp-artifact](https://github.com/neon-bindings/cargo-cp-artifact) utility to run the Rust -build and copy the built library into `./build/Release/index.node`. +This command uses the napi build utility to run the Rust build and copy the built library into `./build/Release/index.node`. Prebuild requires that the binary is in `build/Release` as though it was built with node-gyp. ## Client Usage @@ -109,7 +108,7 @@ run().then(() => process.exit()); ## Wallet Usage The following example will create a -new [`Wallet`](https://wiki.iota.org/shimmer/iota-sdk/references/nodejs/classes/Wallet/) [`Account`](https://wiki.iota.org/shimmer/iota-sdk/references/nodejs/classes/Account/) +new [`Wallet`](https://wiki.iota.org/shimmer/iota-sdk/references/nodejs/classes/Wallet/) that connects to the [Shimmer Testnet](https://api.testnet.shimmer.network) using the [`StrongholdSecretManager`](https://wiki.iota.org/shimmer/iota-sdk/references/python/iota_sdk/secret_manager/#strongholdsecretmanager-objects). @@ -117,7 +116,7 @@ that connects to the [Shimmer Testnet](https://api.testnet.shimmer.network) usin import { Wallet, CoinType, WalletOptions } from '@iota/sdk'; const walletOptions: WalletOptions = { - storagePath: `Alice`, // A name to associate with the created account. + storagePath: `Alice`, // A name to associate with the created wallet. clientOptions: { nodes: ['https://api.testnet.shimmer.network'], // The node to connect to. }, @@ -125,7 +124,7 @@ const walletOptions: WalletOptions = { secretManager: { // Setup Stronghold secret manager stronghold: { - snapshotPath: 'vault.stronghold', // The path to store the account snapshot. + snapshotPath: 'vault.stronghold', // The path to store the wallet snapshot. password: 'a-secure-password', // A password to encrypt the stored data. WARNING: Never hardcode passwords in production code. }, }, diff --git a/bindings/nodejs/build.rs b/bindings/nodejs/build.rs new file mode 100644 index 0000000000..64d36cbed1 --- /dev/null +++ b/bindings/nodejs/build.rs @@ -0,0 +1,8 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/bindings/nodejs/examples/.env.example b/bindings/nodejs/examples/.env.example index cacb4f080e..ef36979f75 100644 --- a/bindings/nodejs/examples/.env.example +++ b/bindings/nodejs/examples/.env.example @@ -6,7 +6,7 @@ # Mnemonics (Don't ever use them to manage real funds!) MNEMONIC="endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river" MNEMONIC_2="width scatter jaguar sponsor erosion enable cave since ancient first garden royal luggage exchange ritual exotic play wall clinic ride autumn divert spin exchange" -# The Wallet database folder used to store account data +# The Wallet database folder used to store wallet data WALLET_DB_PATH="./example-walletdb" # The Stronghold snapshot file location used to store secrets STRONGHOLD_SNAPSHOT_PATH="./example.stronghold" diff --git a/bindings/nodejs/examples/client/05-get-address-balance.ts b/bindings/nodejs/examples/client/05-get-address-balance.ts index 9f8491b8cb..1c9b3f6b2d 100644 --- a/bindings/nodejs/examples/client/05-get-address-balance.ts +++ b/bindings/nodejs/examples/client/05-get-address-balance.ts @@ -38,12 +38,12 @@ async function run() { }); // Get output ids of basic outputs that can be controlled by this address without further unlock constraints - const outputIdsResponse = await client.basicOutputIds([ - { address: addresses[0] }, - { hasExpiration: false }, - { hasTimelock: false }, - { hasStorageDepositReturn: false }, - ]); + const outputIdsResponse = await client.basicOutputIds({ + address: addresses[0], + hasExpiration: false, + hasTimelock: false, + hasStorageDepositReturn: false, + }); // Get outputs by their IDs const addressOutputs = await client.getOutputs(outputIdsResponse.items); diff --git a/bindings/nodejs/examples/exchange/1-create-account.ts b/bindings/nodejs/examples/exchange/1-create-account.ts deleted file mode 100644 index c48bf4f490..0000000000 --- a/bindings/nodejs/examples/exchange/1-create-account.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -// This example creates a new database and account. -// Run with command: -// yarn run-example ./exchange/1-create-account.ts - -import { Wallet, WalletOptions, CoinType } from '@iota/sdk'; - -// This example uses secrets in environment variables for simplicity which should not be done in production. -require('dotenv').config({ path: '.env' }); - -async function run() { - try { - for (const envVar of [ - 'WALLET_DB_PATH', - 'NODE_URL', - 'STRONGHOLD_SNAPSHOT_PATH', - 'STRONGHOLD_PASSWORD', - 'MNEMONIC', - ]) - if (!(envVar in process.env)) { - throw new Error( - `.env ${envVar} is undefined, see .env.example`, - ); - } - - const walletOptions: WalletOptions = { - storagePath: process.env.WALLET_DB_PATH, - clientOptions: { - nodes: [process.env.NODE_URL as string], - }, - coinType: CoinType.IOTA, - secretManager: { - stronghold: { - snapshotPath: process.env.STRONGHOLD_SNAPSHOT_PATH, - password: process.env.STRONGHOLD_PASSWORD, - }, - }, - }; - - const wallet = new Wallet(walletOptions); - - // Mnemonic only needs to be set the first time. - await wallet.storeMnemonic(process.env.MNEMONIC as string); - - const account = await wallet.createAccount({ - alias: 'Alice', - }); - - // Set syncOnlyMostBasicOutputs to true if not interested in outputs that are timelocked, - // have a storage deposit return, expiration or are nft/account/foundry outputs. - account.setDefaultSyncOptions({ syncOnlyMostBasicOutputs: true }); - - console.log(account); - } catch (error) { - console.error(error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/1-create-wallet.ts b/bindings/nodejs/examples/exchange/1-create-wallet.ts new file mode 100644 index 0000000000..a7266456f9 --- /dev/null +++ b/bindings/nodejs/examples/exchange/1-create-wallet.ts @@ -0,0 +1,74 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +// This example creates a new database and wallet. +// Run with command: +// yarn run-example ./exchange/1-create-wallet.ts + +import { Wallet, WalletOptions, CoinType, SecretManager } from '@iota/sdk'; + +// This example uses secrets in environment variables for simplicity which should not be done in production. +require('dotenv').config({ path: '.env' }); + +async function run() { + try { + for (const envVar of [ + 'WALLET_DB_PATH', + 'NODE_URL', + 'STRONGHOLD_SNAPSHOT_PATH', + 'STRONGHOLD_PASSWORD', + 'MNEMONIC', + ]) + if (!(envVar in process.env)) { + throw new Error( + `.env ${envVar} is undefined, see .env.example`, + ); + } + + const strongholdSecretManager = { + stronghold: { + snapshotPath: process.env.STRONGHOLD_SNAPSHOT_PATH, + password: process.env.STRONGHOLD_PASSWORD, + }, + }; + + const secretManager = new SecretManager(strongholdSecretManager); + + // A mnemonic can be generated with `Utils.generateMnemonic()`. + // Store the mnemonic in the Stronghold snapshot, this needs to be done only the first time. + // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! + await secretManager.storeMnemonic(process.env.MNEMONIC as string); + + const walletAddress = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'tst', + }); + + const walletOptions: WalletOptions = { + address: walletAddress[0], + storagePath: process.env.WALLET_DB_PATH, + clientOptions: { + nodes: [process.env.NODE_URL as string], + }, + bipPath: { + coinType: CoinType.IOTA, + }, + secretManager: strongholdSecretManager, + }; + + const wallet = new Wallet(walletOptions); + + // Set syncOnlyMostBasicOutputs to true if not interested in outputs that are timelocked, + // have a storage deposit return, expiration or are nft/account/foundry outputs. + wallet.setDefaultSyncOptions({ syncOnlyMostBasicOutputs: true }); + } catch (error) { + console.error(error); + } +} + +run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/2-generate-address.ts b/bindings/nodejs/examples/exchange/2-generate-address.ts deleted file mode 100644 index 6ad4ac8b32..0000000000 --- a/bindings/nodejs/examples/exchange/2-generate-address.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -// This example generates an address for an account. -// Run with command: -// yarn run-example ./exchange/2-generate-address.ts - -import { Wallet } from '@iota/sdk'; - -// This example uses secrets in environment variables for simplicity which should not be done in production. -require('dotenv').config({ path: '.env' }); - -async function run() { - try { - for (const envVar of ['WALLET_DB_PATH', 'STRONGHOLD_PASSWORD']) - if (!(envVar in process.env)) { - throw new Error( - `.env ${envVar} is undefined, see .env.example`, - ); - } - - const wallet = new Wallet({ - storagePath: process.env.WALLET_DB_PATH, - }); - - await wallet.setStrongholdPassword( - process.env.STRONGHOLD_PASSWORD as string, - ); - - const account = await wallet.getAccount('Alice'); - - const address = (await account.generateEd25519Addresses(1))[0]; - - console.log('Address:', address); - } catch (error) { - console.error(error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/exchange/3-check-balance.ts b/bindings/nodejs/examples/exchange/3-check-balance.ts index 53d43b6093..ed9346243a 100644 --- a/bindings/nodejs/examples/exchange/3-check-balance.ts +++ b/bindings/nodejs/examples/exchange/3-check-balance.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -// This example gets the balance of an account. +// This example gets the balance of a wallet. // Run with command: // yarn run-example ./exchange/3-check-balance.ts @@ -21,15 +21,13 @@ async function run() { const wallet = new Wallet({ storagePath: process.env.WALLET_DB_PATH, }); + const address = await wallet.address(); - const account = await wallet.getAccount('Alice'); - const addresses = await account.addresses(); - - console.log('Addresses:', addresses); + console.log('Address:', address); // Set syncOnlyMostBasicOutputs to true if not interested in outputs that are timelocked, // have a storage deposit return, expiration or are nft/account/foundry outputs. - const balance = await account.sync({ syncOnlyMostBasicOutputs: true }); + const balance = await wallet.sync({ syncOnlyMostBasicOutputs: true }); console.log('Balance', balance); diff --git a/bindings/nodejs/examples/exchange/4-listen-events.ts b/bindings/nodejs/examples/exchange/4-listen-events.ts index ac1cd83838..88a6a58d7a 100644 --- a/bindings/nodejs/examples/exchange/4-listen-events.ts +++ b/bindings/nodejs/examples/exchange/4-listen-events.ts @@ -33,15 +33,13 @@ async function run() { // Only interested in new outputs here. await wallet.listen([WalletEventType.NewOutput], callback); - const account = await wallet.getAccount('Alice'); - // Use the faucet to send testnet tokens to your address. console.log( 'Fill your address with the faucet: https://faucet.testnet.shimmer.network/', ); - const addresses = await account.addresses(); - console.log('Send funds to:', addresses[0].address); + const address = await wallet.address(); + console.log('Send funds to:', address); // Sync every 5 seconds until the faucet transaction gets confirmed. for (let i = 0; i < 100; i++) { @@ -50,7 +48,7 @@ async function run() { // Sync to detect new outputs // Set syncOnlyMostBasicOutputs to true if not interested in outputs that are timelocked, // have a storage deposit return, expiration or are nft/account/foundry outputs. - await account.sync({ syncOnlyMostBasicOutputs: true }); + await wallet.sync({ syncOnlyMostBasicOutputs: true }); } } catch (error) { console.error(error); diff --git a/bindings/nodejs/examples/exchange/5-send-amount.ts b/bindings/nodejs/examples/exchange/5-send-amount.ts index 1c09592ee8..e387f1587a 100644 --- a/bindings/nodejs/examples/exchange/5-send-amount.ts +++ b/bindings/nodejs/examples/exchange/5-send-amount.ts @@ -31,14 +31,11 @@ async function run() { process.env.STRONGHOLD_PASSWORD as string, ); - const account = await wallet.getAccount('Alice'); - console.log('Account:', account); - // Set syncOnlyMostBasicOutputs to true if not interested in outputs that are timelocked, // have a storage deposit return, expiration or are nft/account/foundry outputs. - await account.sync({ syncOnlyMostBasicOutputs: true }); + await wallet.sync({ syncOnlyMostBasicOutputs: true }); - const response = await account.send( + const response = await wallet.send( BigInt(1000000), // Replace with the address of your choice! 'rms1qrrv7flg6lz5cssvzv2lsdt8c673khad060l4quev6q09tkm9mgtupgf0h0', diff --git a/bindings/nodejs/examples/how_tos/account_output/create.ts b/bindings/nodejs/examples/how_tos/account_output/create.ts index d5872abc0b..335b3884ba 100644 --- a/bindings/nodejs/examples/how_tos/account_output/create.ts +++ b/bindings/nodejs/examples/how_tos/account_output/create.ts @@ -6,7 +6,7 @@ import { Wallet, initLogger } from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. // // Make sure that `example.stronghold` and `example.walletdb` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // require('dotenv').config({ path: '.env' }); @@ -31,11 +31,8 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); console.log(`Accounts BEFORE:\n`, balance.accounts); @@ -47,19 +44,19 @@ async function run() { console.log('Sending the create-account transaction...'); // Create an account output - const transaction = await account.createAccountOutput(); + const transaction = await wallet.createAccountOutput(); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log( `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - balance = await account.sync(); + balance = await wallet.sync(); console.log(`Accounts AFTER:\n`, balance.accounts); } catch (error) { console.log('Error: ', error); diff --git a/bindings/nodejs/examples/how_tos/account_output/destroy.ts b/bindings/nodejs/examples/how_tos/account_output/destroy.ts index 78d369b0f2..b5f6eb23d9 100644 --- a/bindings/nodejs/examples/how_tos/account_output/destroy.ts +++ b/bindings/nodejs/examples/how_tos/account_output/destroy.ts @@ -27,17 +27,14 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); if (balance.accounts.length == 0) { throw new Error(`No Account output available in account 'Alice'`); } - // We try to destroy the first account output in the account + // We try to destroy the first account output in the wallet const accountId = balance.accounts[0]; console.log( @@ -53,14 +50,14 @@ async function run() { console.log('Sending the destroy-account transaction...'); // Destroy an account output - const transaction = await account + const transaction = await wallet .prepareDestroyAccount(accountId) .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log( @@ -68,7 +65,7 @@ async function run() { ); console.log(`Destroyed account output ${accountId}`); - balance = await account.sync(); + balance = await wallet.sync(); console.log( `Accounts AFTER destroying (${balance.accounts.length}):\n`, balance.accounts, diff --git a/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts b/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts index f754bd1e3e..3e1ec12583 100644 --- a/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/account_wallet/request-funds.ts @@ -6,7 +6,7 @@ import { Utils, Wallet, initLogger } from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. // // Make sure that `example.stronghold` and `example.walletdb` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // require('dotenv').config({ path: '.env' }); @@ -27,10 +27,7 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - // Get the account we generated with `create_wallet` - const account = await wallet.getAccount('Alice'); - - const balance = await account.sync(); + const balance = await wallet.sync(); const totalBaseTokenBalance = balance.baseCoin.total; console.log( @@ -57,7 +54,7 @@ async function run() { basicOutputs: true, }, }; - const totalBaseTokenBalanceAfter = (await account.sync(syncOptions)) + const totalBaseTokenBalanceAfter = (await wallet.sync(syncOptions)) .baseCoin.total; console.log( `Balance after requesting funds on account address: ${totalBaseTokenBalanceAfter}`, diff --git a/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts b/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts index 3d5b375a90..8fe6b32a4b 100644 --- a/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts +++ b/bindings/nodejs/examples/how_tos/account_wallet/transaction.ts @@ -6,7 +6,7 @@ import { Wallet, initLogger, Utils } from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. // // Make sure that `example.stronghold` and `example.walletdb` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // require('dotenv').config({ path: '.env' }); @@ -33,11 +33,9 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); - const balance = await account.sync(syncOptions); + const balance = await wallet.sync(syncOptions); const totalBaseTokenBalance = balance.baseCoin.total; console.log( @@ -54,11 +52,9 @@ async function run() { ); // Find first output unlockable by the account address - const queryParameters = [ - { - address: accountAddress, - }, - ]; + const queryParameters = { + address: accountAddress, + }; const input = ( await (await wallet.getClient()).basicOutputIds(queryParameters) ).items[0]; @@ -74,15 +70,13 @@ async function run() { mandatoryInputs: [input], allowMicroAmount: false, }; - const transaction = await account.sendWithParams(params, options); - await account.reissueTransactionUntilIncluded( - transaction.transactionId, - ); + const transaction = await wallet.sendWithParams(params, options); + await wallet.reissueTransactionUntilIncluded(transaction.transactionId); console.log( `Transaction with custom input: https://explorer.iota.org/testnet/transaction/${transaction.transactionId}`, ); - const totalBaseTokenBalanceAfter = (await account.sync(syncOptions)) + const totalBaseTokenBalanceAfter = (await wallet.sync(syncOptions)) .baseCoin.total; console.log( `Balance after sending funds from account: ${totalBaseTokenBalanceAfter}`, diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts index 042db1296a..af11654fb0 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/check-balance.ts @@ -20,14 +20,12 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - // Sync new outputs from the node. // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _syncBalance = await account.sync(); + const _syncBalance = await wallet.sync(); // After syncing the balance can also be computed with the local data - const balance = await account.getBalance(); + const balance = await wallet.getBalance(); console.log('Balance', balance); } catch (error) { console.error('Error: ', error); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts index 50c1e3c330..e7619c3288 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/consolidate-outputs.ts @@ -9,7 +9,7 @@ require('dotenv').config({ path: '.env' }); // Run with command: // yarn run-example ./how_tos/accounts_and_addresses/consolidate-outputs.ts -// In this example we will consolidate basic outputs from an account with only an AddressUnlockCondition by sending +// In this example we will consolidate basic outputs from an wallet with only an AddressUnlockCondition by sending // them to the same address again. async function run() { initLogger(); @@ -29,23 +29,21 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - // To create an address we need to unlock stronghold. await wallet.setStrongholdPassword( process.env.STRONGHOLD_PASSWORD as string, ); - // Sync account to make sure account is updated with outputs from previous examples - account.sync(); - console.log('Account synced'); + // Sync wallet to make sure wallet is updated with outputs from previous examples + await wallet.sync(); + console.log('Wallet synced'); // List unspent outputs before consolidation. // The output we created with example `request_funds` and the basic output from `mint` have only one // unlock condition and it is an `AddressUnlockCondition`, and so they are valid for consolidation. They have the - // same `AddressUnlockCondition`(the first address of the account), so they will be consolidated into one + // same `AddressUnlockCondition`(the address of the wallet), so they will be consolidated into one // output. - const outputs = await account.unspentOutputs(); + const outputs = await wallet.unspentOutputs(); console.log('Outputs BEFORE consolidation:'); outputs.forEach(({ output, address }, i) => { @@ -64,13 +62,13 @@ async function run() { // Consolidate unspent outputs and print the consolidation transaction ID // Set `force` to true to force the consolidation even though the `output_threshold` isn't reached - const transaction = await account.consolidateOutputs({ + const transaction = await wallet.consolidateOutputs({ force: true, }); console.log('Transaction sent: %s', transaction.transactionId); // Wait for the consolidation transaction to get confirmed - const blockId = account.reissueTransactionUntilIncluded( + const blockId = wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -80,9 +78,9 @@ async function run() { blockId, ); - // Sync account - account.sync(); - console.log('Account synced'); + // Sync wallet + await wallet.sync(); + console.log('Wallet synced'); // Outputs after consolidation console.log('Outputs AFTER consolidation:'); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-address.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-address.ts deleted file mode 100644 index c525a4e16f..0000000000 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-address.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Wallet, initLogger } from '@iota/sdk'; -require('dotenv').config({ path: '.env' }); - -// Run with command: -// yarn run-example ./how_tos/accounts_and_addresses/create-address.ts - -// This example creates an address -async function run() { - initLogger(); - for (const envVar of ['WALLET_DB_PATH', 'STRONGHOLD_PASSWORD']) - if (!(envVar in process.env)) { - throw new Error(`.env ${envVar} is undefined, see .env.example`); - } - - try { - const wallet = new Wallet({ - storagePath: process.env.WALLET_DB_PATH, - }); - - const account = await wallet.getAccount('Alice'); - - // To create an address we need to unlock stronghold. - await wallet.setStrongholdPassword( - process.env.STRONGHOLD_PASSWORD as string, - ); - - const address = (await account.generateEd25519Addresses(1))[0]; - - console.log(`Generated address:`, address.address); - } catch (error) { - console.error('Error: ', error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-account.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-wallet.ts similarity index 53% rename from bindings/nodejs/examples/how_tos/accounts_and_addresses/create-account.ts rename to bindings/nodejs/examples/how_tos/accounts_and_addresses/create-wallet.ts index 6c4d08f274..e713c26fb3 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-account.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/create-wallet.ts @@ -1,15 +1,21 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Wallet, CoinType, initLogger, WalletOptions } from '@iota/sdk'; +import { + Wallet, + CoinType, + initLogger, + WalletOptions, + SecretManager, +} from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. require('dotenv').config({ path: '.env' }); // Run with command: -// yarn run-example ./how_tos/accounts_and_addresses/create-account.ts +// yarn run-example ./how_tos/accounts_and_addresses/create-wallet.ts -// This example creates a new database and account. +// This example creates a new database and wallet. async function run() { initLogger(); for (const envVar of [ @@ -24,32 +30,47 @@ async function run() { } try { + const strongholdSecretManager = { + stronghold: { + snapshotPath: process.env.STRONGHOLD_SNAPSHOT_PATH, + password: process.env.STRONGHOLD_PASSWORD, + }, + }; + + const secretManager = new SecretManager(strongholdSecretManager); + + // A mnemonic can be generated with `Utils.generateMnemonic()`. + // Store the mnemonic in the Stronghold snapshot, this needs to be done only the first time. + // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! + await secretManager.storeMnemonic(process.env.MNEMONIC as string); + + const walletAddress = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'tst', + }); + const walletOptions: WalletOptions = { + address: walletAddress[0], storagePath: process.env.WALLET_DB_PATH, clientOptions: { nodes: [process.env.NODE_URL as string], }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: process.env.STRONGHOLD_SNAPSHOT_PATH, - password: process.env.STRONGHOLD_PASSWORD, - }, + bipPath: { + coinType: CoinType.IOTA, }, + secretManager: strongholdSecretManager, }; const wallet = new Wallet(walletOptions); - // A mnemonic can be generated with `Utils.generateMnemonic()`. - // Store the mnemonic in the Stronghold snapshot, this needs to be done only the first time. - // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! - await wallet.storeMnemonic(process.env.MNEMONIC as string); - - // Create a new account - const account = await wallet.createAccount({ - alias: 'Alice', - }); - console.log('Generated new account:', account.getMetadata().alias); + console.log( + 'Generated wallet with address: ' + (await wallet.address()), + ); } catch (error) { console.error('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts index f3e5662372..2ba9ac7942 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-accounts.ts @@ -9,7 +9,7 @@ require('dotenv').config({ path: '.env' }); // Run with command: // yarn run-example ./how_tos/accounts_and_addresses/list-accounts.ts -// This example lists all accounts in the wallet. +// This example lists all account outputs in the wallet. async function run() { initLogger(); if (!process.env.WALLET_DB_PATH) { @@ -20,10 +20,9 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const accounts = await wallet.getAccounts(); + const accounts = await wallet.accounts(); - for (const account of accounts) - console.log(account.getMetadata().alias); + for (const account of accounts) console.log(account); } catch (error) { console.error('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-addresses.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-addresses.ts deleted file mode 100644 index 02e56eb9b5..0000000000 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-addresses.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { Wallet, initLogger } from '@iota/sdk'; - -// This example uses secrets in environment variables for simplicity which should not be done in production. -require('dotenv').config({ path: '.env' }); - -// Run with command: -// yarn run-example ./how_tos/accounts_and_addresses/list-addresses.ts - -// This example lists all addresses in the account. -async function run() { - initLogger(); - if (!process.env.WALLET_DB_PATH) { - throw new Error('.env WALLET_DB_PATH is undefined, see .env.example'); - } - try { - const wallet = new Wallet({ - storagePath: process.env.WALLET_DB_PATH, - }); - - const account = await wallet.getAccount('Alice'); - - const addresses = await account.addresses(); - - for (const address of addresses) console.log(address.address); - } catch (error) { - console.error('Error: ', error); - } -} - -run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts index a3fe14be53..a25192d276 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-outputs.ts @@ -9,7 +9,7 @@ require('dotenv').config({ path: '.env' }); // Run with command: // yarn run-example ./how_tos/accounts_and_addresses/list-outputs.ts -// This example lists all outputs in the account. +// This example lists all outputs in the wallet. async function run() { initLogger(); if (!process.env.WALLET_DB_PATH) { @@ -20,16 +20,14 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); + await wallet.sync(); - await account.sync(); - - const outputs = await account.outputs(); + const outputs = await wallet.outputs(); console.log('Output ids:'); for (const output of outputs) console.log(output.outputId); - const unspentOutputs = await account.unspentOutputs(); + const unspentOutputs = await wallet.unspentOutputs(); console.log('Unspent output ids:'); for (const output of unspentOutputs) console.log(output.outputId); diff --git a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts index 0cfd55dd81..feb53b0898 100644 --- a/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts +++ b/bindings/nodejs/examples/how_tos/accounts_and_addresses/list-transactions.ts @@ -9,7 +9,7 @@ require('dotenv').config({ path: '.env' }); // Run with command: // yarn run-example ./how_tos/accounts_and_addresses/list-transactions.ts -// This example lists all transactions in the account. +// This example lists all transactions in the wallet. async function run() { initLogger(); if (!process.env.WALLET_DB_PATH) { @@ -19,16 +19,14 @@ async function run() { const wallet = new Wallet({ storagePath: process.env.WALLET_DB_PATH, }); + await wallet.sync({ syncIncomingTransactions: true }); - const account = await wallet.getAccount('Alice'); - await account.sync({ syncIncomingTransactions: true }); - - const transactions = await account.transactions(); + const transactions = await wallet.transactions(); console.log('Sent transactions:'); for (const transaction of transactions) console.log(transaction.transactionId); - const incomingTransactions = await account.incomingTransactions(); + const incomingTransactions = await wallet.incomingTransactions(); console.log('Incoming transactions:'); for (const transaction of incomingTransactions) { console.log(transaction.transactionId); diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts index 13d22cd700..199fab2471 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts @@ -28,9 +28,7 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - - await account.sync(); + await wallet.sync(); // To sign a transaction we need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); @@ -53,11 +51,11 @@ async function run() { ], }); - const transaction = await account.sendOutputs([basicOutput]); + const transaction = await wallet.sendOutputs([basicOutput]); console.log(`Transaction sent: ${transaction.transactionId}`); console.log('Waiting until included in block...'); - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log(`Block sent: ${process.env.EXPLORER_URL}/block/${blockId}`); diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts index 5d586875ed..67a1fb7b53 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts @@ -7,7 +7,7 @@ require('dotenv').config({ path: '.env' }); // Run with command: // yarn run-example ./how_tos/advanced_transactions/claim_transaction.ts -// This example claims all claimable outputs in the account. +// This example claims all claimable outputs in the wallet. async function run() { initLogger(); try { @@ -21,24 +21,22 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - - await account.sync(); + await wallet.sync(); // To sign a transaction we need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); // Get all claimable outputs - const output_ids = await account.claimableOutputs(OutputsToClaim.All); + const output_ids = await wallet.claimableOutputs(OutputsToClaim.All); console.log(`Available outputs to claim:`); for (const output_id of output_ids) { console.log(output_id); } - const transaction = await account.claimOutputs(output_ids); + const transaction = await wallet.claimOutputs(output_ids); console.log(`Transaction sent: ${transaction.transactionId}`); - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log(`Block sent: ${process.env.EXPLORER_URL}/block/${blockId}`); diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts index 22da16ac31..6cb5e31db1 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts @@ -21,9 +21,7 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - - await account.sync(); + await wallet.sync(); // To sign a transaction we need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); @@ -33,13 +31,13 @@ async function run() { 'rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu'; const amount = BigInt(1); - const transaction = await account.send(amount, address, { + const transaction = await wallet.send(amount, address, { allowMicroAmount: true, }); console.log(`Transaction sent: ${transaction.transactionId}`); - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); diff --git a/bindings/nodejs/examples/how_tos/client/get-outputs.ts b/bindings/nodejs/examples/how_tos/client/get-outputs.ts index ebbef29b82..0f58a6b84d 100644 --- a/bindings/nodejs/examples/how_tos/client/get-outputs.ts +++ b/bindings/nodejs/examples/how_tos/client/get-outputs.ts @@ -21,15 +21,13 @@ async function run() { try { // Get output ids of basic outputs that can be controlled by this address without further unlock constraints - const outputIdsResponse = await client.basicOutputIds([ - { - address: - 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', - }, - { hasExpiration: false }, - { hasTimelock: false }, - { hasStorageDepositReturn: false }, - ]); + const outputIdsResponse = await client.basicOutputIds({ + address: + 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', + hasExpiration: false, + hasTimelock: false, + hasStorageDepositReturn: false, + }); console.log('First output of query:'); console.log('ID: ', outputIdsResponse.items[0]); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/burn.ts b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts index 5f68f0fa76..432895eae3 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/burn.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts @@ -3,7 +3,7 @@ import { getUnlockedWallet } from '../../wallet/common'; -// The minimum available native token amount to search for in the account. +// The minimum available native token amount to search for in the wallet. const MIN_AVAILABLE_AMOUNT = BigInt(11); // The amount of the native token to burn. const BURN_AMOUNT = BigInt(1); @@ -13,7 +13,7 @@ const BURN_AMOUNT = BigInt(1); // output that minted it. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/native_tokens/burn.ts @@ -22,11 +22,8 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); // Get a token with sufficient balance const tokenId = balance.nativeTokens.find( @@ -42,21 +39,21 @@ async function run() { throw new Error( `Native token '${tokenId}' doesn't exist or there's not at least '${Number( MIN_AVAILABLE_AMOUNT, - )}' tokens of it in account 'Alice'`, + )}' tokens of it in the wallet`, ); } console.log(`Balance before burning: ${token.available}`); // Burn a native token - const transaction = await account + const transaction = await wallet .prepareBurnNativeToken(token.tokenId, BURN_AMOUNT) .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -64,7 +61,7 @@ async function run() { `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - balance = await account.sync(); + balance = await wallet.sync(); token = balance.nativeTokens.find( (nativeToken) => nativeToken.tokenId == tokenId, diff --git a/bindings/nodejs/examples/how_tos/native_tokens/create.ts b/bindings/nodejs/examples/how_tos/native_tokens/create.ts index 6011237381..81035d82e1 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/create.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/create.ts @@ -13,7 +13,7 @@ const MAXIMUM_SUPPLY = BigInt(100); // In this example we will create a native token. // // Make sure that `example.stronghold` and `example.walletdb` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/native_tokens/create.ts @@ -22,22 +22,19 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - const balance = await account.sync(); + const balance = await wallet.sync(); // We can first check if we already have an account output in our account, because an account output can have // many foundry outputs and therefore we can reuse an existing one if (balance.accounts.length == 0) { // If we don't have an account output, we need to create one - const transaction = await account + const transaction = await wallet .prepareCreateAccountOutput() .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -45,8 +42,8 @@ async function run() { `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - await account.sync(); - console.log('Account synced'); + await wallet.sync(); + console.log('Wallet synced'); } console.log('Preparing transaction to create native token...'); @@ -57,20 +54,20 @@ async function run() { 10, ).withDescription('A native token to test the iota-sdk.'); - // If we omit the AccountAddress field the first address of the account is used by default + // If we omit the AccountAddress field the wallet address is used by default const params: CreateNativeTokenParams = { circulatingSupply: CIRCULATING_SUPPLY, maximumSupply: MAXIMUM_SUPPLY, foundryMetadata: metadata.asHex(), }; - const prepared = await account.prepareCreateNativeToken(params); + const prepared = await wallet.prepareCreateNativeToken(params); const transaction = await prepared.send(); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -81,8 +78,8 @@ async function run() { console.log(`Created token: ${prepared.tokenId()}`); // Ensure the account is synced after creating the native token. - await account.sync(); - console.log('Account synced'); + await wallet.sync(); + console.log('Wallet synced'); } catch (error) { console.log('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts b/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts index 50ac1e83fe..90c557b856 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts @@ -3,11 +3,11 @@ import { getUnlockedWallet } from '../../wallet/common'; -// In this example we will try to destroy the first foundry there is in the account. This is only possible if its +// In this example we will try to destroy the first foundry there is in the wallet. This is only possible if its // circulating supply is 0 and no native tokens were burned. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/native_tokens/destroy-foundry.ts @@ -16,37 +16,34 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); if (balance.foundries.length == 0) { - throw new Error(`No Foundry available in account 'Alice'`); + throw new Error(`No Foundry available in the wallet`); } - // We try to destroy the first foundry in the account + // We try to destroy the first foundry in the wallet const foundry = balance.foundries[0]; console.log(`Foundries before destroying: ${balance.foundries.length}`); // Burn a foundry - const transaction = await account + const transaction = await wallet .prepareDestroyFoundry(foundry) .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log( `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - balance = await account.sync(); + balance = await wallet.sync(); console.log(`Foundries after destroying: ${balance.foundries.length}`); } catch (error) { console.log('Error: ', error); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts index d2381f0888..5826a1c851 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts @@ -9,7 +9,7 @@ const MELT_AMOUNT = BigInt(10); // In this example we will melt an existing native token with its foundry. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/native_tokens/melt.ts @@ -18,11 +18,8 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); if (balance.foundries.length == 0) { throw new Error(`No Foundry available in account 'Alice'`); @@ -36,14 +33,14 @@ async function run() { ); if (token == null) { throw new Error( - `Couldn't find native token '${tokenId}' in the account`, + `Couldn't find native token '${tokenId}' in the wallet`, ); } console.log(`Balance before melting: ${token.available}`); // Melt some of the circulating supply - const transaction = await account.meltNativeToken( + const transaction = await wallet.meltNativeToken( token.tokenId, MELT_AMOUNT, ); @@ -51,7 +48,7 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -59,13 +56,13 @@ async function run() { `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - balance = await account.sync(); + balance = await wallet.sync(); token = balance.nativeTokens.find( (nativeToken) => nativeToken.tokenId == tokenId, ); if (token == null) { throw new Error( - `Couldn't find native token '${tokenId}' in the account`, + `Couldn't find native token '${tokenId}' in the wallet`, ); } console.log(`Balance after melting: ${token.available}`); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/mint.ts b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts index 6fa0aead4b..56a7362824 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/mint.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts @@ -9,7 +9,7 @@ const MINT_AMOUNT = BigInt(10); // In this example we will mint an existing native token with its foundry. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/native_tokens/mint.ts @@ -18,11 +18,8 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); if (balance.foundries.length == 0) { throw new Error(`No Foundry available in account 'Alice'`); @@ -36,20 +33,20 @@ async function run() { ); if (token == null) { throw new Error( - `Couldn't find native token '${tokenId}' in the account`, + `Couldn't find native token '${tokenId}' in the wallet`, ); } console.log(`Balance before minting: ${token.available}`); // Mint some more native tokens - const transaction = await account + const transaction = await wallet .prepareMintNativeToken(token.tokenId, MINT_AMOUNT) .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -57,13 +54,13 @@ async function run() { `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - balance = await account.sync(); + balance = await wallet.sync(); token = balance.nativeTokens.find( (nativeToken) => nativeToken.tokenId == tokenId, ); if (token == null) { throw new Error( - `Couldn't find native token '${tokenId}' in the account`, + `Couldn't find native token '${tokenId}' in the wallet`, ); } console.log(`Balance after minting: ${token.available}`); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/send.ts b/bindings/nodejs/examples/how_tos/native_tokens/send.ts index 01410f4aa4..1bb0a40c1b 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/send.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/send.ts @@ -14,7 +14,7 @@ const RECV_ADDRESS = // In this example we will send a native token. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/native_tokens/send.ts @@ -23,11 +23,8 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); // Get a token with sufficient balance const tokenId = balance.nativeTokens.find( @@ -47,17 +44,17 @@ async function run() { ); if (token == null) { throw new Error( - `Couldn't find native token '${tokenId}' in the account`, + `Couldn't find native token '${tokenId}' in the wallet`, ); } console.log(`Balance before sending: ${token.available}`); - const transaction = await account.sendNativeToken(outputs); + const transaction = await wallet.sendNativeToken(outputs); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -65,13 +62,13 @@ async function run() { `Block included: ${process.env.EXPLORER_URL}/block/${blockId}`, ); - balance = await account.sync(); + balance = await wallet.sync(); token = balance.nativeTokens.find( (nativeToken) => nativeToken.tokenId == tokenId, ); if (token == null) { throw new Error( - `Couldn't find native token '${tokenId}' in the account`, + `Couldn't find native token '${tokenId}' in the wallet`, ); } console.log(`Balance after sending: ${token.available}`); diff --git a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts index 9850ec2e81..1106c6ecbd 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts @@ -14,7 +14,7 @@ require('dotenv').config({ path: '.env' }); // In this example we will mint the issuer NFT for the NFT collection. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/nft_collection/00_mint_issuer_nft.ts @@ -34,11 +34,8 @@ async function run() { // To sign a transaction we need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - await account.sync(); - console.log(`Account synced!`); + await wallet.sync(); + console.log(`Wallet synced!`); // Issue the minting transaction and wait for its inclusion console.log(`Sending NFT minting transaction...`); @@ -47,10 +44,10 @@ async function run() { 'This NFT will be the issuer from the awesome NFT collection', ), }; - const transaction = await account.mintNfts([params]); + const transaction = await wallet.mintNfts([params]); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log( diff --git a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts index f5f607ce3b..6f7a6034ea 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts @@ -12,7 +12,7 @@ const NUM_NFTS_MINTED_PER_TRANSACTION = 50; // In this example we will mint the issuer NFT for the NFT collection. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/nfts/01_mint_collection_nft.ts @@ -32,11 +32,9 @@ async function run() { // To sign a transaction we need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); - // Get the account we generated with `how_tos/accounts_and_addresses/create-account` - const account = await wallet.getAccount('Alice'); - - await account.sync(); - console.log(`Account synced!`); + // Get the wallet we generated with `how_tos/accounts_and_addresses/create-wallet` + await wallet.sync(); + console.log(`Wallet synced!`); // Get the id we generated with `00_mint_issuer_nft` const issuerNftId: NftId = process.argv[2]; @@ -70,10 +68,10 @@ async function run() { i + chunk.length }/${NFT_COLLECTION_SIZE})`, ); - const transaction = await account.mintNfts(chunk); + const transaction = await wallet.mintNfts(chunk); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log( @@ -81,7 +79,7 @@ async function run() { ); // Sync so the new outputs are available again for new transactions - await account.sync(); + await wallet.sync(); } // After the NFTs are minted, the issuer nft can be sent to the so called "null address" diff --git a/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts b/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts index c9ac0ef987..d72d9dba05 100644 --- a/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts @@ -7,7 +7,7 @@ require('dotenv').config({ path: '.env' }); // In this example we will burn an existing nft output. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/nfts/burn_nft.ts @@ -24,19 +24,14 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - // We need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); - // May want to ensure the account is synced before sending a transaction. - let balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + let balance = await wallet.sync(); if (balance.nfts.length == 0) { - throw new Error( - `No NFT available in account '${process.env.ACCOUNT_ALIAS_1}'`, - ); + throw new Error(`No NFT available in the wallet`); } // Get the first nft const nftId = balance.nfts[0]; @@ -44,14 +39,14 @@ async function run() { console.log(`Balance BEFORE burning:\n`, balance); // Burn an NFT - const transaction = await account + const transaction = await wallet .prepareBurnNft(nftId) .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); console.log( @@ -59,7 +54,7 @@ async function run() { ); console.log(`Burned NFT ${nftId}`); - balance = await account.sync(); + balance = await wallet.sync(); console.log(`Balance AFTER burning:\n`, balance); } catch (error) { console.log('Error: ', error); diff --git a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts index de78ab4d04..98378daacf 100644 --- a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts @@ -27,7 +27,7 @@ const NFT2_AMOUNT = '1000000'; // In this example we will mint a new nft. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/nfts/mint_nft.ts @@ -43,10 +43,8 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - - // We send from the first address in the account. - const senderAddress = (await account.addresses())[0].address; + // We send from the address in the wallet. + const senderAddress = await wallet.address(); // We need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); @@ -65,11 +63,11 @@ async function run() { issuer: senderAddress, immutableMetadata: metadata.asHex(), }; - let transaction = await account.mintNfts([params]); + let transaction = await wallet.mintNfts([params]); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - let blockId = await account.reissueTransactionUntilIncluded( + let blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -96,11 +94,11 @@ async function run() { features: [new SenderFeature(new Ed25519Address(hexAddress))], }); - transaction = await account.sendOutputs([output]); + transaction = await wallet.sendOutputs([output]); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - blockId = await account.reissueTransactionUntilIncluded( + blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -110,8 +108,8 @@ async function run() { console.log('Minted NFT 2'); - // Ensure the account is synced after minting. - await account.sync(); + // Ensure the wallet is synced after minting. + await wallet.sync(); } catch (error) { console.error('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts index 77097345c2..907efaa23c 100644 --- a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts @@ -11,7 +11,7 @@ const RECV_ADDRESS = // In this example we will send an NFT (Non-fungible token). // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./how_tos/nfts/send_nft.ts @@ -27,14 +27,11 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - // We need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); - // May want to ensure the account is synced before sending a transaction. - const balance = await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + const balance = await wallet.sync(); if (balance.nfts.length == 0) { throw new Error('No available NFTs'); @@ -50,12 +47,12 @@ async function run() { ]; // Send the full NFT output to the specified address - const transaction = await account.sendNft(outputs); + const transaction = await wallet.sendNft(outputs); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); @@ -64,7 +61,7 @@ async function run() { ); // To send an NFT with expiration unlock condition prepareOutput() can be used like this: - // const output = await account.prepareOutput({ + // const output = await wallet.prepareOutput({ // recipientAddress: 'rms1qz6aj69rumk3qu0ra5ag6p6kk8ga3j8rfjlaym3wefugs3mmxgzfwa6kw3l', // amount: "47000", // unlocks: { @@ -76,7 +73,7 @@ async function run() { // storageDeposit: { returnStrategy: 'Gift' } // }); - // const transaction = await account.sendOutputs([output]); + // const transaction = await wallet.sendOutputs([output]); } catch (error) { console.log('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts index 2a86b934ef..8236e5d3dc 100644 --- a/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/simple_transaction/request-funds.ts @@ -23,10 +23,7 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - // Get the account we generated with `create-account` - const account = await wallet.getAccount('Alice'); - - const address = (await account.addresses())[0].address; + const address = await wallet.address(); console.log(address); const faucetResponse = await ( diff --git a/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts b/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts index 78c4079e5b..ec1249840b 100644 --- a/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts +++ b/bindings/nodejs/examples/how_tos/simple_transaction/simple-transaction.ts @@ -23,9 +23,7 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const account = await wallet.getAccount('Alice'); - - await account.sync(); + await wallet.sync(); // To sign a transaction we need to unlock stronghold. await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD); @@ -35,7 +33,7 @@ async function run() { 'rms1qrrv7flg6lz5cssvzv2lsdt8c673khad060l4quev6q09tkm9mgtupgf0h0'; const amount = BigInt(1000000); - const response = await account.send(amount, address); + const response = await wallet.send(amount, address); console.log( `Block sent: ${process.env.EXPLORER_URL}/block/${response.blockId}`, diff --git a/bindings/nodejs/examples/package-lock.json b/bindings/nodejs/examples/package-lock.json index aebd0a66e0..8f721e37f8 100644 --- a/bindings/nodejs/examples/package-lock.json +++ b/bindings/nodejs/examples/package-lock.json @@ -23,7 +23,6 @@ "dependencies": { "@iota/types": "^1.0.0-beta.15", "@types/node": "^18.15.12", - "cargo-cp-artifact": "^0.1.6", "prebuild-install": "^7.1.1", "typescript": "^4.9.4" }, @@ -85,7 +84,6 @@ "@types/node": "^18.15.12", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.30.7", - "cargo-cp-artifact": "^0.1.6", "dotenv": "^16.0.3", "electron-build-env": "^0.2.0", "eslint": "^8.20.0", diff --git a/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts b/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts index d466cd282e..5efb231547 100644 --- a/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts +++ b/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts @@ -11,10 +11,10 @@ const SEND_MICRO_AMOUNT = BigInt(1); const RECV_ADDRESS = 'rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu'; -// In this example we will send an amount below the minimum storage deposit. +// In this example we will send an amount below the minimum output amount. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./wallet/06-send-micro-transaction.ts @@ -23,11 +23,8 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); - - // May want to ensure the account is synced before sending a transaction. - await account.sync(); + // May want to ensure the wallet is synced before sending a transaction. + await wallet.sync(); console.log( `Sending '${SEND_MICRO_AMOUNT}' coin(s) to '${RECV_ADDRESS}'...`, @@ -36,13 +33,13 @@ async function run() { { address: RECV_ADDRESS, amount: SEND_MICRO_AMOUNT }, ]; - const transaction = await account.sendWithParams(params, { + const transaction = await wallet.sendWithParams(params, { allowMicroAmount: true, }); console.log(`Transaction sent: ${transaction.transactionId}`); // Wait for transaction to get included - const blockId = await account.reissueTransactionUntilIncluded( + const blockId = await wallet.reissueTransactionUntilIncluded( transaction.transactionId, ); diff --git a/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts b/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts index 0b48964e74..2e1faa23e5 100644 --- a/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts +++ b/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts @@ -8,10 +8,10 @@ import { getUnlockedWallet } from './common'; // The amount to build the basic output with const AMOUNT = BigInt(1000000); -// In this example we check if an output has only an address unlock condition and that the address is from the account. +// In this example we check if an output has only an address unlock condition and that the address is from the wallet. // // Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -// running the `how_tos/accounts_and_addresses/create-account` example! +// running the `how_tos/accounts_and_addresses/create-wallet` example! // // Rename `.env.example` to `.env` first, then run // yarn run-example ./wallet/17-check-unlock-conditions.ts @@ -20,40 +20,35 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - // Get the account we generated with `01-create-wallet` - const account = await wallet.getAccount('Alice'); + const walletAddress = await wallet.address(); - const accountAddresses = await account.addresses(); - - const output: Output = await account.prepareOutput({ - recipientAddress: accountAddresses[0].address, + const output: Output = await wallet.prepareOutput({ + recipientAddress: walletAddress, amount: AMOUNT, }); - const hexEncodedAccountAddresses = accountAddresses.map((a) => - Utils.bech32ToHex(a.address), - ); + const hexEncodedwalletAddress = Utils.bech32ToHex(walletAddress); if (output instanceof BasicOutput) { const basicOutput = output as BasicOutput; - let controlledByAccount = false; + let controlledByWallet = false; if ( basicOutput.unlockConditions.length === 1 && basicOutput.unlockConditions[0] instanceof AddressUnlockCondition && - hexEncodedAccountAddresses.includes( + hexEncodedwalletAddress.includes( ( basicOutput .unlockConditions[0] as AddressUnlockCondition ).address.toString(), ) ) { - controlledByAccount = true; + controlledByWallet = true; } console.log( - 'The output has only an address unlock condition and the address is from the account: ' + - controlledByAccount, + 'The output has only an address unlock condition and the address is from the wallet: ' + + controlledByWallet, ); } } catch (error) { diff --git a/bindings/nodejs/examples/wallet/common.ts b/bindings/nodejs/examples/wallet/common.ts index 642d28ff88..4479a2a00f 100644 --- a/bindings/nodejs/examples/wallet/common.ts +++ b/bindings/nodejs/examples/wallet/common.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { initLogger, Wallet, CoinType, WalletOptions } from '@iota/sdk'; +import { initLogger, Wallet, WalletOptions } from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. require('dotenv').config({ path: '.env' }); @@ -21,16 +21,6 @@ async function getUnlockedWallet() { const walletOptions: WalletOptions = { storagePath: process.env.WALLET_DB_PATH, - clientOptions: { - nodes: [process.env.NODE_URL as string], - }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: process.env.STRONGHOLD_SNAPSHOT_PATH, - password: process.env.STRONGHOLD_PASSWORD, - }, - }, }; const wallet = new Wallet(walletOptions); diff --git a/bindings/nodejs/examples/wallet/events.ts b/bindings/nodejs/examples/wallet/events.ts index 0657f4c831..13e7663064 100644 --- a/bindings/nodejs/examples/wallet/events.ts +++ b/bindings/nodejs/examples/wallet/events.ts @@ -25,12 +25,7 @@ async function run() { const wallet = await getUnlockedWallet(); const callback = function (err: any, event: Event) { - console.log( - 'AccountIndex:', - event.accountIndex, - ', Event:', - event.event, - ); + console.log('Event:', event); }; await wallet.listen([], callback); diff --git a/bindings/nodejs/examples/wallet/getting-started.ts b/bindings/nodejs/examples/wallet/getting-started.ts index 1913122e68..541020e59b 100644 --- a/bindings/nodejs/examples/wallet/getting-started.ts +++ b/bindings/nodejs/examples/wallet/getting-started.ts @@ -1,7 +1,13 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Wallet, CoinType, WalletOptions, Utils } from '@iota/sdk'; +import { + Wallet, + CoinType, + WalletOptions, + Utils, + SecretManager, +} from '@iota/sdk'; // Run with command: // yarn run-example ./wallet/getting-started.ts @@ -9,9 +15,6 @@ import { Wallet, CoinType, WalletOptions, Utils } from '@iota/sdk'; // The database path. const WALLET_DB_PATH = 'getting-started-db'; -// A name to associate with the created account. -const ACCOUNT_ALIAS = 'Alice'; - // The node to connect to. const NODE_URL = 'https://api.testnet.shimmer.network'; @@ -19,40 +22,53 @@ const NODE_URL = 'https://api.testnet.shimmer.network'; // WARNING: Never hardcode passwords in production code. const STRONGHOLD_PASSWORD = 'a-secure-password'; -// The path to store the account snapshot. +// The path to store the wallet snapshot. const STRONGHOLD_SNAPSHOT_PATH = 'vault.stronghold'; async function main() { - const walletOptions: WalletOptions = { - storagePath: WALLET_DB_PATH, - clientOptions: { - nodes: [NODE_URL], - }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: STRONGHOLD_SNAPSHOT_PATH, - password: STRONGHOLD_PASSWORD, - }, + const strongholdSecretManager = { + stronghold: { + snapshotPath: STRONGHOLD_SNAPSHOT_PATH, + password: STRONGHOLD_PASSWORD, }, }; - const wallet = new Wallet(walletOptions); + const secretManager = new SecretManager(strongholdSecretManager); // Generate a mnemonic and store its seed in the Stronghold vault. // INFO: It is best practice to back up the mnemonic somewhere secure. const mnemonic = Utils.generateMnemonic(); console.log('Mnemonic:' + mnemonic); - await wallet.storeMnemonic(mnemonic); - // Create an account. - const account = await wallet.createAccount({ - alias: ACCOUNT_ALIAS, + // Store the mnemonic in the Stronghold snapshot, this needs to be done only the first time. + // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! + await secretManager.storeMnemonic(mnemonic); + + const wallet_address = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'tst', }); - // Get the first address and print it. - const address = (await account.addresses())[0]; - console.log(`Address: ${address.address}\n`); + const walletOptions: WalletOptions = { + address: wallet_address[0], + storagePath: WALLET_DB_PATH, + clientOptions: { + nodes: [NODE_URL as string], + }, + bipPath: { + coinType: CoinType.IOTA, + }, + secretManager: strongholdSecretManager, + }; + + const wallet = new Wallet(walletOptions); + + console.log('Generated wallet with address: ' + (await wallet.address())); process.exit(0); } diff --git a/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts b/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts index 0a3663a269..04d67eb36c 100644 --- a/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts +++ b/bindings/nodejs/examples/wallet/migrate-stronghold-snapshot-v2-to-v3.ts @@ -1,12 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { - CoinType, - WalletOptions, - Wallet, - migrateStrongholdSnapshotV2ToV3, -} from '@iota/sdk'; +import { migrateStrongholdSnapshotV2ToV3, SecretManager } from '@iota/sdk'; require('dotenv').config({ path: '.env' }); const v2Path = '../../../sdk/tests/wallet/fixtures/v2.stronghold'; @@ -21,23 +16,16 @@ async function run() { throw new Error(`.env ${envVar} is undefined, see .env.example`); } - let walletOptions: WalletOptions = { - storagePath: process.env.WALLET_DB_PATH, - clientOptions: { - nodes: [process.env.NODE_URL as string], - }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: v2Path, - password: 'current_password', - }, + const strongholdSecretManager = { + stronghold: { + snapshotPath: process.env.STRONGHOLD_SNAPSHOT_PATH, + password: process.env.STRONGHOLD_PASSWORD, }, }; try { // This should fail with error, migration required. - new Wallet(walletOptions); + new SecretManager(strongholdSecretManager); } catch (error) { console.error(error); } @@ -51,22 +39,8 @@ async function run() { 'new_password', ); - walletOptions = { - storagePath: process.env.WALLET_DB_PATH, - clientOptions: { - nodes: [process.env.NODE_URL as string], - }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: v3Path, - password: 'new_password', - }, - }, - }; - // This shouldn't fail anymore as snapshot has been migrated. - new Wallet(walletOptions); + new SecretManager(strongholdSecretManager); } run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/yarn.lock b/bindings/nodejs/examples/yarn.lock index e784520517..8225206635 100644 --- a/bindings/nodejs/examples/yarn.lock +++ b/bindings/nodejs/examples/yarn.lock @@ -679,11 +679,6 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -cargo-cp-artifact@^0.1.6: - version "0.1.8" - resolved "https://registry.yarnpkg.com/cargo-cp-artifact/-/cargo-cp-artifact-0.1.8.tgz#353814f49f6aa76601a4bcb3ea5f3071180b90de" - integrity sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA== - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -888,9 +883,9 @@ destroy@1.2.0: integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== detect-libc@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== diff@^4.0.1: version "4.0.2" @@ -1797,9 +1792,9 @@ next-tick@^1.1.0: integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== node-abi@^3.3.0: - version "3.45.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.45.0.tgz#f568f163a3bfca5aacfce1fbeee1fa2cc98441f5" - integrity sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ== + version "3.51.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.51.0.tgz#970bf595ef5a26a271307f8a4befa02823d4e87d" + integrity sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA== dependencies: semver "^7.3.5" @@ -2129,9 +2124,9 @@ secp256k1@^4.0.1: node-gyp-build "^4.2.0" semver@^7.3.5: - version "7.5.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" - integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" diff --git a/bindings/nodejs/lib/bindings.ts b/bindings/nodejs/lib/bindings.ts index 86e960f1c5..a709d81841 100644 --- a/bindings/nodejs/lib/bindings.ts +++ b/bindings/nodejs/lib/bindings.ts @@ -1,12 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { WalletEventType } from './types/wallet'; -import { Event } from './types/wallet'; -import type { WalletMethodHandler } from './wallet/wallet-method-handler'; import { __UtilsMethods__ } from './types/utils'; -import type { SecretManagerMethodHandler } from './secret_manager/secret-manager-method-handler'; -import type { ClientMethodHandler } from './client/client-method-handler'; // @ts-ignore: path is set to match runtime transpiled js path import addon = require('../build/Release/index.node'); @@ -29,38 +24,6 @@ const { migrateStrongholdSnapshotV2ToV3, } = addon; -const callClientMethodAsync = ( - method: string, - handler: ClientMethodHandler, -): Promise => - new Promise((resolve, reject) => { - callClientMethod(method, handler, (error: Error, result: string) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); - -const callSecretManagerMethodAsync = ( - method: string, - handler: SecretManagerMethodHandler, -): Promise => - new Promise((resolve, reject) => { - callSecretManagerMethod( - method, - handler, - (error: Error, result: string) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }, - ); - }); - const callUtilsMethod = (method: __UtilsMethods__): any => { const response = JSON.parse(callUtilsMethodRust(JSON.stringify(method))); if (response.type == 'error' || response.type == 'panic') { @@ -70,48 +33,18 @@ const callUtilsMethod = (method: __UtilsMethods__): any => { } }; -const listenWalletAsync = ( - eventTypes: WalletEventType[], - callback: (error: Error, event: Event) => void, - handler: WalletMethodHandler, -): Promise => { - listenWallet( - eventTypes, - function (err: any, data: string) { - const parsed = JSON.parse(data); - callback(err, new Event(parsed.accountIndex, parsed.event)); - }, - handler, - ); - return Promise.resolve(); -}; - -const callWalletMethodAsync = ( - method: string, - handler: WalletMethodHandler, -): Promise => - new Promise((resolve, reject) => { - callWalletMethod(method, handler, (error: Error, result: string) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); - export { initLogger, createClient, destroyClient, createSecretManager, createWallet, - callClientMethodAsync, - callSecretManagerMethodAsync, + callClientMethod, + callSecretManagerMethod, callUtilsMethod, - callWalletMethodAsync, + callWalletMethod, destroyWallet, - listenWalletAsync, + listenWallet, getClientFromWallet, getSecretManagerFromWallet, listenMqtt, diff --git a/bindings/nodejs/lib/client/client-method-handler.ts b/bindings/nodejs/lib/client/client-method-handler.ts index fb28a4a006..e2066a5307 100644 --- a/bindings/nodejs/lib/client/client-method-handler.ts +++ b/bindings/nodejs/lib/client/client-method-handler.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { - callClientMethodAsync, + callClientMethod, createClient, listenMqtt, destroyClient, @@ -38,10 +38,7 @@ export class ClientMethodHandler { * @returns A promise that resolves to a JSON string response holding the result of the client method. */ async callMethod(method: __ClientMethods__): Promise { - return callClientMethodAsync( - JSON.stringify(method), - this.methodHandler, - ); + return callClientMethod(this.methodHandler, JSON.stringify(method)); } /** @@ -54,6 +51,6 @@ export class ClientMethodHandler { topics: string[], callback: (error: Error, result: string) => void, ): Promise { - return listenMqtt(topics, callback, this.methodHandler); + return listenMqtt(this.methodHandler, topics, callback); } } diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index 96a9cff86b..31f73c0ccf 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -4,19 +4,21 @@ import { ClientMethodHandler } from './client-method-handler'; import { IClientOptions, - QueryParameter, PreparedTransactionData, INetworkInfo, INode, IAuth, - BasicOutputBuilderParams, AccountOutputBuilderParams, + BasicOutputBuilderParams, FoundryOutputBuilderParams, NftOutputBuilderParams, - FoundryQueryParameter, - NftQueryParameter, - AccountQueryParameter, - GenericQueryParameter, + AccountOutputQueryParameters, + AnchorOutputQueryParameters, + BasicOutputQueryParameters, + DelegationOutputQueryParameters, + FoundryOutputQueryParameters, + NftOutputQueryParameters, + OutputQueryParameters, } from '../types/client'; import type { INodeInfoWrapper } from '../types/client/nodeInfo'; import { @@ -36,8 +38,10 @@ import { parseSignedBlock, SignedBlock, AccountId, + AnchorId, NftId, FoundryId, + DelegationId, IssuerId, UnsignedBlock, parseUnsignedBlock, @@ -96,38 +100,6 @@ export class Client { return JSON.parse(response).payload; } - /** - * Fetch alias/basic/NFT/foundry output IDs based on the given query parameters. - */ - async outputIds( - queryParameters: GenericQueryParameter[], - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'outputIds', - data: { - queryParameters, - }, - }); - - return JSON.parse(response).payload; - } - - /** - * Fetch basic output IDs based on the given query parameters. - */ - async basicOutputIds( - queryParameters: QueryParameter[], - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'basicOutputIds', - data: { - queryParameters, - }, - }); - - return JSON.parse(response).payload; - } - /** * Get output from a given output ID. */ @@ -567,114 +539,6 @@ export class Client { return JSON.parse(response).payload; } - /** - * Get the corresponding output IDs given a list of account query parameters. - * - * @param queryParameters An array of `AccountQueryParameter`s. - * @returns A paginated query response of corresponding output IDs. - */ - async accountOutputIds( - queryParameters: AccountQueryParameter[], - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'accountOutputIds', - data: { - queryParameters, - }, - }); - - return JSON.parse(response).payload; - } - - /** - * Get the corresponding output ID from an account ID. - * - * @param accountId An account ID. - * @returns The corresponding output ID. - */ - async accountOutputId(accountId: AccountId): Promise { - const response = await this.methodHandler.callMethod({ - name: 'accountOutputId', - data: { - accountId, - }, - }); - - return JSON.parse(response).payload; - } - - /** - * Get the corresponding output IDs given a list of NFT query parameters. - * - * @param queryParameters An array of `NftQueryParameter`s. - * @returns A paginated query response of corresponding output IDs. - */ - async nftOutputIds( - queryParameters: NftQueryParameter[], - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'nftOutputIds', - data: { - queryParameters, - }, - }); - - return JSON.parse(response).payload; - } - - /** - * Get the corresponding output ID from an NFT ID. - * - * @param nftId An NFT ID. - * @returns The corresponding output ID. - */ - async nftOutputId(nftId: NftId): Promise { - const response = await this.methodHandler.callMethod({ - name: 'nftOutputId', - data: { - nftId, - }, - }); - - return JSON.parse(response).payload; - } - - /** - * Get the corresponding output IDs given a list of Foundry query parameters. - * - * @param queryParameters An array of `FoundryQueryParameter`s. - * @returns A paginated query response of corresponding output IDs. - */ - async foundryOutputIds( - queryParameters: FoundryQueryParameter[], - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'foundryOutputIds', - data: { - queryParameters, - }, - }); - - return JSON.parse(response).payload; - } - - /** - * Get the corresponding output ID from a Foundry ID. - * - * @param foundryId A Foundry ID. - * @returns The corresponding output ID. - */ - async foundryOutputId(foundryId: FoundryId): Promise { - const response = await this.methodHandler.callMethod({ - name: 'foundryOutputId', - data: { - foundryId, - }, - }); - - return JSON.parse(response).payload; - } - /** * Get outputs from provided output IDs (requests are sent * in parallel and errors are ignored, can be useful for spent outputs) @@ -828,14 +692,14 @@ export class Client { } /** - * Calculate the minimum required storage deposit for an output. + * Calculate the minimum required amount for an output. * - * @param output The output to calculate the minimum deposit amount for. + * @param output The output to calculate the minimum amount for. * @returns The minimum required amount. */ - async minimumRequiredStorageDeposit(output: Output): Promise { + async computeMinimumOutputAmount(output: Output): Promise { const response = await this.methodHandler.callMethod({ - name: 'minimumRequiredStorageDeposit', + name: 'computeMinimumOutputAmount', data: { output, }, @@ -894,4 +758,218 @@ export class Client { return JSON.parse(response).payload; } + + // inx-indexer routes + + /** + * Fetch account/anchor/basic/delegation/NFT/foundry output IDs based on the given query parameters. + */ + async outputIds( + queryParameters: OutputQueryParameters, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'outputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Fetch basic output IDs based on the given query parameters. + */ + async basicOutputIds( + queryParameters: BasicOutputQueryParameters, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'basicOutputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output IDs given a list of account query parameters. + * + * @param outputQueryParameters `AccountOutputQueryParameters`. + * @returns A paginated query response of corresponding output IDs. + */ + async accountOutputIds( + queryParameters: AccountOutputQueryParameters, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'accountOutputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output ID from an account ID. + * + * @param accountId An account ID. + * @returns The corresponding output ID. + */ + async accountOutputId(accountId: AccountId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'accountOutputId', + data: { + accountId, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output IDs given a list of anchor query parameters. + * + * @param outputQueryParameters `AnchorOutputQueryParameters`. + * @returns A paginated query response of corresponding output IDs. + */ + async anchorOutputIds( + queryParameters: AnchorOutputQueryParameters, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'anchorOutputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output ID from an anchor ID. + * + * @param anchorId An anchor ID. + * @returns The corresponding output ID. + */ + async anchorOutputId(anchorId: AnchorId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'anchorOutputId', + data: { + anchorId, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output IDs given a list of delegation query parameters. + * + * @param outputQueryParameters `DelegationOutputQueryParameters`. + * @returns A paginated query response of corresponding output IDs. + */ + async delegationOutputIds( + queryParameters: DelegationOutputQueryParameters, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'delegationOutputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output ID from an delegation ID. + * + * @param delegationId A delegation ID. + * @returns The corresponding output ID. + */ + async delegationOutputId(delegationId: DelegationId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'delegationOutputId', + data: { + delegationId, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output IDs given a list of Foundry query parameters. + * + * @param outputQueryParameters `FoundryOutputQueryParameters`. + * @returns A paginated query response of corresponding output IDs. + */ + async foundryOutputIds( + queryParameters: FoundryOutputQueryParameters, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'foundryOutputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output ID from a Foundry ID. + * + * @param foundryId A Foundry ID. + * @returns The corresponding output ID. + */ + async foundryOutputId(foundryId: FoundryId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'foundryOutputId', + data: { + foundryId, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output IDs given a list of NFT query parameters. + * + * @param outputQueryParameters `NftOutputQueryParameters`. + * @returns A paginated query response of corresponding output IDs. + */ + async nftOutputIds( + queryParameters: NftOutputQueryParameters, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'nftOutputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Get the corresponding output ID from an NFT ID. + * + * @param nftId An NFT ID. + * @returns The corresponding output ID. + */ + async nftOutputId(nftId: NftId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'nftOutputId', + data: { + nftId, + }, + }); + + return JSON.parse(response).payload; + } } diff --git a/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts b/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts index 639aa6a036..275363c3de 100644 --- a/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts +++ b/bindings/nodejs/lib/secret_manager/secret-manager-method-handler.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { - callSecretManagerMethodAsync, + callSecretManagerMethod, createSecretManager, migrateStrongholdSnapshotV2ToV3, } from '../bindings'; @@ -34,9 +34,9 @@ export class SecretManagerMethodHandler { * @returns The JSON response of the method. */ async callMethod(method: __SecretManagerMethods__): Promise { - return callSecretManagerMethodAsync( - JSON.stringify(method), + return callSecretManagerMethod( this.methodHandler, + JSON.stringify(method), ); } } diff --git a/bindings/nodejs/lib/types/block/address.ts b/bindings/nodejs/lib/types/block/address.ts index 14e1308a86..b5b7c4889a 100644 --- a/bindings/nodejs/lib/types/block/address.ts +++ b/bindings/nodejs/lib/types/block/address.ts @@ -20,12 +20,12 @@ enum AddressType { Account = 8, /** An NFT address. */ Nft = 16, - /** An implicit account creation address. */ - ImplicitAccountCreation = 24, /** An Anchor address. */ - Anchor = 28, + Anchor = 24, + /** An implicit account creation address. */ + ImplicitAccountCreation = 32, /** An address with restricted capabilities. */ - Restricted = 40, + Restricted = 48, } /** @@ -62,13 +62,13 @@ abstract class Address { ) as any as AccountAddress; } else if (data.type == AddressType.Nft) { return plainToInstance(NftAddress, data) as any as NftAddress; + } else if (data.type == AddressType.Anchor) { + return plainToInstance(AnchorAddress, data) as any as AnchorAddress; } else if (data.type == AddressType.ImplicitAccountCreation) { return plainToInstance( ImplicitAccountCreationAddress, data, ) as any as ImplicitAccountCreationAddress; - } else if (data.type == AddressType.Anchor) { - return plainToInstance(AnchorAddress, data) as any as AnchorAddress; } else if (data.type == AddressType.Restricted) { return plainToInstance( RestrictedAddress, @@ -142,6 +142,27 @@ class NftAddress extends Address { } } +/** + * An Anchor address. + */ +class AnchorAddress extends Address { + /** + * The anchor ID. + */ + readonly anchorId: AnchorId; + /** + * @param address An anchor address as anchor ID. + */ + constructor(address: AnchorId) { + super(AddressType.Anchor); + this.anchorId = address; + } + + toString(): string { + return this.anchorId; + } +} + /** * An implicit account creation address that can be used to convert a Basic Output to an Account Output. */ @@ -164,27 +185,6 @@ class ImplicitAccountCreationAddress extends Address { } } -/** - * An Anchor address. - */ -class AnchorAddress extends Address { - /** - * The anchor ID. - */ - readonly anchorId: AnchorId; - /** - * @param address An anchor address as anchor ID. - */ - constructor(address: AnchorId) { - super(AddressType.Anchor); - this.anchorId = address; - } - - toString(): string { - return this.anchorId; - } -} - const RestrictedAddressDiscriminator = { property: 'type', subTypes: [ @@ -208,7 +208,7 @@ class RestrictedAddress extends Address { /** * The allowed capabilities bitflags. */ - private allowedCapabilities: HexEncodedString = '0x'; + private allowedCapabilities?: HexEncodedString; /** * @param address An address. */ @@ -227,7 +227,7 @@ class RestrictedAddress extends Address { allowedCapabilities.byteLength, ).toString('hex'); } else { - this.allowedCapabilities = '0x'; + this.allowedCapabilities = undefined; } } @@ -239,13 +239,20 @@ class RestrictedAddress extends Address { } getAllowedCapabilities(): Uint8Array { - return Uint8Array.from( - Buffer.from(this.allowedCapabilities.substring(2), 'hex'), - ); + return this.allowedCapabilities !== undefined + ? Uint8Array.from( + Buffer.from(this.allowedCapabilities.substring(2), 'hex'), + ) + : new Uint8Array(); } toString(): string { - return this.address.toString() + this.allowedCapabilities.substring(2); + return ( + this.address.toString() + + (this.allowedCapabilities !== undefined + ? this.allowedCapabilities.substring(2) + : '') + ); } } @@ -255,11 +262,11 @@ const AddressDiscriminator = { { value: Ed25519Address, name: AddressType.Ed25519 as any }, { value: AccountAddress, name: AddressType.Account as any }, { value: NftAddress, name: AddressType.Nft as any }, + { value: AnchorAddress, name: AddressType.Anchor as any }, { value: ImplicitAccountCreationAddress, name: AddressType.ImplicitAccountCreation as any, }, - { value: AnchorAddress, name: AddressType.Anchor as any }, { value: RestrictedAddress, name: AddressType.Restricted as any }, ], }; @@ -273,4 +280,6 @@ export { AccountAddress, NftAddress, AnchorAddress, + ImplicitAccountCreationAddress, + RestrictedAddress, }; diff --git a/bindings/nodejs/lib/types/block/output/output.ts b/bindings/nodejs/lib/types/block/output/output.ts index 95bfdd45af..fb2fe10cb1 100644 --- a/bindings/nodejs/lib/types/block/output/output.ts +++ b/bindings/nodejs/lib/types/block/output/output.ts @@ -25,14 +25,14 @@ enum OutputType { Basic = 0, /** An Account output. */ Account = 1, + /** An Anchor output. */ + Anchor = 2, /** A Foundry output. */ - Foundry = 2, + Foundry = 3, /** An NFT output. */ - Nft = 3, + Nft = 4, /** A Delegation output. */ - Delegation = 4, - /** An Anchor output. */ - Anchor = 5, + Delegation = 5, } /** @@ -74,6 +74,8 @@ abstract class Output { return plainToInstance(BasicOutput, data) as any as BasicOutput; } else if (data.type == OutputType.Account) { return plainToInstance(AccountOutput, data) as any as AccountOutput; + } else if (data.type == OutputType.Anchor) { + return plainToInstance(AnchorOutput, data) as any as AnchorOutput; } else if (data.type == OutputType.Foundry) { return plainToInstance(FoundryOutput, data) as any as FoundryOutput; } else if (data.type == OutputType.Nft) { @@ -83,8 +85,6 @@ abstract class Output { DelegationOutput, data, ) as any as DelegationOutput; - } else if (data.type == OutputType.Anchor) { - return plainToInstance(AnchorOutput, data) as any as AnchorOutput; } throw new Error('Invalid JSON'); } @@ -144,6 +144,7 @@ abstract class CommonOutput extends Output { return this.nativeTokens; } } + /** * A Basic output. */ @@ -272,71 +273,73 @@ class AnchorOutput extends ImmutableFeaturesOutput { } /** - * An NFT output. + * A Foundry output. */ -class NftOutput extends ImmutableFeaturesOutput { +class FoundryOutput extends ImmutableFeaturesOutput { /** - * Unique identifier of the NFT, which is the BLAKE2b-256 hash of the Output ID that created it. - * Unless its newly minted, then the id is zeroed. + * The serial number of the Foundry with respect to the controlling alias. */ - readonly nftId: NftId; + readonly serialNumber: number; /** - * The amount of (stored) Mana held by the output. + * The token scheme for the Foundry. */ - readonly mana: u64; + @Type(() => TokenScheme, { + discriminator: TokenSchemeDiscriminator, + }) + readonly tokenScheme: TokenScheme; /** * @param amount The amount of the output. - * @param mana The amount of stored mana. - * @param nftId The NFT ID as hex-encoded string. + * @param serialNumber The serial number of the Foundry with respect to the controlling alias. * @param unlockConditions The unlock conditions of the output. + * @param tokenScheme The token scheme for the Foundry. */ constructor( amount: u64, - mana: u64, - nftId: NftId, + serialNumber: number, unlockConditions: UnlockCondition[], + tokenScheme: TokenScheme, ) { - super(OutputType.Nft, amount, unlockConditions); - this.nftId = nftId; - this.mana = mana; + super(OutputType.Foundry, amount, unlockConditions); + this.serialNumber = serialNumber; + this.tokenScheme = tokenScheme; } } + /** - * A Foundry output. + * An NFT output. */ -class FoundryOutput extends ImmutableFeaturesOutput { +class NftOutput extends ImmutableFeaturesOutput { /** - * The serial number of the Foundry with respect to the controlling alias. + * Unique identifier of the NFT, which is the BLAKE2b-256 hash of the Output ID that created it. + * Unless its newly minted, then the id is zeroed. */ - readonly serialNumber: number; + readonly nftId: NftId; /** - * The token scheme for the Foundry. + * The amount of (stored) Mana held by the output. */ - @Type(() => TokenScheme, { - discriminator: TokenSchemeDiscriminator, - }) - readonly tokenScheme: TokenScheme; + readonly mana: u64; /** * @param amount The amount of the output. - * @param serialNumber The serial number of the Foundry with respect to the controlling alias. + * @param mana The amount of stored mana. + * @param nftId The NFT ID as hex-encoded string. * @param unlockConditions The unlock conditions of the output. - * @param tokenScheme The token scheme for the Foundry. */ constructor( amount: u64, - serialNumber: number, + mana: u64, + nftId: NftId, unlockConditions: UnlockCondition[], - tokenScheme: TokenScheme, ) { - super(OutputType.Foundry, amount, unlockConditions); - this.serialNumber = serialNumber; - this.tokenScheme = tokenScheme; + super(OutputType.Nft, amount, unlockConditions); + this.nftId = nftId; + this.mana = mana; } } + /** * A Delegation output. */ @@ -402,8 +405,9 @@ const OutputDiscriminator = { subTypes: [ { value: BasicOutput, name: OutputType.Basic as any }, { value: AccountOutput, name: OutputType.Account as any }, - { value: NftOutput, name: OutputType.Nft as any }, + { value: AnchorOutput, name: OutputType.Anchor as any }, { value: FoundryOutput, name: OutputType.Foundry as any }, + { value: NftOutput, name: OutputType.Nft as any }, { value: DelegationOutput, name: OutputType.Delegation as any }, ], }; @@ -416,7 +420,7 @@ export { BasicOutput, AccountOutput, AnchorOutput, - NftOutput, FoundryOutput, + NftOutput, DelegationOutput, }; diff --git a/bindings/nodejs/lib/types/block/payload/signed_transaction/transaction.ts b/bindings/nodejs/lib/types/block/payload/signed_transaction/transaction.ts index e6e217c52e..0ab9c74689 100644 --- a/bindings/nodejs/lib/types/block/payload/signed_transaction/transaction.ts +++ b/bindings/nodejs/lib/types/block/payload/signed_transaction/transaction.ts @@ -44,7 +44,7 @@ class Transaction { readonly allotments: ManaAllotment[]; - private capabilities: HexEncodedString = '0x'; + private capabilities?: HexEncodedString; @Type(() => Payload, { discriminator: PayloadDiscriminator, @@ -91,7 +91,7 @@ class Transaction { capabilities.byteLength, ).toString('hex'); } else { - this.capabilities = '0x'; + this.capabilities = undefined; } } @@ -102,9 +102,11 @@ class Transaction { /** Get the capability bitflags of the transaction. */ getCapabilities(): Uint8Array { - return Uint8Array.from( - Buffer.from(this.capabilities.substring(2), 'hex'), - ); + return this.capabilities !== undefined + ? Uint8Array.from( + Buffer.from(this.capabilities.substring(2), 'hex'), + ) + : new Uint8Array(); } } diff --git a/bindings/nodejs/lib/types/block/payload/signed_transaction/unlock.ts b/bindings/nodejs/lib/types/block/payload/signed_transaction/unlock.ts index 0df204647d..de2107e7d2 100644 --- a/bindings/nodejs/lib/types/block/payload/signed_transaction/unlock.ts +++ b/bindings/nodejs/lib/types/block/payload/signed_transaction/unlock.ts @@ -21,13 +21,13 @@ enum UnlockType { */ Account = 2, /** - * An NFT unlock. + * An Anchor unlock. */ - Nft = 3, + Anchor = 3, /** - * An Anchor unlock. + * An NFT unlock. */ - Anchor = 4, + Nft = 4, } /** @@ -104,9 +104,9 @@ class AccountUnlock extends Unlock { } /** - * An unlock which must reference a previous unlock which unlocks the NFT that the input is locked to. + * An unlock which must reference a previous unlock which unlocks the anchor that the input is locked to. */ -class NftUnlock extends Unlock { +class AnchorUnlock extends Unlock { /** * The reference. */ @@ -116,15 +116,15 @@ class NftUnlock extends Unlock { * @param reference An index referencing a previous unlock. */ constructor(reference: number) { - super(UnlockType.Nft); + super(UnlockType.Anchor); this.reference = reference; } } /** - * An unlock which must reference a previous unlock which unlocks the anchor that the input is locked to. + * An unlock which must reference a previous unlock which unlocks the NFT that the input is locked to. */ -class AnchorUnlock extends Unlock { +class NftUnlock extends Unlock { /** * The reference. */ @@ -134,7 +134,7 @@ class AnchorUnlock extends Unlock { * @param reference An index referencing a previous unlock. */ constructor(reference: number) { - super(UnlockType.Anchor); + super(UnlockType.Nft); this.reference = reference; } } @@ -154,14 +154,14 @@ const UnlockDiscriminator = { value: AccountUnlock, name: UnlockType.Account as any, }, - { - value: NftUnlock, - name: UnlockType.Nft as any, - }, { value: AnchorUnlock, name: UnlockType.Anchor as any, }, + { + value: NftUnlock, + name: UnlockType.Nft as any, + }, ], }; @@ -171,7 +171,7 @@ export { SignatureUnlock, ReferenceUnlock, AccountUnlock, - NftUnlock, AnchorUnlock, + NftUnlock, UnlockDiscriminator, }; diff --git a/bindings/nodejs/lib/types/client/bridge/client.ts b/bindings/nodejs/lib/types/client/bridge/client.ts index 9af8bb3ae5..786ed7cb4e 100644 --- a/bindings/nodejs/lib/types/client/bridge/client.ts +++ b/bindings/nodejs/lib/types/client/bridge/client.ts @@ -11,18 +11,22 @@ import type { BlockId, FoundryId, IssuerId, + AnchorId, NftId, + DelegationId, Output, OutputId, Payload, } from '../../block'; import type { PreparedTransactionData } from '../prepared-transaction-data'; import type { - AccountQueryParameter, - FoundryQueryParameter, - GenericQueryParameter, - NftQueryParameter, - QueryParameter, + AccountOutputQueryParameters, + AnchorOutputQueryParameters, + BasicOutputQueryParameters, + DelegationOutputQueryParameters, + FoundryOutputQueryParameters, + NftOutputQueryParameters, + OutputQueryParameters, } from '../query-parameters'; import type { IAuth } from '../network'; import type { BasicOutputBuilderParams } from '../output_builder_params/basic-output-params'; @@ -43,20 +47,6 @@ export interface __GetOutputMethod__ { }; } -export interface __GetOutputIdsMethod__ { - name: 'outputIds'; - data: { - queryParameters: GenericQueryParameter[]; - }; -} - -export interface __GetBasicOutputIdsMethod__ { - name: 'basicOutputIds'; - data: { - queryParameters: QueryParameter[]; - }; -} - export interface __GetOutputsMethod__ { name: 'getOutputs'; data: { @@ -221,48 +211,6 @@ export interface __HexPublicKeyToBech32AddressMethod__ { }; } -export interface __AccountOutputIdsMethod__ { - name: 'accountOutputIds'; - data: { - queryParameters: AccountQueryParameter[]; - }; -} - -export interface __AccountOutputIdMethod__ { - name: 'accountOutputId'; - data: { - accountId: AccountId; - }; -} - -export interface __NftOutputIdsMethod__ { - name: 'nftOutputIds'; - data: { - queryParameters: NftQueryParameter[]; - }; -} - -export interface __NftOutputIdMethod__ { - name: 'nftOutputId'; - data: { - nftId: NftId; - }; -} - -export interface __FoundryOutputIdsMethod__ { - name: 'foundryOutputIds'; - data: { - queryParameters: FoundryQueryParameter[]; - }; -} - -export interface __FoundryOutputIdMethod__ { - name: 'foundryOutputId'; - data: { - foundryId: FoundryId; - }; -} - export interface __GetOutputsIgnoreErrorsMethod__ { name: 'getOutputsIgnoreErrors'; data: { @@ -308,8 +256,8 @@ export interface __ClearListenersMethod__ { }; } -export type __MinimumRequiredStorageDepositMethod__ = { - name: 'minimumRequiredStorageDeposit'; +export type __ComputeMinimumOutputAmountMethod__ = { + name: 'computeMinimumOutputAmount'; data: { output: Output; }; @@ -333,3 +281,89 @@ export type __CallPluginRouteMethod__ = { request?: string; }; }; + +// inx-indexer methods + +export interface __GetOutputIdsMethod__ { + name: 'outputIds'; + data: { + queryParameters: OutputQueryParameters; + }; +} + +export interface __GetBasicOutputIdsMethod__ { + name: 'basicOutputIds'; + data: { + queryParameters: BasicOutputQueryParameters; + }; +} + +export interface __AccountOutputIdsMethod__ { + name: 'accountOutputIds'; + data: { + queryParameters: AccountOutputQueryParameters; + }; +} + +export interface __AccountOutputIdMethod__ { + name: 'accountOutputId'; + data: { + accountId: AccountId; + }; +} + +export interface __AnchorOutputIdsMethod__ { + name: 'anchorOutputIds'; + data: { + queryParameters: AnchorOutputQueryParameters; + }; +} + +export interface __AnchorOutputIdMethod__ { + name: 'anchorOutputId'; + data: { + anchorId: AnchorId; + }; +} + +export interface __DelegationOutputIdsMethod__ { + name: 'delegationOutputIds'; + data: { + queryParameters: DelegationOutputQueryParameters; + }; +} + +export interface __DelegationOutputIdMethod__ { + name: 'delegationOutputId'; + data: { + delegationId: DelegationId; + }; +} + +export interface __FoundryOutputIdsMethod__ { + name: 'foundryOutputIds'; + data: { + queryParameters: FoundryOutputQueryParameters; + }; +} + +export interface __FoundryOutputIdMethod__ { + name: 'foundryOutputId'; + data: { + foundryId: FoundryId; + }; +} + +export interface __NftOutputIdsMethod__ { + name: 'nftOutputIds'; + data: { + queryParameters: NftOutputQueryParameters; + }; +} + +export interface __NftOutputIdMethod__ { + name: 'nftOutputId'; + data: { + nftId: NftId; + }; +} diff --git a/bindings/nodejs/lib/types/client/bridge/index.ts b/bindings/nodejs/lib/types/client/bridge/index.ts index 67034eeb92..8535e6086d 100644 --- a/bindings/nodejs/lib/types/client/bridge/index.ts +++ b/bindings/nodejs/lib/types/client/bridge/index.ts @@ -32,10 +32,14 @@ import type { __HexPublicKeyToBech32AddressMethod__, __AccountOutputIdsMethod__, __AccountOutputIdMethod__, - __NftOutputIdsMethod__, - __NftOutputIdMethod__, + __AnchorOutputIdsMethod__, + __AnchorOutputIdMethod__, + __DelegationOutputIdsMethod__, + __DelegationOutputIdMethod__, __FoundryOutputIdsMethod__, __FoundryOutputIdMethod__, + __NftOutputIdsMethod__, + __NftOutputIdMethod__, __GetOutputsIgnoreErrorsMethod__, __FindBlocksMethod__, __UnhealthyNodesMethod__, @@ -45,7 +49,7 @@ import type { __BuildNftOutputMethod__, __ClearListenersMethod__, __SignatureUnlockMethod__, - __MinimumRequiredStorageDepositMethod__, + __ComputeMinimumOutputAmountMethod__, __RequestFundsFromFaucetMethod__, __CallPluginRouteMethod__, } from './client'; @@ -82,10 +86,14 @@ export type __ClientMethods__ = | __HexPublicKeyToBech32AddressMethod__ | __AccountOutputIdsMethod__ | __AccountOutputIdMethod__ - | __NftOutputIdsMethod__ - | __NftOutputIdMethod__ + | __AnchorOutputIdsMethod__ + | __AnchorOutputIdMethod__ + | __DelegationOutputIdsMethod__ + | __DelegationOutputIdMethod__ | __FoundryOutputIdsMethod__ | __FoundryOutputIdMethod__ + | __NftOutputIdsMethod__ + | __NftOutputIdMethod__ | __GetOutputsIgnoreErrorsMethod__ | __FindBlocksMethod__ | __UnhealthyNodesMethod__ @@ -94,6 +102,6 @@ export type __ClientMethods__ = | __BuildFoundryOutputMethod__ | __BuildNftOutputMethod__ | __ClearListenersMethod__ - | __MinimumRequiredStorageDepositMethod__ + | __ComputeMinimumOutputAmountMethod__ | __RequestFundsFromFaucetMethod__ | __CallPluginRouteMethod__; diff --git a/bindings/nodejs/lib/types/client/output_builder_params/basic-output-params.ts b/bindings/nodejs/lib/types/client/output_builder_params/basic-output-params.ts index 96668662be..0ec7993f93 100644 --- a/bindings/nodejs/lib/types/client/output_builder_params/basic-output-params.ts +++ b/bindings/nodejs/lib/types/client/output_builder_params/basic-output-params.ts @@ -14,7 +14,7 @@ import { */ export interface BasicOutputBuilderParams { /** - * If not provided, minimum storage deposit will be used + * If not provided, minimum amount will be used */ amount?: u64 | NumericString; /** diff --git a/bindings/nodejs/lib/types/client/query-parameters.ts b/bindings/nodejs/lib/types/client/query-parameters.ts index 53023b109b..29c9cab157 100644 --- a/bindings/nodejs/lib/types/client/query-parameters.ts +++ b/bindings/nodejs/lib/types/client/query-parameters.ts @@ -2,179 +2,162 @@ // SPDX-License-Identifier: Apache-2.0 import { SlotIndex } from '../block/slot'; -import { Bech32Address } from '../block'; +import { Bech32Address, TokenId } from '../block'; +import { HexEncodedString } from '../utils/hex-encoding'; /** - * Query parameter for filtering output requests + * Common query parameters for output requests. */ -export type QueryParameter = - | Address - | AccountAddress - | HasStorageDepositReturn - | StorageDepositReturnAddress - | HasTimelock - | TimelockedBefore - | TimelockedAfter - | HasExpiration - | ExpiresBefore - | ExpiresAfter - | ExpirationReturnAddress - | Sender - | Tag - | Issuer - | StateController - | Governor - | UnlockableByAddress - | CommonQueryParameters; - -/** Query parameters for filtering Account Outputs */ -export type AccountQueryParameter = - | Address - | Issuer - | Sender - | UnlockableByAddress - | CommonQueryParameters; - -/** Query parameters for filtering Foundry Outputs */ -export type FoundryQueryParameter = AccountAddress | CommonQueryParameters; - -/** Query parameters for filtering Nft Outputs */ -export type NftQueryParameter = - | Address - | HasStorageDepositReturn - | StorageDepositReturnAddress - | HasTimelock - | TimelockedBefore - | TimelockedAfter - | HasExpiration - | ExpiresBefore - | ExpiresAfter - | ExpirationReturnAddress - | Issuer - | Sender - | Tag - | UnlockableByAddress - | CommonQueryParameters; - -/** Shared query parameters*/ -type CommonQueryParameters = - | HasNativeTokens - | MinNativeTokenCount - | MaxNativeTokenCount - | CreatedAfter - | CreatedBefore - | PageSize - | Cursor; - -/** Query parameters for filtering alias/basic/NFT/foundry Outputs*/ -export type GenericQueryParameter = - | UnlockableByAddress - | HasNativeTokens - | MinNativeTokenCount - | MaxNativeTokenCount - | CreatedAfter - | CreatedBefore - | PageSize - | Cursor; - -/** Bech32-encoded address that should be searched for. */ -interface Address { - address: Bech32Address; +export interface CommonOutputQueryParameters { + /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is + /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. + pageSize?: number; + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty string is provided, only + /// the first page is returned. + cursor?: string; + /// Returns outputs that were created before a certain slot index. + createdBefore?: SlotIndex; + /// Returns outputs that were created after a certain slot index. + createdAfter?: SlotIndex; } -/** Filter foundry outputs based on bech32-encoded address of the controlling account. */ -interface AccountAddress { - accountAddress: Bech32Address; -} -/** Filters outputs based on the presence of storage deposit return unlock condition. */ -interface HasStorageDepositReturn { - hasStorageDepositReturn: boolean; -} -/** Filter outputs based on the presence of a specific Bech32-encoded return address - * in the storage deposit return unlock condition. + +/** + * Query parameters for output requests. */ -interface StorageDepositReturnAddress { - storageDepositReturnAddress: Bech32Address; -} -/** Filters outputs based on the presence of timelock unlock condition. */ -interface HasTimelock { - hasTimelock: boolean; -} -/** Return outputs that are timelocked before a certain slot index. */ -interface TimelockedBefore { - timelockedBefore: SlotIndex; -} -/** Return outputs that are timelocked after a certain slot index. */ -interface TimelockedAfter { - timelockedAfter: SlotIndex; +export interface OutputQueryParameters extends CommonOutputQueryParameters { + /// Filters outputs based on the presence of a native token. + hasNativeToken?: boolean; + /// Filters outputs based on the presence of a specific native token. + nativeToken?: TokenId; + /// Returns outputs that are unlockable by the bech32 address. + unlockableByAddress?: Bech32Address; } -/** Filters outputs based on the presence of expiration unlock condition. */ -interface HasExpiration { - hasExpiration: boolean; -} -/** Filters outputs based on the presence of native tokens. */ -interface HasNativeTokens { - hasNativeTokens: boolean; -} -/** Filters outputs that have at most a certain number of distinct native tokens. */ -interface MaxNativeTokenCount { - maxNativeTokenCount: number; -} -/** Filters outputs that have at least a certain number of distinct native tokens. */ -interface MinNativeTokenCount { - minNativeTokenCount: number; -} -/** Return outputs that expire before a certain slot index. */ -interface ExpiresBefore { - expiresBefore: SlotIndex; -} -/** Return outputs that expire after a certain slot index. */ -interface ExpiresAfter { - expiresAfter: SlotIndex; -} -/** Filter outputs based on the presence of a specific Bech32-encoded return - * address in the expiration unlock condition. - * */ -interface ExpirationReturnAddress { - expirationReturnAddress: Bech32Address; -} -/** Filter for a certain sender */ -interface Sender { - sender: string; -} -/** Filter for a certain tag */ -interface Tag { - tag: string; -} -/** Return outputs that were created before a certain slot index. */ -interface CreatedBefore { - createdBefore: SlotIndex; -} -/** Return outputs that were created after a certain slot index. */ -interface CreatedAfter { - createdAfter: SlotIndex; -} -/** Pass the cursor(confirmationMS+outputId.pageSize) to start the results from */ -interface Cursor { - cursor: string; +/** + * Query parameters for basic output requests. + */ +export interface BasicOutputQueryParameters + extends CommonOutputQueryParameters { + /// Filters outputs based on the presence of a native token. + hasNativeToken?: boolean; + /// Filters outputs based on the presence of a specific native token. + nativeToken?: TokenId; + /// Returns outputs that are unlockable by the bech32 address. + unlockableByAddress?: Bech32Address; + /// Bech32-encoded address that should be searched for. + address?: Bech32Address; + /// Filters outputs based on the presence of storage deposit return unlock condition. + hasStorageDepositReturn?: boolean; + /// Filters outputs based on the presence of a specific return address in the storage deposit return unlock + /// condition. + storageDepositReturnAddress?: Bech32Address; + /// Filters outputs based on the presence of expiration unlock condition. + hasExpiration?: boolean; + /// Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock + /// condition. + expirationReturnAddress?: Bech32Address; + /// Returns outputs that expire before a certain slot index. + expiresBefore?: SlotIndex; + /// Returns outputs that expire after a certain slot index. + expiresAfter?: SlotIndex; + /// Filters outputs based on the presence of timelock unlock condition. + hasTimelock?: boolean; + /// Returns outputs that are timelocked before a certain slot index. + timelockedBefore?: SlotIndex; + /// Returns outputs that are timelocked after a certain slot index. + timelockedAfter?: SlotIndex; + /// Filters outputs based on the presence of validated Sender (bech32 encoded). + sender?: Bech32Address; + /// Filters outputs based on matching Tag Block. + tag?: HexEncodedString; } -/** Filter for a certain issuer */ -interface Issuer { - issuer: string; + +/** + * Query parameters for account output requests. + */ +export interface AccountOutputQueryParameters + extends CommonOutputQueryParameters { + /// Bech32-encoded address that should be searched for. + address?: Bech32Address; + /// Filters outputs based on bech32-encoded issuer address. + issuer?: Bech32Address; + /// Filters outputs based on the presence of validated Sender (bech32 encoded). + sender?: Bech32Address; } -/** Filter outputs based on bech32-encoded state controller address. */ -interface StateController { - stateController: Bech32Address; + +/** + * Query parameters for anchor output requests. + */ +export interface AnchorOutputQueryParameters + extends CommonOutputQueryParameters { + /// Returns outputs that are unlockable by the bech32 address. + unlockableByAddress?: Bech32Address; + /// Filters outputs based on bech32-encoded state controller address. + stateController?: Bech32Address; + /// Filters outputs based on bech32-encoded governor (governance controller) address. + governor?: Bech32Address; + /// Filters outputs based on bech32-encoded issuer address. + issuer?: Bech32Address; + /// Filters outputs based on the presence of validated Sender (bech32 encoded). + sender?: Bech32Address; } -/** Filter outputs based on bech32-encoded governor (governance controller) address. */ -interface Governor { - governor: Bech32Address; + +/** + * Query parameters for delegation output requests. + */ +export interface DelegationOutputQueryParameters + extends CommonOutputQueryParameters { + /// Bech32-encoded address that should be searched for. + address?: Bech32Address; + /// Filter foundry outputs based on bech32-encoded address of the validator. + validator?: Bech32Address; } -/** Define the page size for the results. */ -interface PageSize { - pageSize: number; + +/** + * Query parameters for foundry output requests. + */ +export interface FoundryOutputQueryParameters + extends CommonOutputQueryParameters { + /// Filters outputs based on the presence of a native token. + hasNativeToken?: boolean; + /// Filters outputs based on the presence of a specific native token. + nativeToken?: TokenId; + /// Filter foundry outputs based on bech32-encoded address of the controlling account. + account?: Bech32Address; } -/** Returns outputs that are unlockable by the bech32 address. */ -interface UnlockableByAddress { - unlockableByAddress: Bech32Address; + +/** + * Query parameters for NFT output requests. + */ +export interface NftOutputQueryParameters extends CommonOutputQueryParameters { + /// Returns outputs that are unlockable by the bech32 address. + unlockableByAddress?: Bech32Address; + /// Bech32-encoded address that should be searched for. + address?: Bech32Address; + /// Filters outputs based on the presence of storage deposit return unlock condition. + hasStorageDepositReturn?: boolean; + /// Filters outputs based on the presence of a specific return address in the storage deposit return unlock + /// condition. + storageDepositReturnAddress?: Bech32Address; + /// Filters outputs based on the presence of expiration unlock condition. + hasExpiration?: boolean; + /// Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock + /// condition. + expirationReturnAddress?: Bech32Address; + /// Returns outputs that expire before a certain slot index. + expiresBefore?: SlotIndex; + /// Returns outputs that expire after a certain slot index. + expiresAfter?: SlotIndex; + /// Filters outputs based on the presence of timelock unlock condition. + hasTimelock?: boolean; + /// Returns outputs that are timelocked before a certain slot index. + timelockedBefore?: SlotIndex; + /// Returns outputs that are timelocked after a certain slot index. + timelockedAfter?: SlotIndex; + /// Filters outputs based on bech32-encoded issuer address. + issuer?: Bech32Address; + /// Filters outputs based on the presence of validated Sender (bech32 encoded). + sender?: Bech32Address; + /// Filters outputs based on matching Tag Block. + tag?: HexEncodedString; } diff --git a/bindings/nodejs/lib/types/wallet/account.ts b/bindings/nodejs/lib/types/wallet/account.ts deleted file mode 100644 index 5c4b947b0a..0000000000 --- a/bindings/nodejs/lib/types/wallet/account.ts +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2021-2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { Bip44Address, AddressWithUnspentOutputs } from './address'; -import { AccountId, FoundryId, NftId } from '../block/id'; -import type { OutputData } from './output'; -import type { TransactionWithMetadata } from './transaction'; -import { CoinType } from '../../client'; -import { HexEncodedString, u256, u64 } from '../utils'; -import { Bech32Address } from '../block/address'; - -/** - * Account identifier - * Could be the account index (number) or account alias (string) - */ -export type AccountIdentifier = number | string; - -/** A balance */ -export interface Balance { - /** The balance of the base coin */ - baseCoin: BaseCoinBalance; - /** The required storage deposit for the outputs */ - requiredStorageDeposit: RequiredStorageDeposit; - /** The balance of the native tokens */ - nativeTokens: NativeTokenBalance[]; - /** Nft outputs */ - nfts: string[]; - /** Account outputs */ - accounts: string[]; - /** Foundry outputs */ - foundries: string[]; - /** - * Outputs with multiple unlock conditions and if they can currently be spent or not. If there is a - * TimelockUnlockCondition or ExpirationUnlockCondition this can change at any time - */ - potentiallyLockedOutputs: { [outputId: string]: boolean }; -} - -/** The balance of the base coin */ -export interface BaseCoinBalance { - /** The total amount of the outputs */ - total: u64; - /** The amount of the outputs that aren't used in a transaction */ - available: u64; - /** Voting power */ - votingPower: string; -} - -/** The required storage deposit per output type */ -export interface RequiredStorageDeposit { - /** The required amount for Alias outputs. */ - account: u64; - /** The required amount for Basic outputs. */ - basic: u64; - /** The required amount for Foundry outputs. */ - foundry: u64; - /** The required amount for NFT outputs. */ - nft: u64; -} - -/** The balance of a native token */ -export interface NativeTokenBalance { - /** The native token id. */ - tokenId: HexEncodedString; - /** Some metadata of the native token. */ - metadata?: string; - /** The total native token balance. */ - total: u256; - /** The available amount of the total native token balance. */ - available: u256; -} - -/** Sync options for an account */ -export interface SyncOptions { - /** - * Specific Bech32 encoded addresses of the account to sync, if addresses are provided, - * then `address_start_index` will be ignored - */ - addresses?: Bech32Address[]; - /** - * Address index from which to start syncing addresses. 0 by default, using a higher index will be faster because - * addresses with a lower index will be skipped, but could result in a wrong balance for that reason - */ - addressStartIndex?: number; - /** - * Address index from which to start syncing internal addresses. 0 by default, using a higher index will be faster - * because addresses with a lower index will be skipped, but could result in a wrong balance for that reason - */ - addressStartIndexInternal?: number; - /** - * Usually syncing is skipped if it's called in between 200ms, because there can only be new changes every - * milestone and calling it twice "at the same time" will not return new data - * When this to true, we will sync anyways, even if it's called 0ms after the las sync finished. Default: false. - */ - forceSyncing?: boolean; - /// Try to sync transactions from incoming outputs with their inputs. Some data may not be obtained if it has been - /// pruned. - syncIncomingTransactions?: boolean; - /** Checks pending transactions and reissues them if necessary. Default: true. */ - syncPendingTransactions?: boolean; - /** Specifies what outputs should be synced for the ed25519 addresses from the account. */ - account?: AccountSyncOptions; - /** Specifies what outputs should be synced for the address of an account output. */ - // TODO Rename when we are done with Account changes https://github.com/iotaledger/iota-sdk/issues/647. - alias?: AliasSyncOptions; - /** Specifies what outputs should be synced for the address of an nft output. */ - nft?: NftSyncOptions; - /** Specifies if only basic outputs with an AddressUnlockCondition alone should be synced, will overwrite `account`, `alias` and `nft` options. Default: false. */ - syncOnlyMostBasicOutputs?: boolean; - /** Sync native token foundries, so their metadata can be returned in the balance. Default: false. */ - syncNativeTokenFoundries?: boolean; -} - -/** Specifies what outputs should be synced for the ed25519 addresses from the account. */ -export interface AccountSyncOptions { - /** Whether to sync Basic outputs. */ - basicOutputs?: boolean; - /** Whether to sync Account outputs. */ - accountOutputs?: boolean; - /** Whether to sync NFT outputs. */ - nftOutputs?: boolean; -} - -/** Specifies what outputs should be synced for the address of an account output. */ -export interface AliasSyncOptions { - /** Whether to sync Basic outputs. */ - basicOutputs?: boolean; - /** Whether to sync Account outputs. */ - accountOutputs?: boolean; - /** Whether to sync NFT outputs. */ - nftOutputs?: boolean; - /** Whether to sync foundry outputs. */ - foundryOutputs?: boolean; -} - -/** Specifies what outputs should be synced for the address of an nft output. */ -export interface NftSyncOptions { - /** Whether to sync Basic outputs. */ - basicOutputs?: boolean; - /** Whether to sync Account outputs. */ - accountOutputs?: boolean; - /** Whether to sync NFT outputs. */ - nftOutputs?: boolean; -} - -/** The account object. */ -export interface AccountMeta { - /** The account index. */ - index: number; - /** The type of coin managed with the account. */ - coinType: CoinType; - /** The alias name of the account. */ - alias: string; - /** All public addresses. */ - publicAddresses: Bip44Address[]; - /** All internal addresses. */ - internalAddresses: Bip44Address[]; - /** All addresses with unspent outputs. */ - addressesWithUnspentOutputs: AddressWithUnspentOutputs[]; - /** All outputs of the account. */ - outputs: { [outputId: string]: OutputData }; - /** All IDs of unspent outputs that are currently used as inputs for transactions. */ - lockedOutputs: Set; - /** All unspent outputs of the account. */ - unspentOutputs: { [outputId: string]: OutputData }; - /** All transactions of the account. */ - transactions: { [transactionId: string]: TransactionWithMetadata }; - /** All pending transactions of the account. */ - pendingTransactions: Set; - /** All incoming transactions of the account (with their inputs if available and not already pruned). */ - incomingTransactions: { - [transactionId: string]: [TransactionWithMetadata]; - }; -} - -/** The account metadata. */ -export interface AccountMetadata { - /** The account alias */ - alias: string; - /** The used coin type */ - coinType: CoinType; - /** The account index which will be used in the BIP32 path */ - index: number; -} - -/** Options for account creation. */ -export interface CreateAccountPayload { - /** An account alias name. */ - alias?: string; - /** The Bech32 HRP (human readable part) to use. */ - bech32Hrp?: string; - /** BIP44 addresses to use. */ - addresses?: Bip44Address[]; -} - -/** Options to filter outputs */ -export interface FilterOptions { - /** Filter all outputs where the booked milestone index is below the specified timestamp */ - lowerBoundBookedTimestamp?: number; - /** Filter all outputs where the booked milestone index is above the specified timestamp */ - upperBoundBookedTimestamp?: number; - /** Filter all outputs for the provided types (Basic = 3, Account = 4, Foundry = 5, NFT = 6) */ - outputTypes?: number[]; - /** Return all account outputs matching these IDs. */ - accountIds?: AccountId[]; - /** Return all foundry outputs matching these IDs. */ - foundryIds?: FoundryId[]; - /** Return all NFT outputs matching these IDs. */ - nftIds?: NftId[]; -} diff --git a/bindings/nodejs/lib/types/wallet/address.ts b/bindings/nodejs/lib/types/wallet/address.ts index b844c50095..d655fb8016 100644 --- a/bindings/nodejs/lib/types/wallet/address.ts +++ b/bindings/nodejs/lib/types/wallet/address.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { SlotIndex } from '../block/slot'; -import { Bech32Address, NftId, OutputId, TokenId } from '../block'; +import { Bech32Address, NftId, TokenId } from '../block'; import { NumericString, u256, u64 } from '../utils'; /** A Bip44 address */ @@ -24,7 +24,7 @@ export interface SendParams { /** * Bech32 encoded address, to which the storage deposit will be returned if one is necessary * given the provided amount. If a storage deposit is needed and a return address is not provided, it will - * default to the first address of the account. + * default to the address of the wallet. */ returnAddress?: string; /** @@ -35,19 +35,7 @@ export interface SendParams { expiration?: SlotIndex; } -/** Address with unspent outputs */ -export interface AddressWithUnspentOutputs { - /** The Bech32 address. */ - address: Bech32Address; - /** The address key index. */ - keyIndex: number; - /** Whether the address is a public or an internal (change) address. */ - internal: boolean; - /** The IDs of associated unspent outputs. */ - outputIds: OutputId[]; -} - -/** Address with a native token */ +/** Address with native token */ export interface SendNativeTokenParams { /** The Bech32 address. */ address: Bech32Address; @@ -55,7 +43,7 @@ export interface SendNativeTokenParams { nativeToken: [TokenId, u256][]; /** * Bech32 encoded address, to which the storage deposit will be returned. - * Default will use the first address of the account. + * Default will use the address of the wallet. */ returnAddress?: Bech32Address; /** diff --git a/bindings/nodejs/lib/types/wallet/bridge/account.ts b/bindings/nodejs/lib/types/wallet/bridge/account.ts index e597154c75..32dc65c10c 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/account.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/account.ts @@ -1,7 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { SyncOptions, FilterOptions } from '../account'; import type { SendParams, SendNativeTokenParams, @@ -25,10 +24,12 @@ import type { } from '../participation'; import type { ConsolidationParams } from '../consolidation-params'; import { + FilterOptions, HexEncodedAmount, NumericString, Output, OutputId, + SyncOptions, TokenId, TransactionId, } from '../../'; @@ -145,6 +146,18 @@ export type __PendingTransactionsMethod__ = { name: 'pendingTransactions'; }; +export type __ImplicitAccountCreationAddressMethod__ = { + name: 'implicitAccountCreationAddress'; +}; + +export type __AccountsMethod__ = { + name: 'accounts'; +}; + +export type __ImplicitAccountsMethod__ = { + name: 'implicitAccounts'; +}; + export type __IncomingTransactionsMethod__ = { name: 'incomingTransactions'; }; diff --git a/bindings/nodejs/lib/types/wallet/bridge/index.ts b/bindings/nodejs/lib/types/wallet/bridge/index.ts index fc96daea69..2869168102 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/index.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/index.ts @@ -1,20 +1,19 @@ -import type { AccountIdentifier } from '../account'; import type { __PrepareBurnMethod__, __ClaimOutputsMethod__, __PrepareConsolidateOutputsMethod__, __PrepareCreateAccountOutputMethod__, __DeregisterParticipationEventMethod__, - __GenerateEd25519AddressesMethod__, __GetBalanceMethod__, __GetOutputMethod__, __GetFoundryOutputMethod__, __ClaimableOutputsMethod__, __GetTransactionMethod__, - __AddressesMethod__, - __AddressesWithUnspentOutputsMethod__, __OutputsMethod__, __PendingTransactionsMethod__, + __ImplicitAccountCreationAddressMethod__, + __AccountsMethod__, + __ImplicitAccountsMethod__, __IncomingTransactionsMethod__, __TransactionsMethod__, __UnspentOutputsMethod__, @@ -48,23 +47,14 @@ import type { __PrepareIncreaseVotingPowerMethod__, __PrepareDecreaseVotingPowerMethod__, __PrepareStopParticipatingMethod__, -} from './account'; -import type { __BackupMethod__, __ChangeStrongholdPasswordMethod__, __ClearStrongholdPasswordMethod__, __ClearListenersMethod__, - __CreateAccountMethod__, __EmitTestEventMethod__, __GenerateMnemonicMethod__, - __GetAccountMethod__, - __GetAccountIndexesMethod__, - __GetAccountsMethod__, __GetLedgerNanoStatusMethod__, - __GenerateEd25519AddressMethod__, __IsStrongholdPasswordAvailableMethod__, - __RecoverAccountsMethod__, - __RemoveLatestAccountMethod__, __RestoreBackupMethod__, __SetClientOptionsMethod__, __SetStrongholdPasswordClearIntervalMethod__, @@ -73,15 +63,15 @@ import type { __StopBackgroundSyncMethod__, __StoreMnemonicMethod__, __UpdateNodeAuthMethod__, + __AddressMethod__, } from './wallet'; -export type __AccountMethod__ = +export type __WalletMethod__ = | __PrepareBurnMethod__ | __ClaimOutputsMethod__ | __PrepareConsolidateOutputsMethod__ | __PrepareCreateAccountOutputMethod__ | __DeregisterParticipationEventMethod__ - | __GenerateEd25519AddressesMethod__ | __GetBalanceMethod__ | __GetOutputMethod__ | __GetIncomingTransactionMethod__ @@ -92,10 +82,11 @@ export type __AccountMethod__ = | __GetParticipationEventsMethod__ | __GetParticipationEventStatusMethod__ | __GetTransactionMethod__ - | __AddressesMethod__ - | __AddressesWithUnspentOutputsMethod__ | __OutputsMethod__ | __PendingTransactionsMethod__ + | __ImplicitAccountCreationAddressMethod__ + | __AccountsMethod__ + | __ImplicitAccountsMethod__ | __IncomingTransactionsMethod__ | __TransactionsMethod__ | __UnspentOutputsMethod__ @@ -123,33 +114,15 @@ export type __AccountMethod__ = | __PrepareStopParticipatingMethod__ | __GetParticipationOverviewMethod__ | __PrepareIncreaseVotingPowerMethod__ - | __PrepareDecreaseVotingPowerMethod__; - -export type __CallAccountMethodMethod__ = { - name: 'callAccountMethod'; - data: { - accountId: AccountIdentifier; - method: __AccountMethod__; - }; -}; - -export type __Method__ = + | __PrepareDecreaseVotingPowerMethod__ | __BackupMethod__ - | __CallAccountMethodMethod__ | __ChangeStrongholdPasswordMethod__ | __ClearListenersMethod__ | __ClearStrongholdPasswordMethod__ - | __CreateAccountMethod__ | __EmitTestEventMethod__ | __GenerateMnemonicMethod__ - | __GetAccountMethod__ - | __GetAccountIndexesMethod__ - | __GetAccountsMethod__ | __GetLedgerNanoStatusMethod__ - | __GenerateEd25519AddressMethod__ | __IsStrongholdPasswordAvailableMethod__ - | __RecoverAccountsMethod__ - | __RemoveLatestAccountMethod__ | __RestoreBackupMethod__ | __SetClientOptionsMethod__ | __SetStrongholdPasswordClearIntervalMethod__ @@ -157,4 +130,5 @@ export type __Method__ = | __StartBackgroundSyncMethod__ | __StopBackgroundSyncMethod__ | __StoreMnemonicMethod__ - | __UpdateNodeAuthMethod__; + | __UpdateNodeAuthMethod__ + | __AddressMethod__; diff --git a/bindings/nodejs/lib/types/wallet/bridge/wallet.ts b/bindings/nodejs/lib/types/wallet/bridge/wallet.ts index 473050a7cb..63dc52caa8 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/wallet.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/wallet.ts @@ -1,11 +1,44 @@ -import type { - AccountIdentifier, - CreateAccountPayload, - SyncOptions, -} from '../account'; -import type { GenerateAddressOptions } from '../address'; +import type { SyncOptions, FilterOptions } from '../wallet'; import type { WalletEventType, WalletEvent } from '../event'; -import type { IAuth, IClientOptions } from '../../client'; +import type { + IAuth, + IClientOptions, + Burn, + INode, + PreparedTransactionData, +} from '../../client'; +import type { + SendParams, + SendNativeTokensParams, + SendNftParams, +} from '../address'; +import type { OutputParams } from '../output-params'; +import type { OutputsToClaim } from '../output'; +import type { SignedTransactionData } from '../signed-transaction-data'; +import type { + AccountOutputParams, + CreateNativeTokenParams, + TransactionOptions, + MintNftParams, +} from '../transaction-options'; +import type { + ParticipationEventId, + ParticipationEventRegistrationOptions, + ParticipationEventType, +} from '../participation'; +import type { ConsolidationParams } from '../consolidation-params'; +import { + HexEncodedAmount, + NumericString, + Output, + OutputId, + TokenId, + TransactionId, +} from '../../'; + +export type __AccountsMethod__ = { + name: 'accounts'; +}; export type __BackupMethod__ = { name: 'backup'; @@ -32,11 +65,6 @@ export type __ClearListenersMethod__ = { data: { eventTypes: WalletEventType[] }; }; -export type __CreateAccountMethod__ = { - name: 'createAccount'; - data: CreateAccountPayload; -}; - export type __EmitTestEventMethod__ = { name: 'emitTestEvent'; data: { event: WalletEvent }; @@ -46,51 +74,14 @@ export type __GenerateMnemonicMethod__ = { name: 'generateMnemonic'; }; -export type __GetAccountIndexesMethod__ = { - name: 'getAccountIndexes'; -}; - -export type __GetAccountsMethod__ = { - name: 'getAccounts'; -}; - -export type __GetAccountMethod__ = { - name: 'getAccount'; - data: { accountId: AccountIdentifier }; -}; - export type __GetLedgerNanoStatusMethod__ = { name: 'getLedgerNanoStatus'; }; -export type __GenerateEd25519AddressMethod__ = { - name: 'generateEd25519Address'; - data: { - accountIndex: number; - addressIndex: number; - options?: GenerateAddressOptions; - bech32Hrp?: string; - }; -}; - export type __IsStrongholdPasswordAvailableMethod__ = { name: 'isStrongholdPasswordAvailable'; }; -export type __RecoverAccountsMethod__ = { - name: 'recoverAccounts'; - data: { - accountStartIndex: number; - accountGapLimit: number; - addressGapLimit: number; - syncOptions?: SyncOptions; - }; -}; - -export type __RemoveLatestAccountMethod__ = { - name: 'removeLatestAccount'; -}; - export type __RestoreBackupMethod__ = { name: 'restoreBackup'; data: { @@ -137,3 +128,336 @@ export type __UpdateNodeAuthMethod__ = { name: 'updateNodeAuth'; data: { url: string; auth?: IAuth }; }; + +export type __PrepareBurnMethod__ = { + name: 'prepareBurn'; + data: { + burn: Burn; + options?: TransactionOptions; + }; +}; + +export type __ClaimOutputsMethod__ = { + name: 'claimOutputs'; + data: { + outputIdsToClaim: OutputId[]; + }; +}; + +export type __PrepareConsolidateOutputsMethod__ = { + name: 'prepareConsolidateOutputs'; + data: { + params: ConsolidationParams; + }; +}; + +export type __PrepareCreateAccountOutputMethod__ = { + name: 'prepareCreateAccountOutput'; + data: { + params?: AccountOutputParams; + options?: TransactionOptions; + }; +}; + +export type __PrepareMeltNativeTokenMethod__ = { + name: 'prepareMeltNativeToken'; + data: { + tokenId: TokenId; + meltAmount: HexEncodedAmount; + options?: TransactionOptions; + }; +}; + +export type __DeregisterParticipationEventMethod__ = { + name: 'deregisterParticipationEvent'; + data: { + eventId: ParticipationEventId; + }; +}; + +export type __GetBalanceMethod__ = { + name: 'getBalance'; +}; + +export type __GetIncomingTransactionMethod__ = { + name: 'getIncomingTransaction'; + data: { + transactionId: TransactionId; + }; +}; + +export type __GetOutputMethod__ = { + name: 'getOutput'; + data: { + outputId: OutputId; + }; +}; + +export type __GetFoundryOutputMethod__ = { + name: 'getFoundryOutput'; + data: { + tokenId: TokenId; + }; +}; + +export type __ClaimableOutputsMethod__ = { + name: 'claimableOutputs'; + data: { + outputsToClaim: OutputsToClaim; + }; +}; + +export type __GetTransactionMethod__ = { + name: 'getTransaction'; + data: { + transactionId: TransactionId; + }; +}; + +export type __AddressMethod__ = { + name: 'getAddress'; +}; + +export type __OutputsMethod__ = { + name: 'outputs'; + data: { + filterOptions?: FilterOptions; + }; +}; + +export type __PendingTransactionsMethod__ = { + name: 'pendingTransactions'; +}; + +export type __ImplicitAccountCreationAddressMethod__ = { + name: 'implicitAccountCreationAddress'; +}; + +export type __ImplicitAccountsMethod__ = { + name: 'implicitAccounts'; +}; + +export type __IncomingTransactionsMethod__ = { + name: 'incomingTransactions'; +}; + +export type __TransactionsMethod__ = { + name: 'transactions'; +}; + +export type __UnspentOutputsMethod__ = { + name: 'unspentOutputs'; + data: { + filterOptions?: FilterOptions; + }; +}; + +export type __PrepareMintNativeTokenMethod__ = { + name: 'prepareMintNativeToken'; + data: { + tokenId: TokenId; + mintAmount: HexEncodedAmount; + options?: TransactionOptions; + }; +}; + +export type __PrepareCreateNativeTokenMethod__ = { + name: 'prepareCreateNativeToken'; + data: { + params: CreateNativeTokenParams; + options?: TransactionOptions; + }; +}; + +export type __PrepareMintNftsMethod__ = { + name: 'prepareMintNfts'; + data: { + params: MintNftParams[]; + options?: TransactionOptions; + }; +}; + +export type __PrepareOutputMethod__ = { + name: 'prepareOutput'; + data: { + params: OutputParams; + transactionOptions?: TransactionOptions; + }; +}; + +export type __PrepareSendMethod__ = { + name: 'prepareSend'; + data: { + params: SendParams[]; + options?: TransactionOptions; + }; +}; + +export type __PrepareTransactionMethod__ = { + name: 'prepareTransaction'; + data: { + outputs: Output[]; + options?: TransactionOptions; + }; +}; + +export type __RegisterParticipationEventsMethod__ = { + name: 'registerParticipationEvents'; + data: { + options: ParticipationEventRegistrationOptions; + }; +}; + +export type __ReissueTransactionUntilIncludedMethod__ = { + name: 'reissueTransactionUntilIncluded'; + data: { + transactionId: TransactionId; + interval?: number; + maxAttempts?: number; + }; +}; + +export type __SendMethod__ = { + name: 'send'; + data: { + amount: NumericString; + address: string; + options?: TransactionOptions; + }; +}; + +export type __SendWithParamsMethod__ = { + name: 'sendWithParams'; + data: { + params: SendParams[]; + options?: TransactionOptions; + }; +}; + +export type __PrepareSendNativeTokensMethod__ = { + name: 'prepareSendNativeTokens'; + data: { + params: SendNativeTokensParams[]; + options?: TransactionOptions; + }; +}; + +export type __PrepareSendNftMethod__ = { + name: 'prepareSendNft'; + data: { + params: SendNftParams[]; + options?: TransactionOptions; + }; +}; + +export type __SendOutputsMethod__ = { + name: 'sendOutputs'; + data: { + outputs: Output[]; + options?: TransactionOptions; + }; +}; + +export type __SetAliasMethod__ = { + name: 'setAlias'; + data: { + alias: string; + }; +}; + +export type __SetDefaultSyncOptionsMethod__ = { + name: 'setDefaultSyncOptions'; + data: { + options: SyncOptions; + }; +}; + +export type __SignTransactionMethod__ = { + name: 'signTransaction'; + data: { + preparedTransactionData: PreparedTransactionData; + }; +}; + +export type __SignAndSubmitTransactionMethod__ = { + name: 'signAndSubmitTransaction'; + data: { + preparedTransactionData: PreparedTransactionData; + }; +}; + +export type __SubmitAndStoreTransactionMethod__ = { + name: 'submitAndStoreTransaction'; + data: { + signedTransactionData: SignedTransactionData; + }; +}; + +export type __SyncAccountMethod__ = { + name: 'sync'; + data: { + options?: SyncOptions; + }; +}; + +export type __PrepareVoteMethod__ = { + name: 'prepareVote'; + data: { + eventId?: ParticipationEventId; + answers?: number[]; + }; +}; + +export type __PrepareStopParticipatingMethod__ = { + name: 'prepareStopParticipating'; + data: { + eventId: ParticipationEventId; + }; +}; + +export type __GetParticipationOverviewMethod__ = { + name: 'getParticipationOverview'; + data: { + eventIds?: ParticipationEventId[]; + }; +}; + +export type __PrepareIncreaseVotingPowerMethod__ = { + name: 'prepareIncreaseVotingPower'; + data: { + amount: NumericString; + }; +}; + +export type __GetParticipationEventMethod__ = { + name: 'getParticipationEvent'; + data: { + eventId: ParticipationEventId; + }; +}; + +export type __GetParticipationEventIdsMethod__ = { + name: 'getParticipationEventIds'; + data: { + node: INode; + eventType?: ParticipationEventType; + }; +}; + +export type __GetParticipationEventsMethod__ = { + name: 'getParticipationEvents'; +}; + +export type __GetParticipationEventStatusMethod__ = { + name: 'getParticipationEventStatus'; + data: { + eventId: ParticipationEventId; + }; +}; + +export type __PrepareDecreaseVotingPowerMethod__ = { + name: 'prepareDecreaseVotingPower'; + data: { + amount: NumericString; + }; +}; diff --git a/bindings/nodejs/lib/types/wallet/index.ts b/bindings/nodejs/lib/types/wallet/index.ts index f60a142026..4f217263fd 100644 --- a/bindings/nodejs/lib/types/wallet/index.ts +++ b/bindings/nodejs/lib/types/wallet/index.ts @@ -1,7 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -export * from './account'; export * from './wallet'; export * from './address'; export * from './bridge'; diff --git a/bindings/nodejs/lib/types/wallet/output-params.ts b/bindings/nodejs/lib/types/wallet/output-params.ts index 0474a24bba..1e9be9073d 100644 --- a/bindings/nodejs/lib/types/wallet/output-params.ts +++ b/bindings/nodejs/lib/types/wallet/output-params.ts @@ -58,7 +58,7 @@ export interface StorageDeposit { returnStrategy?: ReturnStrategy; /** * Determines whether the storage deposit will automatically add excess small funds when necessary. - * For example, given an account has 20 tokens and wants to send 15 tokens, and the minimum storage deposit + * For example, given an account has 20 tokens and wants to send 15 tokens, and the minimum amount * is 10 tokens, it wouldn't be possible to create an output with the 5 token remainder. If this flag is enabled, * the 5 tokens will be added to the output automatically. */ @@ -67,8 +67,8 @@ export interface StorageDeposit { /** Return strategy for the storage deposit. */ export enum ReturnStrategy { - /** A storage deposit return unlock condition will be added with the required minimum storage deposit. */ + /** A storage deposit return unlock condition will be added with the additional amount needed to satisfy storage costs. */ Return = 'Return', - /** The recipient address will get the additional amount to reach the minimum storage deposit gifted. */ + /** The recipient address will receive the additional amount needed to satisfy storage costs. */ Gift = 'Gift', } diff --git a/bindings/nodejs/lib/types/wallet/prepared-create-token-transaction.ts b/bindings/nodejs/lib/types/wallet/prepared-create-token-transaction.ts index 0e708f812a..9fe0e0d726 100644 --- a/bindings/nodejs/lib/types/wallet/prepared-create-token-transaction.ts +++ b/bindings/nodejs/lib/types/wallet/prepared-create-token-transaction.ts @@ -1,8 +1,7 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Account, PreparedCreateNativeTokenTransactionData } from '../..'; - +import { Wallet, PreparedCreateNativeTokenTransactionData } from '../..'; import { PreparedTransaction } from './prepared-transaction'; /* @@ -13,13 +12,13 @@ export class PreparedCreateNativeTokenTransaction extends PreparedTransaction { /** * @param preparedData Prepared data to create a Native Token. - * @param account A wallet account. + * @param wallet A wallet. */ constructor( preparedData: PreparedCreateNativeTokenTransactionData, - account: Account, + wallet: Wallet, ) { - super(preparedData.transaction, account); + super(preparedData.transaction, wallet); this._tokenId = preparedData.tokenId; } diff --git a/bindings/nodejs/lib/types/wallet/prepared-transaction.ts b/bindings/nodejs/lib/types/wallet/prepared-transaction.ts index 41e1cc62d3..89ca2cfaaa 100644 --- a/bindings/nodejs/lib/types/wallet/prepared-transaction.ts +++ b/bindings/nodejs/lib/types/wallet/prepared-transaction.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { - Account, + Wallet, PreparedTransactionData, SignedTransactionData, TransactionWithMetadata, @@ -11,20 +11,20 @@ import { /** * PreparedTransaction` is a class that represents prepared transaction data, which * is useful for offline signing. It contains the prepared transaction data and an - * `Account` object. It provides methods to retrieve the prepared transaction data, sign + * `Wallet` object. It provides methods to retrieve the prepared transaction data, sign * the transaction and sign+submit/send the transaction. */ export class PreparedTransaction { readonly _preparedData: PreparedTransactionData; - readonly _account: Account; + readonly _wallet: Wallet; /** * @param preparedData Prepared data to sign and submit a transaction. - * @param account A wallet account. + * @param wallet A wallet. */ - constructor(preparedData: PreparedTransactionData, account: Account) { + constructor(preparedData: PreparedTransactionData, wallet: Wallet) { this._preparedData = preparedData; - this._account = account; + this._wallet = wallet; } /** @@ -53,7 +53,7 @@ export class PreparedTransaction { } /** - * This function signs a prepared transaction using the account's private key and returns + * This function signs a prepared transaction using the wallet's private key and returns * the signed transaction. * * Returns: @@ -61,7 +61,7 @@ export class PreparedTransaction { * A `Promise` that resolves to a `SignedTransactionData` object. */ public async sign(): Promise { - return this._account.signTransaction(this.preparedTransactionData()); + return this._wallet.signTransaction(this.preparedTransactionData()); } /** @@ -72,7 +72,7 @@ export class PreparedTransaction { * A Promise that resolves to a TransactionWithMetadata object. */ public async signAndSubmitTransaction(): Promise { - return this._account.signAndSubmitTransaction( + return this._wallet.signAndSubmitTransaction( this.preparedTransactionData(), ); } diff --git a/bindings/nodejs/lib/types/wallet/transaction-options.ts b/bindings/nodejs/lib/types/wallet/transaction-options.ts index eebf1c2e2c..5184335c61 100644 --- a/bindings/nodejs/lib/types/wallet/transaction-options.ts +++ b/bindings/nodejs/lib/types/wallet/transaction-options.ts @@ -77,7 +77,7 @@ export interface CreateNativeTokenParams { /** Options for minting NFTs. */ export interface MintNftParams { /** Bech32 encoded address to which the Nft will be minted. Default will use the - * first address of the account + * address of the wallet. */ address?: Bech32Address; /** Bech32 encoded sender address **/ @@ -95,7 +95,7 @@ export interface MintNftParams { /** Options for the account output creation */ export interface AccountOutputParams { /** Bech32 encoded address to which the Nft will be minted. Default will use the - * first address of the account + * address of the wallet. */ address?: Bech32Address; /** Hex encoded bytes */ diff --git a/bindings/nodejs/lib/types/wallet/wallet.ts b/bindings/nodejs/lib/types/wallet/wallet.ts index 6b4c48451b..85e2d77d92 100644 --- a/bindings/nodejs/lib/types/wallet/wallet.ts +++ b/bindings/nodejs/lib/types/wallet/wallet.ts @@ -1,14 +1,154 @@ -import { IClientOptions, CoinType } from '../client'; -import { SecretManagerType } from '../secret_manager/secret-manager'; +// Copyright 2021-2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { AccountId, DelegationId, FoundryId, NftId } from '../block/id'; +import { HexEncodedString, u256, u64 } from '../utils'; +import { IClientOptions } from '../client'; +import { Bip44, SecretManagerType } from '../secret_manager/secret-manager'; +import { Bech32Address } from '../block'; /** Options for the Wallet builder. */ export interface WalletOptions { - /** The path to the wallet database. */ - storagePath?: string; + /** The wallet address. */ + address?: Bech32Address; + /** The alias of the wallet. */ + alias?: string; + /** The the BIP44 path of the wallet. */ + bipPath?: Bip44; /** The node client options. */ clientOptions?: IClientOptions; - /** The type of coin stored with the wallet. */ - coinType?: CoinType; /** The secret manager to use. */ secretManager?: SecretManagerType; + /** The path to the wallet database. */ + storagePath?: string; +} + +/** A balance */ +export interface Balance { + /** The balance of the base coin */ + baseCoin: BaseCoinBalance; + /** The required storage deposit for the outputs */ + requiredStorageDeposit: RequiredStorageDeposit; + /** The balance of the native tokens */ + nativeTokens: NativeTokenBalance[]; + /** Nft outputs */ + nfts: string[]; + /** Account outputs */ + accounts: string[]; + /** Foundry outputs */ + foundries: string[]; + /** + * Outputs with multiple unlock conditions and if they can currently be spent or not. If there is a + * TimelockUnlockCondition or ExpirationUnlockCondition this can change at any time + */ + potentiallyLockedOutputs: { [outputId: string]: boolean }; +} + +/** The balance of the base coin */ +export interface BaseCoinBalance { + /** The total amount of the outputs */ + total: u64; + /** The amount of the outputs that aren't used in a transaction */ + available: u64; + /** Voting power */ + votingPower: string; +} + +/** The required storage deposit per output type */ +export interface RequiredStorageDeposit { + /** The required amount for Alias outputs. */ + account: u64; + /** The required amount for Basic outputs. */ + basic: u64; + /** The required amount for Foundry outputs. */ + foundry: u64; + /** The required amount for NFT outputs. */ + nft: u64; +} + +/** The balance of a native token */ +export interface NativeTokenBalance { + /** The native token id. */ + tokenId: HexEncodedString; + /** Some metadata of the native token. */ + metadata?: string; + /** The total native token balance. */ + total: u256; + /** The available amount of the total native token balance. */ + available: u256; +} + +/** Sync options for a wallet */ +export interface SyncOptions { + /** + * Usually syncing is skipped if it's called in between 200ms, because there can only be new changes every + * milestone and calling it twice "at the same time" will not return new data + * When this to true, we will sync anyways, even if it's called 0ms after the las sync finished. Default: false. + */ + forceSyncing?: boolean; + /// Try to sync transactions from incoming outputs with their inputs. Some data may not be obtained if it has been + /// pruned. + syncIncomingTransactions?: boolean; + /** Checks pending transactions and reissues them if necessary. Default: true. */ + syncPendingTransactions?: boolean; + /** Specifies what outputs should be synced for the ed25519 address from the wallet. */ + wallet?: WalletSyncOptions; + /** Specifies what outputs should be synced for the address of an account output. */ + account?: AccountSyncOptions; + /** Specifies what outputs should be synced for the address of an nft output. */ + nft?: NftSyncOptions; + /** Specifies if only basic outputs with an AddressUnlockCondition alone should be synced, will overwrite `wallet`, `account` and `nft` options. Default: false. */ + syncOnlyMostBasicOutputs?: boolean; + /** Sync native token foundries, so their metadata can be returned in the balance. Default: false. */ + syncNativeTokenFoundries?: boolean; +} + +/** Specifies what outputs should be synced for the ed25519 address from the wallet. */ +export interface WalletSyncOptions { + /** Whether to sync Basic outputs. */ + basicOutputs?: boolean; + /** Whether to sync Account outputs. */ + accountOutputs?: boolean; + /** Whether to sync NFT outputs. */ + nftOutputs?: boolean; +} + +/** Specifies what outputs should be synced for the address of an account output. */ +export interface AccountSyncOptions { + /** Whether to sync Basic outputs. */ + basicOutputs?: boolean; + /** Whether to sync Account outputs. */ + accountOutputs?: boolean; + /** Whether to sync NFT outputs. */ + nftOutputs?: boolean; + /** Whether to sync foundry outputs. */ + foundryOutputs?: boolean; +} + +/** Specifies what outputs should be synced for the address of an nft output. */ +export interface NftSyncOptions { + /** Whether to sync Basic outputs. */ + basicOutputs?: boolean; + /** Whether to sync Account outputs. */ + accountOutputs?: boolean; + /** Whether to sync NFT outputs. */ + nftOutputs?: boolean; +} + +/** Options to filter outputs */ +export interface FilterOptions { + /** Filter all outputs where the booked milestone index is below the specified timestamp */ + lowerBoundBookedTimestamp?: number; + /** Filter all outputs where the booked milestone index is above the specified timestamp */ + upperBoundBookedTimestamp?: number; + /** Filter all outputs for the provided types (Basic = 3, Account = 4, Foundry = 5, NFT = 6) */ + outputTypes?: number[]; + /** Return all account outputs matching these IDs. */ + accountIds?: AccountId[]; + /** Return all foundry outputs matching these IDs. */ + foundryIds?: FoundryId[]; + /** Return all NFT outputs matching these IDs. */ + nftIds?: NftId[]; + /** Return all delegation outputs matching these IDs. */ + delegationIds?: DelegationId[]; } diff --git a/bindings/nodejs/lib/wallet/account.ts b/bindings/nodejs/lib/wallet/account.ts deleted file mode 100644 index 527894be26..0000000000 --- a/bindings/nodejs/lib/wallet/account.ts +++ /dev/null @@ -1,1595 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { WalletMethodHandler } from './wallet-method-handler'; -import { - Balance, - AccountMetadata, - SyncOptions, - AccountMeta, - Bip44Address, - SendParams, - SendNativeTokenParams, - SendNftParams, - AddressWithUnspentOutputs, - AccountOutputParams, - FilterOptions, - GenerateAddressOptions, - CreateNativeTokenParams, - MintNftParams, - OutputData, - OutputParams, - OutputsToClaim, - TransactionWithMetadata, - TransactionOptions, - ParticipationOverview, - ParticipationEventId, - ParticipationEventStatus, - ParticipationEventType, - ParticipationEventWithNodes, - ParticipationEventRegistrationOptions, - ParticipationEventMap, - SignedTransactionData, - PreparedTransaction, - PreparedCreateNativeTokenTransactionData, - ConsolidationParams, -} from '../types/wallet'; -import { INode, Burn, PreparedTransactionData } from '../client'; -import { - Output, - FoundryOutput, - Response, - PreparedCreateNativeTokenTransaction, - u64, - u256, - NftId, - TokenId, - OutputId, - AccountId, - FoundryId, - TransactionId, - NumericString, - Bech32Address, -} from '../types'; -import { plainToInstance } from 'class-transformer'; -import { bigIntToHex, hexToBigInt } from '../types/utils/hex-encoding'; - -/** The Account class. */ -export class Account { - // private because the data isn't updated - private meta: AccountMeta; - private methodHandler: WalletMethodHandler; - - /** - * @param accountMeta An instance of `AccountMeta`. - * @param methodHandler A instance of `WalletMethodHandler`. - */ - constructor(accountMeta: AccountMeta, methodHandler: WalletMethodHandler) { - this.meta = accountMeta; - this.methodHandler = methodHandler; - } - - /** - * A generic function that can be used to burn native tokens, nfts, foundries and accounts. - * @param burn The outputs or native tokens to burn - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The transaction. - */ - async burn( - burn: Burn, - transactionOptions?: TransactionOptions, - ): Promise { - return (await this.prepareBurn(burn, transactionOptions)).send(); - } - - /** - * A generic function that can be used to prepare to burn native tokens, nfts, foundries and accounts. - * @param burn The outputs or native tokens to burn - * @param transactionOptions Additional transaction options - * @returns The prepared transaction. - */ - async prepareBurn( - burn: Burn, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareBurn', - data: { - burn, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Burn native tokens. This doesn't require the foundry output which minted them, but will not increase - * the foundries `melted_tokens` field, which makes it impossible to destroy the foundry output. Therefore it's - * recommended to use melting, if the foundry output is available. - * @param tokenId The native token id. - * @param burnAmount The to be burned amount. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareBurnNativeToken( - tokenId: TokenId, - burnAmount: u256, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareBurn', - data: { - burn: { - nativeTokens: new Map([[tokenId, burnAmount]]), - }, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Burn an nft output. - * @param nftId The NftId. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareBurnNft( - nftId: NftId, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareBurn', - data: { - burn: { - nfts: [nftId], - }, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Claim basic or nft outputs that have additional unlock conditions - * to their `AddressUnlockCondition` from the account. - * @param outputIds The outputs to claim. - * @returns The resulting transaction. - */ - async claimOutputs( - outputIds: OutputId[], - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'claimOutputs', - data: { - outputIdsToClaim: outputIds, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * Consolidate basic outputs with only an `AddressUnlockCondition` from an account - * by sending them to an own address again if the output amount is greater or - * equal to the output consolidation threshold. - * @param params Consolidation options. - * @returns The consolidation transaction. - */ - async consolidateOutputs( - params: ConsolidationParams, - ): Promise { - return (await this.prepareConsolidateOutputs(params)).send(); - } - - /** - * Consolidate basic outputs with only an `AddressUnlockCondition` from an account - * by sending them to an own address again if the output amount is greater or - * equal to the output consolidation threshold. - * @param params Consolidation options. - * @returns The prepared consolidation transaction. - */ - async prepareConsolidateOutputs( - params: ConsolidationParams, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareConsolidateOutputs', - data: { - params, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Creates an account output. - * @param params The account output options. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The transaction. - */ - async createAccountOutput( - params?: AccountOutputParams, - transactionOptions?: TransactionOptions, - ): Promise { - return ( - await this.prepareCreateAccountOutput(params, transactionOptions) - ).send(); - } - - /** - * Creates an account output. - * @param params The account output options. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareCreateAccountOutput( - params?: AccountOutputParams, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareCreateAccountOutput', - data: { - params, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Melt native tokens. This happens with the foundry output which minted them, by increasing its - * `melted_tokens` field. - * @param tokenId The native token id. - * @param meltAmount To be melted amount. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The transaction. - */ - async meltNativeToken( - tokenId: TokenId, - meltAmount: bigint, - transactionOptions?: TransactionOptions, - ): Promise { - return ( - await this.prepareMeltNativeToken( - tokenId, - meltAmount, - transactionOptions, - ) - ).send(); - } - - /** - * Melt native tokens. This happens with the foundry output which minted them, by increasing its - * `melted_tokens` field. - * @param tokenId The native token id. - * @param meltAmount To be melted amount. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareMeltNativeToken( - tokenId: TokenId, - meltAmount: u256, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareMeltNativeToken', - data: { - tokenId, - meltAmount: bigIntToHex(meltAmount), - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Deregister a participation event. - * - * @param eventId The id of the participation event to deregister. - */ - async deregisterParticipationEvent( - eventId: ParticipationEventId, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'deregisterParticipationEvent', - data: { - eventId, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Destroy an account output. - * - * @param accountId The AccountId. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareDestroyAccount( - accountId: AccountId, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareBurn', - data: { - burn: { - accounts: [accountId], - }, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Function to destroy a foundry output with a circulating supply of 0. - * Native tokens in the foundry (minted by other foundries) will be transacted to the controlling account. - * @param foundryId The FoundryId. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareDestroyFoundry( - foundryId: FoundryId, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareBurn', - data: { - burn: { - foundries: [foundryId], - }, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Generate new unused Ed25519 addresses. - * - * @param amount The amount of addresses to generate. - * @param options Options for address generation. - * @returns The addresses. - */ - async generateEd25519Addresses( - amount: number, - options?: GenerateAddressOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'generateEd25519Addresses', - data: { - amount, - options, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Get the account balance. - * - * @returns The account balance. - */ - async getBalance(): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getBalance', - }, - ); - const payload = JSON.parse(response).payload; - return this.adjustBalancePayload(payload); - } - - /** - * Converts hex encoded or decimal strings of amounts to `bigint` - * for the balance payload. - */ - private adjustBalancePayload(payload: any): Balance { - for (let i = 0; i < payload.nativeTokens.length; i++) { - payload.nativeTokens[i].total = hexToBigInt( - payload.nativeTokens[i].total, - ); - payload.nativeTokens[i].available = hexToBigInt( - payload.nativeTokens[i].available, - ); - } - payload.baseCoin.total = BigInt(payload.baseCoin.total); - payload.baseCoin.available = BigInt(payload.baseCoin.available); - - payload.requiredStorageDeposit.account = BigInt( - payload.requiredStorageDeposit.account, - ); - payload.requiredStorageDeposit.basic = BigInt( - payload.requiredStorageDeposit.basic, - ); - payload.requiredStorageDeposit.foundry = BigInt( - payload.requiredStorageDeposit.foundry, - ); - payload.requiredStorageDeposit.nft = BigInt( - payload.requiredStorageDeposit.nft, - ); - - return payload; - } - - /** - * Get the data for an output. - * @param outputId The output to get. - * @returns The `OutputData`. - */ - async getOutput(outputId: OutputId): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getOutput', - data: { - outputId, - }, - }, - ); - const parsed = JSON.parse(response) as Response; - return plainToInstance(OutputData, parsed.payload); - } - - /** - * Get a participation event. - * - * @param eventId The ID of the event to get. - */ - async getParticipationEvent( - eventId: ParticipationEventId, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getParticipationEvent', - data: { - eventId, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Get IDs of participation events of a certain type. - * - * @param node The node to get events from. - * @param eventType The type of events to get. - */ - async getParticipationEventIds( - node: INode, - eventType?: ParticipationEventType, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getParticipationEventIds', - data: { - node, - eventType, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Get all participation events. - */ - async getParticipationEvents(): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getParticipationEvents', - }, - ); - return JSON.parse(response).payload; - } - - /** - * Get the participation event status by its ID. - * - * @param eventId The ID of the event status to get. - */ - async getParticipationEventStatus( - eventId: ParticipationEventId, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getParticipationEventStatus', - data: { - eventId, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Get a `FoundryOutput` by native token ID. It will try to get the foundry from - * the account, if it isn't in the account it will try to get it from the node. - * - * @param tokenId The native token ID to get the foundry for. - * @returns The `FoundryOutput` that minted the token. - */ - async getFoundryOutput(tokenId: TokenId): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getFoundryOutput', - data: { - tokenId, - }, - }, - ); - return Output.parse(JSON.parse(response).payload) as FoundryOutput; - } - - /** - * Get outputs with additional unlock conditions. - * - * @param outputs The type of outputs to claim. - * @returns The output IDs of the unlockable outputs. - */ - async claimableOutputs(outputs: OutputsToClaim): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'claimableOutputs', - data: { - outputsToClaim: outputs, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Get a transaction stored in the account. - * - * @param transactionId The ID of the transaction to get. - * @returns The transaction. - */ - async getTransaction( - transactionId: TransactionId, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getTransaction', - data: { - transactionId, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * Get the transaction with inputs of an incoming transaction stored in the account - * List might not be complete, if the node pruned the data already - * - * @param transactionId The ID of the transaction to get. - * @returns The transaction. - */ - async getIncomingTransaction( - transactionId: TransactionId, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getIncomingTransaction', - data: { - transactionId, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * List all the addresses of the account. - * - * @returns The addresses. - */ - async addresses(): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'addresses', - }, - ); - - return JSON.parse(response).payload; - } - - /** - * List the addresses of the account with unspent outputs. - * - * @returns The addresses. - */ - async addressesWithUnspentOutputs(): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'addressesWithUnspentOutputs', - }, - ); - - return JSON.parse(response).payload; - } - - /** - * List all outputs of the account. - * - * @param filterOptions Options to filter the to be returned outputs. - * @returns The outputs with metadata. - */ - async outputs(filterOptions?: FilterOptions): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'outputs', - data: { filterOptions }, - }, - ); - - const parsed = JSON.parse(response) as Response; - return plainToInstance(OutputData, parsed.payload); - } - - /** - * List all the pending transactions of the account. - * - * @returns The transactions. - */ - async pendingTransactions(): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'pendingTransactions', - }, - ); - const parsed = JSON.parse(response) as Response< - TransactionWithMetadata[] - >; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * List all incoming transactions of the account. - * - * @returns The incoming transactions with their inputs. - */ - async incomingTransactions(): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'incomingTransactions', - }, - ); - const parsed = JSON.parse(response) as Response< - TransactionWithMetadata[] - >; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * List all the transactions of the account. - * - * @returns The transactions. - */ - async transactions(): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'transactions', - }, - ); - const parsed = JSON.parse(response) as Response< - TransactionWithMetadata[] - >; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * List all the unspent outputs of the account. - * - * @param filterOptions Options to filter the to be returned outputs. - * @returns The outputs with metadata. - */ - async unspentOutputs(filterOptions?: FilterOptions): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'unspentOutputs', - data: { filterOptions }, - }, - ); - const parsed = JSON.parse(response) as Response; - return plainToInstance(OutputData, parsed.payload); - } - - /** - * Get the accounts metadata. - * - * @returns The accounts metadata. - */ - getMetadata(): AccountMetadata { - return { - alias: this.meta.alias, - coinType: this.meta.coinType, - index: this.meta.index, - }; - } - - /** - * Mint additional native tokens. - * - * @param tokenId The native token id. - * @param mintAmount To be minted amount. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The minting transaction. - */ - async mintNativeToken( - tokenId: TokenId, - mintAmount: bigint, - transactionOptions?: TransactionOptions, - ): Promise { - return ( - await this.prepareMintNativeToken( - tokenId, - mintAmount, - transactionOptions, - ) - ).send(); - } - - /** - * Mint additional native tokens. - * - * @param tokenId The native token id. - * @param mintAmount To be minted amount. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared minting transaction. - */ - async prepareMintNativeToken( - tokenId: string, - mintAmount: u256, - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareMintNativeToken', - data: { - tokenId, - mintAmount: bigIntToHex(mintAmount), - options: transactionOptions, - }, - }, - ); - - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Create a native token. - * - * @param params The options for creating a native token. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The created transaction. - */ - async createNativeToken( - params: CreateNativeTokenParams, - transactionOptions?: TransactionOptions, - ): Promise { - return ( - await this.prepareCreateNativeToken(params, transactionOptions) - ).send(); - } - - /** - * Create a native token. - * - * @param params The options for creating a native token. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The created transaction and the token ID. - */ - async prepareCreateNativeToken( - params: CreateNativeTokenParams, - transactionOptions?: TransactionOptions, - ): Promise { - const adjustedParams: any = params; - adjustedParams.circulatingSupply = bigIntToHex( - params.circulatingSupply, - ); - adjustedParams.maximumSupply = bigIntToHex(params.maximumSupply); - - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareCreateNativeToken', - data: { - params: adjustedParams, - options: transactionOptions, - }, - }, - ); - - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedCreateNativeTokenTransaction( - plainToInstance( - PreparedCreateNativeTokenTransactionData, - parsed.payload, - ), - this, - ); - } - - /** - * Mint NFTs. - * - * @param params The options for minting nfts. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The minting transaction. - */ - async mintNfts( - params: MintNftParams[], - transactionOptions?: TransactionOptions, - ): Promise { - return (await this.prepareMintNfts(params, transactionOptions)).send(); - } - - /** - * Mint NFTs. - * - * @param params The options for minting nfts. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared minting transaction. - */ - async prepareMintNfts( - params: MintNftParams[], - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareMintNfts', - data: { - params, - options: transactionOptions, - }, - }, - ); - - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Prepare an output for sending, useful for offline signing. - * - * @param options The options for preparing an output. If the amount is - * below the minimum required storage deposit, by default the remaining - * amount will automatically be added with a `StorageDepositReturn` `UnlockCondition`, - * when setting the `ReturnStrategy` to `gift`, the full minimum required - * storage deposit will be sent to the recipient. When the assets contain - * an nft id, the data from the existing `NftOutput` will be used, just with - * the address unlock conditions replaced. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared output. - */ - async prepareOutput( - params: OutputParams, - transactionOptions?: TransactionOptions, - ): Promise { - if (typeof params.amount === 'bigint') { - params.amount = params.amount.toString(10); - } - - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareOutput', - data: { - params, - transactionOptions, - }, - }, - ); - - return Output.parse(JSON.parse(response).payload); - } - - /** - * Prepare to send base coins, useful for offline signing. - * - * @param params Address with amounts to send. - * @param options Additional transaction options - * or custom inputs. - * @returns The prepared transaction data. - */ - async prepareSend( - params: SendParams[], - options?: TransactionOptions, - ): Promise { - for (let i = 0; i < params.length; i++) { - if (typeof params[i].amount === 'bigint') { - params[i].amount = params[i].amount.toString(10); - } - } - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareSend', - data: { - params, - options, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Send a transaction. - * - * @param outputs Outputs to use in the transaction. - * @param options Additional transaction options - * or custom inputs. - * @returns The transaction data. - */ - async sendTransaction( - outputs: Output[], - options?: TransactionOptions, - ): Promise { - return (await this.prepareTransaction(outputs, options)).send(); - } - - /** - * Prepare a transaction, useful for offline signing. - * - * @param outputs Outputs to use in the transaction. - * @param options Additional transaction options - * or custom inputs. - * @returns The prepared transaction data. - */ - async prepareTransaction( - outputs: Output[], - options?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareTransaction', - data: { - outputs, - options, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Register participation events. - * - * @param options Options to register participation events. - * @returns A mapping between event IDs and their corresponding event data. - */ - async registerParticipationEvents( - options: ParticipationEventRegistrationOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'registerParticipationEvents', - data: { - options, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Reissues a transaction sent from the account for a provided transaction id until it's - * included (referenced by a milestone). Returns the included block id. - */ - async reissueTransactionUntilIncluded( - transactionId: string, - interval?: number, - maxAttempts?: number, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'reissueTransactionUntilIncluded', - data: { - transactionId, - interval, - maxAttempts, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Send base coins to an address. - * - * @param amount Amount of coins. - * @param address Receiving address. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The sent transaction. - */ - async send( - amount: u64 | NumericString, - address: Bech32Address, - transactionOptions?: TransactionOptions, - ): Promise { - if (typeof amount === 'bigint') { - amount = amount.toString(10); - } - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'send', - data: { - amount, - address, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * Send base coins with amounts from input addresses. - * - * @param params Addresses with amounts. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The sent transaction. - */ - async sendWithParams( - params: SendParams[], - transactionOptions?: TransactionOptions, - ): Promise { - for (let i = 0; i < params.length; i++) { - if (typeof params[i].amount === 'bigint') { - params[i].amount = params[i].amount.toString(10); - } - } - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'sendWithParams', - data: { - params, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * Send a native token. - * - * @param params Addresses amounts and native tokens. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The transaction. - */ - async sendNativeToken( - params: SendNativeTokenParams[], - transactionOptions?: TransactionOptions, - ): Promise { - return ( - await this.prepareSendNativeToken(params, transactionOptions) - ).send(); - } - - /** - * Send a native token. - * - * @param params Addresses amounts and native tokens. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareSendNativeToken( - params: SendNativeTokenParams[], - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareSendNativeToken', - data: { - params, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Send NFT. - * - * @param params Addresses and nft ids. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The transaction. - */ - async sendNft( - params: SendNftParams[], - transactionOptions?: TransactionOptions, - ): Promise { - return (await this.prepareSendNft(params, transactionOptions)).send(); - } - - /** - * Send NFT. - * - * @param params Addresses and nft ids. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The prepared transaction. - */ - async prepareSendNft( - params: SendNftParams[], - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareSendNft', - data: { - params, - options: transactionOptions, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Send outputs in a transaction. - * - * @param outputs The outputs to send. - * @param transactionOptions Additional transaction options - * or custom inputs. - * @returns The sent transaction. - */ - async sendOutputs( - outputs: Output[], - transactionOptions?: TransactionOptions, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'sendOutputs', - data: { - outputs, - options: transactionOptions, - }, - }, - ); - - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * Set the alias for the account - * - * @param alias The account alias to set. - */ - async setAlias(alias: string): Promise { - await this.methodHandler.callAccountMethod(this.meta.index, { - name: 'setAlias', - data: { - alias, - }, - }); - } - - /** - * Set the fallback SyncOptions for account syncing. - * If storage is enabled, will persist during restarts. - * - * @param options The sync options to set. - */ - async setDefaultSyncOptions(options: SyncOptions): Promise { - await this.methodHandler.callAccountMethod(this.meta.index, { - name: 'setDefaultSyncOptions', - data: { - options, - }, - }); - } - - /** - * Sign a prepared transaction, useful for offline signing. - * - * @param preparedTransactionData The prepared transaction data to sign. - * @returns The signed transaction. - */ - async signTransaction( - preparedTransactionData: PreparedTransactionData, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'signTransaction', - data: { - preparedTransactionData, - }, - }, - ); - const parsed = JSON.parse(response) as Response; - return plainToInstance(SignedTransactionData, parsed.payload); - } - - /** - * Sign a prepared transaction, and send it. - * - * @param preparedTransactionData The prepared transaction data to sign and submit. - * @returns The transaction. - */ - async signAndSubmitTransaction( - preparedTransactionData: PreparedTransactionData, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'signAndSubmitTransaction', - data: { - preparedTransactionData, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * Validate the transaction, submit it to a node and store it in the account. - * - * @param signedTransactionData A signed transaction to submit and store. - * @returns The sent transaction. - */ - async submitAndStoreTransaction( - signedTransactionData: SignedTransactionData, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'submitAndStoreTransaction', - data: { - signedTransactionData, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); - } - - /** - * Sync the account by fetching new information from the nodes. - * Will also reissue pending transactions if necessary. - * A custom default can be set using setDefaultSyncOptions. - * - * @param options Optional synchronization options. - * @returns The account balance. - */ - async sync(options?: SyncOptions): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'sync', - data: { - options, - }, - }, - ); - const payload = JSON.parse(response).payload; - return this.adjustBalancePayload(payload); - } - - /** - * Prepare a vote. - * - * @param eventId The participation event ID. - * @param answers Answers for a voting event, can be empty. - * @returns An instance of `PreparedTransaction`. - */ - async prepareVote( - eventId?: ParticipationEventId, - answers?: number[], - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareVote', - data: { - eventId, - answers, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Prepare stop participating in an event. - * - * @param eventId The event ID to stop participating in. - * @returns An instance of `PreparedTransaction`. - */ - async prepareStopParticipating( - eventId: ParticipationEventId, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareStopParticipating', - data: { - eventId, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Calculates the voting overview of an account. - * - * @param eventIds Optional, filters participations only for provided events. - * @returns An instance of `ParticipationOverview` - */ - async getParticipationOverview( - eventIds?: ParticipationEventId[], - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'getParticipationOverview', - data: { - eventIds, - }, - }, - ); - return JSON.parse(response).payload; - } - - /** - * Prepare to increase the voting power. - * - * @param amount The amount to increase the voting power by. - * @returns An instance of `PreparedTransaction`. - */ - async prepareIncreaseVotingPower( - amount: NumericString, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareIncreaseVotingPower', - data: { - amount, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } - - /** - * Prepare to decrease the voting power. - * - * @param amount The amount to decrease the voting power by. - * @returns An instance of `PreparedTransaction`. - */ - async prepareDecreaseVotingPower( - amount: NumericString, - ): Promise { - const response = await this.methodHandler.callAccountMethod( - this.meta.index, - { - name: 'prepareDecreaseVotingPower', - data: { - amount, - }, - }, - ); - const parsed = JSON.parse( - response, - ) as Response; - return new PreparedTransaction( - plainToInstance(PreparedTransactionData, parsed.payload), - this, - ); - } -} diff --git a/bindings/nodejs/lib/wallet/index.ts b/bindings/nodejs/lib/wallet/index.ts index 8b1189339e..47fd932e03 100644 --- a/bindings/nodejs/lib/wallet/index.ts +++ b/bindings/nodejs/lib/wallet/index.ts @@ -1,7 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -export * from './account'; export * from './wallet'; export * from './wallet-method-handler'; export * from '../types/wallet'; diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index 970a857e04..1e937b778b 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import { - callWalletMethodAsync, + callWalletMethod, createWallet, - listenWalletAsync, + listenWallet, destroyWallet, getClientFromWallet, getSecretManagerFromWallet, @@ -12,9 +12,7 @@ import { import type { WalletEventType, WalletOptions, - __Method__, - __AccountMethod__, - AccountIdentifier, + __WalletMethod__, Event, } from '../types/wallet'; import { Client } from '../client'; @@ -28,14 +26,7 @@ export class WalletMethodHandler { * @param options The wallet options. */ constructor(options?: WalletOptions) { - const walletOptions = { - storagePath: options?.storagePath, - clientOptions: options?.clientOptions, - coinType: options?.coinType, - secretManager: options?.secretManager, - }; - - this.methodHandler = createWallet(JSON.stringify(walletOptions)); + this.methodHandler = createWallet(JSON.stringify(options)); } /** @@ -43,8 +34,9 @@ export class WalletMethodHandler { * * @param method The wallet method to call. */ - async callMethod(method: __Method__): Promise { - return callWalletMethodAsync( + async callMethod(method: __WalletMethod__): Promise { + return callWalletMethod( + this.methodHandler, // mapToObject is required to convert maps to array since they otherwise get serialized as `[{}]` even if not empty JSON.stringify(method, function mapToObject(_key, value) { if (value instanceof Map) { @@ -53,7 +45,6 @@ export class WalletMethodHandler { return value; } }), - this.methodHandler, ).catch((error: Error) => { try { if (error.message !== undefined) { @@ -68,25 +59,6 @@ export class WalletMethodHandler { }); } - /** - * Call an account method on the Rust backend. - * - * @param accountIndex The account index. - * @param method The account method to call. - */ - async callAccountMethod( - accountIndex: AccountIdentifier, - method: __AccountMethod__, - ): Promise { - return this.callMethod({ - name: 'callAccountMethod', - data: { - accountId: accountIndex, - method, - }, - }); - } - /** * Listen to wallet events. * @@ -97,7 +69,7 @@ export class WalletMethodHandler { eventTypes: WalletEventType[], callback: (error: Error, event: Event) => void, ): Promise { - return listenWalletAsync(eventTypes, callback, this.methodHandler); + return listenWallet(this.methodHandler, eventTypes, callback); } async destroy(): Promise { diff --git a/bindings/nodejs/lib/wallet/wallet.ts b/bindings/nodejs/lib/wallet/wallet.ts index be7951086f..545d7e7fa3 100644 --- a/bindings/nodejs/lib/wallet/wallet.ts +++ b/bindings/nodejs/lib/wallet/wallet.ts @@ -2,20 +2,59 @@ // SPDX-License-Identifier: Apache-2.0 import { WalletMethodHandler } from './wallet-method-handler'; -import { Account } from './account'; - +import { + Balance, + SyncOptions, + SendParams, + SendNativeTokensParams, + SendNftParams, + AccountOutputParams, + FilterOptions, + CreateNativeTokenParams, + MintNftParams, + OutputData, + OutputParams, + OutputsToClaim, + TransactionWithMetadata, + TransactionOptions, + ParticipationOverview, + ParticipationEventId, + ParticipationEventStatus, + ParticipationEventType, + ParticipationEventWithNodes, + ParticipationEventRegistrationOptions, + ParticipationEventMap, + SignedTransactionData, + PreparedTransaction, + PreparedCreateNativeTokenTransactionData, + ConsolidationParams, +} from '../types/wallet'; +import { Client, INode, Burn, PreparedTransactionData } from '../client'; +import { + Output, + FoundryOutput, + Response, + PreparedCreateNativeTokenTransaction, + u64, + u256, + NftId, + TokenId, + OutputId, + AccountId, + FoundryId, + TransactionId, + NumericString, + Bech32Address, +} from '../types'; +import { plainToInstance } from 'class-transformer'; +import { bigIntToHex, hexToBigInt } from '../types/utils/hex-encoding'; import type { - AccountIdentifier, WalletOptions, - CreateAccountPayload, WalletEventType, - GenerateAddressOptions, - SyncOptions, WalletEvent, Event, } from '../types/wallet'; import { IAuth, IClientOptions, LedgerNanoStatus } from '../types/client'; -import { Client } from '../client'; import { SecretManager } from '../secret_manager'; /** The Wallet class. */ @@ -67,17 +106,6 @@ export class Wallet { }); } - /** - * Create a new account. - */ - async createAccount(data: CreateAccountPayload): Promise { - const response = await this.methodHandler.callMethod({ - name: 'createAccount', - data, - }); - return new Account(JSON.parse(response).payload, this.methodHandler); - } - /** * Destroy the Wallet and drop its database connection. */ @@ -95,52 +123,6 @@ export class Wallet { }); } - /** - * Get an account by its alias or index. - */ - async getAccount(accountId: AccountIdentifier): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getAccount', - data: { accountId }, - }); - - const account = new Account( - JSON.parse(response).payload, - this.methodHandler, - ); - - return account; - } - - /** - * Get all account indexes. - */ - async getAccountIndexes(): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getAccountIndexes', - }); - - return JSON.parse(response).payload; - } - - /** - * Get all accounts. - */ - async getAccounts(): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getAccounts', - }); - - const { payload } = JSON.parse(response); - - const accounts: Account[] = []; - - for (const account of payload) { - accounts.push(new Account(account, this.methodHandler)); - } - return accounts; - } - /** * Get client. */ @@ -155,27 +137,6 @@ export class Wallet { return this.methodHandler.getSecretManager(); } - /** - * Generate an address without storing it. - */ - async generateEd25519Address( - accountIndex: number, - addressIndex: number, - options?: GenerateAddressOptions, - bech32Hrp?: string, - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'generateEd25519Address', - data: { - accountIndex, - addressIndex, - options, - bech32Hrp, - }, - }); - return JSON.parse(response).payload; - } - /** * Get the status for a Ledger Nano. */ @@ -217,41 +178,6 @@ export class Wallet { return JSON.parse(response).payload; } - /** - * Find accounts with unspent outputs. - */ - async recoverAccounts( - accountStartIndex: number, - accountGapLimit: number, - addressGapLimit: number, - syncOptions: SyncOptions, - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'recoverAccounts', - data: { - accountStartIndex, - accountGapLimit, - addressGapLimit, - syncOptions, - }, - }); - const accounts: Account[] = []; - - for (const account of JSON.parse(response).payload) { - accounts.push(new Account(account, this.methodHandler)); - } - return accounts; - } - - /** - * Delete the latest account. - */ - async removeLatestAccount(): Promise { - await this.methodHandler.callMethod({ - name: 'removeLatestAccount', - }); - } - /** * Restore a backup from a Stronghold file * Replaces client_options, coin_type, secret_manager and accounts. Returns an error if accounts were already created @@ -259,8 +185,7 @@ export class Wallet { * stored, it will be gone. * if ignore_if_coin_type_mismatch is provided client options will not be restored * if ignore_if_coin_type_mismatch == true, client options coin type and accounts will not be restored if the cointype doesn't match - * if ignore_if_bech32_hrp_mismatch == Some("rms"), but addresses have something different like "smr", no accounts - * will be restored. + * If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current address, the wallet will not be restored. */ async restoreBackup( source: string, @@ -355,4 +280,1372 @@ export class Wallet { data: { url, auth }, }); } + + /** + * Returns the accounts of the wallet. + * + * @returns The accounts of the wallet. + */ + async accounts(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'accounts', + }); + + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputData, parsed.payload); + } + + /** + * A generic function that can be used to burn native tokens, nfts, foundries and accounts. + * @param burn The outputs or native tokens to burn + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async burn( + burn: Burn, + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareBurn(burn, transactionOptions)).send(); + } + + /** + * A generic function that can be used to prepare to burn native tokens, nfts, foundries and accounts. + * @param burn The outputs or native tokens to burn + * @param transactionOptions Additional transaction options + * @returns The prepared transaction. + */ + async prepareBurn( + burn: Burn, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareBurn', + data: { + burn, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Burn native tokens. This doesn't require the foundry output which minted them, but will not increase + * the foundries `melted_tokens` field, which makes it impossible to destroy the foundry output. Therefore it's + * recommended to use melting, if the foundry output is available. + * @param tokenId The native token id. + * @param burnAmount The to be burned amount. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareBurnNativeToken( + tokenId: TokenId, + burnAmount: u256, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareBurn', + data: { + burn: { + nativeTokens: new Map([[tokenId, burnAmount]]), + }, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Burn an nft output. + * @param nftId The NftId. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareBurnNft( + nftId: NftId, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareBurn', + data: { + burn: { + nfts: [nftId], + }, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Claim basic or nft outputs that have additional unlock conditions + * to their `AddressUnlockCondition` from the wallet. + * @param outputIds The outputs to claim. + * @returns The resulting transaction. + */ + async claimOutputs( + outputIds: OutputId[], + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'claimOutputs', + data: { + outputIdsToClaim: outputIds, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Consolidate basic outputs with only an `AddressUnlockCondition` from a wallet + * by sending them to an own address again if the output amount is greater or + * equal to the output consolidation threshold. + * @param params Consolidation options. + * @returns The consolidation transaction. + */ + async consolidateOutputs( + params: ConsolidationParams, + ): Promise { + return (await this.prepareConsolidateOutputs(params)).send(); + } + + /** + * Consolidate basic outputs with only an `AddressUnlockCondition` from a wallet + * by sending them to an own address again if the output amount is greater or + * equal to the output consolidation threshold. + * @param params Consolidation options. + * @returns The prepared consolidation transaction. + */ + async prepareConsolidateOutputs( + params: ConsolidationParams, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareConsolidateOutputs', + data: { + params, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Creates an account output. + * @param params The account output options. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async createAccountOutput( + params?: AccountOutputParams, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareCreateAccountOutput(params, transactionOptions) + ).send(); + } + + /** + * Creates an account output. + * @param params The account output options. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareCreateAccountOutput( + params?: AccountOutputParams, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareCreateAccountOutput', + data: { + params, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Melt native tokens. This happens with the foundry output which minted them, by increasing its + * `melted_tokens` field. + * @param tokenId The native token id. + * @param meltAmount To be melted amount. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async meltNativeToken( + tokenId: TokenId, + meltAmount: bigint, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareMeltNativeToken( + tokenId, + meltAmount, + transactionOptions, + ) + ).send(); + } + + /** + * Melt native tokens. This happens with the foundry output which minted them, by increasing its + * `melted_tokens` field. + * @param tokenId The native token id. + * @param meltAmount To be melted amount. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareMeltNativeToken( + tokenId: TokenId, + meltAmount: u256, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareMeltNativeToken', + data: { + tokenId, + meltAmount: bigIntToHex(meltAmount), + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Deregister a participation event. + * + * @param eventId The id of the participation event to deregister. + */ + async deregisterParticipationEvent( + eventId: ParticipationEventId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'deregisterParticipationEvent', + data: { + eventId, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Destroy an account output. + * + * @param accountId The AccountId. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareDestroyAccount( + accountId: AccountId, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareBurn', + data: { + burn: { + accounts: [accountId], + }, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Function to destroy a foundry output with a circulating supply of 0. + * Native tokens in the foundry (minted by other foundries) will be transacted to the controlling account. + * @param foundryId The FoundryId. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareDestroyFoundry( + foundryId: FoundryId, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareBurn', + data: { + burn: { + foundries: [foundryId], + }, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Get the account balance. + * + * @returns The account balance. + */ + async getBalance(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getBalance', + }); + const payload = JSON.parse(response).payload; + return this.adjustBalancePayload(payload); + } + + /** + * Converts hex encoded or decimal strings of amounts to `bigint` + * for the balance payload. + */ + private adjustBalancePayload(payload: any): Balance { + for (let i = 0; i < payload.nativeTokens.length; i++) { + payload.nativeTokens[i].total = hexToBigInt( + payload.nativeTokens[i].total, + ); + payload.nativeTokens[i].available = hexToBigInt( + payload.nativeTokens[i].available, + ); + } + payload.baseCoin.total = BigInt(payload.baseCoin.total); + payload.baseCoin.available = BigInt(payload.baseCoin.available); + + payload.requiredStorageDeposit.account = BigInt( + payload.requiredStorageDeposit.account, + ); + payload.requiredStorageDeposit.basic = BigInt( + payload.requiredStorageDeposit.basic, + ); + payload.requiredStorageDeposit.foundry = BigInt( + payload.requiredStorageDeposit.foundry, + ); + payload.requiredStorageDeposit.nft = BigInt( + payload.requiredStorageDeposit.nft, + ); + + return payload; + } + + /** + * Get the data for an output. + * @param outputId The output to get. + * @returns The `OutputData`. + */ + async getOutput(outputId: OutputId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getOutput', + data: { + outputId, + }, + }); + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputData, parsed.payload); + } + + /** + * Get a participation event. + * + * @param eventId The ID of the event to get. + */ + async getParticipationEvent( + eventId: ParticipationEventId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getParticipationEvent', + data: { + eventId, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get IDs of participation events of a certain type. + * + * @param node The node to get events from. + * @param eventType The type of events to get. + */ + async getParticipationEventIds( + node: INode, + eventType?: ParticipationEventType, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getParticipationEventIds', + data: { + node, + eventType, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get all participation events. + */ + async getParticipationEvents(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getParticipationEvents', + }); + return JSON.parse(response).payload; + } + + /** + * Get the participation event status by its ID. + * + * @param eventId The ID of the event status to get. + */ + async getParticipationEventStatus( + eventId: ParticipationEventId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getParticipationEventStatus', + data: { + eventId, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get a `FoundryOutput` by native token ID. It will try to get the foundry from + * the account, if it isn't in the wallet it will try to get it from the node. + * + * @param tokenId The native token ID to get the foundry for. + * @returns The `FoundryOutput` that minted the token. + */ + async getFoundryOutput(tokenId: TokenId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getFoundryOutput', + data: { + tokenId, + }, + }); + return Output.parse(JSON.parse(response).payload) as FoundryOutput; + } + + /** + * Get outputs with additional unlock conditions. + * + * @param outputs The type of outputs to claim. + * @returns The output IDs of the unlockable outputs. + */ + async claimableOutputs(outputs: OutputsToClaim): Promise { + const response = await this.methodHandler.callMethod({ + name: 'claimableOutputs', + data: { + outputsToClaim: outputs, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get a transaction stored in the wallet. + * + * @param transactionId The ID of the transaction to get. + * @returns The transaction. + */ + async getTransaction( + transactionId: TransactionId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getTransaction', + data: { + transactionId, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Get the transaction with inputs of an incoming transaction stored in the wallet + * List might not be complete, if the node pruned the data already + * + * @param transactionId The ID of the transaction to get. + * @returns The transaction. + */ + async getIncomingTransaction( + transactionId: TransactionId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getIncomingTransaction', + data: { + transactionId, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Get the address of the wallet. + * + * @returns The address. + */ + async address(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getAddress', + }); + + return JSON.parse(response).payload; + } + + /** + * List all outputs of the wallet. + * + * @param filterOptions Options to filter the to be returned outputs. + * @returns The outputs with metadata. + */ + async outputs(filterOptions?: FilterOptions): Promise { + const response = await this.methodHandler.callMethod({ + name: 'outputs', + data: { filterOptions }, + }); + + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputData, parsed.payload); + } + + /** + * List all the pending transactions of the wallet. + * + * @returns The transactions. + */ + async pendingTransactions(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'pendingTransactions', + }); + const parsed = JSON.parse(response) as Response< + TransactionWithMetadata[] + >; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Returns the implicit account creation address of the wallet if it is Ed25519 based. + * + * @returns The implicit account creation address. + */ + async implicitAccountCreationAddress(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'implicitAccountCreationAddress', + }); + + return JSON.parse(response).payload; + } + + /** + * Returns the implicit accounts of the wallet. + * + * @returns The implicit accounts of the wallet. + */ + async implicitAccounts(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'implicitAccounts', + }); + + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputData, parsed.payload); + } + + /** + * List all incoming transactions of the wallet. + * + * @returns The incoming transactions with their inputs. + */ + async incomingTransactions(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'incomingTransactions', + }); + const parsed = JSON.parse(response) as Response< + TransactionWithMetadata[] + >; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * List all the transactions of the wallet. + * + * @returns The transactions. + */ + async transactions(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'transactions', + }); + const parsed = JSON.parse(response) as Response< + TransactionWithMetadata[] + >; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * List all the unspent outputs of the wallet. + * + * @param filterOptions Options to filter the to be returned outputs. + * @returns The outputs with metadata. + */ + async unspentOutputs(filterOptions?: FilterOptions): Promise { + const response = await this.methodHandler.callMethod({ + name: 'unspentOutputs', + data: { filterOptions }, + }); + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputData, parsed.payload); + } + + /** + * Mint additional native tokens. + * + * @param tokenId The native token id. + * @param mintAmount To be minted amount. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The minting transaction. + */ + async mintNativeToken( + tokenId: TokenId, + mintAmount: bigint, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareMintNativeToken( + tokenId, + mintAmount, + transactionOptions, + ) + ).send(); + } + + /** + * Mint additional native tokens. + * + * @param tokenId The native token id. + * @param mintAmount To be minted amount. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared minting transaction. + */ + async prepareMintNativeToken( + tokenId: string, + mintAmount: u256, + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareMintNativeToken', + data: { + tokenId, + mintAmount: bigIntToHex(mintAmount), + options: transactionOptions, + }, + }); + + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Create a native token. + * + * @param params The options for creating a native token. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The created transaction. + */ + async createNativeToken( + params: CreateNativeTokenParams, + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareCreateNativeToken(params, transactionOptions) + ).send(); + } + + /** + * Create a native token. + * + * @param params The options for creating a native token. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The created transaction and the token ID. + */ + async prepareCreateNativeToken( + params: CreateNativeTokenParams, + transactionOptions?: TransactionOptions, + ): Promise { + const adjustedParams: any = params; + adjustedParams.circulatingSupply = bigIntToHex( + params.circulatingSupply, + ); + adjustedParams.maximumSupply = bigIntToHex(params.maximumSupply); + + const response = await this.methodHandler.callMethod({ + name: 'prepareCreateNativeToken', + data: { + params: adjustedParams, + options: transactionOptions, + }, + }); + + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedCreateNativeTokenTransaction( + plainToInstance( + PreparedCreateNativeTokenTransactionData, + parsed.payload, + ), + this, + ); + } + + /** + * Mint NFTs. + * + * @param params The options for minting nfts. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The minting transaction. + */ + async mintNfts( + params: MintNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareMintNfts(params, transactionOptions)).send(); + } + + /** + * Mint NFTs. + * + * @param params The options for minting nfts. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared minting transaction. + */ + async prepareMintNfts( + params: MintNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareMintNfts', + data: { + params, + options: transactionOptions, + }, + }); + + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Prepare an output for sending, useful for offline signing. + * + * @param options The options for preparing an output. If the amount is + * below the minimum required storage deposit, by default the remaining + * amount will automatically be added with a `StorageDepositReturn` `UnlockCondition`, + * when setting the `ReturnStrategy` to `gift`, the full minimum required + * storage deposit will be sent to the recipient. When the assets contain + * an nft id, the data from the existing `NftOutput` will be used, just with + * the address unlock conditions replaced. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared output. + */ + async prepareOutput( + params: OutputParams, + transactionOptions?: TransactionOptions, + ): Promise { + if (typeof params.amount === 'bigint') { + params.amount = params.amount.toString(10); + } + + const response = await this.methodHandler.callMethod({ + name: 'prepareOutput', + data: { + params, + transactionOptions, + }, + }); + + return Output.parse(JSON.parse(response).payload); + } + + /** + * Prepare to send base coins, useful for offline signing. + * + * @param params Address with amounts to send. + * @param options Additional transaction options + * or custom inputs. + * @returns The prepared transaction data. + */ + async prepareSend( + params: SendParams[], + options?: TransactionOptions, + ): Promise { + for (let i = 0; i < params.length; i++) { + if (typeof params[i].amount === 'bigint') { + params[i].amount = params[i].amount.toString(10); + } + } + const response = await this.methodHandler.callMethod({ + name: 'prepareSend', + data: { + params, + options, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Send a transaction. + * + * @param outputs Outputs to use in the transaction. + * @param options Additional transaction options + * or custom inputs. + * @returns The transaction data. + */ + async sendTransaction( + outputs: Output[], + options?: TransactionOptions, + ): Promise { + return (await this.prepareTransaction(outputs, options)).send(); + } + + /** + * Prepare a transaction, useful for offline signing. + * + * @param outputs Outputs to use in the transaction. + * @param options Additional transaction options + * or custom inputs. + * @returns The prepared transaction data. + */ + async prepareTransaction( + outputs: Output[], + options?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareTransaction', + data: { + outputs, + options, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Register participation events. + * + * @param options Options to register participation events. + * @returns A mapping between event IDs and their corresponding event data. + */ + async registerParticipationEvents( + options: ParticipationEventRegistrationOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'registerParticipationEvents', + data: { + options, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Reissues a transaction sent from the wallet for a provided transaction id until it's + * included (referenced by a milestone). Returns the included block id. + */ + async reissueTransactionUntilIncluded( + transactionId: string, + interval?: number, + maxAttempts?: number, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'reissueTransactionUntilIncluded', + data: { + transactionId, + interval, + maxAttempts, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Send base coins to an address. + * + * @param amount Amount of coins. + * @param address Receiving address. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The sent transaction. + */ + async send( + amount: u64 | NumericString, + address: Bech32Address, + transactionOptions?: TransactionOptions, + ): Promise { + if (typeof amount === 'bigint') { + amount = amount.toString(10); + } + const response = await this.methodHandler.callMethod({ + name: 'send', + data: { + amount, + address, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Send base coins with amounts from input addresses. + * + * @param params Addresses with amounts. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The sent transaction. + */ + async sendWithParams( + params: SendParams[], + transactionOptions?: TransactionOptions, + ): Promise { + for (let i = 0; i < params.length; i++) { + if (typeof params[i].amount === 'bigint') { + params[i].amount = params[i].amount.toString(10); + } + } + const response = await this.methodHandler.callMethod({ + name: 'sendWithParams', + data: { + params, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Send native tokens. + * + * @param params Addresses amounts and native tokens. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async sendNativeTokens( + params: SendNativeTokensParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return ( + await this.prepareSendNativeTokens(params, transactionOptions) + ).send(); + } + + /** + * Send native tokens. + * + * @param params Addresses amounts and native tokens. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareSendNativeTokens( + params: SendNativeTokensParams[], + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareSendNativeTokens', + data: { + params, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Send NFT. + * + * @param params Addresses and nft ids. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The transaction. + */ + async sendNft( + params: SendNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + return (await this.prepareSendNft(params, transactionOptions)).send(); + } + + /** + * Send NFT. + * + * @param params Addresses and nft ids. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The prepared transaction. + */ + async prepareSendNft( + params: SendNftParams[], + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareSendNft', + data: { + params, + options: transactionOptions, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Send outputs in a transaction. + * + * @param outputs The outputs to send. + * @param transactionOptions Additional transaction options + * or custom inputs. + * @returns The sent transaction. + */ + async sendOutputs( + outputs: Output[], + transactionOptions?: TransactionOptions, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'sendOutputs', + data: { + outputs, + options: transactionOptions, + }, + }); + + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Set the alias for the account + * + * @param alias The account alias to set. + */ + async setAlias(alias: string): Promise { + await this.methodHandler.callMethod({ + name: 'setAlias', + data: { + alias, + }, + }); + } + + /** + * Set the fallback SyncOptions for account syncing. + * If storage is enabled, will persist during restarts. + * + * @param options The sync options to set. + */ + async setDefaultSyncOptions(options: SyncOptions): Promise { + await this.methodHandler.callMethod({ + name: 'setDefaultSyncOptions', + data: { + options, + }, + }); + } + + /** + * Sign a prepared transaction, useful for offline signing. + * + * @param preparedTransactionData The prepared transaction data to sign. + * @returns The signed transaction. + */ + async signTransaction( + preparedTransactionData: PreparedTransactionData, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'signTransaction', + data: { + preparedTransactionData, + }, + }); + const parsed = JSON.parse(response) as Response; + return plainToInstance(SignedTransactionData, parsed.payload); + } + + /** + * Sign a prepared transaction, and send it. + * + * @param preparedTransactionData The prepared transaction data to sign and submit. + * @returns The transaction. + */ + async signAndSubmitTransaction( + preparedTransactionData: PreparedTransactionData, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'signAndSubmitTransaction', + data: { + preparedTransactionData, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Validate the transaction, submit it to a node and store it in the wallet. + * + * @param signedTransactionData A signed transaction to submit and store. + * @returns The sent transaction. + */ + async submitAndStoreTransaction( + signedTransactionData: SignedTransactionData, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'submitAndStoreTransaction', + data: { + signedTransactionData, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(TransactionWithMetadata, parsed.payload); + } + + /** + * Sync the account by fetching new information from the nodes. + * Will also reissue pending transactions if necessary. + * A custom default can be set using setDefaultSyncOptions. + * + * @param options Optional synchronization options. + * @returns The account balance. + */ + async sync(options?: SyncOptions): Promise { + const response = await this.methodHandler.callMethod({ + name: 'sync', + data: { + options, + }, + }); + const payload = JSON.parse(response).payload; + return this.adjustBalancePayload(payload); + } + + /** + * Prepare a vote. + * + * @param eventId The participation event ID. + * @param answers Answers for a voting event, can be empty. + * @returns An instance of `PreparedTransaction`. + */ + async prepareVote( + eventId?: ParticipationEventId, + answers?: number[], + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareVote', + data: { + eventId, + answers, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Prepare stop participating in an event. + * + * @param eventId The event ID to stop participating in. + * @returns An instance of `PreparedTransaction`. + */ + async prepareStopParticipating( + eventId: ParticipationEventId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareStopParticipating', + data: { + eventId, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Calculates the voting overview of a wallet. + * + * @param eventIds Optional, filters participations only for provided events. + * @returns An instance of `ParticipationOverview` + */ + async getParticipationOverview( + eventIds?: ParticipationEventId[], + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getParticipationOverview', + data: { + eventIds, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Prepare to increase the voting power. + * + * @param amount The amount to increase the voting power by. + * @returns An instance of `PreparedTransaction`. + */ + async prepareIncreaseVotingPower( + amount: NumericString, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareIncreaseVotingPower', + data: { + amount, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } + + /** + * Prepare to decrease the voting power. + * + * @param amount The amount to decrease the voting power by. + * @returns An instance of `PreparedTransaction`. + */ + async prepareDecreaseVotingPower( + amount: NumericString, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'prepareDecreaseVotingPower', + data: { + amount, + }, + }); + const parsed = JSON.parse( + response, + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); + } } diff --git a/bindings/nodejs/package-lock.json b/bindings/nodejs/package-lock.json index b46ade0065..4094d96ed9 100644 --- a/bindings/nodejs/package-lock.json +++ b/bindings/nodejs/package-lock.json @@ -11,13 +11,13 @@ "license": "Apache-2.0", "dependencies": { "@types/node": "^18.15.12", - "cargo-cp-artifact": "^0.1.6", "class-transformer": "^0.5.1", "prebuild-install": "^7.1.1", "reflect-metadata": "^0.1.13", "typescript": "^4.9.4" }, "devDependencies": { + "@napi-rs/cli": "^1.0.0", "@types/jest": "^29.4.0", "@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/parser": "^5.30.7", @@ -59,12 +59,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.10", + "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" }, "engines": { @@ -197,12 +197,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -237,22 +237,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -344,9 +344,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -376,12 +376,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -461,9 +461,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -650,33 +650,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -694,13 +694,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1255,6 +1255,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/cli": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-1.3.5.tgz", + "integrity": "sha512-Z0KZIciemioYODTyO908v2AtL8Zg4sohQDD+dyHeHmOiOfaez/y/xQ8XnpOHc2W5fRidKUW+MVWyTtpLTbKsqw==", + "dev": true, + "dependencies": { + "inquirer": "^8.1.3" + }, + "bin": { + "napi": "scripts/index.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2288,6 +2307,18 @@ "readable-stream": "^3.0.1" } }, + "node_modules/block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==", + "dev": true, + "dependencies": { + "inherits": "~2.0.0" + }, + "engines": { + "node": "0.4 || >=0.5.8" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -2460,14 +2491,6 @@ } ] }, - "node_modules/cargo-cp-artifact": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.8.tgz", - "integrity": "sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA==", - "bin": { - "cargo-cp-artifact": "bin/cargo-cp-artifact.js" - } - }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -2511,6 +2534,12 @@ "node": ">=10" } }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -2551,6 +2580,39 @@ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2588,6 +2650,15 @@ "node": ">=8" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -2997,6 +3068,18 @@ "node": ">=0.10.0" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3579,6 +3662,20 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -3661,6 +3758,30 @@ "bser": "2.1.1" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4150,6 +4271,18 @@ "through2": "~0.6.3" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4242,6 +4375,69 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -4320,6 +4516,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-iojs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-iojs/-/is-iojs-1.1.0.tgz", @@ -4374,6 +4579,18 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -5281,6 +5498,22 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5502,6 +5735,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -5532,9 +5771,9 @@ "dev": true }, "node_modules/node-abi": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.46.0.tgz", - "integrity": "sha512-LXvP3AqTIrtvH/jllXjkNVbYifpRbt9ThTtymSMSuHmhugQLAWr99QQFTm+ZRht9ziUvdGOgB+esme1C6iE6Lg==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", + "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", "dependencies": { "semver": "^7.3.5" }, @@ -5691,6 +5930,18 @@ "semver": "bin/semver" } }, + "node_modules/node-ninja/node_modules/tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "deprecated": "This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.", + "dev": true, + "dependencies": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, "node_modules/node-ninja/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -5886,6 +6137,18 @@ "semver": "bin/semver" } }, + "node_modules/nw-gyp/node_modules/tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "deprecated": "This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.", + "dev": true, + "dependencies": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, "node_modules/nw-gyp/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -5956,6 +6219,40 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -6574,6 +6871,19 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6608,6 +6918,15 @@ "node": "0.12.* || 4.* || 6.* || >= 7.*" } }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6651,6 +6970,21 @@ } ] }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -6901,9 +7235,9 @@ "dev": true }, "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "dependencies": { "asn1": "~0.2.3", @@ -7248,6 +7582,12 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/through2": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", @@ -7282,6 +7622,18 @@ "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", "dev": true }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -7818,6 +8170,15 @@ "node": ">=10.13.0" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webpack": { "version": "5.88.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", @@ -8152,12 +8513,12 @@ } }, "@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.22.10", + "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" }, "dependencies": { @@ -8263,12 +8624,12 @@ } }, "@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -8296,19 +8657,19 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { @@ -8373,9 +8734,9 @@ "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -8396,12 +8757,12 @@ } }, "@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -8465,9 +8826,9 @@ } }, "@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -8597,30 +8958,30 @@ } }, "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -8634,13 +8995,13 @@ } }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -9069,6 +9430,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@napi-rs/cli": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-1.3.5.tgz", + "integrity": "sha512-Z0KZIciemioYODTyO908v2AtL8Zg4sohQDD+dyHeHmOiOfaez/y/xQ8XnpOHc2W5fRidKUW+MVWyTtpLTbKsqw==", + "dev": true, + "requires": { + "inquirer": "^8.1.3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -9891,6 +10261,15 @@ "readable-stream": "^3.0.1" } }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -9997,11 +10376,6 @@ "integrity": "sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ==", "dev": true }, - "cargo-cp-artifact": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.8.tgz", - "integrity": "sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA==" - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -10033,6 +10407,12 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -10061,6 +10441,27 @@ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -10091,6 +10492,12 @@ } } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -10426,6 +10833,15 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -10876,6 +11292,17 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -10948,6 +11375,23 @@ "bser": "2.1.1" } }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + } + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -11334,6 +11778,15 @@ "through2": "~0.6.3" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -11391,6 +11844,59 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -11448,6 +11954,12 @@ "is-extglob": "^2.1.1" } }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, "is-iojs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-iojs/-/is-iojs-1.1.0.tgz", @@ -11487,6 +11999,12 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -12204,6 +12722,16 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -12385,6 +12913,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -12415,9 +12949,9 @@ "dev": true }, "node-abi": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.46.0.tgz", - "integrity": "sha512-LXvP3AqTIrtvH/jllXjkNVbYifpRbt9ThTtymSMSuHmhugQLAWr99QQFTm+ZRht9ziUvdGOgB+esme1C6iE6Lg==", + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", + "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", "requires": { "semver": "^7.3.5" } @@ -12543,6 +13077,16 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, + "tar": { + "version": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12696,6 +13240,16 @@ "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==", "dev": true }, + "tar": { + "version": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12750,6 +13304,36 @@ "type-check": "^0.4.0" } }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + } + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -13201,6 +13785,16 @@ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -13222,6 +13816,12 @@ "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", "dev": true }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -13237,6 +13837,23 @@ "integrity": "sha512-iFPgh7SatHXOG1ClcpdwHI63geV3Hc/iL6crGSyBlH2PY7Rm/za+zoKz6FfY/Qlw5K7JwSol8pseO8fN6CMhhQ==", "dev": true }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -13415,9 +14032,9 @@ "dev": true }, "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -13674,6 +14291,12 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "through2": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", @@ -13710,6 +14333,15 @@ } } }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -14109,6 +14741,15 @@ "graceful-fs": "^4.1.2" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webpack": { "version": "5.88.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", diff --git a/bindings/nodejs/package.json b/bindings/nodejs/package.json index 0983e95fea..717c95b1b1 100644 --- a/bindings/nodejs/package.json +++ b/bindings/nodejs/package.json @@ -5,19 +5,19 @@ "main": "out/index.js", "types": "out/index.d.ts", "scripts": { + "build": "napi build --release && tsc", + "build:debug": "napi build", "lint": "eslint --ignore-path .eslintignore --ext .js,.ts .", "format": "prettier --ignore-path .eslintignore -w \"{,*/**/}*.{ts,js,json}\"", "format-check": "prettier --ignore-path .eslintignore -c \"{,*/**/}*.{ts,js,json}\"", - "build": "node scripts/neon-build && tsc", - "build:neon": "cargo-cp-artifact -ac iota-sdk-nodejs ./index.node -- cargo build --profile=production --message-format=json-render-diagnostics", - "prebuild-x64": "prebuild --runtime napi --target 6 --prepack scripts/neon-build.js --strip --arch x64", - "prebuild-macos-arm64": "prebuild --runtime napi --target 6 --prepack 'yarn run neon-build-macos-arm64' --strip --arch arm64", - "neon-build-macos-arm64": "cargo-cp-artifact -ac iota-sdk-nodejs ./index.node -- cargo build --profile=production --message-format=json-render-diagnostics --target aarch64-apple-darwin && node -e \"require('./scripts/move-artifact.js')()\"", - "prebuild-linux-arm64": "prebuild --runtime napi --target 6 --prepack 'yarn run neon-build-linux-arm64' --strip --arch arm64", - "neon-build-linux-arm64": "cargo-cp-artifact -ac iota-sdk-nodejs ./index.node -- cargo build --profile=production --message-format=json-render-diagnostics --target aarch64-unknown-linux-gnu && node -e \"require('./scripts/move-artifact.js')()\"", - "prebuild-windows-arm64": "prebuild --runtime napi --target 6 --prepack 'yarn run neon-build-windows-arm64' --strip --arch arm64", - "neon-build-windows-arm64": "cargo-cp-artifact -ac iota-sdk-nodejs ./index.node -- cargo build --profile=production --message-format=json-render-diagnostics --target aarch64-pc-windows-msvc && node -e \"require('./scripts/move-artifact.js')()\"", - "rebuild": "node scripts/neon-build && tsc && node scripts/strip.js", + "prebuild-x64": "prebuild --runtime napi --target 6 --prepack scripts/build.js --strip --arch x64", + "prebuild-macos-arm64": "prebuild --runtime napi --target 6 --prepack 'yarn run napi-build-macos-arm64' --strip --arch arm64", + "napi-build-macos-arm64": "napi build --cargo-flags=--profile=production --target aarch64-apple-darwin", + "prebuild-linux-arm64": "prebuild --runtime napi --target 6 --prepack 'yarn run napi-build-linux-arm64' --strip --arch arm64", + "napi-build-linux-arm64": "napi build --cargo-flags=--profile=production --target aarch64-unknown-linux-gnu", + "prebuild-windows-arm64": "prebuild --runtime napi --target 6 --prepack 'yarn run napi-build-windows-arm64' --strip --arch arm64", + "napi-build-windows-arm64": "napi build --cargo-flags=--profile=production --target aarch64-pc-windows-msvc", + "rebuild": "node scripts/build && tsc && node scripts/strip.js", "install": "prebuild-install --runtime napi --tag-prefix=iota-sdk-nodejs-v && tsc || npm run rebuild", "test": "jest", "test-webpack": "cd tests/webpack && webpack-cli build --config ./webpack.config.js ", @@ -27,7 +27,6 @@ "license": "Apache-2.0", "dependencies": { "@types/node": "^18.15.12", - "cargo-cp-artifact": "^0.1.6", "class-transformer": "^0.5.1", "prebuild-install": "^7.1.1", "reflect-metadata": "^0.1.13", @@ -49,7 +48,8 @@ "typedoc": "^0.24.6", "typedoc-plugin-markdown": "^3.14.0", "webpack": "^5.88.2", - "webpack-cli": "^5.1.4" + "webpack-cli": "^5.1.4", + "@napi-rs/cli": "^1.0.0" }, "overrides": { "tar@<=4.4.17": "^4.4.19", @@ -75,5 +75,8 @@ "bugs": { "url": "https://github.com/iotaledger/iota-sdk/issues" }, - "homepage": "https://github.com/iotaledger/iota-sdk#readme" + "homepage": "https://github.com/iotaledger/iota-sdk#readme", + "napi": { + "name": "build/Release/index" + } } diff --git a/bindings/nodejs/scripts/neon-build.js b/bindings/nodejs/scripts/build.js similarity index 61% rename from bindings/nodejs/scripts/neon-build.js rename to bindings/nodejs/scripts/build.js index 742bc2868c..6397cb0966 100644 --- a/bindings/nodejs/scripts/neon-build.js +++ b/bindings/nodejs/scripts/build.js @@ -1,10 +1,9 @@ const { resolve } = require('path'); const { spawnSync } = require('child_process'); -const moveArtifact = require('./move-artifact'); -// Passing "--prepack 'yarn build:neon'" causes problems on Windows, so this is a workaround +// Passing "--prepack 'yarn build'" causes problems on Windows, so this is a workaround -const { status } = spawnSync(process.platform === 'win32' ? 'yarn.cmd' : 'yarn', ['build:neon'], { +const { status } = spawnSync(process.platform === 'win32' ? 'yarn.cmd' : 'yarn', ['build'], { stdio: 'inherit', cwd: resolve(__dirname, '../'), }); @@ -14,5 +13,3 @@ if (status === null) { } else if (status > 0) { process.exit(status); } - -moveArtifact(); diff --git a/bindings/nodejs/scripts/move-artifact.js b/bindings/nodejs/scripts/move-artifact.js deleted file mode 100644 index 7b63c5e122..0000000000 --- a/bindings/nodejs/scripts/move-artifact.js +++ /dev/null @@ -1,15 +0,0 @@ -const { existsSync, mkdirSync, renameSync } = require('fs'); -const { resolve } = require('path'); - -// Prebuild requires that the binary be in `build/Release` as though it was built with node-gyp - -const moveArtifact = () => { - const path = resolve(__dirname, '../build/Release'); - - if (!existsSync(path)) { - mkdirSync(path, { recursive: true }); - } - renameSync(resolve(__dirname, '../index.node'), resolve(path, 'index.node')); -}; - -module.exports = moveArtifact; diff --git a/bindings/nodejs/scripts/strip.js b/bindings/nodejs/scripts/strip.js index fa99aa5360..944f1b350f 100644 --- a/bindings/nodejs/scripts/strip.js +++ b/bindings/nodejs/scripts/strip.js @@ -1,3 +1,4 @@ +// Removes symbols and debug information from the binary to reduce its size. const { resolve } = require('path'); const { spawnSync } = require('child_process'); diff --git a/bindings/nodejs/src/client.rs b/bindings/nodejs/src/client.rs index da62dca605..b84f35ff31 100644 --- a/bindings/nodejs/src/client.rs +++ b/bindings/nodejs/src/client.rs @@ -6,157 +6,82 @@ use std::sync::Arc; use iota_sdk_bindings_core::{ call_client_method as rust_call_client_method, iota_sdk::client::{mqtt::Topic, Client, ClientBuilder}, - listen_mqtt as rust_listen_mqtt, ClientMethod, Response, Result, + listen_mqtt as rust_listen_mqtt, ClientMethod, Response, }; -use neon::prelude::*; +use napi::{bindgen_prelude::External, threadsafe_function::ThreadsafeFunction, Error, Result, Status}; +use napi_derive::napi; use tokio::sync::RwLock; -type JsCallback = Root>; +use crate::NodejsError; -// Wrapper so we can destroy the ClientMethodHandler -pub type ClientMethodHandlerWrapperInner = Arc>>; -// Wrapper because we can't impl Finalize on ClientMethodHandlerWrapperInner -pub struct ClientMethodHandlerWrapper(pub ClientMethodHandlerWrapperInner); -pub struct ClientMethodHandler { - channel: Channel, - client: Client, -} - -impl Finalize for ClientMethodHandlerWrapper {} - -impl ClientMethodHandler { - pub fn new(channel: Channel, options: String) -> Result { - let runtime = tokio::runtime::Runtime::new().expect("error initializing client"); - let client = runtime.block_on(ClientBuilder::new().from_json(&options)?.finish())?; - - Ok(Self { channel, client }) - } - - pub(crate) fn new_with_client(channel: Channel, client: Client) -> Self { - Self { channel, client } - } +pub type ClientMethodHandler = Arc>>; - async fn call_method(&self, serialized_method: String) -> (String, bool) { - match serde_json::from_str::(&serialized_method) { - Ok(method) => { - let res = rust_call_client_method(&self.client, method).await; - let mut is_err = matches!(res, Response::Error(_) | Response::Panic(_)); +#[napi(js_name = "createClient")] +pub fn create_client(options: String) -> Result> { + let runtime = tokio::runtime::Runtime::new().map_err(NodejsError::from)?; + let client = runtime + .block_on( + ClientBuilder::new() + .from_json(&options) + .map_err(NodejsError::from)? + .finish(), + ) + .map_err(NodejsError::from)?; - let msg = match serde_json::to_string(&res) { - Ok(msg) => msg, - Err(e) => { - is_err = true; - serde_json::to_string(&Response::Error(e.into())).expect("json to string error") - } - }; - - (msg, is_err) - } - Err(e) => { - log::error!("{:?}", e); - (format!("Couldn't parse to method with error - {e:?}"), true) - } - } - } + Ok(External::new(Arc::new(RwLock::new(Some(client))))) } -pub fn create_client(mut cx: FunctionContext) -> JsResult> { - let options = cx.argument::(0)?; - let options = options.value(&mut cx); - let channel = cx.channel(); - let method_handler = ClientMethodHandler::new(channel, options) - .or_else(|e| cx.throw_error(serde_json::to_string(&Response::Error(e)).expect("json to string error")))?; - Ok(cx.boxed(ClientMethodHandlerWrapper(Arc::new(RwLock::new(Some(method_handler)))))) +#[napi(js_name = "destroyClient")] +pub async fn destroy_client(client: External) { + *client.as_ref().write().await = None; } -pub fn destroy_client(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - *method_handler.write().await = None; - deferred.settle_with(&channel, move |mut cx| Ok(cx.undefined())); - }); - Ok(promise) -} - -pub fn call_client_method(mut cx: FunctionContext) -> JsResult { - let method = cx.argument::(0)?; - let method = method.value(&mut cx); - let method_handler = Arc::clone(&cx.argument::>(1)?.0); - let callback = cx.argument::(2)?.root(&mut cx); - - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let (response, is_error) = method_handler.call_method(method).await; - method_handler.channel.send(move |mut cx| { - let cb = callback.into_inner(&mut cx); - let this = cx.undefined(); - - let args = [ - if is_error { - cx.string(response.clone()).upcast::() - } else { - cx.undefined().upcast::() - }, - cx.string(response).upcast::(), - ]; - - cb.call(&mut cx, this, args)?; - - Ok(()) - }); - } else { - panic!("Client got destroyed") +#[napi(js_name = "callClientMethod")] +pub async fn call_client_method(client: External, method: String) -> Result { + let client_method = serde_json::from_str::(&method).map_err(NodejsError::from)?; + + if let Some(client) = &*client.as_ref().read().await { + let res = rust_call_client_method(client, client_method).await; + if matches!(res, Response::Error(_) | Response::Panic(_)) { + return Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&res).map_err(NodejsError::from)?, + )); } - }); - Ok(cx.undefined()) -} - -// MQTT -pub fn listen_mqtt(mut cx: FunctionContext) -> JsResult { - let js_arr_handle: Handle = cx.argument(0)?; - let vec: Vec> = js_arr_handle.to_vec(&mut cx)?; - let mut topics = Vec::with_capacity(vec.len()); - for topic_string in vec { - let topic = topic_string.downcast::(&mut cx).unwrap(); - topics.push(Topic::new(topic.value(&mut cx).as_str()).expect("invalid MQTT topic")); + Ok(serde_json::to_string(&res).map_err(NodejsError::from)?) + } else { + Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&Response::Panic("Client got destroyed".to_string())).map_err(NodejsError::from)?, + )) } - - let callback = Arc::new(cx.argument::(1)?.root(&mut cx)); - let method_handler = Arc::clone(&cx.argument::>(2)?.0); - let (deferred, promise) = cx.promise(); - - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let channel0 = method_handler.channel.clone(); - let channel1 = method_handler.channel.clone(); - rust_listen_mqtt(&method_handler.client, topics, move |event_data| { - call_event_callback(&channel0, event_data, callback.clone()) - }) - .await; - - deferred.settle_with(&channel1, move |mut cx| Ok(cx.undefined())); - } else { - panic!("Client got destroyed") - } - }); - - Ok(promise) } -fn call_event_callback(channel: &neon::event::Channel, event_data: String, callback: Arc) { - channel.send(move |mut cx| { - let cb = (*callback).to_inner(&mut cx); - let this = cx.undefined(); - let args = [ - cx.undefined().upcast::(), - cx.string(event_data).upcast::(), - ]; - - cb.call(&mut cx, this, args)?; +#[napi(js_name = "listenMqtt")] +pub async fn listen_mqtt( + client: External, + topics: Vec, + callback: ThreadsafeFunction, +) -> Result<()> { + let mut validated_topics = Vec::with_capacity(topics.len()); + for topic_string in topics { + validated_topics.push(Topic::new(topic_string).map_err(NodejsError::from)?); + } + if let Some(client) = &*client.as_ref().read().await { + rust_listen_mqtt(client, validated_topics, move |event_data| { + callback.call( + Ok(event_data), + napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, + ); + }) + .await; Ok(()) - }); + } else { + Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&Response::Panic("Client got destroyed".to_string())).map_err(NodejsError::from)?, + )) + } } diff --git a/bindings/nodejs/src/lib.rs b/bindings/nodejs/src/lib.rs index 2d8b429f3d..f574b947ca 100644 --- a/bindings/nodejs/src/lib.rs +++ b/bindings/nodejs/src/lib.rs @@ -1,70 +1,65 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod client; -mod secret_manager; -mod wallet; +pub mod client; +pub mod secret_manager; +pub mod wallet; use iota_sdk_bindings_core::{ call_utils_method as rust_call_utils_method, init_logger as rust_init_logger, Response, UtilsMethod, }; -use neon::prelude::*; -use once_cell::sync::Lazy; -use tokio::runtime::Runtime; +use napi::{Error, Result, Status}; +use napi_derive::napi; -pub static RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap()); +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum NodejsError { + /// Bindings core errors. + #[error(transparent)] + Core(#[from] iota_sdk_bindings_core::Error), + /// Client errors. + #[error(transparent)] + Client(#[from] iota_sdk_bindings_core::iota_sdk::client::Error), + /// Mqtt errors. + #[error(transparent)] + Mqtt(#[from] iota_sdk_bindings_core::iota_sdk::client::node_api::mqtt::Error), + /// SerdeJson errors. + #[error(transparent)] + SerdeJson(#[from] serde_json::error::Error), + /// IO error. + #[error(transparent)] + Io(#[from] std::io::Error), + /// Wallet errors. + #[error(transparent)] + Wallet(#[from] iota_sdk_bindings_core::iota_sdk::wallet::Error), +} + +impl From for Error { + fn from(error: NodejsError) -> Self { + Error::new(Status::GenericFailure, error.to_string()) + } +} -pub fn init_logger(mut cx: FunctionContext) -> JsResult { - let config = cx.argument::(0)?.value(&mut cx); +#[napi(js_name = "initLogger")] +pub fn init_logger(config: String) -> Result<()> { match rust_init_logger(config) { - Ok(_) => Ok(cx.undefined()), - Err(err) => { - cx.throw_error(serde_json::to_string(&Response::Panic(err.to_string())).expect("json to string error")) - } + Ok(_) => Ok(()), + Err(err) => Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&Response::Panic(err.to_string())).map_err(NodejsError::from)?, + )), } } -pub fn call_utils_method(mut cx: FunctionContext) -> JsResult { - let method = cx.argument::(0)?.value(&mut cx); - let method = match serde_json::from_str::(&method) { +#[napi(js_name = "callUtilsMethodRust")] +pub fn call_utils_method(method_json: String) -> Result { + let method = match serde_json::from_str::(&method_json) { Ok(method) => method, Err(err) => { - return Ok(cx.string(serde_json::to_string(&Response::Error(err.into())).expect("json to string error"))); + return Ok(serde_json::to_string(&Response::Error(err.into())).map_err(NodejsError::from)?); } }; let response = rust_call_utils_method(method); - Ok(cx.string(serde_json::to_string(&response).unwrap())) -} - -#[neon::main] -fn main(mut cx: ModuleContext) -> NeonResult<()> { - cx.export_function("initLogger", init_logger)?; - - cx.export_function("callUtilsMethodRust", call_utils_method)?; - - // Client - cx.export_function("callClientMethod", client::call_client_method)?; - cx.export_function("createClient", client::create_client)?; - cx.export_function("destroyClient", client::destroy_client)?; - - // MQTT - cx.export_function("listenMqtt", client::listen_mqtt)?; - - cx.export_function("callSecretManagerMethod", secret_manager::call_secret_manager_method)?; - cx.export_function("createSecretManager", secret_manager::create_secret_manager)?; - cx.export_function( - "migrateStrongholdSnapshotV2ToV3", - secret_manager::migrate_stronghold_snapshot_v2_to_v3, - )?; - - // Wallet - cx.export_function("callWalletMethod", wallet::call_wallet_method)?; - cx.export_function("createWallet", wallet::create_wallet)?; - cx.export_function("destroyWallet", wallet::destroy_wallet)?; - cx.export_function("getClientFromWallet", wallet::get_client)?; - cx.export_function("getSecretManagerFromWallet", wallet::get_secret_manager)?; - cx.export_function("listenWallet", wallet::listen_wallet)?; - - Ok(()) + Ok(serde_json::to_string(&response).map_err(NodejsError::from)?) } diff --git a/bindings/nodejs/src/secret_manager.rs b/bindings/nodejs/src/secret_manager.rs index cc134ebbe0..b7c39c4071 100644 --- a/bindings/nodejs/src/secret_manager.rs +++ b/bindings/nodejs/src/secret_manager.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::{ops::Deref, sync::Arc}; +use std::sync::Arc; use iota_sdk_bindings_core::{ call_secret_manager_method as rust_call_secret_manager_method, @@ -9,135 +9,64 @@ use iota_sdk_bindings_core::{ secret::{SecretManager, SecretManagerDto}, stronghold::StrongholdAdapter, }, - Response, Result, SecretManagerMethod, + Response, SecretManagerMethod, }; -use neon::prelude::*; +use napi::{bindgen_prelude::External, Error, Result, Status}; +use napi_derive::napi; use tokio::sync::RwLock; -pub struct SecretManagerMethodHandler { - channel: Channel, - secret_manager: Arc>, -} - -impl Finalize for SecretManagerMethodHandler {} +use crate::NodejsError; -impl SecretManagerMethodHandler { - fn new(channel: Channel, options: String) -> Result> { - let secret_manager_dto = serde_json::from_str::(&options)?; - let secret_manager = SecretManager::try_from(secret_manager_dto)?; +pub type SecretManagerMethodHandler = Arc>; - Ok(Arc::new(Self { - channel, - secret_manager: Arc::new(RwLock::new(secret_manager)), - })) - } - - pub fn new_with_secret_manager(channel: Channel, secret_manager: Arc>) -> Arc { - Arc::new(Self { - channel, - secret_manager, - }) - } +#[napi(js_name = "createSecretManager")] +pub fn create_secret_manager(options: String) -> Result> { + let secret_manager_dto = serde_json::from_str::(&options).map_err(NodejsError::from)?; + let secret_manager = SecretManager::try_from(secret_manager_dto).map_err(NodejsError::from)?; - async fn call_method(&self, method: String) -> (String, bool) { - match serde_json::from_str::(&method) { - Ok(method) => { - let res = { - let secret_manager = self.secret_manager.read().await; - rust_call_secret_manager_method(&*secret_manager, method).await - }; - let mut is_err = matches!(res, Response::Error(_) | Response::Panic(_)); - - let msg = match serde_json::to_string(&res) { - Ok(msg) => msg, - Err(e) => { - is_err = true; - serde_json::to_string(&Response::Error(e.into())).expect("json to string error") - } - }; - - (msg, is_err) - } - Err(e) => { - log::error!("{:?}", e); - (format!("Couldn't parse to method with error - {e:?}"), true) - } - } - } + Ok(External::new(Arc::new(RwLock::new(secret_manager)))) } -pub fn create_secret_manager(mut cx: FunctionContext) -> JsResult>> { - let options = cx.argument::(0)?; - let options = options.value(&mut cx); - let channel = cx.channel(); - - let method_handler = SecretManagerMethodHandler::new(channel, options) - .or_else(|e| cx.throw_error(serde_json::to_string(&Response::Error(e)).expect("json to string error")))?; - - Ok(cx.boxed(method_handler)) -} - -pub fn call_secret_manager_method(mut cx: FunctionContext) -> JsResult { - let method = cx.argument::(0)?; - let method = method.value(&mut cx); - let method_handler = Arc::clone(cx.argument::>>(1)?.deref()); - let callback = cx.argument::(2)?.root(&mut cx); - - crate::RUNTIME.spawn(async move { - let (response, is_error) = method_handler.call_method(method).await; - method_handler.channel.send(move |mut cx| { - let cb = callback.into_inner(&mut cx); - let this = cx.undefined(); - - let args = vec![ - if is_error { - cx.string(response.clone()).upcast::() - } else { - cx.undefined().upcast::() - }, - cx.string(response).upcast::(), - ]; - - cb.call(&mut cx, this, args)?; - - Ok(()) - }); - }); +#[napi(js_name = "callSecretManagerMethod")] +pub async fn call_secret_manager_method( + secret_manager: External, + method: String, +) -> Result { + let secret_manager_method = serde_json::from_str::(&method).map_err(NodejsError::from)?; + + let res = rust_call_secret_manager_method(&*secret_manager.as_ref().read().await, secret_manager_method).await; + if matches!(res, Response::Error(_) | Response::Panic(_)) { + return Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&res).map_err(NodejsError::from)?, + )); + } - Ok(cx.undefined()) + Ok(serde_json::to_string(&res).map_err(NodejsError::from)?) } -pub fn migrate_stronghold_snapshot_v2_to_v3(mut cx: FunctionContext) -> JsResult { - let current_path = cx.argument::(0)?.value(&mut cx); - let current_password = cx.argument::(1)?.value(&mut cx).into(); - let salt = cx.argument::(2)?.value(&mut cx); - let rounds = cx.argument::(3)?.value(&mut cx); - let new_path = cx - .argument_opt(4) - .map(|opt| opt.downcast_or_throw::(&mut cx)) - .transpose()? - .map(|opt| opt.value(&mut cx)); - let new_password = cx - .argument_opt(5) - .map(|opt| opt.downcast_or_throw::(&mut cx)) - .transpose()? - .map(|opt| opt.value(&mut cx)) - .map(Into::into); +#[napi(js_name = "migrateStrongholdSnapshotV2ToV3")] +pub fn migrate_stronghold_snapshot_v2_to_v3( + current_path: String, + current_password: String, + salt: String, + rounds: u32, + new_path: Option, + new_password: Option, +) -> Result<()> { + let current_password = current_password.into(); + let new_password = new_password.map(Into::into); StrongholdAdapter::migrate_snapshot_v2_to_v3( ¤t_path, current_password, salt, - rounds as u32, + rounds, new_path.as_ref(), new_password, ) - .or_else(|e| { - cx.throw_error( - serde_json::to_string(&Response::Error(e.into())) - .expect("the response is generated manually, so unwrap is safe."), - ) - })?; + .map_err(iota_sdk_bindings_core::iota_sdk::client::Error::from) + .map_err(NodejsError::from)?; - Ok(cx.undefined()) + Ok(()) } diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index 8363def0cd..6beb831491 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -5,221 +5,104 @@ use std::sync::Arc; use iota_sdk_bindings_core::{ call_wallet_method as rust_call_wallet_method, - iota_sdk::wallet::{ - events::types::{Event, WalletEventType}, - Wallet, - }, - Response, Result, WalletMethod, WalletOptions, + iota_sdk::wallet::{events::WalletEventType, Wallet}, + Response, WalletMethod, WalletOptions, }; -use neon::prelude::*; +use napi::{bindgen_prelude::External, threadsafe_function::ThreadsafeFunction, Error, Result, Status}; +use napi_derive::napi; use tokio::sync::RwLock; -use crate::{ - client::{ClientMethodHandler, ClientMethodHandlerWrapper}, - secret_manager::SecretManagerMethodHandler, -}; - -// Wrapper so we can destroy the WalletMethodHandler -pub type WalletMethodHandlerWrapperInner = Arc>>; -// Wrapper because we can't impl Finalize on WalletMethodHandlerWrapperInner -pub struct WalletMethodHandlerWrapper(pub WalletMethodHandlerWrapperInner); -impl Finalize for WalletMethodHandlerWrapper {} - -pub struct WalletMethodHandler { - channel: Channel, - wallet: Wallet, -} - -type JsCallback = Root>; +use crate::{client::ClientMethodHandler, secret_manager::SecretManagerMethodHandler, NodejsError}; -impl WalletMethodHandler { - fn new(channel: Channel, options: String) -> Result { - let wallet_options = serde_json::from_str::(&options)?; +pub type WalletMethodHandler = Arc>>; - let wallet = crate::RUNTIME.block_on(async move { wallet_options.build().await })?; +#[napi(js_name = "createWallet")] +pub fn create_wallet(options: String) -> Result> { + let wallet_options = serde_json::from_str::(&options).map_err(NodejsError::from)?; + let runtime = tokio::runtime::Runtime::new().map_err(NodejsError::from)?; + let wallet = runtime.block_on(wallet_options.build()).map_err(NodejsError::from)?; - Ok(Self { channel, wallet }) - } - - async fn call_method(&self, method: String) -> (String, bool) { - match serde_json::from_str::(&method) { - Ok(method) => { - let res = rust_call_wallet_method(&self.wallet, method).await; - let mut is_err = matches!(res, Response::Error(_) | Response::Panic(_)); - - let msg = match serde_json::to_string(&res) { - Ok(msg) => msg, - Err(e) => { - is_err = true; - serde_json::to_string(&Response::Error(e.into())).expect("json to string error") - } - }; - - (msg, is_err) - } - Err(e) => { - log::error!("{:?}", e); - ( - serde_json::to_string(&Response::Error(e.into())).expect("json to string error"), - true, - ) - } - } - } + Ok(External::new(Arc::new(RwLock::new(Some(wallet))))) } -impl Finalize for WalletMethodHandler {} - -fn call_event_callback(channel: &neon::event::Channel, event_data: Event, callback: Arc) { - channel.send(move |mut cx| { - let cb = (*callback).to_inner(&mut cx); - let this = cx.undefined(); - let args = [ - cx.undefined().upcast::(), - cx.string(serde_json::to_string(&event_data).unwrap()) - .upcast::(), - ]; - - cb.call(&mut cx, this, args)?; - - Ok(()) - }); +#[napi(js_name = "destroyWallet")] +pub async fn destroy_wallet(wallet: External) { + *wallet.as_ref().write().await = None; } -pub fn create_wallet(mut cx: FunctionContext) -> JsResult> { - let options = cx.argument::(0)?; - let options = options.value(&mut cx); - let channel = cx.channel(); - let method_handler = WalletMethodHandler::new(channel, options) - .or_else(|e| cx.throw_error(serde_json::to_string(&Response::Error(e)).expect("json to string error")))?; - - Ok(cx.boxed(WalletMethodHandlerWrapper(Arc::new(RwLock::new(Some(method_handler)))))) -} - -pub fn call_wallet_method(mut cx: FunctionContext) -> JsResult { - let method = cx.argument::(0)?; - let method = method.value(&mut cx); - let method_handler = Arc::clone(&cx.argument::>(1)?.0); - let callback = cx.argument::(2)?.root(&mut cx); - - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let (response, is_error) = method_handler.call_method(method).await; - method_handler.channel.send(move |mut cx| { - let cb = callback.into_inner(&mut cx); - let this = cx.undefined(); - - let args = [ - if is_error { - cx.string(response.clone()).upcast::() - } else { - cx.undefined().upcast::() - }, - cx.string(response).upcast::(), - ]; - - cb.call(&mut cx, this, args)?; - - Ok(()) - }); - } else { - panic!("Wallet got destroyed") +#[napi(js_name = "callWalletMethod")] +pub async fn call_wallet_method(wallet: External, method: String) -> Result { + let wallet_method = serde_json::from_str::(&method).map_err(NodejsError::from)?; + + if let Some(wallet) = &*wallet.as_ref().read().await { + let res = rust_call_wallet_method(wallet, wallet_method).await; + if matches!(res, Response::Error(_) | Response::Panic(_)) { + return Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&res).map_err(NodejsError::from)?, + )); } - }); - Ok(cx.undefined()) + Ok(serde_json::to_string(&res).map_err(NodejsError::from)?) + } else { + Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).map_err(NodejsError::from)?, + )) + } } -pub fn listen_wallet(mut cx: FunctionContext) -> JsResult { - let js_arr_handle: Handle = cx.argument(0)?; - let vec: Vec> = js_arr_handle.to_vec(&mut cx)?; - let mut event_types = Vec::with_capacity(vec.len()); - for event_string in vec { - let event_type = event_string.downcast_or_throw::(&mut cx)?; - let wallet_event_type = - WalletEventType::try_from(event_type.value(&mut cx) as u8).or_else(|e| cx.throw_error(e))?; - event_types.push(wallet_event_type); +#[napi(js_name = "listenWallet")] +pub async fn listen_wallet( + wallet: External, + event_types: Vec, + callback: ThreadsafeFunction, +) -> Result<()> { + let mut validated_event_types = Vec::with_capacity(event_types.len()); + for event_type in event_types { + validated_event_types.push(WalletEventType::try_from(event_type).map_err(NodejsError::from)?); } - let callback = Arc::new(cx.argument::(1)?.root(&mut cx)); - let method_handler = Arc::clone(&cx.argument::>(2)?.0); - - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let channel = method_handler.channel.clone(); - method_handler - .wallet - .listen(event_types, move |event_data| { - call_event_callback(&channel, event_data.clone(), callback.clone()) - }) - .await; - } else { - panic!("Wallet got destroyed") - } - }); - - Ok(cx.undefined()) -} - -pub fn destroy_wallet(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - *method_handler.write().await = None; - deferred.settle_with(&channel, move |mut cx| Ok(cx.undefined())); - }); - Ok(promise) + if let Some(wallet) = &*wallet.as_ref().read().await { + wallet + .listen(validated_event_types, move |event_data| { + callback.call( + serde_json::to_string(event_data) + .map_err(NodejsError::from) + .map_err(Error::from), + napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking, + ); + }) + .await; + Ok(()) + } else { + Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).map_err(NodejsError::from)?, + )) + } } -pub fn get_client(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let client_method_handler = - ClientMethodHandler::new_with_client(channel.clone(), method_handler.wallet.client().clone()); - deferred.settle_with(&channel, move |mut cx| { - Ok(cx.boxed(ClientMethodHandlerWrapper(Arc::new(RwLock::new(Some( - client_method_handler, - )))))) - }); - } else { - deferred.settle_with(&channel, move |mut cx| { - cx.error( - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())) - .expect("json to string error"), - ) - }); - } - }); - - Ok(promise) +#[napi(js_name = "getClient")] +pub async fn get_client(wallet: External) -> Result> { + if let Some(wallet) = &*wallet.as_ref().read().await { + Ok(External::new(Arc::new(RwLock::new(Some(wallet.client().clone()))))) + } else { + Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).map_err(NodejsError::from)?, + )) + } } -pub fn get_secret_manager(mut cx: FunctionContext) -> JsResult { - let method_handler = Arc::clone(&cx.argument::>(0)?.0); - let channel = cx.channel(); - - let (deferred, promise) = cx.promise(); - crate::RUNTIME.spawn(async move { - if let Some(method_handler) = &*method_handler.read().await { - let secret_manager_method_handler = SecretManagerMethodHandler::new_with_secret_manager( - channel.clone(), - method_handler.wallet.get_secret_manager().clone(), - ); - deferred.settle_with(&channel, move |mut cx| Ok(cx.boxed(secret_manager_method_handler))); - } else { - deferred.settle_with(&channel, move |mut cx| { - cx.error( - serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())) - .expect("json to string error"), - ) - }); - } - }); - - Ok(promise) +#[napi(js_name = "getSecretManager")] +pub async fn get_secret_manager(wallet: External) -> Result> { + if let Some(wallet) = &*wallet.as_ref().read().await { + Ok(External::new(wallet.get_secret_manager().clone())) + } else { + Err(Error::new( + Status::GenericFailure, + serde_json::to_string(&Response::Panic("Wallet got destroyed".to_string())).map_err(NodejsError::from)?, + )) + } } diff --git a/bindings/nodejs/tests/wallet/wallet.spec.ts b/bindings/nodejs/tests/wallet/wallet.spec.ts index 09e1d48784..427d374559 100644 --- a/bindings/nodejs/tests/wallet/wallet.spec.ts +++ b/bindings/nodejs/tests/wallet/wallet.spec.ts @@ -4,119 +4,93 @@ import 'reflect-metadata'; import { describe, it, expect } from '@jest/globals'; -import { Wallet, CoinType, WalletOptions } from '../../lib/'; +import { Wallet, CoinType, WalletOptions, SecretManager } from '../../lib/'; describe('Wallet', () => { - it('create account', async () => { - let storagePath = 'test-create-account'; + it('create wallet', async () => { + let storagePath = 'test-create-wallet'; removeDir(storagePath); - const walletOptions = { - storagePath: './test-create-account', - clientOptions: { - nodes: ['https://api.testnet.shimmer.network'], - }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: `./${storagePath}/wallet.stronghold`, - password: `A12345678*`, - }, + const strongholdSecretManager = { + stronghold: { + snapshotPath: `./${storagePath}/wallet.stronghold`, + password: `A12345678*`, }, }; - const wallet = new Wallet(walletOptions); - await wallet.storeMnemonic( - 'vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim', - ); - - const account = await wallet.createAccount({ - alias: 'Alice', - }); - - expect(account.getMetadata().index).toStrictEqual(0); + const secretManager = new SecretManager(strongholdSecretManager); - await wallet.destroy() - removeDir(storagePath) - }, 8000); + await secretManager.storeMnemonic('vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim',); - it('generate address', async () => { - let storagePath = 'test-generate-address'; - removeDir(storagePath); + const wallet_address = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'tst', + }); const walletOptions: WalletOptions = { - storagePath, + address: wallet_address[0], + storagePath: './test-create-wallet', clientOptions: { nodes: ['https://api.testnet.shimmer.network'], }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: `./${storagePath}/wallet.stronghold`, - password: `A12345678*`, - }, + bipPath: { + coinType: CoinType.IOTA, }, + secretManager: strongholdSecretManager, }; + const wallet = new Wallet(walletOptions); - await wallet.storeMnemonic( - 'vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim', - ); - - const address = await wallet.generateEd25519Address( - 0, - 0, - { internal: false, ledgerNanoPrompt: false }, - 'rms', - ); - - expect(address).toStrictEqual( - 'rms1qpqzgvcehafmlxh87zrf9w8ck8q2kw5070ztf68ylhzk89en9a4fy5jqrg8', - ); - - const anotherAddress = await wallet.generateEd25519Address( - 10, - 10, - { internal: true, ledgerNanoPrompt: false }, - 'tst', - ); - - expect(anotherAddress).toStrictEqual( - 'tst1qzp37j45rkfmqn05fapq66vyw0vkmz5zqhmeuey5fked0wt4ry43jeqp2wv', - ); await wallet.destroy() removeDir(storagePath) }, 8000); + it('recreate wallet', async () => { let storagePath = 'test-recreate-wallet'; removeDir(storagePath); - const walletOptions = { - storagePath, + const strongholdSecretManager = { + stronghold: { + snapshotPath: `./${storagePath}/wallet.stronghold`, + password: `A12345678*`, + }, + }; + + const secretManager = new SecretManager(strongholdSecretManager); + + await secretManager.storeMnemonic('vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim',); + + const wallet_address = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'tst', + }); + + const walletOptions: WalletOptions = { + address: wallet_address[0], + storagePath: './test-recreate-wallet', clientOptions: { nodes: ['https://api.testnet.shimmer.network'], }, - coinType: CoinType.Shimmer, - secretManager: { - stronghold: { - snapshotPath: `./${storagePath}/wallet.stronghold`, - password: `A12345678*`, - }, + bipPath: { + coinType: CoinType.IOTA, }, + secretManager: strongholdSecretManager, }; - const wallet = new Wallet(walletOptions); - await wallet.storeMnemonic( - 'vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim', - ); - const account = await wallet.createAccount({ - alias: 'Alice', - }); - - expect(account.getMetadata().index).toStrictEqual(0); + const wallet = new Wallet(walletOptions); const client = await wallet.getClient(); const hrp = await client.getBech32Hrp(); @@ -124,9 +98,7 @@ describe('Wallet', () => { await wallet.destroy(); - const recreatedWallet = new Wallet(walletOptions); - const accounts = await recreatedWallet.getAccounts(); - expect(accounts.length).toStrictEqual(1); + const recreatedWallet = new Wallet({ storagePath: './test-recreate-wallet' }); await recreatedWallet.destroy() removeDir(storagePath) diff --git a/bindings/nodejs/yarn.lock b/bindings/nodejs/yarn.lock index a9463d9696..ba918689f7 100644 --- a/bindings/nodejs/yarn.lock +++ b/bindings/nodejs/yarn.lock @@ -4,54 +4,54 @@ "@aashutoshrathi/word-wrap@^1.2.3": version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + resolved "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== "@ampproject/remapping@^2.2.0": version "2.2.1" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz" integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== dependencies: "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.13": version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz" integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: "@babel/highlight" "^7.22.13" chalk "^2.4.2" "@babel/compat-data@^7.22.9": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.2.tgz#6a12ced93455827037bfb5ed8492820d60fc32cc" - integrity sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ== + version "7.22.9" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== "@babel/core@^7.11.6", "@babel/core@^7.12.3": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" - integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ== + version "7.22.10" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz" + integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.23.0" - "@babel/helpers" "^7.23.2" - "@babel/parser" "^7.23.0" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" - convert-source-map "^2.0.0" + "@babel/code-frame" "^7.22.10" + "@babel/generator" "^7.22.10" + "@babel/helper-compilation-targets" "^7.22.10" + "@babel/helper-module-transforms" "^7.22.9" + "@babel/helpers" "^7.22.10" + "@babel/parser" "^7.22.10" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.10" + "@babel/types" "^7.22.10" + convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.3" + json5 "^2.2.2" semver "^6.3.1" -"@babel/generator@^7.23.0", "@babel/generator@^7.7.2": +"@babel/generator@^7.22.10", "@babel/generator@^7.23.0", "@babel/generator@^7.7.2": version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz" integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== dependencies: "@babel/types" "^7.23.0" @@ -59,25 +59,25 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== +"@babel/helper-compilation-targets@^7.22.10": + version "7.22.10" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz" + integrity sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q== dependencies: "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" + "@babel/helper-validator-option" "^7.22.5" browserslist "^4.21.9" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-environment-visitor@^7.22.20": +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== "@babel/helper-function-name@^7.23.0": version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz" integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== dependencies: "@babel/template" "^7.22.15" @@ -85,196 +85,196 @@ "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: "@babel/types" "^7.22.5" -"@babel/helper-module-imports@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" - integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== +"@babel/helper-module-imports@^7.22.5": + version "7.22.5" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz" + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== dependencies: - "@babel/types" "^7.22.15" + "@babel/types" "^7.22.5" -"@babel/helper-module-transforms@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e" - integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw== +"@babel/helper-module-transforms@^7.22.9": + version "7.22.9" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz" + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-validator-identifier" "^7.22.5" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-simple-access@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz" integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== dependencies: "@babel/types" "^7.22.5" "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz" integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: "@babel/types" "^7.22.5" "@babel/helper-string-parser@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.22.20": +"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-option@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" - integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== +"@babel/helper-validator-option@^7.22.5": + version "7.22.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz" + integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== -"@babel/helpers@^7.23.2": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767" - integrity sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ== +"@babel/helpers@^7.22.10": + version "7.22.10" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz" + integrity sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw== dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.2" - "@babel/types" "^7.23.0" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.10" + "@babel/types" "^7.22.10" "@babel/highlight@^7.22.13": version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz" integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== dependencies: "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.10", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz" integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz" integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz" integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/template@^7.22.15", "@babel/template@^7.3.3": +"@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz" integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== dependencies: "@babel/code-frame" "^7.22.13" "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.23.2": +"@babel/traverse@^7.22.10": version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz" integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== dependencies: "@babel/code-frame" "^7.22.13" @@ -288,9 +288,9 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.3": +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.3": version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz" integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== dependencies: "@babel/helper-string-parser" "^7.22.5" @@ -299,29 +299,29 @@ "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@discoveryjs/json-ext@^0.5.0": version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + version "4.6.2" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz" + integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== "@eslint/eslintrc@^2.1.2": version "2.1.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz" integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" @@ -334,33 +334,33 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c" - integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA== +"@eslint/js@^8.47.0": + version "8.47.0" + resolved "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz" + integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og== -"@humanwhocodes/config-array@^0.11.13": - version "0.11.13" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" - integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== dependencies: - "@humanwhocodes/object-schema" "^2.0.1" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" - integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -371,112 +371,112 @@ "@istanbuljs/schema@^0.1.2": version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== +"@jest/console@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/console/-/console-29.6.2.tgz" + integrity sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w== dependencies: - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" + jest-message-util "^29.6.2" + jest-util "^29.6.2" slash "^3.0.0" -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== +"@jest/core@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/core/-/core-29.6.2.tgz" + integrity sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg== dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/console" "^29.6.2" + "@jest/reporters" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" + jest-changed-files "^29.5.0" + jest-config "^29.6.2" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-resolve-dependencies "^29.6.2" + jest-runner "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + jest-watcher "^29.6.2" micromatch "^4.0.4" - pretty-format "^29.7.0" + pretty-format "^29.6.2" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== +"@jest/environment@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.6.2.tgz" + integrity sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q== dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.7.0" + jest-mock "^29.6.2" -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== +"@jest/expect-utils@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz" + integrity sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg== dependencies: - jest-get-type "^29.6.3" + jest-get-type "^29.4.3" -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== +"@jest/expect@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz" + integrity sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg== dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" + expect "^29.6.2" + jest-snapshot "^29.6.2" -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== +"@jest/fake-timers@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.2.tgz" + integrity sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA== dependencies: - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" + jest-util "^29.6.2" -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== +"@jest/globals@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.6.2.tgz" + integrity sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw== dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/types" "^29.6.1" + jest-mock "^29.6.2" -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== +"@jest/reporters@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.2.tgz" + integrity sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/console" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" @@ -485,81 +485,81 @@ glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" + istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" + jest-message-util "^29.6.2" + jest-util "^29.6.2" + jest-worker "^29.6.2" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== +"@jest/schemas@^29.6.0": + version "29.6.0" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz" + integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== dependencies: "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== +"@jest/source-map@^29.6.0": + version "29.6.0" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz" + integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== dependencies: "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== +"@jest/test-result@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.2.tgz" + integrity sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw== dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/console" "^29.6.2" + "@jest/types" "^29.6.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== +"@jest/test-sequencer@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz" + integrity sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw== dependencies: - "@jest/test-result" "^29.7.0" + "@jest/test-result" "^29.6.2" graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" + jest-haste-map "^29.6.2" slash "^3.0.0" -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== +"@jest/transform@^29.6.2": + version "29.6.2" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz" + integrity sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" + jest-haste-map "^29.6.2" + jest-regex-util "^29.4.3" + jest-util "^29.6.2" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== +"@jest/types@^29.6.1": + version "29.6.1" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz" + integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== dependencies: - "@jest/schemas" "^29.6.3" + "@jest/schemas" "^29.6.0" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -568,7 +568,7 @@ "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz" integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: "@jridgewell/set-array" "^1.0.1" @@ -577,17 +577,17 @@ "@jridgewell/resolve-uri@^3.1.0": version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== "@jridgewell/set-array@^1.0.1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/source-map@^0.3.3": version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz" integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== dependencies: "@jridgewell/gen-mapping" "^0.3.0" @@ -595,20 +595,27 @@ "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== + version "0.3.19" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@napi-rs/cli@^1.0.0": + version "1.3.5" + resolved "https://registry.npmjs.org/@napi-rs/cli/-/cli-1.3.5.tgz" + integrity sha512-Z0KZIciemioYODTyO908v2AtL8Zg4sohQDD+dyHeHmOiOfaez/y/xQ8XnpOHc2W5fRidKUW+MVWyTtpLTbKsqw== + dependencies: + inquirer "^8.1.3" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -616,12 +623,12 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -629,27 +636,27 @@ "@sinclair/typebox@^0.27.8": version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@sinonjs/commons@^3.0.0": version "3.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz" integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz" integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: "@sinonjs/commons" "^3.0.0" "@types/babel__core@^7.1.14": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.3.tgz#d5625a50b6f18244425a1359a858c73d70340778" - integrity sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA== + version "7.20.1" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz" + integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== dependencies: "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" @@ -658,126 +665,117 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.6" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.6.tgz#676f89f67dc8ddaae923f70ebc5f1fa800c031a8" - integrity sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w== + version "7.6.4" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.3.tgz#db9ac539a2fe05cfe9e168b24f360701bde41f5f" - integrity sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ== + version "7.4.1" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.3.tgz#a971aa47441b28ef17884ff945d0551265a2d058" - integrity sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw== + version "7.20.1" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz" + integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== dependencies: "@babel/types" "^7.20.7" "@types/eslint-scope@^3.7.3": - version "3.7.6" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.6.tgz#585578b368ed170e67de8aae7b93f54a1b2fdc26" - integrity sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ== + version "3.7.5" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz" + integrity sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.44.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.6.tgz#60e564551966dd255f4c01c459f0b4fb87068603" - integrity sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw== + version "8.44.3" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz" + integrity sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g== dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*", "@types/estree@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.3.tgz#2be19e759a3dd18c79f9f436bd7363556c1a73dd" - integrity sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ== + version "1.0.2" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz" + integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA== "@types/graceful-fs@^4.1.3": - version "4.1.8" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.8.tgz#417e461e4dc79d957dc3107f45fe4973b09c2915" - integrity sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw== + version "4.1.6" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#fdfdd69fa16d530047d9963635bd77c71a08c068" - integrity sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ== + version "2.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.2.tgz#394798d5f727402eb5ec99eb9618ffcd2b7645a1" - integrity sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w== + version "3.0.0" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.3.tgz#0313e2608e6d6955d195f55361ddeebd4b74c6e7" - integrity sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg== + version "3.0.1" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^29.4.0": - version "29.5.6" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.6.tgz#f4cf7ef1b5b0bfc1aa744e41b24d9cc52533130b" - integrity sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w== + version "29.5.3" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz" + integrity sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA== dependencies: expect "^29.0.0" pretty-format "^29.0.0" "@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.14" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.14.tgz#74a97a5573980802f32c8e47b663530ab3b6b7d1" - integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw== + version "7.0.12" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== -"@types/node@*": - version "20.8.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.9.tgz#646390b4fab269abce59c308fc286dcd818a2b08" - integrity sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg== - dependencies: - undici-types "~5.26.4" - -"@types/node@^18.15.12": - version "18.18.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.7.tgz#bb3a7068dc4ba421b6968f2a259298b3a4e129e8" - integrity sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ== - dependencies: - undici-types "~5.26.4" +"@types/node@*", "@types/node@^18.15.12": + version "18.17.5" + resolved "https://registry.npmjs.org/@types/node/-/node-18.17.5.tgz" + integrity sha512-xNbS75FxH6P4UXTPUJp/zNPq6/xsfdJKussCWNOnz4aULWIRwMgP1LgaB5RiBnMX1DPCYenuqGZfnIAx5mbFLA== "@types/semver@^7.3.12": - version "7.5.4" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff" - integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ== + version "7.5.0" + resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== "@types/stack-utils@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.2.tgz#01284dde9ef4e6d8cef6422798d9a3ad18a66f8b" - integrity sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw== + version "2.0.1" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/yargs-parser@*": - version "21.0.2" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.2.tgz#7bd04c5da378496ef1695a1008bf8f71847a8b8b" - integrity sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw== + version "21.0.0" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.8": - version "17.0.29" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.29.tgz#06aabc72497b798c643c812a8b561537fea760cf" - integrity sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA== + version "17.0.24" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.30.7": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz" integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== dependencies: "@eslint-community/regexpp" "^4.4.0" @@ -793,7 +791,7 @@ "@typescript-eslint/parser@^5.30.7": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz" integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== dependencies: "@typescript-eslint/scope-manager" "5.62.0" @@ -803,7 +801,7 @@ "@typescript-eslint/scope-manager@5.62.0": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz" integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== dependencies: "@typescript-eslint/types" "5.62.0" @@ -811,7 +809,7 @@ "@typescript-eslint/type-utils@5.62.0": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz" integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== dependencies: "@typescript-eslint/typescript-estree" "5.62.0" @@ -821,12 +819,12 @@ "@typescript-eslint/types@5.62.0": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz" integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== dependencies: "@typescript-eslint/types" "5.62.0" @@ -839,7 +837,7 @@ "@typescript-eslint/utils@5.62.0": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz" integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" @@ -853,20 +851,15 @@ "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz" integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== dependencies: "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz" integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== dependencies: "@webassemblyjs/helper-numbers" "1.11.6" @@ -874,22 +867,22 @@ "@webassemblyjs/floating-point-hex-parser@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz" integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== "@webassemblyjs/helper-api-error@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz" integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== "@webassemblyjs/helper-buffer@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz" integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== "@webassemblyjs/helper-numbers@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz" integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== dependencies: "@webassemblyjs/floating-point-hex-parser" "1.11.6" @@ -898,12 +891,12 @@ "@webassemblyjs/helper-wasm-bytecode@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz" integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== "@webassemblyjs/helper-wasm-section@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz" integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== dependencies: "@webassemblyjs/ast" "1.11.6" @@ -913,26 +906,26 @@ "@webassemblyjs/ieee754@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz" integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== dependencies: "@xtuc/ieee754" "^1.2.0" "@webassemblyjs/leb128@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz" integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== dependencies: "@xtuc/long" "4.2.2" "@webassemblyjs/utf8@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz" integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== "@webassemblyjs/wasm-edit@^1.11.5": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz" integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== dependencies: "@webassemblyjs/ast" "1.11.6" @@ -946,7 +939,7 @@ "@webassemblyjs/wasm-gen@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz" integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== dependencies: "@webassemblyjs/ast" "1.11.6" @@ -957,7 +950,7 @@ "@webassemblyjs/wasm-opt@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz" integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== dependencies: "@webassemblyjs/ast" "1.11.6" @@ -967,7 +960,7 @@ "@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz" integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== dependencies: "@webassemblyjs/ast" "1.11.6" @@ -979,7 +972,7 @@ "@webassemblyjs/wast-printer@1.11.6": version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz" integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== dependencies: "@webassemblyjs/ast" "1.11.6" @@ -987,62 +980,62 @@ "@webpack-cli/configtest@^2.1.1": version "2.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" + resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz" integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== "@webpack-cli/info@^2.0.2": version "2.0.2" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" + resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz" integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== "@webpack-cli/serve@^2.0.5": version "2.0.5" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" + resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz" integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== "@xtuc/long@4.2.2": version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== abbrev@1: version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== acorn-import-assertions@^1.9.0: version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== after@~0.8.1: version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + resolved "https://registry.npmjs.org/after/-/after-0.8.2.tgz" integrity sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA== ajv-keywords@^3.5.2: version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -1052,58 +1045,58 @@ ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: amdefine@>=0.0.4: version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + resolved "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg== ansi-escapes@^4.2.1: version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-sequence-parser@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf" + resolved "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz" integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== ansi@^0.3.0, ansi@~0.3.0, ansi@~0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21" + resolved "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz" integrity sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A== anymatch@^3.0.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -1111,12 +1104,12 @@ anymatch@^3.0.3: aproba@^1.0.3: version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== are-we-there-yet@~1.0.0: version "1.0.6" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.0.6.tgz#a2d28c93102aa6cc96245a26cb954de06ec53f0c" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.6.tgz" integrity sha512-Zfw6bteqM9gQXZ1BIWOgM8xEwMrUGoyL8nW13+O+OOgNX3YhuDN1GDgg1NzdTlmm3j+9sHy7uBZ12r+z9lXnZQ== dependencies: delegates "^1.0.0" @@ -1124,7 +1117,7 @@ are-we-there-yet@~1.0.0: are-we-there-yet@~1.1.2: version "1.1.7" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz" integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== dependencies: delegates "^1.0.0" @@ -1132,19 +1125,19 @@ are-we-there-yet@~1.1.2: argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== array-index@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9" + resolved "https://registry.npmjs.org/array-index/-/array-index-1.0.0.tgz" integrity sha512-jesyNbBkLQgGZMSwA1FanaFjalb1mZUGxGeUEkSDidzgrbjBGhvizJkaItdhkt8eIHFOJC7nDsrXk+BaehTdRw== dependencies: debug "^2.2.0" @@ -1152,52 +1145,52 @@ array-index@^1.0.0: array-union@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== asn1@~0.2.3: version "0.2.6" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== aws-sign2@~0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== aws4@^1.8.0: version "1.12.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + resolved "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz" integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== +babel-jest@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz" + integrity sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A== dependencies: - "@jest/transform" "^29.7.0" + "@jest/transform" "^29.6.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" + babel-preset-jest "^29.5.0" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" babel-plugin-istanbul@^6.1.1: version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -1206,10 +1199,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== +babel-plugin-jest-hoist@^29.5.0: + version "29.5.0" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz" + integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -1218,7 +1211,7 @@ babel-plugin-jest-hoist@^29.6.3: babel-preset-current-node-syntax@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -1234,47 +1227,47 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== +babel-preset-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz" + integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== dependencies: - babel-plugin-jest-hoist "^29.6.3" + babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bcrypt-pbkdf@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== dependencies: tweetnacl "^0.14.3" big-integer@^1.6.17: version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== binary@~0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + resolved "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz" integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg== dependencies: buffers "~0.1.1" chainsaw "~0.1.0" -bl@^4.0.3: +bl@^4.0.3, bl@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" @@ -1283,24 +1276,24 @@ bl@^4.0.3: bl@~3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f" + resolved "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz" integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ== dependencies: readable-stream "^3.0.1" bluebird@^3: version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bluebird@~3.4.1: version "3.4.7" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz" integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -1308,65 +1301,65 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" braces@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" browserslist@^4.14.5, browserslist@^4.21.9: - version "4.22.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== + version "4.21.10" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz" + integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" + caniuse-lite "^1.0.30001517" + electron-to-chromium "^1.4.477" node-releases "^2.0.13" - update-browserslist-db "^1.0.13" + update-browserslist-db "^1.0.11" bs-logger@0.x: version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" bser@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" buffer-from@^0.1.1: version "0.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz" integrity sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg== buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-indexof-polyfill@~1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + resolved "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz" integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== buffer-shims@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + resolved "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" integrity sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g== buffer@^5.5.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -1374,63 +1367,58 @@ buffer@^5.5.0: buffers@~0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + resolved "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz" integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^2.0.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz" integrity sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw== camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001541: - version "1.0.30001554" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz#ba80d88dff9acbc0cd4b7535fc30e0191c5e2e2a" - integrity sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ== - -cargo-cp-artifact@^0.1.6: - version "0.1.8" - resolved "https://registry.yarnpkg.com/cargo-cp-artifact/-/cargo-cp-artifact-0.1.8.tgz#353814f49f6aa76601a4bcb3ea5f3071180b90de" - integrity sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA== +caniuse-lite@^1.0.30001517: + version "1.0.30001521" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001521.tgz" + integrity sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ== caseless@~0.12.0: version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== chainsaw@~0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + resolved "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz" integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ== dependencies: traverse ">=0.3.0 <0.4" chalk@^2.4.2: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -1438,37 +1426,59 @@ chalk@^4.0.0: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + chownr@^1.1.1, chownr@^1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== chrome-trace-event@^1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + version "3.8.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: version "1.2.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== class-transformer@^0.5.1: version "0.5.1" - resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" + resolved "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz" integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.9.1" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz" + integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + cliui@^3.0.3: version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + resolved "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz" integrity sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w== dependencies: string-width "^1.0.1" @@ -1477,7 +1487,7 @@ cliui@^3.0.3: cliui@^8.0.1: version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" @@ -1486,16 +1496,21 @@ cliui@^8.0.1: clone-deep@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: is-plain-object "^2.0.4" kind-of "^6.0.2" shallow-clone "^3.0.0" +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + cmake-js@~5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/cmake-js/-/cmake-js-5.2.0.tgz#6d72014269a5d23a754a6d170cde9ed2d75eb411" + resolved "https://registry.npmjs.org/cmake-js/-/cmake-js-5.2.0.tgz" integrity sha512-/HLhzoBEOLKGdE1FLwH5ggzRt67AWTb4IErg4rm+bTC+R0DKUobojDyp17dSswDVPosdoPmHXjKxbJiyBZfQeg== dependencies: bluebird "^3" @@ -1518,113 +1533,100 @@ cmake-js@~5.2.0: co@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== code-point-at@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== collect-v8-coverage@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colorette@^2.0.14: version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" commander@2.9.x: version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" integrity sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A== dependencies: graceful-readlink ">= 1.0.0" commander@^10.0.1: version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== commander@^2.20.0, commander@^2.9.0: version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== +convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -core-util-is@1.0.2: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -1633,7 +1635,7 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: d@1, d@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== dependencies: es5-ext "^0.10.50" @@ -1641,28 +1643,28 @@ d@1, d@^1.0.1: dashdash@^1.12.0: version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== dependencies: assert-plus "^1.0.0" debug@^2.2.0: version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" decamelize@^1.1.1: version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decompress-response@^3.3.0: @@ -1674,90 +1676,97 @@ decompress-response@^3.3.0: dedent@^1.0.0: version "1.5.1" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + resolved "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz" integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== deep-extend@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== delegates@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== detect-libc@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz" integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== detect-newline@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" dotenv@^16.0.3: version "16.3.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== duplexer2@~0.0.2: version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" + resolved "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz" integrity sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g== dependencies: readable-stream "~1.1.9" duplexer2@~0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + resolved "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz" integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== dependencies: readable-stream "^2.0.2" each-series-async@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/each-series-async/-/each-series-async-1.0.1.tgz#7e3f8dfa5af934663960e5a17561362909b34328" + resolved "https://registry.npmjs.org/each-series-async/-/each-series-async-1.0.1.tgz" integrity sha512-G4zip/Ewpwr6JQxW7+2RNgkPd09h/UNec5UlvA/xKwl4qf5blyBNK6a/zjQc3MojgsxaOb93B9v3T92QU6IMVg== ecc-jsbn@~0.1.1: version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== dependencies: jsbn "~0.1.0" @@ -1765,37 +1774,37 @@ ecc-jsbn@~0.1.1: electron-build-env@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/electron-build-env/-/electron-build-env-0.2.0.tgz#5649ee3e5fd006e267086caa945b77a1fa220b92" + resolved "https://registry.npmjs.org/electron-build-env/-/electron-build-env-0.2.0.tgz" integrity sha512-L431TbXtXe6iw3ko7ITr/qCu+jumVKLAhCDyhqfab6421LGlawVcT88Ws/DHR57+1lkLN1POQqwNOkjPwQJQmQ== dependencies: commander "^2.9.0" mkdirp "^0.5.1" -electron-to-chromium@^1.4.535: - version "1.4.566" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.566.tgz#5c5ba1d2dc895f4887043f0cc7e61798c7e5919a" - integrity sha512-mv+fAy27uOmTVlUULy15U3DVJ+jg+8iyKH1bpwboCRhtDC69GKf1PPTZvEIhCyDr81RFqfxZJYrbgp933a1vtg== +electron-to-chromium@^1.4.477: + version "1.4.492" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.492.tgz" + integrity sha512-36K9b/6skMVwAIEsC7GiQ8I8N3soCALVSHqWHzNDtGemAcI9Xu8hP02cywWM0A794rTHm0b0zHPeLJHtgFVamQ== emittery@^0.13.1: version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" enhanced-resolve@^5.15.0: version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz" integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: graceful-fs "^4.2.4" @@ -1803,29 +1812,29 @@ enhanced-resolve@^5.15.0: env-paths@^2.2.0: version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== envinfo@^7.7.3: version "7.10.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz" integrity sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es-module-lexer@^1.2.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz" integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== es5-ext@^0.10.35, es5-ext@^0.10.50: version "0.10.62" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz" integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== dependencies: es6-iterator "^2.0.3" @@ -1834,7 +1843,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.50: es6-iterator@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== dependencies: d "1" @@ -1843,7 +1852,7 @@ es6-iterator@^2.0.3: es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== dependencies: d "^1.0.1" @@ -1851,32 +1860,32 @@ es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@^3.1.3: escalade@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^8.5.0: version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz" integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" @@ -1884,7 +1893,7 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: eslint-scope@^7.2.2: version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" @@ -1892,22 +1901,21 @@ eslint-scope@^7.2.2: eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.20.0: - version "8.52.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.52.0.tgz#d0cd4a1fac06427a61ef9242b9353f36ea7062fc" - integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg== + version "8.47.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz" + integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.52.0" - "@humanwhocodes/config-array" "^0.11.13" + "@eslint/js" "^8.47.0" + "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -1941,7 +1949,7 @@ eslint@^8.20.0: espree@^9.6.0, espree@^9.6.1: version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: acorn "^8.9.0" @@ -1950,46 +1958,46 @@ espree@^9.6.0, espree@^9.6.1: esprima@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1: version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== events@^3.2.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== execa@^5.0.0: version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -2004,62 +2012,67 @@ execa@^5.0.0: execspawn@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/execspawn/-/execspawn-1.0.1.tgz#8286f9dde7cecde7905fbdc04e24f368f23f8da6" + resolved "https://registry.npmjs.org/execspawn/-/execspawn-1.0.1.tgz" integrity sha512-s2k06Jy9i8CUkYe0+DxRlvtkZoOkwwfhB+Xxo5HGUtrISVW2m98jO2tr67DGRFxZwkjQqloA3v/tNtjhBRBieg== dependencies: util-extend "^1.0.1" exit@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expand-template@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -expect@^29.0.0, expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== +expect@^29.0.0, expect@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz" + integrity sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA== dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" + "@jest/expect-utils" "^29.6.2" + "@types/node" "*" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" ext@^1.1.2: version "1.7.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== dependencies: type "^2.7.2" extend@~3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extsprintf@1.3.0: +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.2.9: version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -2070,50 +2083,57 @@ fast-glob@^3.2.9: fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastest-levenshtein@^1.0.12: version "1.0.16" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz" integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" fb-watchman@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -2121,39 +2141,33 @@ find-up@^4.0.0, find-up@^4.1.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.1.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.1.tgz#a02a15fdec25a8f844ff7cc658f03dd99eb4609b" - integrity sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q== + version "3.0.4" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^3.2.9" - keyv "^4.5.3" + flatted "^3.1.0" rimraf "^3.0.2" -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.9: - version "3.2.9" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" - integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== forever-agent@~0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== form-data@~2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== dependencies: asynckit "^0.4.0" @@ -2162,12 +2176,12 @@ form-data@~2.3.2: fs-constants@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== fs-extra@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz" integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== dependencies: graceful-fs "^4.1.2" @@ -2176,14 +2190,14 @@ fs-extra@^5.0.0: fs-minipass@^1.2.7: version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== dependencies: minipass "^2.6.0" fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^2.3.2: @@ -2193,7 +2207,7 @@ fsevents@^2.3.2: fstream@^1.0.0, fstream@~1.0.10: version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz" integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== dependencies: graceful-fs "^4.1.2" @@ -2201,14 +2215,14 @@ fstream@^1.0.0, fstream@~1.0.10: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== gauge@~1.2.0, gauge@~1.2.5: version "1.2.7" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93" + resolved "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz" integrity sha512-fVbU2wRE91yDvKUnrIaQlHKAWKY5e08PmztCrwuH5YVQ+Z/p3d0ny2T48o6uvAAXHIUnfaQdHkmxYbQft1eHVA== dependencies: ansi "^0.3.0" @@ -2219,7 +2233,7 @@ gauge@~1.2.0, gauge@~1.2.5: gauge@~2.7.3: version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + resolved "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz" integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg== dependencies: aproba "^1.0.3" @@ -2233,34 +2247,34 @@ gauge@~2.7.3: gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== getpass@^0.1.1: version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== dependencies: assert-plus "^1.0.0" ghreleases@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/ghreleases/-/ghreleases-3.0.2.tgz#1bdb6d31ec03a24a0d80f58f5e9a84a4db725818" + resolved "https://registry.npmjs.org/ghreleases/-/ghreleases-3.0.2.tgz" integrity sha512-QiR9mIYvRG7hd8JuQYoxeBNOelVuTp2DpdiByRywbCDBSJufK9Vq7VuhD8B+5uviMxZx2AEkCzye61Us9gYgnw== dependencies: after "~0.8.1" @@ -2272,14 +2286,14 @@ ghreleases@^3.0.2: ghrepos@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/ghrepos/-/ghrepos-2.1.0.tgz#abaf558b690b722c70c7ad45076f6f9be8e495e1" + resolved "https://registry.npmjs.org/ghrepos/-/ghrepos-2.1.0.tgz" integrity sha512-6GM0ohSDTAv7xD6GsKfxJiV/CajoofRyUwu0E8l29d1o6lFAUxmmyMP/FH33afA20ZrXzxxcTtN6TsYvudMoAg== dependencies: ghutils "~3.2.0" ghutils@~3.2.0: version "3.2.6" - resolved "https://registry.yarnpkg.com/ghutils/-/ghutils-3.2.6.tgz#d43986e267da02787464d97a6489659e4609bb1f" + resolved "https://registry.npmjs.org/ghutils/-/ghutils-3.2.6.tgz" integrity sha512-WpYHgLQkqU7Cv147wKUEThyj6qKHCdnAG2CL9RRsRQImVdLGdVqblJ3JUnj3ToQwgm1ALPS+FXgR0448AgGPUg== dependencies: jsonist "~2.1.0" @@ -2287,31 +2301,31 @@ ghutils@~3.2.0: github-from-package@0.0.0: version "0.0.0" - resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== glob-parent@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" glob-to-regexp@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== "glob@3 || 4 || 5 || 6 || 7", glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -2323,7 +2337,7 @@ glob-to-regexp@^0.4.1: glob@5.0.x: version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + resolved "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" integrity sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA== dependencies: inflight "^1.0.4" @@ -2334,19 +2348,19 @@ glob@5.0.x: globals@^11.1.0: version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.23.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" - integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + version "13.21.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz" + integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== dependencies: type-fest "^0.20.2" globby@^11.1.0: version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" @@ -2358,22 +2372,22 @@ globby@^11.1.0: graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== "graceful-readlink@>= 1.0.0": version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w== graphemer@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== handlebars@^4.7.7: version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz" integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== dependencies: minimist "^1.2.5" @@ -2385,12 +2399,12 @@ handlebars@^4.7.7: har-schema@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== har-validator@~5.1.3: version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz" integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: ajv "^6.12.3" @@ -2398,34 +2412,34 @@ har-validator@~5.1.3: has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-unicode@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: - function-bind "^1.1.2" + function-bind "^1.1.1" html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== http-signature@~1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== dependencies: assert-plus "^1.0.0" @@ -2434,31 +2448,38 @@ http-signature@~1.2.0: human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== hyperquest@~2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/hyperquest/-/hyperquest-2.1.3.tgz#523127d7a343181b40bf324e231d2576edf52633" + resolved "https://registry.npmjs.org/hyperquest/-/hyperquest-2.1.3.tgz" integrity sha512-fUuDOrB47PqNK/BAMOS13v41UoaqIxqSLHX6CAbOD7OfT+/GCWO1/vPLfTNutOeXrv1ikuaZ3yux+33Z9vh+rw== dependencies: buffer-from "^0.1.1" duplexer2 "~0.0.2" through2 "~0.6.3" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@^1.1.13: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^5.2.0: version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -2466,7 +2487,7 @@ import-fresh@^3.2.1: import-local@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" @@ -2474,12 +2495,12 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" @@ -2487,130 +2508,161 @@ inflight@^1.0.4: inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@~1.3.0: version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +inquirer@^8.1.3: + version "8.2.6" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz" + integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^6.0.1" + interpret@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== invert-kv@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz" integrity sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ== is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + version "2.13.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: - hasown "^2.0.0" + has "^1.0.3" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-iojs@^1.0.1: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-iojs/-/is-iojs-1.1.0.tgz#4c11033b5d5d94d6eab3775dedc9be7d008325f1" + resolved "https://registry.npmjs.org/is-iojs/-/is-iojs-1.1.0.tgz" integrity sha512-tLn1j3wYSL6DkvEI+V/j0pKohpa5jk+ER74v6S4SgCXnjS0WA+DoZbwZBrrhgwksMvtuwndyGeG5F8YMsoBzSA== is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-path-inside@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-object@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-stream@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-typedarray@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + isarray@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isobject@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== isstream@~0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^5.0.4: +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" @@ -2619,20 +2671,9 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" -istanbul-lib-instrument@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf" - integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - istanbul-lib-report@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz" integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" @@ -2641,7 +2682,7 @@ istanbul-lib-report@^3.0.0: istanbul-lib-source-maps@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" @@ -2650,387 +2691,387 @@ istanbul-lib-source-maps@^4.0.0: istanbul-reports@^3.1.3: version "3.1.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz" integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== +jest-changed-files@^29.5.0: + version "29.5.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz" + integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== dependencies: execa "^5.0.0" - jest-util "^29.7.0" p-limit "^3.1.0" -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== +jest-circus@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.2.tgz" + integrity sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw== dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" + jest-each "^29.6.2" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" p-limit "^3.1.0" - pretty-format "^29.7.0" + pretty-format "^29.6.2" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== +jest-cli@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.2.tgz" + integrity sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q== dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/core" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" chalk "^4.0.0" - create-jest "^29.7.0" exit "^0.1.2" + graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" + jest-config "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== +jest-config@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.6.2.tgz" + integrity sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" + "@jest/test-sequencer" "^29.6.2" + "@jest/types" "^29.6.1" + babel-jest "^29.6.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" + jest-circus "^29.6.2" + jest-environment-node "^29.6.2" + jest-get-type "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-runner "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.7.0" + pretty-format "^29.6.2" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== +jest-diff@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz" + integrity sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA== dependencies: chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.6.2" -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== +jest-docblock@^29.4.3: + version "29.4.3" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz" + integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== dependencies: detect-newline "^3.0.0" -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== +jest-each@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.6.2.tgz" + integrity sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw== dependencies: - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" + jest-get-type "^29.4.3" + jest-util "^29.6.2" + pretty-format "^29.6.2" + +jest-environment-node@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.2.tgz" + integrity sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ== + dependencies: + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" + jest-mock "^29.6.2" + jest-util "^29.6.2" -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== +jest-haste-map@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz" + integrity sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA== dependencies: - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" + jest-regex-util "^29.4.3" + jest-util "^29.6.2" + jest-worker "^29.6.2" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== +jest-leak-detector@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz" + integrity sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ== dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" + jest-get-type "^29.4.3" + pretty-format "^29.6.2" -jest-matcher-utils@^29.5.0, jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== +jest-matcher-utils@^29.5.0, jest-matcher-utils@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz" + integrity sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ== dependencies: chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" + jest-diff "^29.6.2" + jest-get-type "^29.4.3" + pretty-format "^29.6.2" -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== +jest-message-util@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz" + integrity sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.7.0" + pretty-format "^29.6.2" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== +jest-mock@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.2.tgz" + integrity sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg== dependencies: - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-util "^29.7.0" + jest-util "^29.6.2" jest-pnp-resolver@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== +jest-regex-util@^29.4.3: + version "29.4.3" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz" + integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== +jest-resolve-dependencies@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz" + integrity sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w== dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" + jest-regex-util "^29.4.3" + jest-snapshot "^29.6.2" -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== +jest-resolve@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.2.tgz" + integrity sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" + jest-haste-map "^29.6.2" jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" + jest-util "^29.6.2" + jest-validate "^29.6.2" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== +jest-runner@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.2.tgz" + integrity sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w== dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/console" "^29.6.2" + "@jest/environment" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" + jest-docblock "^29.4.3" + jest-environment-node "^29.6.2" + jest-haste-map "^29.6.2" + jest-leak-detector "^29.6.2" + jest-message-util "^29.6.2" + jest-resolve "^29.6.2" + jest-runtime "^29.6.2" + jest-util "^29.6.2" + jest-watcher "^29.6.2" + jest-worker "^29.6.2" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" +jest-runtime@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.2.tgz" + integrity sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg== + dependencies: + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/globals" "^29.6.2" + "@jest/source-map" "^29.6.0" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== +jest-snapshot@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz" + integrity sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/expect-utils" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.7.0" + expect "^29.6.2" graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" + jest-diff "^29.6.2" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" natural-compare "^1.4.0" - pretty-format "^29.7.0" + pretty-format "^29.6.2" semver "^7.5.3" -jest-util@^29.0.0, jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== +jest-util@^29.0.0, jest-util@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz" + integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== dependencies: - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== +jest-validate@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.2.tgz" + integrity sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg== dependencies: - "@jest/types" "^29.6.3" + "@jest/types" "^29.6.1" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.6.3" + jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.7.0" + pretty-format "^29.6.2" -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== +jest-watcher@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.2.tgz" + integrity sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA== dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.7.0" + jest-util "^29.6.2" string-length "^4.0.1" jest-worker@^27.4.5: version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== +jest-worker@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz" + integrity sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ== dependencies: "@types/node" "*" - jest-util "^29.7.0" + jest-util "^29.6.2" merge-stream "^2.0.0" supports-color "^8.0.0" jest@^29.4.2: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + version "29.6.2" + resolved "https://registry.npmjs.org/jest/-/jest-29.6.2.tgz" + integrity sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg== dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/core" "^29.6.2" + "@jest/types" "^29.6.1" import-local "^3.0.2" - jest-cli "^29.7.0" + jest-cli "^29.6.2" js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -3038,71 +3079,66 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" jsbn@~0.1.0: version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-schema@0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stringify-safe@~5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@^2.2.3: +json5@^2.2.2, json5@^2.2.3: version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonc-parser@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz" integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== jsonfile@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" jsonist@~2.1.0: version "2.1.2" - resolved "https://registry.yarnpkg.com/jsonist/-/jsonist-2.1.2.tgz#c1377311e8fc857abe7aa3df197116a911f95324" + resolved "https://registry.npmjs.org/jsonist/-/jsonist-2.1.2.tgz" integrity sha512-8yqmWJAC2VaYoSKQAbsfgCpGY5o/1etWzx6ZxaZrC4iGaHrHUZEo+a2MyF8w+2uTavTlHdLWaZUoR19UfBstxQ== dependencies: bl "~3.0.0" @@ -3112,7 +3148,7 @@ jsonist@~2.1.0: jsprim@^1.2.2: version "1.4.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz" integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" @@ -3120,38 +3156,31 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - kind-of@^6.0.2: version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== kleur@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== lcid@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + resolved "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" integrity sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw== dependencies: invert-kv "^1.0.0" leven@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -3159,131 +3188,139 @@ levn@^0.4.1: lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== listenercount@~1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + resolved "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz" integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== loader-runner@^4.2.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash.memoize@4.x: version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.pad@^4.1.0: version "4.5.1" - resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70" + resolved "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz" integrity sha512-mvUHifnLqM+03YNzeTBS1/Gr6JRFjd3rRx88FHWUvamVaT9k2O/kXha3yBSOwB9/DTQrSTLJNHvLBBt2FdX7Mg== lodash.padend@^4.1.0: version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" + resolved "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz" integrity sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw== lodash.padstart@^4.1.0: version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b" + resolved "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz" integrity sha512-sW73O6S8+Tg66eY56DBk85aQzzUJDtpoXFBgELMd5P/SotAguo+1kYO6RuYgXxA4HJH3LFTFPASX6ET6bjfriw== lodash.uniq@^4.5.0: version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@^4: +lodash@^4, lodash@^4.17.21: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" lunr@^2.3.9: version "2.3.9" - resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== make-dir@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz" integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== dependencies: semver "^7.5.3" make-error@1.x: version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.12: version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" marked@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== memory-stream@0: version "0.0.3" - resolved "https://registry.yarnpkg.com/memory-stream/-/memory-stream-0.0.3.tgz#ebe8dd1c3b8bc38c0e7941e9ddd5aebe6b4de83f" + resolved "https://registry.npmjs.org/memory-stream/-/memory-stream-0.0.3.tgz" integrity sha512-q0D3m846qY6ZkIt+19ZemU5vH56lpOZZwoJc3AICARKh/menBuayQUjAGPrqtHQQMUYERSdOrej92J9kz7LgYA== dependencies: readable-stream "~1.0.26-2" merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: braces "^3.0.2" @@ -3291,19 +3328,19 @@ micromatch@^4.0.4: mime-db@1.52.0: version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19: version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== mimic-response@^1.0.0: @@ -3313,26 +3350,26 @@ mimic-response@^1.0.0: "minimatch@2 || 3", minimatch@3, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^9.0.0: version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" minimist@^1.1.2, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + resolved "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== dependencies: safe-buffer "^5.1.2" @@ -3340,68 +3377,73 @@ minipass@^2.6.0, minipass@^2.9.0: minizlib@^1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: minipass "^2.9.0" mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: minimist "^1.2.6" ms@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + napi-build-utils@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== natural-compare-lite@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + resolved "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz" integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== neo-async@^2.6.2: version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== next-tick@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== node-abi@^3.0.0, node-abi@^3.3.0: version "3.51.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.51.0.tgz#970bf595ef5a26a271307f8a4befa02823d4e87d" + resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz" integrity sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA== dependencies: semver "^7.3.5" node-gyp@^6.0.1: version "6.1.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-6.1.0.tgz#64e31c61a4695ad304c1d5b82cf6b7c79cc79f3f" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-6.1.0.tgz" integrity sha512-h4A2zDlOujeeaaTx06r4Vy+8MZ1679lU+wbCKDS4ZtvY2A37DESo37oejIw0mtmR3+rvNwts5B6Kpt1KrNYdNw== dependencies: env-paths "^2.2.0" @@ -3418,12 +3460,12 @@ node-gyp@^6.0.1: node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-ninja@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/node-ninja/-/node-ninja-1.0.2.tgz#20a09e57b92e2df591993d4bf098ac3e727062b6" + resolved "https://registry.npmjs.org/node-ninja/-/node-ninja-1.0.2.tgz" integrity sha512-wMtWsG2QZI1Z5V7GciX9OI2DVT0PuDRIDQfe3L3rJsQ1qN1Gm3QQhoNtb4PMRi7gq4ByvEIYtPwHC7YbEf5yxw== dependencies: fstream "^1.0.0" @@ -3443,24 +3485,24 @@ node-ninja@^1.0.1: node-releases@^2.0.13: version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== noop-logger@^0.1.0: version "0.1.1" - resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + resolved "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz" integrity sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ== "nopt@2 || 3": version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + resolved "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg== dependencies: abbrev "1" nopt@^4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz" integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== dependencies: abbrev "1" @@ -3468,26 +3510,26 @@ nopt@^4.0.1: normalize-path@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-path@^2.0.2: version "2.0.4" - resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64" + resolved "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz" integrity sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw== dependencies: which "^1.2.10" npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" npm-which@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" + resolved "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz" integrity sha512-CM8vMpeFQ7MAPin0U3wzDhSGV0hMHNwHU0wjo402IVizPDrs45jSfSuoC+wThevY88LQti8VvaAnqYAeVy3I1A== dependencies: commander "^2.9.0" @@ -3496,7 +3538,7 @@ npm-which@^3.0.1: "npmlog@0 || 1 || 2": version "2.0.4" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-2.0.4.tgz#98b52530f2514ca90d09ec5b22c8846722375692" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-2.0.4.tgz" integrity sha512-DaL6RTb8Qh4tMe2ttPT1qWccETy2Vi5/8p+htMpLBeXJTr2CAqnF5WQtSP2eFpvaNbhLZ5uilDb98mRm4Q+lZQ== dependencies: ansi "~0.3.1" @@ -3505,7 +3547,7 @@ npm-which@^3.0.1: "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.1, npmlog@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== dependencies: are-we-there-yet "~1.1.2" @@ -3515,7 +3557,7 @@ npm-which@^3.0.1: npmlog@^1.2.0: version "1.2.1" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-1.2.1.tgz#28e7be619609b53f7ad1dd300a10d64d716268b6" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-1.2.1.tgz" integrity sha512-1J5KqSRvESP6XbjPaXt2H6qDzgizLTM7x0y1cXIjP2PpvdCqyNC7TO3cPRKsuYlElbi/DwkzRRdG2zpmE0IktQ== dependencies: ansi "~0.3.0" @@ -3524,12 +3566,12 @@ npmlog@^1.2.0: number-is-nan@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== nw-gyp@^3.6.3: version "3.6.6" - resolved "https://registry.yarnpkg.com/nw-gyp/-/nw-gyp-3.6.6.tgz#0231d603d09665053ea48843d6888d13a4b92fb1" + resolved "https://registry.npmjs.org/nw-gyp/-/nw-gyp-3.6.6.tgz" integrity sha512-FeMnpFQWtEEMJ1BrSfK3T62CjuxaNl0mNHqdrxFcIF5XQdC3gaZYW4n+77lQLk8PE3Upfknkl9VRo6gDKJIHuA== dependencies: fstream "^1.0.0" @@ -3548,31 +3590,31 @@ nw-gyp@^3.6.3: oauth-sign@~0.9.0: version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== object-assign@^4.1.0: version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" optionator@^0.9.3: version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz" integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: "@aashutoshrathi/word-wrap" "^1.2.3" @@ -3582,26 +3624,41 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + os-homedir@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== os-locale@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + resolved "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz" integrity sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g== dependencies: lcid "^1.0.0" -os-tmpdir@^1.0.0: +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== osenv@0, osenv@^0.1.4: version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + resolved "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== dependencies: os-homedir "^1.0.0" @@ -3609,47 +3666,47 @@ osenv@0, osenv@^0.1.4: p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -3659,66 +3716,66 @@ parse-json@^5.2.0: path-array@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-array/-/path-array-1.0.1.tgz#7e2f0f35f07a2015122b868b7eac0eb2c4fec271" + resolved "https://registry.npmjs.org/path-array/-/path-array-1.0.1.tgz" integrity sha512-teWG2rJTJJZi2kINKOsHcdIuHP7jy3D7pAsVgdhxMq8kaL2RnS5sg7YTlrClMVCIItcVbPTPI6eMBEoNxYahLA== dependencies: array-index "^1.0.0" path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== performance-now@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pirates@^4.0.4: version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" prebuild-install@^7.1.1: version "7.1.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz" integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== dependencies: detect-libc "^2.0.0" @@ -3736,7 +3793,7 @@ prebuild-install@^7.1.1: prebuild@^11.0.4: version "11.0.4" - resolved "https://registry.yarnpkg.com/prebuild/-/prebuild-11.0.4.tgz#5e4a0659e2b98219ad58808b356c7938b0f9e326" + resolved "https://registry.npmjs.org/prebuild/-/prebuild-11.0.4.tgz" integrity sha512-n23Rzql2m8ldFpwcFyouGUrg9VByEF2IroEZlNvPLiQwTJWucTNxIaZEoyVe2AxPRzQb6Eph2ytObxVWm4FA7Q== dependencies: cmake-js "~5.2.0" @@ -3762,36 +3819,36 @@ prebuild@^11.0.4: prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^2.8.3: version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -pretty-format@^29.0.0, pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== +pretty-format@^29.0.0, pretty-format@^29.6.2: + version "29.6.2" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz" + integrity sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg== dependencies: - "@jest/schemas" "^29.6.3" + "@jest/schemas" "^29.6.0" ansi-styles "^5.0.0" react-is "^18.0.0" process-nextick-args@~1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw== process-nextick-args@~2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== prompts@^2.0.1: version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" @@ -3804,7 +3861,7 @@ psl@^1.1.33: pump@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" @@ -3812,17 +3869,17 @@ pump@^3.0.0: punycode@^2.1.0, punycode@^2.1.1: version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: - version "6.0.4" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" - integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== + version "6.0.2" + resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== qs@~6.5.2: version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + resolved "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz" integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== querystringify@^2.1.1: @@ -3832,19 +3889,19 @@ querystringify@^2.1.1: queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== randombytes@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" rc@^1.0.3, rc@^1.2.7: version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: deep-extend "^0.6.0" @@ -3854,12 +3911,12 @@ rc@^1.0.3, rc@^1.2.7: react-is@^18.0.0: version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.26-2: version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== dependencies: core-util-is "~1.0.0" @@ -3867,9 +3924,9 @@ react-is@^18.0.0: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.6: +"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.6: version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" @@ -3880,9 +3937,22 @@ react-is@^18.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^2.0.2, readable-stream@~2.1.5: + version "2.1.5" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz" + integrity sha512-NkXT2AER7VKXeXtJNSaWLpWIhmtSE3K2PguaLEeWr4JILghcIKqoLt1A3wHrnpDC5+ekf8gfk1GKWkFXe4odMw== + dependencies: + buffer-shims "^1.0.0" + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" @@ -3891,7 +3961,7 @@ readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0: readable-stream@~1.1.9: version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== dependencies: core-util-is "~1.0.0" @@ -3899,34 +3969,21 @@ readable-stream@~1.1.9: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@~2.1.5: - version "2.1.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" - integrity sha512-NkXT2AER7VKXeXtJNSaWLpWIhmtSE3K2PguaLEeWr4JILghcIKqoLt1A3wHrnpDC5+ekf8gfk1GKWkFXe4odMw== - dependencies: - buffer-shims "^1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - rechoir@^0.8.0: version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== dependencies: resolve "^1.20.0" reflect-metadata@^0.1.13: version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== request@2, request@^2.54.0, request@^2.88.0: version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" @@ -3952,7 +4009,7 @@ request@2, request@^2.54.0, request@^2.88.0: require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== requires-port@^1.0.0: @@ -3962,96 +4019,116 @@ requires-port@^1.0.0: resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== resolve@^1.20.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + version "1.22.4" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== dependencies: is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + reusify@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@2, rimraf@^2.6.3: version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" rsvp@^3.0.13: version "3.6.2" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" + resolved "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz" integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw== +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" run-waterfall@^1.1.6: version "1.1.7" - resolved "https://registry.yarnpkg.com/run-waterfall/-/run-waterfall-1.1.7.tgz#ae368b549b2f5171f86c2924492cab3352a6e9c5" + resolved "https://registry.npmjs.org/run-waterfall/-/run-waterfall-1.1.7.tgz" integrity sha512-iFPgh7SatHXOG1ClcpdwHI63geV3Hc/iL6crGSyBlH2PY7Rm/za+zoKz6FfY/Qlw5K7JwSol8pseO8fN6CMhhQ== +rxjs@^7.5.5: + version "7.8.1" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" -"semver@2.x || 3.x || 4 || 5", semver@^4.3.3, semver@^5.0.3, semver@^5.7.1, semver@^6.3.0, semver@^6.3.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@~5.3.0: +"semver@2.x || 3.x || 4 || 5", semver@^4.3.3, semver@^5.0.3, semver@^5.7.1, semver@^6.3.0, semver@^6.3.1, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@~5.3.0: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -4060,58 +4137,58 @@ schema-utils@^3.1.1, schema-utils@^3.2.0: serialize-javascript@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz" integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== dependencies: randombytes "^2.1.0" set-blocking@~2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== setimmediate@~1.0.4: version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== shallow-clone@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: kind-of "^6.0.2" shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shiki@^0.14.1: - version "0.14.5" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.5.tgz#375dd214e57eccb04f0daf35a32aa615861deb93" - integrity sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw== + version "0.14.3" + resolved "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz" + integrity sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g== dependencies: ansi-sequence-parser "^1.1.0" jsonc-parser "^3.2.0" vscode-oniguruma "^1.7.0" vscode-textmate "^8.0.0" -signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== simple-concat@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== simple-get@^2.8.2, simple-get@^4.0.0: @@ -4125,22 +4202,22 @@ simple-get@^2.8.2, simple-get@^4.0.0: simple-mime@~0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/simple-mime/-/simple-mime-0.1.0.tgz#95f517c4f466d7cff561a71fc9dab2596ea9ef2e" + resolved "https://registry.npmjs.org/simple-mime/-/simple-mime-0.1.0.tgz" integrity sha512-2EoTElzj77w0hV4lW6nWdA+MR+81hviMBhEc/ppUi0+Q311EFCvwKrGS7dcxqvGRKnUdbAyqPJtBQbRYgmtmvQ== sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== source-map-support@0.5.13: version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" @@ -4148,14 +4225,14 @@ source-map-support@0.5.13: source-map-support@~0.2.8: version "0.2.10" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz" integrity sha512-gGKOSat73z0V8wBKo9AGxZZyekczBireh1hHktbt+kb9acsCB5OfVCF2DCWlztcQ3r5oNN7f2BL0B2xOcoJ/DQ== dependencies: source-map "0.1.32" source-map-support@~0.5.20: version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" @@ -4163,29 +4240,29 @@ source-map-support@~0.5.20: source-map@0.1.32: version "0.1.32" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.32.tgz#c8b6c167797ba4740a8ea33252162ff08591b266" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz" integrity sha512-htQyLrrRLkQ87Zfrir4/yN+vAUd6DNjVayEjTSHXu29AYQJw57I4/xEL/M6p6E/woPNJwvZt6rVlzc7gFEJccQ== dependencies: amdefine ">=0.0.4" source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== splitargs@0: version "0.0.7" - resolved "https://registry.yarnpkg.com/splitargs/-/splitargs-0.0.7.tgz#fe9f7ae657371b33b10cb80da143cf8249cf6b3b" + resolved "https://registry.npmjs.org/splitargs/-/splitargs-0.0.7.tgz" integrity sha512-UUFYD2oWbNwULH6WoVtLUOw8ch586B+HUqcsAjjjeoBQAM1bD4wZRXu01koaxyd8UeYpybWqW4h+lO1Okv40Tg== sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sshpk@^1.7.0: version "1.18.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz" integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== dependencies: asn1 "~0.2.3" @@ -4200,31 +4277,31 @@ sshpk@^1.7.0: stack-utils@^2.0.3: version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" string-length@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^1.0.1: +string-width@^1.0.1, "string-width@^1.0.2 || 2 || 3 || 4": version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== dependencies: code-point-at "^1.0.0" is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -4233,91 +4310,91 @@ string-width@^1.0.1: string_decoder@^1.1.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" string_decoder@~0.10.x: version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== string_decoder@~1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strip-json-comments@~2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-color@^8.0.0: version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== tapable@^2.1.1, tapable@^2.2.0: version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== tar-fs@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== dependencies: chownr "^1.1.1" @@ -4327,7 +4404,7 @@ tar-fs@^2.0.0: tar-stream@^2.1.0, tar-stream@^2.1.4: version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" @@ -4338,7 +4415,7 @@ tar-stream@^2.1.0, tar-stream@^2.1.4: tar@^2.0.0, tar@^4, tar@^4.4.12, tar@^4.4.19: version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + resolved "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== dependencies: chownr "^1.1.4" @@ -4351,7 +4428,7 @@ tar@^2.0.0, tar@^4, tar@^4.4.12, tar@^4.4.19: terser-webpack-plugin@^5.3.7: version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz" integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== dependencies: "@jridgewell/trace-mapping" "^0.3.17" @@ -4361,9 +4438,9 @@ terser-webpack-plugin@^5.3.7: terser "^5.16.8" terser@^5.16.8: - version "5.22.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.22.0.tgz#4f18103f84c5c9437aafb7a14918273310a8a49d" - integrity sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw== + version "5.20.0" + resolved "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz" + integrity sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -4372,7 +4449,7 @@ terser@^5.16.8: test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -4381,30 +4458,42 @@ test-exclude@^6.0.0: text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== through2@~0.6.3: version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + resolved "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" integrity sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg== dependencies: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" +through@^2.3.6: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" @@ -4421,7 +4510,7 @@ tough-cookie@^4.1.3, tough-cookie@~2.5.0: traceur@0.0.x: version "0.0.111" - resolved "https://registry.yarnpkg.com/traceur/-/traceur-0.0.111.tgz#c04de74d14696c3373427de4fc08ecaf913fc3a1" + resolved "https://registry.npmjs.org/traceur/-/traceur-0.0.111.tgz" integrity sha512-Zy0NCrl3+k1VZvDrZGQJHjLM4Hwz7XHSedhVTdsbV3RNWVtgw/GUP44Rl5WqqcctLkzyQ60eTU2jxfLrlrjWZQ== dependencies: commander "2.9.x" @@ -4432,12 +4521,12 @@ traceur@0.0.x: "traverse@>=0.3.0 <0.4": version "0.3.9" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + resolved "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz" integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== ts-jest@^29.0.5: version "29.1.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz" integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== dependencies: bs-logger "0.x" @@ -4451,70 +4540,75 @@ ts-jest@^29.0.5: tslib@^1.8.1: version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsutils@^3.21.0: version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" tunnel-agent@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== dependencies: safe-buffer "^5.0.1" tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-detect@4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type@^1.0.1: version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.7.2: version "2.7.2" - resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== typedoc-plugin-markdown@^3.14.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.16.0.tgz#98da250271aafade8b6740a8116a97cd3941abcd" - integrity sha512-eeiC78fDNGFwemPIHiwRC+mEC7W5jwt3fceUev2gJ2nFnXpVHo8eRrpC9BLWZDee6ehnz/sPmNjizbXwpfaTBw== + version "3.15.4" + resolved "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.15.4.tgz" + integrity sha512-KpjFL/NDrQAbY147oIoOgob2vAdEchsMcTVd6+e6H2lC1l5xhi48bhP/fMJI7qYQ8th5nubervgqw51z7gY66A== dependencies: handlebars "^4.7.7" typedoc@^0.24.6: version "0.24.8" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.24.8.tgz#cce9f47ba6a8d52389f5e583716a2b3b4335b63e" + resolved "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz" integrity sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w== dependencies: lunr "^2.3.9" @@ -4524,22 +4618,17 @@ typedoc@^0.24.6: typescript@^4.9.4: version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== uglify-js@^3.1.4: version "3.17.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz" integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - universalify@^0.1.0: version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^0.2.0: @@ -4549,7 +4638,7 @@ universalify@^0.2.0: unzipper@^0.8.13: version "0.8.14" - resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.8.14.tgz#ade0524cd2fc14d11b8de258be22f9d247d3f79b" + resolved "https://registry.npmjs.org/unzipper/-/unzipper-0.8.14.tgz" integrity sha512-8rFtE7EP5ssOwGpN2dt1Q4njl0N1hUXJ7sSPz0leU2hRdq6+pra57z4YPBlVqm40vcgv6ooKZEAx48fMTv9x4w== dependencies: big-integer "^1.6.17" @@ -4562,24 +4651,24 @@ unzipper@^0.8.13: readable-stream "~2.1.5" setimmediate "~1.0.4" -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" url-join@0: version "0.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8" + resolved "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz" integrity sha512-H6dnQ/yPAAVzMQRvEvyz01hhfQL5qRWSEt7BX8t9DqnPw9BjMb64fjIRq76Uvf1hkHp+mTZvEVJ5guXOT0Xqaw== url-parse@^1.5.3: @@ -4592,36 +4681,36 @@ url-parse@^1.5.3: url-template@~2.0.6: version "2.0.8" - resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" + resolved "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz" integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw== util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util-extend@^1.0.1: version "1.0.3" - resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" + resolved "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz" integrity sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA== uuid@^3.3.2: version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-to-istanbul@^9.0.1: - version "9.1.3" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz#ea456604101cd18005ac2cae3cdd1aa058a6306b" - integrity sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg== + version "9.1.0" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" + convert-source-map "^1.6.0" verror@1.10.0: version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== dependencies: assert-plus "^1.0.0" @@ -4630,32 +4719,39 @@ verror@1.10.0: vscode-oniguruma@^1.7.0: version "1.7.0" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + resolved "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz" integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== vscode-textmate@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" + resolved "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz" integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== walker@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" watchpack@^2.4.0: version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz" integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + webpack-cli@^5.1.4: version "5.1.4" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" + resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz" integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== dependencies: "@discoveryjs/json-ext" "^0.5.0" @@ -4673,23 +4769,22 @@ webpack-cli@^5.1.4: webpack-merge "^5.7.3" webpack-merge@^5.7.3: - version "5.10.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" - integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + version "5.9.0" + resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz" + integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg== dependencies: clone-deep "^4.0.1" - flat "^5.0.2" wildcard "^2.0.0" webpack-sources@^3.2.3: version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.88.2: - version "5.89.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" - integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== + version "5.88.2" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz" + integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" @@ -4718,33 +4813,33 @@ webpack@^5.88.2: which@1, which@^1.0.9, which@^1.2.10, which@^1.3.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" wide-align@^1.1.0: version "1.1.5" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== dependencies: string-width "^1.0.2 || 2 || 3 || 4" wildcard@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz" integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== window-size@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + resolved "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz" integrity sha512-2thx4pB0cV3h+Bw7QmMXcEbdmOzv9t0HFplJH/Lz6yu60hXYy5RT8rUu+wlIreVxWsGN20mo+MHeCSfUpQBwPw== word-wrap@^1.2.4: @@ -4754,20 +4849,29 @@ word-wrap@^1.2.4: wordwrap@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== wrap-ansi@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz" integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw== dependencies: string-width "^1.0.1" strip-ansi "^3.0.1" +wrap-ansi@^6.0.1: + version "6.2.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -4776,12 +4880,12 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" @@ -4789,37 +4893,37 @@ write-file-atomic@^4.0.2: "xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^3.2.0: version "3.2.2" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz" integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== y18n@^5.0.5: version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.3.1: version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" @@ -4832,7 +4936,7 @@ yargs@^17.3.1: yargs@^3.6.0: version "3.32.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" + resolved "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz" integrity sha512-ONJZiimStfZzhKamYvR/xvmgW3uEkAUFSP91y2caTEPhzF6uP2JfPiVZcq66b/YR0C3uitxSV7+T1x8p5bkmMg== dependencies: camelcase "^2.0.1" @@ -4845,5 +4949,5 @@ yargs@^3.6.0: yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/bindings/python/examples/.env.example b/bindings/python/examples/.env.example index cacb4f080e..ef36979f75 100644 --- a/bindings/python/examples/.env.example +++ b/bindings/python/examples/.env.example @@ -6,7 +6,7 @@ # Mnemonics (Don't ever use them to manage real funds!) MNEMONIC="endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river" MNEMONIC_2="width scatter jaguar sponsor erosion enable cave since ancient first garden royal luggage exchange ritual exotic play wall clinic ride autumn divert spin exchange" -# The Wallet database folder used to store account data +# The Wallet database folder used to store wallet data WALLET_DB_PATH="./example-walletdb" # The Stronghold snapshot file location used to store secrets STRONGHOLD_SNAPSHOT_PATH="./example.stronghold" diff --git a/bindings/python/examples/client/05_get_address_balance.py b/bindings/python/examples/client/05_get_address_balance.py index 5e01246f86..d849fd69ea 100644 --- a/bindings/python/examples/client/05_get_address_balance.py +++ b/bindings/python/examples/client/05_get_address_balance.py @@ -2,7 +2,7 @@ from dotenv import load_dotenv -from iota_sdk import Client, NodeIndexerAPI +from iota_sdk import Client, NodeIndexerAPI, FeatureType load_dotenv() @@ -12,7 +12,7 @@ client = Client(nodes=[node_url]) ADDRESS = 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy' -query_parameters = NodeIndexerAPI.QueryParameters( +query_parameters = NodeIndexerAPI.BasicOutputQueryParameters( address=ADDRESS, has_expiration=False, has_timelock=False, @@ -35,8 +35,10 @@ for output_with_metadata in outputs: output = output_with_metadata.output total_amount += output.amount - if output.native_tokens: - native_tokens.append(output.native_tokens) + native_token = [feature for feature in output.features if feature.type + == FeatureType.NativeToken] + if native_token: + native_tokens.append(native_token) print( f'Outputs controlled by {ADDRESS} have {total_amount} glow and native tokens: {native_tokens}') diff --git a/bindings/python/examples/exchange/1_create_account.py b/bindings/python/examples/exchange/1_create_account.py index 6146522fe0..d870accd6d 100644 --- a/bindings/python/examples/exchange/1_create_account.py +++ b/bindings/python/examples/exchange/1_create_account.py @@ -1,7 +1,7 @@ # Copyright 2023 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 -# This example creates a new database and account. +# This example creates a new database and wallet. import os diff --git a/bindings/python/examples/how_tos/account_wallet/transaction.py b/bindings/python/examples/how_tos/account_wallet/transaction.py index 1842a2d617..8cf9ba73e7 100644 --- a/bindings/python/examples/how_tos/account_wallet/transaction.py +++ b/bindings/python/examples/how_tos/account_wallet/transaction.py @@ -32,7 +32,8 @@ account_id, wallet.get_client().get_bech32_hrp()) # Find first output unlockable by the account address -query_parameters = NodeIndexerAPI.QueryParameters(account_address) +query_parameters = NodeIndexerAPI.BasicOutputQueryParameters( + address=account_address) inputs = [wallet.get_client().basic_output_ids(query_parameters).items[0]] params = [SendParams( diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py index 129a53de1e..261446ff26 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/consolidate_outputs.py @@ -2,7 +2,7 @@ from dotenv import load_dotenv -from iota_sdk import ConsolidationParams, Utils, Wallet +from iota_sdk import ConsolidationParams, Utils, Wallet, FeatureType # In this example we will consolidate basic outputs from an account with only an AddressUnlockCondition by sending # them to the same address again. @@ -27,7 +27,7 @@ # List unspent outputs before consolidation. # The output we created with example `request_funds` and the basic output from `mint` have only one # unlock condition and it is an `AddressUnlockCondition`, and so they are valid for consolidation. They have the -# same `AddressUnlockCondition`(the first address of the account), so they will be consolidated into one +# same `AddressUnlockCondition`(the address of the wallet), so they will be consolidated into one # output. outputs = account.unspent_outputs() print('Outputs BEFORE consolidation:') @@ -38,7 +38,8 @@ '- address: {}\n- amount: {}\n- native tokens: {}'.format( Utils.hex_to_bech32(output_data.address.pub_key_hash, 'rms'), output_data.output.amount, - output_data.output.native_tokens + [feature for feature in output_data.output.features if feature.type + == FeatureType.NativeToken] ) ) @@ -71,6 +72,7 @@ '- address: {}\n- amount: {}\n- native tokens: {}'.format( Utils.hex_to_bech32(output_data.address.pub_key_hash, 'rms'), output_data.output.amount, - output_data.output.native_tokens + [feature for feature in output_data.output.features if feature.type + == FeatureType.NativeToken] ) ) diff --git a/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py b/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py index 55dbc1abb0..09758a6c23 100644 --- a/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py +++ b/bindings/python/examples/how_tos/accounts_and_addresses/create_account.py @@ -6,7 +6,7 @@ load_dotenv() -# This example creates a new database and account +# This example creates a new database and wallet node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') client_options = ClientOptions(nodes=[node_url]) diff --git a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py index ae6975fe7f..2c6af5ee3e 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py @@ -6,7 +6,7 @@ load_dotenv() -# In this example we will send an amount below the minimum storage deposit +# In this example we will send an amount below the minimum amount wallet = Wallet(os.environ['WALLET_DB_PATH']) diff --git a/bindings/python/examples/how_tos/client/get_outputs.py b/bindings/python/examples/how_tos/client/get_outputs.py index 7c9428ac33..9e03b62dc7 100644 --- a/bindings/python/examples/how_tos/client/get_outputs.py +++ b/bindings/python/examples/how_tos/client/get_outputs.py @@ -12,7 +12,7 @@ # Create a Client instance client = Client(nodes=[node_url]) -query_parameters = NodeIndexerAPI.QueryParameters( +query_parameters = NodeIndexerAPI.BasicOutputQueryParameters( address='rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', has_expiration=False, has_timelock=False, diff --git a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py index 3d47fada8f..36e2897b9e 100644 --- a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py @@ -28,21 +28,21 @@ ) -transaction = account.mint_nfts([params]) +tx = account.mint_nfts([params]) # Wait for transaction to get included block_id = account.reissue_transaction_until_included( - transaction.transaction_id) + tx.transaction_id) print( f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}') -essence = transaction.payload["essence"] +transaction = tx.payload.transaction -for outputIndex, output in enumerate(essence["outputs"]): +for outputIndex, output in enumerate(transaction.outputs): # New minted NFT id is empty in the output if output["type"] == 6 and output["nftId"] == '0x0000000000000000000000000000000000000000000000000000000000000000': outputId = Utils.compute_output_id( - transaction.transaction_id, outputIndex) + tx.transaction_id, outputIndex) nftId = Utils.compute_nft_id(outputId) print(f'New minted NFT id: {nftId}') diff --git a/bindings/python/examples/wallet/backup.py b/bindings/python/examples/wallet/backup.py index b34ad45433..b5302bb3df 100644 --- a/bindings/python/examples/wallet/backup.py +++ b/bindings/python/examples/wallet/backup.py @@ -6,7 +6,7 @@ load_dotenv() -# This example creates a new database and account +# This example creates a new database and wallet node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') client_options = ClientOptions(nodes=[node_url]) diff --git a/bindings/python/examples/wallet/logger.py b/bindings/python/examples/wallet/logger.py index c10b2aa806..30c0019a14 100644 --- a/bindings/python/examples/wallet/logger.py +++ b/bindings/python/examples/wallet/logger.py @@ -9,7 +9,7 @@ load_dotenv() -# This example creates a new database and account and write debug logs in +# This example creates a new database and wallet and write debug logs in # `wallet.log`. log_config = { diff --git a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py index 12c74dfe07..6bbfb52dc6 100644 --- a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py @@ -34,7 +34,7 @@ wallet.set_stronghold_password(os.environ["STRONGHOLD_PASSWORD"]) # Signs prepared transaction offline. -signed_transaction_data = account.sign_transaction_essence( +signed_transaction_data = account.sign_transaction( prepared_transaction_data) print("Signed transaction.") diff --git a/bindings/python/iota_sdk/__init__.py b/bindings/python/iota_sdk/__init__.py index 7b244a3be0..a08515c175 100644 --- a/bindings/python/iota_sdk/__init__.py +++ b/bindings/python/iota_sdk/__init__.py @@ -41,6 +41,7 @@ from .types.send_params import * from .types.token_scheme import * from .types.transaction import * +from .types.transaction_with_metadata import * from .types.transaction_data import * from .types.transaction_options import * from .types.unlock import * diff --git a/bindings/python/iota_sdk/client/_node_indexer_api.py b/bindings/python/iota_sdk/client/_node_indexer_api.py index 0964506c4d..28f402348b 100644 --- a/bindings/python/iota_sdk/client/_node_indexer_api.py +++ b/bindings/python/iota_sdk/client/_node_indexer_api.py @@ -5,7 +5,7 @@ from typing import Dict, Optional from abc import ABCMeta, abstractmethod -from iota_sdk.types.common import HexStr, json +from iota_sdk.types.common import HexStr, json, SlotIndex from iota_sdk.types.output_id import OutputId @@ -31,101 +31,233 @@ class NodeIndexerAPI(metaclass=ABCMeta): @json @dataclass - class QueryParameters: - """Query parameters + class CommonQueryParameters: + """Common Query parameters **Attributes:** - address : + + page_size: + The maximum amount of items returned in one call. If there are more items, a cursor to the next page is + returned too. The parameter is ignored when pageSize is defined via the cursor parameter. + cursor: + Starts the search from the cursor (createdSlotIndex+outputId.pageSize). + If an empty string is provided, only the first page is returned. + created_before: + Returns outputs that were created before a certain slot index. + created_after: + Returns outputs that were created after a certain slot index. + """ + page_size: Optional[int] = None + cursor: Optional[str] = None + created_before: Optional[SlotIndex] = None + created_after: Optional[SlotIndex] = None + + @json + @dataclass + class OutputQueryParameters(CommonQueryParameters): + """Output Query parameters + + **Attributes:** + + has_native_token: + Filters outputs based on the presence of a native token. + native_token: + Filters outputs based on the presence of a specific native token. + unlockable_by_address: + Returns outputs that are unlockable by the bech32 address. + """ + has_native_token: Optional[bool] = None + native_token: Optional[HexStr] = None + unlockable_by_address: Optional[str] = None + + @json + @dataclass + class BasicOutputQueryParameters(CommonQueryParameters): + """Basic Output Query parameters + + **Attributes: ** + has_native_token: + Filters outputs based on the presence of a native token. + native_token: + Filters outputs based on the presence of a specific native token. + unlockable_by_address: + Returns outputs that are unlockable by the bech32 address. + address: Bech32-encoded address that should be searched for. - account_address : - Filter foundry outputs based on bech32-encoded address of the controlling account. - created_after : - Returns outputs that were created after a certain Unix timestamp. - created_before : - Returns outputs that were created before a certain Unix timestamp. - cursor : - Starts the search from the cursor (confirmationMS+outputId.pageSize). - expiration_return_address : - Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock - condition. - expires_after : - Returns outputs that expire after a certain Unix timestamp. - expires_before : - Returns outputs that expire before a certain Unix timestamp. - governor : - Filters outputs based on bech32-encoded governor (governance controller) address. - has_expiration : - Filters outputs based on the presence of expiration unlock condition. - has_native_tokens : - Filters outputs based on the presence of native tokens. - has_storage_deposit_return : + has_storage_deposit_return: Filters outputs based on the presence of storage deposit return unlock condition. - has_timelock : + storage_deposit_return_address: + Filters outputs based on the presence of a specific return address in the storage deposit return unlock condition. + has_expiration: + Filters outputs based on the presence of expiration unlock condition. + expiration_return_address: + Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock condition. + expires_before: + Returns outputs that expire before a certain slot index. + expires_after: + Returns outputs that expire after a certain slot index. + has_timelock: Filters outputs based on the presence of timelock unlock condition. - issuer: - Filters outputs based on bech32-encoded issuer address. - max_native_token_count : - Filters outputs that have at most a certain number of distinct native tokens. - min_native_token_count : - Filters outputs that have at least a certain number of distinct native tokens. - page_size : - The maximum amount of items returned in one call. If there are more items, a cursor to the next page is - returned too. The parameter is ignored when pageSize is defined via the cursor parameter. - sender : + timelocked_before: + Returns outputs that are timelocked before a certain slot index. + timelocked_after: + Returns outputs that are timelocked after a certain slot index. + sender: Filters outputs based on the presence of validated Sender (bech32 encoded). - state_controller : - Filters outputs based on bech32-encoded state controller address. - storage_deposit_return_address : - Filters outputs based on the presence of a specific return address in the storage deposit return unlock - condition. - tag : + tag: Filters outputs based on matching Tag Block. - timelocked_after : - Returns outputs that are timelocked after a certain Unix timestamp. - timelocked_before : - Returns outputs that are timelocked before a certain Unix timestamp. - unlockable_by_address : - Returns outputs that are unlockable by the bech32 address. """ + has_native_token: Optional[bool] = None + native_token: Optional[HexStr] = None + unlockable_by_address: Optional[str] = None address: Optional[str] = None - account_address: Optional[str] = None - created_after: Optional[int] = None - created_before: Optional[int] = None - cursor: Optional[str] = None - expiration_return_address: Optional[str] = None - expires_after: Optional[int] = None - expires_before: Optional[int] = None - governor: Optional[str] = None - has_expiration: Optional[bool] = None - has_native_tokens: Optional[bool] = None has_storage_deposit_return: Optional[bool] = None + storage_deposit_return_address: Optional[str] = None + has_expiration: Optional[bool] = None + expiration_return_address: Optional[str] = None + expires_before: Optional[SlotIndex] = None + expires_after: Optional[SlotIndex] = None has_timelock: Optional[bool] = None + timelocked_before: Optional[SlotIndex] = None + timelocked_after: Optional[SlotIndex] = None + sender: Optional[str] = None + tag: Optional[HexStr] = None + + @json + @dataclass + class AccountOutputQueryParameters(CommonQueryParameters): + """Account Output Query parameters + + **Attributes: ** + address: + Bech32-encoded address that should be searched for. + issuer: + Filters outputs based on bech32-encoded issuer address. + sender: + Filters outputs based on the presence of validated Sender (bech32 encoded). + """ + address: Optional[str] = None issuer: Optional[str] = None - max_native_token_count: Optional[int] = None - min_native_token_count: Optional[int] = None - page_size: Optional[int] = None sender: Optional[str] = None + + @json + @dataclass + class AnchorOutputQueryParameters(CommonQueryParameters): + """Anchor Output Query parameters + + **Attributes: ** + unlockable_by_address: + Returns outputs that are unlockable by the bech32 address. + state_controller: + Filters outputs based on bech32-encoded state controller address. + governor: + Filters outputs based on bech32-encoded governor (governance controller) address. + issuer: + Filters outputs based on bech32-encoded issuer address. + sender: + Filters outputs based on the presence of validated Sender (bech32 encoded). + """ + unlockable_by_address: Optional[str] = None state_controller: Optional[str] = None - storage_deposit_return_address: Optional[str] = None - tag: Optional[str] = None - timelocked_after: Optional[int] = None - timelocked_before: Optional[int] = None + governor: Optional[str] = None + issuer: Optional[str] = None + sender: Optional[str] = None + + @json + @dataclass + class DelegationOutputQueryParameters(CommonQueryParameters): + """Delegation Output Query parameters + + **Attributes: ** + address: + Bech32-encoded address that should be searched for. + validator: + Filter foundry outputs based on bech32-encoded address of the validator. + """ + address: Optional[str] = None + validator: Optional[str] = None + + @json + @dataclass + class FoundryOutputQueryParameters(CommonQueryParameters): + """Foundry Output Query parameters + + **Attributes: ** + has_native_token: + Filters outputs based on the presence of a native token. + native_token: + Filters outputs based on the presence of a specific native token. + account: + Filter foundry outputs based on bech32-encoded address of the controlling account. + """ + has_native_token: Optional[bool] = None + native_token: Optional[HexStr] = None + account: Optional[str] = None + + @json + @dataclass + class NftOutputQueryParameters(CommonQueryParameters): + """NFT Output Query parameters + + **Attributes: ** + unlockable_by_address: + Returns outputs that are unlockable by the bech32 address. + address: + Bech32-encoded address that should be searched for. + has_storage_deposit_return: + Filters outputs based on the presence of storage deposit return unlock condition. + storage_deposit_return_address: + Filters outputs based on the presence of a specific return address in the storage deposit return unlock condition. + has_expiration: + Filters outputs based on the presence of expiration unlock condition. + expiration_return_address: + Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock condition. + expires_before: + Returns outputs that expire before a certain slot index. + expires_after: + Returns outputs that expire after a certain slot index. + has_timelock: + Filters outputs based on the presence of timelock unlock condition. + timelocked_before: + Returns outputs that are timelocked before a certain slot index. + timelocked_after: + Returns outputs that are timelocked after a certain slot index. + issuer: + Filters outputs based on bech32-encoded issuer address. + sender: + Filters outputs based on the presence of validated Sender (bech32 encoded). + tag: + Filters outputs based on matching Tag Block. + """ unlockable_by_address: Optional[str] = None + address: Optional[str] = None + has_storage_deposit_return: Optional[bool] = None + storage_deposit_return_address: Optional[str] = None + has_expiration: Optional[bool] = None + expiration_return_address: Optional[str] = None + expires_before: Optional[SlotIndex] = None + expires_after: Optional[SlotIndex] = None + has_timelock: Optional[bool] = None + timelocked_before: Optional[SlotIndex] = None + timelocked_after: Optional[SlotIndex] = None + issuer: Optional[str] = None + sender: Optional[str] = None + tag: Optional[HexStr] = None @abstractmethod def _call_method(self, name, data=None): return {} def output_ids( - self, query_parameters: QueryParameters) -> OutputIdsResponse: - """Fetch alias/basic/NFT/foundry output IDs from the given query parameters. + self, query_parameters: OutputQueryParameters) -> OutputIdsResponse: + """Fetch account/anchor/basic/delegation/NFT/foundry output IDs from the given query parameters. Supported query parameters are: "hasNativeTokens", "minNativeTokenCount", "maxNativeTokenCount", "unlockableByAddress", "createdBefore", "createdAfter", "cursor", "pageSize". Returns: The corresponding output IDs of the outputs. """ - query_parameters_camelized = query_parameters.as_dict() + query_parameters_camelized = query_parameters.to_dict() response = self._call_method('outputIds', { 'queryParameters': query_parameters_camelized, @@ -133,7 +265,7 @@ def output_ids( return OutputIdsResponse(response) def basic_output_ids( - self, query_parameters: QueryParameters) -> OutputIdsResponse: + self, query_parameters: BasicOutputQueryParameters) -> OutputIdsResponse: """Fetch basic output IDs from the given query parameters. Returns: @@ -148,7 +280,7 @@ def basic_output_ids( return OutputIdsResponse(response) def account_output_ids( - self, query_parameters: QueryParameters) -> OutputIdsResponse: + self, query_parameters: AccountOutputQueryParameters) -> OutputIdsResponse: """Fetch account output IDs from the given query parameters. Returns: @@ -172,33 +304,58 @@ def account_output_id(self, account_id: HexStr) -> OutputId: 'accountId': account_id })) - def nft_output_ids( - self, query_parameters: QueryParameters) -> OutputIdsResponse: - """Fetch NFT output IDs from the given query parameters. + def anchor_output_ids( + self, query_parameters: AnchorOutputQueryParameters) -> OutputIdsResponse: + """Fetch anchor output IDs from the given query parameters. Returns: - The corresponding output IDs of the NFT outputs. + The corresponding output IDs of the anchor outputs. """ query_parameters_camelized = query_parameters.to_dict() - response = self._call_method('nftOutputIds', { + response = self._call_method('anchorOutputIds', { 'queryParameters': query_parameters_camelized, }) return OutputIdsResponse(response) - def nft_output_id(self, nft_id: HexStr) -> OutputId: - """Fetch NFT output ID from the given NFT ID. + def anchor_output_id(self, anchor_id: HexStr) -> OutputId: + """Fetch anchor output ID from the given anchor ID. Returns: - The output ID of the NFT output. + The output ID of the anchor output. """ - return OutputId.from_string(self._call_method('nftOutputId', { - 'nftId': nft_id + return OutputId.from_string(self._call_method('anchorOutputId', { + 'anchorId': anchor_id + })) + + def delegation_output_ids( + self, query_parameters: DelegationOutputQueryParameters) -> OutputIdsResponse: + """Fetch delegation output IDs from the given query parameters. + + Returns: + The corresponding output IDs of the delegation outputs. + """ + + query_parameters_camelized = query_parameters.to_dict() + + response = self._call_method('delegationOutputIds', { + 'queryParameters': query_parameters_camelized, + }) + return OutputIdsResponse(response) + + def delegation_output_id(self, delegation_id: HexStr) -> OutputId: + """Fetch delegation output ID from the given delegation ID. + + Returns: + The output ID of the delegation output. + """ + return OutputId.from_string(self._call_method('delegationOutputId', { + 'delegationId': delegation_id })) def foundry_output_ids( - self, query_parameters: QueryParameters) -> OutputIdsResponse: + self, query_parameters: FoundryOutputQueryParameters) -> OutputIdsResponse: """Fetch foundry Output IDs from the given query parameters. Returns: @@ -221,3 +378,28 @@ def foundry_output_id(self, foundry_id: HexStr) -> OutputId: return OutputId.from_string(self._call_method('foundryOutputId', { 'foundryId': foundry_id })) + + def nft_output_ids( + self, query_parameters: NftOutputQueryParameters) -> OutputIdsResponse: + """Fetch NFT output IDs from the given query parameters. + + Returns: + The corresponding output IDs of the NFT outputs. + """ + + query_parameters_camelized = query_parameters.to_dict() + + response = self._call_method('nftOutputIds', { + 'queryParameters': query_parameters_camelized, + }) + return OutputIdsResponse(response) + + def nft_output_id(self, nft_id: HexStr) -> OutputId: + """Fetch NFT output ID from the given NFT ID. + + Returns: + The output ID of the NFT output. + """ + return OutputId.from_string(self._call_method('nftOutputId', { + 'nftId': nft_id + })) diff --git a/bindings/python/iota_sdk/client/_utils.py b/bindings/python/iota_sdk/client/_utils.py index d38d867e80..29678c6d46 100644 --- a/bindings/python/iota_sdk/client/_utils.py +++ b/bindings/python/iota_sdk/client/_utils.py @@ -49,11 +49,11 @@ def hex_public_key_to_bech32_address( 'bech32Hrp': bech32_hrp }) - def minimum_required_storage_deposit(self, output: Output) -> int: - """Minimum required storage deposit. + def computer_minimum_output_amount(self, output: Output) -> int: + """Minimum required output amount. """ return int(self._call_method( - 'minimumRequiredStorageDeposit', { + 'computeMinimumOutputAmount', { 'output': output.to_dict() } )) diff --git a/bindings/python/iota_sdk/client/client.py b/bindings/python/iota_sdk/client/client.py index 79736ad1f8..06e38a42cc 100644 --- a/bindings/python/iota_sdk/client/client.py +++ b/bindings/python/iota_sdk/client/client.py @@ -5,24 +5,20 @@ from datetime import timedelta from typing import Any, Dict, List, Optional, Union import humps -from dacite import from_dict from iota_sdk.external import create_client, call_client_method, listen_mqtt from iota_sdk.client._node_core_api import NodeCoreAPI from iota_sdk.client._node_indexer_api import NodeIndexerAPI from iota_sdk.client._high_level_api import HighLevelAPI from iota_sdk.client._utils import ClientUtils -from iota_sdk.secret_manager.secret_manager import LedgerNanoSecretManager, MnemonicSecretManager, StrongholdSecretManager, SeedSecretManager from iota_sdk.types.block.signed_block import UnsignedBlock from iota_sdk.types.common import HexStr, Node from iota_sdk.types.feature import Feature -from iota_sdk.types.native_token import NativeToken from iota_sdk.types.network_info import NetworkInfo from iota_sdk.types.output import AccountOutput, BasicOutput, FoundryOutput, NftOutput, deserialize_output -from iota_sdk.types.payload import Payload, TransactionPayload +from iota_sdk.types.payload import Payload from iota_sdk.types.token_scheme import SimpleTokenScheme from iota_sdk.types.unlock_condition import UnlockCondition -from iota_sdk.types.transaction_data import PreparedTransactionData class ClientError(Exception): @@ -154,7 +150,6 @@ def build_account_output(self, unlock_conditions: List[UnlockCondition], amount: Optional[int] = None, mana: Optional[int] = None, - native_tokens: Optional[List[NativeToken]] = None, foundry_counter: Optional[int] = None, features: Optional[List[Feature]] = None, immutable_features: Optional[List[Feature]] = None) -> AccountOutput: @@ -165,7 +160,6 @@ def build_account_output(self, unlock_conditions: The unlock conditions for the new output. amount: The amount of base coins in the new output. mana: Amount of stored Mana held by this output. - native_tokens: Native tokens added to the new output. foundry_counter: A counter that denotes the number of foundries created by this account output. features: A list of features. immutable_features: A list of immutable features. @@ -177,10 +171,6 @@ def build_account_output(self, unlock_conditions = [unlock_condition.to_dict() for unlock_condition in unlock_conditions] - if native_tokens: - native_tokens = [native_token.to_dict() - for native_token in native_tokens] - if features: features = [feature.to_dict() for feature in features] if immutable_features: @@ -198,7 +188,6 @@ def build_account_output(self, 'unlockConditions': unlock_conditions, 'amount': amount, 'mana': mana, - 'nativeTokens': native_tokens, 'foundryCounter': foundry_counter, 'features': features, 'immutableFeatures': immutable_features @@ -208,7 +197,6 @@ def build_basic_output(self, unlock_conditions: List[UnlockCondition], amount: Optional[int] = None, mana: Optional[int] = None, - native_tokens: Optional[List[NativeToken]] = None, features: Optional[List[Feature]] = None) -> BasicOutput: """Build a BasicOutput. @@ -216,7 +204,6 @@ def build_basic_output(self, unlock_conditions: The unlock conditions for the new output. amount: The amount of base coins in the new output. mana: Amount of stored Mana held by this output. - native_tokens: Native tokens added to the new output. features: Features that add utility to the output but do not impose unlocking conditions. Returns: @@ -226,10 +213,6 @@ def build_basic_output(self, unlock_conditions = [unlock_condition.to_dict() for unlock_condition in unlock_conditions] - if native_tokens: - native_tokens = [native_token.to_dict() - for native_token in native_tokens] - if features: features = [feature.to_dict() for feature in features] @@ -243,7 +226,6 @@ def build_basic_output(self, 'unlockConditions': unlock_conditions, 'amount': amount, 'mana': mana, - 'nativeTokens': native_tokens, 'features': features, })) @@ -252,7 +234,6 @@ def build_foundry_output(self, token_scheme: SimpleTokenScheme, unlock_conditions: List[UnlockCondition], amount: Optional[int] = None, - native_tokens: Optional[List[NativeToken]] = None, features: Optional[List[Feature]] = None, immutable_features: Optional[List[Feature]] = None) -> FoundryOutput: """Build a FoundryOutput. @@ -262,7 +243,6 @@ def build_foundry_output(self, token_scheme: Defines the supply control scheme of the tokens controlled by the foundry. Currently only a simple scheme is supported. unlock_conditions: The unlock conditions for the new output. amount: The amount of base coins in the new output. - native_tokens: Native tokens added to the new output. features: Features that add utility to the output but do not impose unlocking conditions. immutable_features: Features that add utility to the output but do not impose unlocking conditions. These features need to be kept in future transitions of the UTXO state machine. @@ -273,10 +253,6 @@ def build_foundry_output(self, unlock_conditions = [unlock_condition.to_dict() for unlock_condition in unlock_conditions] - if native_tokens: - native_tokens = [native_token.__dict__ - for native_token in native_tokens] - if features: features = [feature.to_dict() for feature in features] if immutable_features: @@ -291,7 +267,6 @@ def build_foundry_output(self, 'tokenScheme': token_scheme.to_dict(), 'unlockConditions': unlock_conditions, 'amount': amount, - 'nativeTokens': native_tokens, 'features': features, 'immutableFeatures': immutable_features })) @@ -301,7 +276,6 @@ def build_nft_output(self, unlock_conditions: List[UnlockCondition], amount: Optional[int] = None, mana: Optional[int] = None, - native_tokens: Optional[List[NativeToken]] = None, features: Optional[List[Feature]] = None, immutable_features: Optional[List[Feature]] = None) -> NftOutput: """Build an NftOutput. @@ -311,7 +285,6 @@ def build_nft_output(self, unlock_conditions: The unlock conditions for the new output. amount: The amount of base coins in the new output. mana: Amount of stored Mana held by this output. - native_tokens: Native tokens added to the new output. features: Features that add utility to the output but do not impose unlocking conditions. immutable_features: Features that add utility to the output but do not impose unlocking conditions. These features need to be kept in future transitions of the UTXO state machine. @@ -322,10 +295,6 @@ def build_nft_output(self, unlock_conditions = [unlock_condition.to_dict() for unlock_condition in unlock_conditions] - if native_tokens: - native_tokens = [native_token.__dict__ - for native_token in native_tokens] - if features: features = [feature.to_dict() for feature in features] if immutable_features: @@ -343,7 +312,6 @@ def build_nft_output(self, 'unlockConditions': unlock_conditions, 'amount': amount, 'mana': mana, - 'nativeTokens': native_tokens, 'features': features, 'immutableFeatures': immutable_features })) @@ -373,21 +341,6 @@ def unhealthy_nodes(self) -> List[Dict[str, Any]]: """ return self._call_method('unhealthyNodes') - def sign_transaction( - self, - secret_manager: Union[LedgerNanoSecretManager, MnemonicSecretManager, SeedSecretManager, StrongholdSecretManager], - prepared_transaction_data: PreparedTransactionData) -> TransactionPayload: - """Sign a transaction. - - Args: - secret_manager: One of the supported secret managers. - prepared_transaction_data: a prepared transaction to sign. - """ - return from_dict(TransactionPayload, self._call_method('signTransaction', { - 'secretManager': secret_manager, - 'preparedTransactionData': prepared_transaction_data - })) - def build_basic_block( self, issuer_id: HexStr, diff --git a/bindings/python/iota_sdk/secret_manager/secret_manager.py b/bindings/python/iota_sdk/secret_manager/secret_manager.py index 97c9fec4cc..1aa65e9886 100644 --- a/bindings/python/iota_sdk/secret_manager/secret_manager.py +++ b/bindings/python/iota_sdk/secret_manager/secret_manager.py @@ -11,7 +11,7 @@ from iota_sdk.types.common import HexStr from iota_sdk.types.signature import Ed25519Signature, Bip44 from iota_sdk.types.transaction_data import PreparedTransactionData -from iota_sdk.types.payload import TransactionPayload +from iota_sdk.types.payload import SignedTransactionPayload class LedgerNanoSecretManager(dict): @@ -268,13 +268,13 @@ def sign_secp256k1_ecdsa(self, message: HexStr, chain: Bip44): }) def sign_transaction( - self, prepared_transaction_data: PreparedTransactionData) -> TransactionPayload: + self, prepared_transaction_data: PreparedTransactionData) -> SignedTransactionPayload: """Sign a transaction. Args: prepare_transaction_data: The prepared transaction data that needs to be signed. """ - return from_dict(TransactionPayload, self._call_method('signTransaction', { + return from_dict(SignedTransactionPayload, self._call_method('signTransaction', { 'preparedTransactionData': prepared_transaction_data.to_dict() })) diff --git a/bindings/python/iota_sdk/types/address.py b/bindings/python/iota_sdk/types/address.py index dc4294b271..36697010b5 100644 --- a/bindings/python/iota_sdk/types/address.py +++ b/bindings/python/iota_sdk/types/address.py @@ -3,7 +3,7 @@ from enum import IntEnum from dataclasses import dataclass, field -from typing import Any, Dict, List, TypeAlias, Union +from typing import Any, Dict, List, Optional, TypeAlias, Union from iota_sdk.types.common import HexStr, json @@ -14,14 +14,17 @@ class AddressType(IntEnum): ED25519 (0): Ed25519 address. ACCOUNT (8): Account address. NFT (16): Nft address. - IMPLICIT_ACCOUNT_CREATION (24): Implicit Account Creation address. - RESTRICTED (40): Address with restricted capabilities. + ANCHOR (24): Anchor address. + IMPLICIT_ACCOUNT_CREATION (32): Implicit Account Creation address. + RESTRICTED (48): Address with restricted capabilities. + """ ED25519 = 0 ACCOUNT = 8 NFT = 16 - IMPLICIT_ACCOUNT_CREATION = 24 - RESTRICTED = 40 + ANCHOR = 24 + IMPLICIT_ACCOUNT_CREATION = 32 + RESTRICTED = 48 @json @@ -63,6 +66,20 @@ class NFTAddress: type: int = field(default_factory=lambda: int(AddressType.NFT), init=False) +@json +@dataclass +class AnchorAddress: + """Represents an Anchor address. + Attributes: + anchor_id: The hex encoded anchor id. + """ + anchor_id: HexStr + type: int = field( + default_factory=lambda: int( + AddressType.ANCHOR), + init=False) + + @json @dataclass class ImplicitAccountCreationAddress: @@ -88,7 +105,8 @@ def from_dict(addr_dict: dict): """ Creates an implicit account creation address from a dictionary representation. """ - return ImplicitAccountCreationAddress(Ed25519Address(addr_dict['pubKeyHash'])) + return ImplicitAccountCreationAddress( + Ed25519Address(addr_dict['pubKeyHash'])) @json @@ -100,7 +118,7 @@ class RestrictedAddress: allowed_capabilities: The allowed capabilities bitflags. """ address: Union[Ed25519Address, AccountAddress, NFTAddress] - allowed_capabilities: HexStr = field(default='0x', init=False) + allowed_capabilities: Optional[HexStr] = field(default=None, init=False) type: int = field(default_factory=lambda: int( AddressType.RESTRICTED), init=False) @@ -109,7 +127,10 @@ def with_allowed_capabilities(self, capabilities: bytes): Attributes: capabilities: The allowed capabilities bitflags. """ - self.allowed_capabilities = '0x' + capabilities.hex() + if any(c != 0 for c in capabilities): + self.allowed_capabilities = '0x' + capabilities.hex() + else: + self.allowed_capabilities = None @json @@ -123,8 +144,12 @@ class AddressWithUnspentOutputs(): output_ids: bool -Address: TypeAlias = Union[Ed25519Address, AccountAddress, - NFTAddress, ImplicitAccountCreationAddress, RestrictedAddress] +Address: TypeAlias = Union[Ed25519Address, + AccountAddress, + NFTAddress, + AnchorAddress, + ImplicitAccountCreationAddress, + RestrictedAddress] def deserialize_address(d: Dict[str, Any]) -> Address: @@ -141,6 +166,8 @@ def deserialize_address(d: Dict[str, Any]) -> Address: return AccountAddress.from_dict(d) if address_type == AddressType.NFT: return NFTAddress.from_dict(d) + if address_type == AddressType.ANCHOR: + return AnchorAddress.from_dict(d) if address_type == AddressType.IMPLICIT_ACCOUNT_CREATION: return ImplicitAccountCreationAddress.from_dict(d) if address_type == AddressType.RESTRICTED: diff --git a/bindings/python/iota_sdk/types/balance.py b/bindings/python/iota_sdk/types/balance.py index 5144c0897b..791dbb96c3 100644 --- a/bindings/python/iota_sdk/types/balance.py +++ b/bindings/python/iota_sdk/types/balance.py @@ -5,7 +5,7 @@ from typing import List, Optional from dataclasses import dataclass, field from dataclasses_json import config -from iota_sdk.types.common import HexStr, json +from iota_sdk.types.common import hex_str_decoder, HexStr, json @json @@ -62,8 +62,14 @@ class NativeTokensBalance: metadata: Some metadata of the native token. """ token_id: HexStr - total: HexStr - available: HexStr + total: int = field(metadata=config( + encoder=hex, + decoder=hex_str_decoder, + )) + available: int = field(metadata=config( + encoder=hex, + decoder=hex_str_decoder, + )) metadata: Optional[HexStr] diff --git a/bindings/python/iota_sdk/types/block/basic.py b/bindings/python/iota_sdk/types/block/basic.py index 77495d54c8..b7c6661958 100644 --- a/bindings/python/iota_sdk/types/block/basic.py +++ b/bindings/python/iota_sdk/types/block/basic.py @@ -31,5 +31,5 @@ class BasicBlock: )) payload: Optional[Payload] = None type: int = field( - default_factory=lambda: BlockType.Basic, + default_factory=lambda: int(BlockType.Basic), init=False) diff --git a/bindings/python/iota_sdk/types/block/validation.py b/bindings/python/iota_sdk/types/block/validation.py index bd99ea61e1..9f9c070732 100644 --- a/bindings/python/iota_sdk/types/block/validation.py +++ b/bindings/python/iota_sdk/types/block/validation.py @@ -28,5 +28,5 @@ class ValidationBlock: highest_supported_version: int protocol_parameters_hash: HexStr type: int = field( - default_factory=lambda: BlockType.Validation, + default_factory=lambda: int(BlockType.Validation), init=False) diff --git a/bindings/python/iota_sdk/types/common.py b/bindings/python/iota_sdk/types/common.py index 261f62e3f2..3cd9ee4218 100644 --- a/bindings/python/iota_sdk/types/common.py +++ b/bindings/python/iota_sdk/types/common.py @@ -112,6 +112,11 @@ def opt_int_encoder(value): return None +def hex_str_decoder(value: str) -> int: + """Parses a given string as a hexadecimal integer.""" + return int(value, 16) + + @json @dataclass class AddressAndAmount(): diff --git a/bindings/python/iota_sdk/types/essence.py b/bindings/python/iota_sdk/types/essence.py deleted file mode 100644 index 32df2042e6..0000000000 --- a/bindings/python/iota_sdk/types/essence.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2023 IOTA Stiftung -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations -from enum import IntEnum -from typing import TYPE_CHECKING, Optional, List, TypeAlias - -from dataclasses import dataclass, field - -from iota_sdk.types.common import HexStr, json, SlotIndex -from iota_sdk.types.mana import ManaAllotment -from iota_sdk.types.input import UtxoInput -from iota_sdk.types.context_input import ContextInput -from iota_sdk.types.output import Output - -# Required to prevent circular import -if TYPE_CHECKING: - from iota_sdk.types.payload import Payload - - -class EssenceType(IntEnum): - """Block payload types. - - Attributes: - RegularTransactionEssence (2): A Regular Transaction Essence. - """ - RegularTransactionEssence = 2 - - -@json -@dataclass -class RegularTransactionEssence: - """Describes the essence data making up a transaction by defining its inputs, outputs, and an optional payload. - - Attributes: - network_id: The unique value denoting whether the block was meant for mainnet, shimmer, testnet, or a private network. - It consists of the first 8 bytes of the BLAKE2b-256 hash of the network name. - creation_slot: The slot index in which the transaction was created. - inputs: The inputs to consume in order to fund the outputs of the Transaction Payload. - inputs_commitment: BLAKE2b-256 hash serving as a commitment to the serialized outputs referenced by Inputs. - outputs: The outputs that are created by the Transaction Payload - context_inputs: The inputs that provide additional contextual information for the execution of a transaction. - allotments: The allotments of Mana which which will be added upon commitment of the slot. - capabilities: The capability bitflags of the transaction. - payload: An optional tagged data payload - """ - network_id: str - creation_slot: SlotIndex - inputs: List[UtxoInput] - inputs_commitment: HexStr - outputs: List[Output] - context_inputs: Optional[List[ContextInput]] = None - allotments: Optional[List[ManaAllotment]] = None - capabilities: HexStr = field(default='0x', init=False) - payload: Optional[Payload] = None - type: int = field( - default_factory=lambda: EssenceType.RegularTransactionEssence, - init=False) - - def with_capabilities(self, capabilities: bytes): - """Sets the transaction capabilities from a byte array. - Attributes: - capabilities: The transaction capabilities bitflags. - """ - self.capabilities = '0x' + capabilities.hex() - - -TransactionEssence: TypeAlias = RegularTransactionEssence diff --git a/bindings/python/iota_sdk/types/feature.py b/bindings/python/iota_sdk/types/feature.py index 7b392f2ca1..cb4064f4ac 100644 --- a/bindings/python/iota_sdk/types/feature.py +++ b/bindings/python/iota_sdk/types/feature.py @@ -7,7 +7,7 @@ from dataclasses_json import config from iota_sdk.types.address import Address, deserialize_address from iota_sdk.types.block_issuer_key import BlockIssuerKey -from iota_sdk.types.common import EpochIndex, HexStr, json, SlotIndex +from iota_sdk.types.common import EpochIndex, HexStr, hex_str_decoder, json, SlotIndex class FeatureType(IntEnum): @@ -18,15 +18,17 @@ class FeatureType(IntEnum): Issuer (1): The issuer feature. Metadata (2): The metadata feature. Tag (3): The tag feature. - BlockIssuer (4): The block issuer feature. - Staking (5): The staking feature. + NativeToken (4): The native token feature. + BlockIssuer (5): The block issuer feature. + Staking (6): The staking feature. """ Sender = 0 Issuer = 1 Metadata = 2 Tag = 3 - BlockIssuer = 4 - Staking = 5 + NativeToken = 4 + BlockIssuer = 5 + Staking = 6 @json @@ -88,6 +90,22 @@ class TagFeature: type: int = field(default_factory=lambda: int(FeatureType.Tag), init=False) +@json +@dataclass +class NativeTokenFeature: + """Contains a native token. + id: The unique identifier of the native token. + amount: The amount of native tokens. + """ + id: HexStr + amount: int = field(metadata=config( + encoder=hex, + decoder=hex_str_decoder, + )) + type: int = field(default_factory=lambda: int( + FeatureType.NativeToken), init=False) + + @json @dataclass class BlockIssuerFeature: @@ -129,7 +147,7 @@ class StakingFeature: Feature: TypeAlias = Union[SenderFeature, IssuerFeature, - MetadataFeature, TagFeature, BlockIssuerFeature, StakingFeature] + MetadataFeature, TagFeature, NativeTokenFeature, BlockIssuerFeature, StakingFeature] def deserialize_feature(d: Dict[str, Any]) -> Feature: @@ -148,6 +166,8 @@ def deserialize_feature(d: Dict[str, Any]) -> Feature: return MetadataFeature.from_dict(d) if feature_type == FeatureType.Tag: return TagFeature.from_dict(d) + if feature_type == FeatureType.NativeToken: + return NativeTokenFeature.from_dict(d) if feature_type == FeatureType.BlockIssuer: return BlockIssuerFeature.from_dict(d) if feature_type == FeatureType.Staking: diff --git a/bindings/python/iota_sdk/types/native_token.py b/bindings/python/iota_sdk/types/native_token.py index 7232f8dacc..c382c31070 100644 --- a/bindings/python/iota_sdk/types/native_token.py +++ b/bindings/python/iota_sdk/types/native_token.py @@ -1,9 +1,10 @@ # Copyright 2023 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass +from dataclasses import dataclass, field +from dataclasses_json import config -from iota_sdk.types.common import HexStr, json +from iota_sdk.types.common import hex_str_decoder, HexStr, json @json @@ -16,4 +17,7 @@ class NativeToken(): amount: The amount of native tokens. """ id: HexStr - amount: HexStr + amount: int = field(metadata=config( + encoder=hex, + decoder=hex_str_decoder, + )) diff --git a/bindings/python/iota_sdk/types/output.py b/bindings/python/iota_sdk/types/output.py index 3bf4a93186..345bd73fab 100644 --- a/bindings/python/iota_sdk/types/output.py +++ b/bindings/python/iota_sdk/types/output.py @@ -7,10 +7,9 @@ from dataclasses import dataclass, field from dataclasses_json import config from iota_sdk.types.common import HexStr, json, EpochIndex -from iota_sdk.types.feature import deserialize_features, SenderFeature, IssuerFeature, MetadataFeature, TagFeature -from iota_sdk.types.native_token import NativeToken +from iota_sdk.types.feature import deserialize_features, SenderFeature, IssuerFeature, MetadataFeature, TagFeature, NativeTokenFeature from iota_sdk.types.token_scheme import SimpleTokenScheme -from iota_sdk.types.unlock_condition import deserialize_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition, ExpirationUnlockCondition, ImmutableAccountAddressUnlockCondition +from iota_sdk.types.unlock_condition import deserialize_unlock_conditions, AddressUnlockCondition, StateControllerAddressUnlockCondition, GovernorAddressUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition, ExpirationUnlockCondition, ImmutableAccountAddressUnlockCondition class OutputType(IntEnum): @@ -19,15 +18,18 @@ class OutputType(IntEnum): Attributes: Basic (0): A basic output. Account (1): An account output. - Foundry (2): A foundry output. - Nft (3): An NFT output. - Delegation (4): A delegation output. + Anchor (2): An anchor output. + Foundry (3): A foundry output. + Nft (4): An NFT output. + Delegation (5): A delegation output. + """ Basic = 0 Account = 1 - Foundry = 2 - Nft = 3 - Delegation = 4 + Anchor = 2 + Foundry = 3 + Nft = 4 + Delegation = 5 @json @@ -43,8 +45,6 @@ class BasicOutput: The conditions to unlock the output. features : Features that add utility to the output but do not impose unlocking conditions. - native_tokens : - Native tokens added to the new output. type : The type of output. """ @@ -59,11 +59,10 @@ class BasicOutput: decoder=deserialize_unlock_conditions )) features: Optional[List[Union[SenderFeature, - MetadataFeature, TagFeature]]] = field(default=None, - metadata=config( - decoder=deserialize_features - )) - native_tokens: Optional[List[NativeToken]] = None + MetadataFeature, TagFeature, NativeTokenFeature]]] = field(default=None, + metadata=config( + decoder=deserialize_features + )) type: int = field( default_factory=lambda: int( OutputType.Basic), @@ -87,8 +86,6 @@ class AccountOutput: A counter that denotes the number of foundries created by this account output. features : Features that add utility to the output but do not impose unlocking conditions. - native_tokens : - Native tokens added to the new output. immutable_features : Features that add utility to the output but do not impose unlocking conditions. These features need to be kept in future transitions of the UTXO state machine. type : @@ -116,13 +113,66 @@ class AccountOutput: metadata=config( decoder=deserialize_features )) - native_tokens: Optional[List[NativeToken]] = None type: int = field( default_factory=lambda: int( OutputType.Account), init=False) +@json +@dataclass +class AnchorOutput: + """Describes an anchor output. + Attributes: + amount : + The base coin amount of the output. + mana : + Amount of stored Mana held by this output. + anchor_id : + The anchor ID if it's an anchor output. + state_index : + A counter that must increase by 1 every time the anchor is state transitioned. + unlock_conditions: + The conditions to unlock the output. + features : + Features that add utility to the output but do not impose unlocking conditions. + immutable_features : + Features that add utility to the output but do not impose unlocking conditions. These features need to be kept in future transitions of the UTXO state machine. + state_metadata : + Metadata that can only be changed by the state controller. + type : + The type of output. + """ + amount: int = field(metadata=config( + encoder=str + )) + mana: int = field(metadata=config( + encoder=str + )) + anchor_id: HexStr + state_index: int + unlock_conditions: List[Union[StateControllerAddressUnlockCondition, + GovernorAddressUnlockCondition]] = field( + metadata=config( + decoder=deserialize_unlock_conditions + )) + features: Optional[List[Union[SenderFeature, + MetadataFeature]]] = field(default=None, + metadata=config( + decoder=deserialize_features + )) + immutable_features: Optional[List[Union[IssuerFeature, + MetadataFeature]]] = field(default=None, + metadata=config( + decoder=deserialize_features + )) + state_metadata: Optional[HexStr] = None + type: int = field( + default_factory=lambda: int( + OutputType.Anchor), + init=False) + + @json @dataclass class FoundryOutput: @@ -134,8 +184,6 @@ class FoundryOutput: The conditions to unlock the output. features : Features that add utility to the output but do not impose unlocking conditions. - native_tokens : - Native tokens added to the new output. immutable_features : Features that add utility to the output but do not impose unlocking conditions. These features need to be kept in future transitions of the UTXO state machine. serial_number : @@ -151,15 +199,14 @@ class FoundryOutput: serial_number: int token_scheme: SimpleTokenScheme unlock_conditions: List[ImmutableAccountAddressUnlockCondition] - features: Optional[List[MetadataFeature]] = field(default=None, - metadata=config( - decoder=deserialize_features - )) + features: Optional[List[Union[MetadataFeature, NativeTokenFeature]]] = field(default=None, + metadata=config( + decoder=deserialize_features + )) immutable_features: Optional[List[MetadataFeature]] = field(default=None, metadata=config( decoder=deserialize_features )) - native_tokens: Optional[List[NativeToken]] = None type: int = field( default_factory=lambda: int( OutputType.Foundry), @@ -181,8 +228,6 @@ class NftOutput: The NFT ID if it's an NFT output. features : Features that add utility to the output but do not impose unlocking conditions. - native_tokens : - Native tokens added to the new output. immutable_features : Features that add utility to the output but do not impose unlocking conditions. These features need to be kept in future transitions of the UTXO state machine. type : @@ -210,7 +255,6 @@ class NftOutput: metadata=config( decoder=deserialize_features )) - native_tokens: Optional[List[NativeToken]] = None type: int = field(default_factory=lambda: int(OutputType.Nft), init=False) @@ -245,8 +289,12 @@ class DelegationOutput: OutputType.Delegation), init=False) -Output: TypeAlias = Union[BasicOutput, AccountOutput, - FoundryOutput, NftOutput, DelegationOutput] +Output: TypeAlias = Union[BasicOutput, + AccountOutput, + AnchorOutput, + FoundryOutput, + NftOutput, + DelegationOutput] def deserialize_output(d: Dict[str, Any]) -> Output: @@ -261,6 +309,8 @@ def deserialize_output(d: Dict[str, Any]) -> Output: return BasicOutput.from_dict(d) if output_type == OutputType.Account: return AccountOutput.from_dict(d) + if output_type == OutputType.Anchor: + return AnchorOutput.from_dict(d) if output_type == OutputType.Foundry: return FoundryOutput.from_dict(d) if output_type == OutputType.Nft: diff --git a/bindings/python/iota_sdk/types/payload.py b/bindings/python/iota_sdk/types/payload.py index 9685c504d1..dd222b435a 100644 --- a/bindings/python/iota_sdk/types/payload.py +++ b/bindings/python/iota_sdk/types/payload.py @@ -6,7 +6,7 @@ from typing import Any, Dict, List, TypeAlias, Union from dataclasses import dataclass, field from iota_sdk.types.common import HexStr, json -from iota_sdk.types.essence import TransactionEssence +from iota_sdk.types.transaction import Transaction from iota_sdk.types.unlock import SignatureUnlock, ReferenceUnlock @@ -15,11 +15,11 @@ class PayloadType(IntEnum): Attributes: TaggedData (0): A tagged data payload. - Transaction (1): A transaction payload. + SignedTransaction (1): A signed transaction payload. CandidacyAnnouncement (2): A candidacy announcement payload. """ TaggedData = 0 - Transaction = 1 + SignedTransaction = 1 CandidacyAnnouncement = 2 @@ -42,18 +42,18 @@ class TaggedDataPayload: @json @dataclass -class TransactionPayload: - """A transaction payload. +class SignedTransactionPayload: + """A signed transaction payload. Attributes: - essence: The transaction essence. + transaction: The transaction. unlocks: The unlocks of the transaction. """ - essence: TransactionEssence + transaction: Transaction unlocks: List[Union[SignatureUnlock, ReferenceUnlock]] type: int = field( default_factory=lambda: int( - PayloadType.Transaction), + PayloadType.SignedTransaction), init=False) @@ -69,7 +69,7 @@ class CandidacyAnnouncementPayload: Payload: TypeAlias = Union[TaggedDataPayload, - TransactionPayload, CandidacyAnnouncementPayload] + SignedTransactionPayload, CandidacyAnnouncementPayload] def deserialize_payload(d: Dict[str, Any]) -> Payload: @@ -82,8 +82,8 @@ def deserialize_payload(d: Dict[str, Any]) -> Payload: payload_type = d['type'] if payload_type == PayloadType.TaggedData: return TaggedDataPayload.from_dict(d) - if payload_type == PayloadType.Transaction: - return TransactionPayload.from_dict(d) + if payload_type == PayloadType.SignedTransaction: + return SignedTransactionPayload.from_dict(d) if payload_type == PayloadType.CandidacyAnnouncement: return CandidacyAnnouncementPayload.from_dict(d) raise Exception(f'invalid payload type: {payload_type}') diff --git a/bindings/python/iota_sdk/types/send_params.py b/bindings/python/iota_sdk/types/send_params.py index 479b02569f..94a40383ec 100644 --- a/bindings/python/iota_sdk/types/send_params.py +++ b/bindings/python/iota_sdk/types/send_params.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from typing import Optional, List from dataclasses_json import config -from iota_sdk.types.common import HexStr, json +from iota_sdk.types.common import hex_str_decoder, HexStr, json from iota_sdk.types.native_token import NativeToken @@ -72,10 +72,12 @@ class CreateNativeTokenParams(): account_id: The ID of the corresponding account. """ circulating_supply: int = field(metadata=config( - encoder=str + encoder=hex, + decoder=hex_str_decoder, )) maximum_supply: int = field(metadata=config( - encoder=str + encoder=hex, + decoder=hex_str_decoder, )) foundry_metadata: Optional[str] = None account_id: Optional[str] = None @@ -87,7 +89,7 @@ class MintNftParams(): """Parameters for minting NFTs. Attributes: - address: A Bech32 encoded address to which the NFT will be minted. Default will use the first address of the account. + address: A Bech32 encoded address to which the NFT will be minted. Default will use the address of the wallet. sender: An NFT sender feature. metadata: An NFT metadata feature. tag: An NFT tag feature. @@ -108,7 +110,7 @@ class CreateAccountOutputParams(): """Parameters for creating accounts. Attributes: - address: A Bech32 encoded address which will control the account. Default will use the first address of the account. + address: A Bech32 encoded address which will control the account. Default will use the address of the wallet. immutable_metadata: Immutable account metadata. metadata: Account metadata. """ diff --git a/bindings/python/iota_sdk/types/signature.py b/bindings/python/iota_sdk/types/signature.py index 0112b9a3f5..1ccf1b3551 100644 --- a/bindings/python/iota_sdk/types/signature.py +++ b/bindings/python/iota_sdk/types/signature.py @@ -18,7 +18,7 @@ class Ed25519Signature: """ public_key: HexStr signature: HexStr - type: int = field(default_factory=lambda: 0, init=False) + type: int = field(default=0, init=False) Signature: TypeAlias = Ed25519Signature diff --git a/bindings/python/iota_sdk/types/token_scheme.py b/bindings/python/iota_sdk/types/token_scheme.py index d243509d0c..3a60e9282d 100644 --- a/bindings/python/iota_sdk/types/token_scheme.py +++ b/bindings/python/iota_sdk/types/token_scheme.py @@ -1,9 +1,10 @@ # Copyright 2023 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass, field from typing import TypeAlias -from iota_sdk.types.common import HexStr, json +from dataclasses import dataclass, field +from dataclasses_json import config +from iota_sdk.types.common import hex_str_decoder, json @json @@ -17,10 +18,19 @@ class SimpleTokenScheme: maximum_supply: The maximum supply of the token. type: The type code of the token scheme. """ - minted_tokens: HexStr - melted_tokens: HexStr - maximum_supply: HexStr - type: int = field(default_factory=lambda: 0, init=False) + minted_tokens: int = field(metadata=config( + encoder=hex, + decoder=hex_str_decoder, + )) + melted_tokens: int = field(metadata=config( + encoder=hex, + decoder=hex_str_decoder, + )) + maximum_supply: int = field(metadata=config( + encoder=hex, + decoder=hex_str_decoder, + )) + type: int = field(default=0, init=False) TokenScheme: TypeAlias = SimpleTokenScheme diff --git a/bindings/python/iota_sdk/types/transaction.py b/bindings/python/iota_sdk/types/transaction.py index 7ae9266109..23d6304432 100644 --- a/bindings/python/iota_sdk/types/transaction.py +++ b/bindings/python/iota_sdk/types/transaction.py @@ -2,51 +2,52 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations -from dataclasses import dataclass -from typing import List, Optional -from enum import Enum -from iota_sdk.types.common import HexStr, json -from iota_sdk.types.output_metadata import OutputWithMetadata -from iota_sdk.types.payload import TransactionPayload +from typing import TYPE_CHECKING, Optional, List +from dataclasses import dataclass, field -class InclusionState(str, Enum): - """Inclusion state variants of a transaction. +from iota_sdk.types.common import HexStr, json, SlotIndex +from iota_sdk.types.mana import ManaAllotment +from iota_sdk.types.input import UtxoInput +from iota_sdk.types.context_input import ContextInput +from iota_sdk.types.output import Output - Attributes: - Pending: The transaction is pending. - Confirmed: The transaction is confirmed. - Conflicting: The transaction is conflicting. - UnknownPruned: The transaction is unknown or already pruned. - """ - Pending = 'pending' - Confirmed = 'confirmed' - Conflicting = 'conflicting' - UnknownPruned = 'unknownPruned' +# Required to prevent circular import +if TYPE_CHECKING: + from iota_sdk.types.payload import Payload @json @dataclass class Transaction: - """A transaction with some metadata. + """A transaction consuming inputs, creating outputs and carrying an optional payload. Attributes: - payload: The transaction payload. - inclusion_state: The inclusion state of the transaction. - timestamp: The timestamp of the transaction. - transaction_id: The ID of the corresponding transaction. - network_id: The ID of the network this transaction was issued in. - incoming: Indicates whether the transaction was created by the wallet or whether it was sent by someone else and is incoming. - inputs: The inputs of the transaction. - note: A note attached to the transaction. - block_id: The ID of the block that holds the transaction. + network_id: The unique value denoting whether the block was meant for mainnet, shimmer, testnet, or a private network. + It consists of the first 8 bytes of the BLAKE2b-256 hash of the network name. + creation_slot: The slot index in which the transaction was created. + context_inputs: The inputs that provide additional contextual information for the execution of a transaction. + inputs: The inputs to consume in order to fund the outputs of the Transaction Payload. + allotments: The allotments of Mana which which will be added upon commitment of the slot. + capabilities: The capability bitflags of the transaction. + outputs: The outputs that are created by the Transaction Payload + payload: An optional tagged data payload """ - payload: TransactionPayload - inclusion_state: InclusionState - timestamp: int - transaction_id: HexStr - network_id: int - incoming: bool - inputs = List[OutputWithMetadata] - note: Optional[str] = None - block_id: Optional[HexStr] = None + network_id: str + creation_slot: SlotIndex + context_inputs: List[ContextInput] + inputs: List[UtxoInput] + allotments: List[ManaAllotment] + capabilities: Optional[HexStr] = field(default=None, init=False) + outputs: List[Output] + payload: Optional[Payload] = None + + def with_capabilities(self, capabilities: bytes): + """Sets the transaction capabilities from a byte array. + Attributes: + capabilities: The transaction capabilities bitflags. + """ + if any(c != 0 for c in capabilities): + self.capabilities = '0x' + capabilities.hex() + else: + self.capabilities = None diff --git a/bindings/python/iota_sdk/types/transaction_data.py b/bindings/python/iota_sdk/types/transaction_data.py index e7ab0f5092..0f38781bb4 100644 --- a/bindings/python/iota_sdk/types/transaction_data.py +++ b/bindings/python/iota_sdk/types/transaction_data.py @@ -7,8 +7,8 @@ from iota_sdk.types.address import Address from iota_sdk.types.output import Output from iota_sdk.types.output_metadata import OutputMetadata -from iota_sdk.types.essence import TransactionEssence -from iota_sdk.types.payload import TransactionPayload +from iota_sdk.types.transaction import Transaction +from iota_sdk.types.payload import SignedTransactionPayload from iota_sdk.types.signature import Bip44 from iota_sdk.types.common import json @@ -49,11 +49,11 @@ class PreparedTransactionData: """Helper class for offline signing. Attributes: - essence: The transaction essence. + transaction: The transaction. inputs_data: Data about the inputs which is required for signing. remainder: Data about a remainder. """ - essence: TransactionEssence + transaction: Transaction inputs_data: List[InputSigningData] remainder: Optional[RemainderData] = None @@ -64,8 +64,8 @@ class SignedTransactionData: """Helper class for offline signing. Attributes: - transaction_payload: The transaction payload. + payload: The transaction payload. inputs_data: Data about the inputs consumed in the transaction. """ - transaction_payload: TransactionPayload + payload: SignedTransactionPayload inputs_data: List[InputSigningData] diff --git a/bindings/python/iota_sdk/types/transaction_with_metadata.py b/bindings/python/iota_sdk/types/transaction_with_metadata.py new file mode 100644 index 0000000000..53dba6cd44 --- /dev/null +++ b/bindings/python/iota_sdk/types/transaction_with_metadata.py @@ -0,0 +1,52 @@ +# Copyright 2023 IOTA Stiftung +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations +from dataclasses import dataclass +from typing import List, Optional +from enum import Enum +from iota_sdk.types.common import HexStr, json +from iota_sdk.types.output_metadata import OutputWithMetadata +from iota_sdk.types.payload import SignedTransactionPayload + + +class InclusionState(str, Enum): + """Inclusion state variants of a transaction. + + Attributes: + Pending: The transaction is pending. + Confirmed: The transaction is confirmed. + Conflicting: The transaction is conflicting. + UnknownPruned: The transaction is unknown or already pruned. + """ + Pending = 'pending' + Confirmed = 'confirmed' + Conflicting = 'conflicting' + UnknownPruned = 'unknownPruned' + + +@json +@dataclass +class TransactionWithMetadata: + """A transaction with some metadata. + + Attributes: + payload: The transaction payload. + inclusion_state: The inclusion state of the transaction. + timestamp: The timestamp of the transaction. + transaction_id: The ID of the corresponding transaction. + network_id: The ID of the network this transaction was issued in. + incoming: Indicates whether the transaction was created by the wallet or whether it was sent by someone else and is incoming. + inputs: The inputs of the transaction. + note: A note attached to the transaction. + block_id: The ID of the block that holds the transaction. + """ + payload: SignedTransactionPayload + inclusion_state: InclusionState + timestamp: int + transaction_id: HexStr + network_id: int + incoming: bool + inputs = List[OutputWithMetadata] + note: Optional[str] = None + block_id: Optional[HexStr] = None diff --git a/bindings/python/iota_sdk/types/unlock.py b/bindings/python/iota_sdk/types/unlock.py index 83abb597aa..4a99672783 100644 --- a/bindings/python/iota_sdk/types/unlock.py +++ b/bindings/python/iota_sdk/types/unlock.py @@ -4,7 +4,7 @@ from __future__ import annotations from dataclasses import dataclass, field from enum import IntEnum -from typing import Dict, List, Union, Any +from typing import Dict, List, TypeAlias, Union, Any from iota_sdk.types.signature import Ed25519Signature from iota_sdk.types.common import json @@ -16,25 +16,20 @@ class UnlockType(IntEnum): Signature (0): An unlock holding a signature unlocking one or more inputs. Reference (1): An unlock which must reference a previous unlock which unlocks also the input at the same index as this Reference Unlock. Account (2): An unlock which must reference a previous unlock which unlocks the account that the input is locked to. - Nft (3): An unlock which must reference a previous unlock which unlocks the NFT that the input is locked to. + Anchor (3): An unlock which must reference a previous unlock which unlocks the anchor that the input is locked to. + Nft (4): An unlock which must reference a previous unlock which unlocks the NFT that the input is locked to. + """ Signature = 0 Reference = 1 Account = 2 - Nft = 3 + Anchor = 3 + Nft = 4 @json @dataclass -class Unlock: - """Unlock type. - """ - type: int - - -@json -@dataclass -class SignatureUnlock(Unlock): +class SignatureUnlock: """An unlock holding a signature unlocking one or more inputs. """ signature: Ed25519Signature @@ -46,7 +41,7 @@ class SignatureUnlock(Unlock): @json @dataclass -class ReferenceUnlock(Unlock): +class ReferenceUnlock: """An unlock which must reference a previous unlock which unlocks also the input at the same index as this Reference Unlock. """ reference: int @@ -68,6 +63,18 @@ class AccountUnlock: init=False) +@json +@dataclass +class AnchorUnlock: + """An unlock which must reference a previous unlock which unlocks the anchor that the input is locked to. + """ + reference: int + type: int = field( + default_factory=lambda: int( + UnlockType.Anchor), + init=False) + + @json @dataclass class NftUnlock: @@ -77,8 +84,14 @@ class NftUnlock: type: int = field(default_factory=lambda: int(UnlockType.Nft), init=False) -def deserialize_unlock(d: Dict[str, Any]) -> Union[SignatureUnlock, - ReferenceUnlock, AccountUnlock, NftUnlock]: +Unlock: TypeAlias = Union[SignatureUnlock, + ReferenceUnlock, + AccountUnlock, + AnchorUnlock, + NftUnlock] + + +def deserialize_unlock(d: Dict[str, Any]) -> Unlock: """ Takes a dictionary as input and returns an instance of a specific class based on the value of the 'type' key in the dictionary. @@ -92,13 +105,14 @@ def deserialize_unlock(d: Dict[str, Any]) -> Union[SignatureUnlock, return ReferenceUnlock.from_dict(d) if unlock_type == UnlockType.Account: return AccountUnlock.from_dict(d) + if unlock_type == UnlockType.Anchor: + return AnchorUnlock.from_dict(d) if unlock_type == UnlockType.Nft: return NftUnlock.from_dict(d) raise Exception(f'invalid unlock type: {unlock_type}') -def deserialize_unlocks(dicts: List[Dict[str, Any]]) -> List[Union[SignatureUnlock, - ReferenceUnlock, AccountUnlock, NftUnlock]]: +def deserialize_unlocks(dicts: List[Dict[str, Any]]) -> List[Unlock]: """ Takes a list of dictionaries as input and returns a list with specific instances of classes based on the value of the 'type' key in the dictionary. diff --git a/bindings/python/iota_sdk/utils.py b/bindings/python/iota_sdk/utils.py index e889f74cfb..687a507d9c 100644 --- a/bindings/python/iota_sdk/utils.py +++ b/bindings/python/iota_sdk/utils.py @@ -8,12 +8,12 @@ from iota_sdk.types.signature import Ed25519Signature from iota_sdk.types.address import Address, deserialize_address from iota_sdk.types.common import HexStr -from iota_sdk.types.essence import TransactionEssence +from iota_sdk.types.transaction import Transaction from iota_sdk.types.node_info import ProtocolParameters from iota_sdk.types.output_id import OutputId from iota_sdk.types.output import Output from iota_sdk.external import call_utils_method -from iota_sdk.types.payload import TransactionPayload +from iota_sdk.types.payload import SignedTransactionPayload # Required to prevent circular import if TYPE_CHECKING: @@ -177,19 +177,19 @@ def block_id(block: SignedBlock, params: ProtocolParameters) -> HexStr: }) @staticmethod - def transaction_id(transaction_payload: TransactionPayload) -> HexStr: + def transaction_id(payload: SignedTransactionPayload) -> HexStr: """ Compute the transaction ID (Blake2b256 hash of the provided transaction payload) of a transaction payload. """ return _call_method('transactionId', { - 'payload': transaction_payload.as_dict() + 'payload': payload.as_dict() }) @staticmethod - def transaction_signing_hash(essence: TransactionEssence) -> HexStr: + def transaction_signing_hash(transaction: Transaction) -> HexStr: """ Compute the signing hash of a transaction. """ return _call_method('transactionSigningHash', { - 'essence': essence.to_dict(), + 'transaction': transaction.to_dict(), }) @staticmethod diff --git a/bindings/python/iota_sdk/wallet/account.py b/bindings/python/iota_sdk/wallet/account.py index 9f83e8d316..1608522b95 100644 --- a/bindings/python/iota_sdk/wallet/account.py +++ b/bindings/python/iota_sdk/wallet/account.py @@ -20,7 +20,7 @@ from iota_sdk.types.output_params import OutputParams from iota_sdk.types.transaction_data import PreparedTransactionData, SignedTransactionData from iota_sdk.types.send_params import CreateAccountOutputParams, CreateNativeTokenParams, MintNftParams, SendNativeTokenParams, SendNftParams, SendParams -from iota_sdk.types.transaction import Transaction +from iota_sdk.types.transaction_with_metadata import TransactionWithMetadata from iota_sdk.types.transaction_options import TransactionOptions from iota_sdk.types.consolidation_params import ConsolidationParams @@ -82,7 +82,7 @@ def get_metadata(self) -> AccountMetadata: self.meta["alias"], self.meta["coinType"], self.meta["index"]) def burn( - self, burn: Burn, options: Optional[TransactionOptions] = None) -> Transaction: + self, burn: Burn, options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """A generic function that can be used to burn native tokens, nfts, foundries and aliases. """ return self.prepare_burn(burn, options).send() @@ -129,7 +129,7 @@ def prepare_burn_nft(self, return PreparedTransaction(self, prepared) def consolidate_outputs( - self, params: ConsolidationParams) -> Transaction: + self, params: ConsolidationParams) -> TransactionWithMetadata: """Consolidate outputs. """ return self.prepare_consolidate_outputs(params).send() @@ -147,7 +147,7 @@ def prepare_consolidate_outputs( def create_account_output(self, params: Optional[CreateAccountOutputParams] = None, - options: Optional[TransactionOptions] = None) -> Transaction: + options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Create an account output. """ return self.prepare_create_account_output(params, options).send() @@ -221,10 +221,11 @@ def get_output(self, output_id: OutputId) -> OutputData: } )) - def get_transaction(self, transaction_id: HexStr) -> Transaction: + def get_transaction( + self, transaction_id: HexStr) -> TransactionWithMetadata: """Get transaction. """ - return Transaction.from_dict(self._call_account_method( + return TransactionWithMetadata.from_dict(self._call_account_method( 'getTransaction', { 'transactionId': transaction_id } @@ -270,21 +271,44 @@ def unspent_outputs( ) return [from_dict(OutputData, o) for o in outputs] - def incoming_transactions(self) -> List[Transaction]: + def implicit_account_creation_address(self) -> str: + """Returns the implicit account creation address of the wallet if it is Ed25519 based. + """ + return self._call_account_method( + 'implicitAccountCreationAddress' + ) + + def accounts(self) -> List[OutputData]: + """Returns the accounts of the wallet. + """ + outputs = self._call_account_method( + 'accounts' + ) + return [from_dict(OutputData, o) for o in outputs] + + def implicit_accounts(self) -> List[OutputData]: + """Returns the implicit accounts of the wallet. + """ + outputs = self._call_account_method( + 'implicitAccounts' + ) + return [from_dict(OutputData, o) for o in outputs] + + def incoming_transactions(self) -> List[TransactionWithMetadata]: """Returns all incoming transactions of the account. """ transactions = self._call_account_method( 'incomingTransactions' ) - return [Transaction.from_dict(tx) for tx in transactions] + return [TransactionWithMetadata.from_dict(tx) for tx in transactions] - def transactions(self) -> List[Transaction]: + def transactions(self) -> List[TransactionWithMetadata]: """Returns all transaction of the account. """ transactions = self._call_account_method( 'transactions' ) - return [Transaction.from_dict(tx) for tx in transactions] + return [TransactionWithMetadata.from_dict(tx) for tx in transactions] def pending_transactions(self): """Returns all pending transactions of the account. @@ -292,10 +316,10 @@ def pending_transactions(self): transactions = self._call_account_method( 'pendingTransactions' ) - return [Transaction.from_dict(tx) for tx in transactions] + return [TransactionWithMetadata.from_dict(tx) for tx in transactions] def create_native_token(self, params: CreateNativeTokenParams, - options: Optional[TransactionOptions] = None) -> Transaction: + options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Create native token. """ return self.prepare_create_native_token(params, options).send() @@ -316,7 +340,7 @@ def prepare_create_native_token(self, params: CreateNativeTokenParams, def melt_native_token(self, token_id: HexStr, melt_amount: int, - options: Optional[TransactionOptions] = None) -> Transaction: + options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Melt native tokens. This happens with the foundry output which minted them, by increasing it's `melted_tokens` field. """ @@ -340,7 +364,7 @@ def prepare_melt_native_token(self, return PreparedTransaction(self, prepared) def mint_native_token(self, token_id: HexStr, mint_amount: int, - options: Optional[TransactionOptions] = None) -> Transaction: + options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Mint additional native tokens. """ return self.prepare_mint_native_token( @@ -360,7 +384,7 @@ def prepare_mint_native_token(self, token_id: HexStr, mint_amount: int, return PreparedTransaction(self, prepared) def mint_nfts(self, params: List[MintNftParams], - options: Optional[TransactionOptions] = None) -> Transaction: + options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Mint NFTs. """ return self.prepare_mint_nfts(params, options).send() @@ -413,7 +437,7 @@ def prepare_send(self, params: List[SendParams], return PreparedTransaction(self, prepared) def send_transaction( - self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> Transaction: + self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send a transaction. """ return self.prepare_transaction(outputs, options).send() @@ -455,10 +479,10 @@ def sync(self, options: Optional[SyncOptions] = None) -> Balance: )) def send(self, amount: int, address: str, - options: Optional[TransactionOptions] = None) -> Transaction: + options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send base coins. """ - return Transaction.from_dict(self._call_account_method( + return TransactionWithMetadata.from_dict(self._call_account_method( 'send', { 'amount': str(amount), 'address': address, @@ -467,10 +491,10 @@ def send(self, amount: int, address: str, )) def send_with_params( - self, params: List[SendParams], options: Optional[TransactionOptions] = None) -> Transaction: + self, params: List[SendParams], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send base coins to multiple addresses or with additional parameters. """ - return Transaction.from_dict(self._call_account_method( + return TransactionWithMetadata.from_dict(self._call_account_method( 'sendWithParams', { 'params': [param.to_dict() for param in params], 'options': options @@ -478,7 +502,7 @@ def send_with_params( )) def send_native_token( - self, params: List[SendNativeTokenParams], options: Optional[TransactionOptions] = None) -> Transaction: + self, params: List[SendNativeTokenParams], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send native tokens. """ return self.prepare_send_native_token(params, options).send() @@ -498,7 +522,7 @@ def prepare_send_native_token( return PreparedTransaction(self, prepared) def send_nft(self, params: List[SendNftParams], - options: Optional[TransactionOptions] = None) -> Transaction: + options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send nft. """ return self.prepare_send_nft(params, options).send() @@ -534,51 +558,51 @@ def set_default_sync_options(self, options: SyncOptions): } ) - def sign_transaction_essence( + def sign_transaction( self, prepared_transaction_data: PreparedTransactionData) -> SignedTransactionData: - """Sign a transaction essence. + """Sign a transaction. """ return SignedTransactionData.from_dict(self._call_account_method( - 'signTransactionEssence', { + 'signTransaction', { 'preparedTransactionData': prepared_transaction_data } )) def sign_and_submit_transaction( - self, prepared_transaction_data: PreparedTransactionData) -> Transaction: + self, prepared_transaction_data: PreparedTransactionData) -> TransactionWithMetadata: """Validate the transaction, sign it, submit it to a node and store it in the account. """ - return Transaction.from_dict(self._call_account_method( + return TransactionWithMetadata.from_dict(self._call_account_method( 'signAndSubmitTransaction', { 'preparedTransactionData': prepared_transaction_data } )) def submit_and_store_transaction( - self, signed_transaction_data: SignedTransactionData) -> Transaction: + self, signed_transaction_data: SignedTransactionData) -> TransactionWithMetadata: """Submit and store transaction. """ - return Transaction.from_dict(self._call_account_method( + return TransactionWithMetadata.from_dict(self._call_account_method( 'submitAndStoreTransaction', { 'signedTransactionData': signed_transaction_data } )) def claim_outputs( - self, output_ids_to_claim: List[OutputId]) -> Transaction: + self, output_ids_to_claim: List[OutputId]) -> TransactionWithMetadata: """Claim outputs. """ - return Transaction.from_dict(self._call_account_method( + return TransactionWithMetadata.from_dict(self._call_account_method( 'claimOutputs', { 'outputIdsToClaim': output_ids_to_claim } )) def send_outputs( - self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> Transaction: + self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send outputs in a transaction. """ - return Transaction.from_dict(self._call_account_method( + return TransactionWithMetadata.from_dict(self._call_account_method( 'sendOutputs', { 'outputs': outputs, 'options': options, diff --git a/bindings/python/iota_sdk/wallet/prepared_transaction.py b/bindings/python/iota_sdk/wallet/prepared_transaction.py index c2a9e0b802..80c3fde12e 100644 --- a/bindings/python/iota_sdk/wallet/prepared_transaction.py +++ b/bindings/python/iota_sdk/wallet/prepared_transaction.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Dict, Union from dacite import from_dict -from iota_sdk.types.transaction import Transaction +from iota_sdk.types.transaction_with_metadata import TransactionWithMetadata from iota_sdk.types.transaction_data import PreparedTransactionData # Required to prevent circular import @@ -42,7 +42,7 @@ def prepared_transaction_data(self) -> PreparedTransactionData: self.prepared_transaction_data_dto, PreparedTransactionData) else from_dict( PreparedTransactionData, self.prepared_transaction_data_dto) - def send(self) -> Transaction: + def send(self) -> TransactionWithMetadata: """Send a transaction. Internally just calls `sign_and_submit_transaction`. Returns: @@ -51,13 +51,13 @@ def send(self) -> Transaction: return self.sign_and_submit_transaction() def sign(self): - """Sign a prepared transaction essence using the account's private key and returns - the signed transaction essence. + """Sign a prepared transaction using the account's private key and returns + the signed transaction. """ - return self.account.sign_transaction_essence( + return self.account.sign_transaction( self.prepared_transaction_data()) - def sign_and_submit_transaction(self) -> Transaction: + def sign_and_submit_transaction(self) -> TransactionWithMetadata: """Sign and submit a transaction using prepared transaction data. Returns: diff --git a/bindings/python/iota_sdk/wallet/sync_options.py b/bindings/python/iota_sdk/wallet/sync_options.py index 324ab7c5c6..4e4526ebe7 100644 --- a/bindings/python/iota_sdk/wallet/sync_options.py +++ b/bindings/python/iota_sdk/wallet/sync_options.py @@ -1,7 +1,7 @@ # Copyright 2023 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 -from typing import List, Optional +from typing import Optional from dataclasses import dataclass from iota_sdk.types.common import json @@ -62,17 +62,6 @@ class SyncOptions(): """The synchronization options. **Attributes** - addresses : - Specific Bech32 encoded addresses of the account to sync. If addresses are provided, - then `address_start_index` will be ignored. - address_start_index : - Address index from which to start syncing addresses. 0 by default. - Using a higher index will be faster because addresses with a lower index will be skipped, - but this could result in a wrong balance for that reason. - address_start_index_internal : - Address index from which to start syncing internal addresses. 0 by default. - Using a higher index will be faster because addresses with a lower index will be skipped, - but this could result in a wrong balance for internal addresses for that reason. force_syncing : Usually syncing is skipped if it's called in between 200ms, because there can only be new changes every milestone and calling it twice "at the same time" will not return new data. @@ -96,9 +85,6 @@ class SyncOptions(): Sync native token foundries, so their metadata can be returned in the balance. """ - addresses: Optional[List[str]] = None - address_start_index: Optional[int] = None - address_start_index_internal: Optional[int] = None force_syncing: Optional[bool] = None sync_incoming_transactions: Optional[bool] = None sync_pending_transactions: Optional[bool] = None diff --git a/bindings/python/tests/test_block.py b/bindings/python/tests/test_block.py index 222af92562..8a76a91b87 100644 --- a/bindings/python/tests/test_block.py +++ b/bindings/python/tests/test_block.py @@ -85,22 +85,21 @@ def test_basic_block_with_tx_payload(): "shallowLikeParents": [], "maxBurnedMana": "180500", "payload": {"type": 1, - "essence": {"type": 1, - "networkId": "1856588631910923207", - "inputs": [{"type": 0, - "transactionId": "0xc6765035e75e319e9cd55ab16e7619f6cd658e7f421c71d9fe276c77fdf3f5b3", - "transactionOutputIndex": 1}], - "inputsCommitment": "0x2468f946993ac949c890d7f895797c6b86075dc1e1556f04f3772903eaf51932", - "outputs": [{"type": 3, - "amount": "1000000", - "unlockConditions": [{"type": 0, - "address": {"type": 0, - "pubKeyHash": "0xa119005b26d46fc74cf9188b3cef8d01623e68146741ee698cabefd425dc01be"}}]}, - {"type": 3, - "amount": "995000000", - "unlockConditions": [{"type": 0, - "address": {"type": 0, - "pubKeyHash": "0xa119005b26d46fc74cf9188b3cef8d01623e68146741ee698cabefd425dc01be"}}]}]}, + "transaction": { + "networkId": "1856588631910923207", + "inputs": [{"type": 0, + "transactionId": "0xc6765035e75e319e9cd55ab16e7619f6cd658e7f421c71d9fe276c77fdf3f5b3", + "transactionOutputIndex": 1}], + "outputs": [{"type": 3, + "amount": "1000000", + "unlockConditions": [{"type": 0, + "address": {"type": 0, + "pubKeyHash": "0xa119005b26d46fc74cf9188b3cef8d01623e68146741ee698cabefd425dc01be"}}]}, + {"type": 3, + "amount": "995000000", + "unlockConditions": [{"type": 0, + "address": {"type": 0, + "pubKeyHash": "0xa119005b26d46fc74cf9188b3cef8d01623e68146741ee698cabefd425dc01be"}}]}]}, "unlocks": [{"type": 0, "signature": {"type": 0, "publicKey": "0xa7af600976f440ec97d7bddbf17eacf0bfbf710e8cfb4ae3eae475d4ae8e1b16", @@ -108,7 +107,7 @@ def test_basic_block_with_tx_payload(): block = BasicBlock.from_dict(block_dict) assert block.to_dict() == block_dict assert isinstance(block.payload, get_args(Payload)) - assert block.payload.type == PayloadType.Transaction + assert block.payload.type == PayloadType.SignedTransaction @pytest.mark.skip(reason="https://github.com/iotaledger/iota-sdk/issues/1387") @@ -117,8 +116,8 @@ def test_basic_block_with_tx_payload_all_output_types(): "type": 0, "strongParents": [ "0x053296e7434e8a4d602f8db30a5aaf16c01140212fe79d8132137cda1c38a60a", "0x559ec1d9a31c55bd27588ada2ade70fb5b13764ddd600e29c3b018761ba30e15", "0xe78e8cdbbeda89e3408eed51b77e0db5ba035f5f3bf79a8365435bba40697693", "0xee9d6e45dbc080694e6c827fecbc31ad9f654cf57404bc98f4cbca033f8e3139"], "weakParents": [], "shallowLikeParents": [], "payload": { - "type": 1, "essence": { - "type": 1, "networkId": "1856588631910923207", "inputs": [ + "type": 1, "transaction": { + "networkId": "1856588631910923207", "inputs": [ { "type": 0, "transactionId": "0xa49f5a764c3fe22f702b5b238a75a648faae1863f61c14fac51ba58d26acb823", "transactionOutputIndex": 9}, { "type": 0, "transactionId": "0x6f23b39ebe433f8b522d2e4360186cd3e6b21baf46c0a591c801161e505330b4", "transactionOutputIndex": 0}, { @@ -249,7 +248,7 @@ def test_basic_block_with_tx_payload_all_output_types(): block = BasicBlock.from_dict(block_dict) assert block.to_dict() == block_dict assert isinstance(block.payload, get_args(Payload)) - assert block.payload.type == PayloadType.Transaction + assert block.payload.type == PayloadType.SignedTransaction @pytest.mark.skip(reason="https://github.com/iotaledger/iota-sdk/issues/1387") @@ -264,28 +263,28 @@ def test_basic_block_with_tx_payload_with_tagged_data_payload(): "shallowLikeParents": [], "maxBurnedMana": "180500", "payload": {"type": 1, - "essence": {"type": 1, - "networkId": "1856588631910923207", - "inputs": [{"type": 0, - "transactionId": "0xeccfbdb73c0a4c9c0301b53a17e5aa301fbf0b079db9e88ff0e32e9e64214b28", - "transactionOutputIndex": 5}, - {"type": 0, - "transactionId": "0xf8052938858750c9c69b92b615a685fa2bb5833912b264142fc724e9510b0d0e", - "transactionOutputIndex": 0}], - "inputsCommitment": "0x9702f2a625db14db2f67289828a9fdbe342477393572b9165b19964b2449061a", - "outputs": [{"type": 3, - "amount": "1000000", - "unlockConditions": [{"type": 0, - "address": {"type": 0, - "pubKeyHash": "0x60200bad8137a704216e84f8f9acfe65b972d9f4155becb4815282b03cef99fe"}}]}, - {"type": 3, - "amount": "50600", - "unlockConditions": [{"type": 0, - "address": {"type": 0, - "pubKeyHash": "0x74e8b1f10396eb5e8aeb16d666416802722436a88b5dd1a88e59c170b724c9cc"}}]}], - "payload": {"type": 5, - "tag": "0x746167", - "data": "0x64617461"}}, + "transaction": { + "networkId": "1856588631910923207", + "inputs": [{"type": 0, + "transactionId": "0xeccfbdb73c0a4c9c0301b53a17e5aa301fbf0b079db9e88ff0e32e9e64214b28", + "transactionOutputIndex": 5}, + {"type": 0, + "transactionId": "0xf8052938858750c9c69b92b615a685fa2bb5833912b264142fc724e9510b0d0e", + "transactionOutputIndex": 0}], + "inputsCommitment": "0x9702f2a625db14db2f67289828a9fdbe342477393572b9165b19964b2449061a", + "outputs": [{"type": 3, + "amount": "1000000", + "unlockConditions": [{"type": 0, + "address": {"type": 0, + "pubKeyHash": "0x60200bad8137a704216e84f8f9acfe65b972d9f4155becb4815282b03cef99fe"}}]}, + {"type": 3, + "amount": "50600", + "unlockConditions": [{"type": 0, + "address": {"type": 0, + "pubKeyHash": "0x74e8b1f10396eb5e8aeb16d666416802722436a88b5dd1a88e59c170b724c9cc"}}]}], + "payload": {"type": 5, + "tag": "0x746167", + "data": "0x64617461"}}, "unlocks": [{"type": 0, "signature": {"type": 0, "publicKey": "0x67b7fc3f78763c9394fc4fcdb52cf3a973b6e064bdc3defb40a6cb2c880e6f5c", @@ -295,4 +294,4 @@ def test_basic_block_with_tx_payload_with_tagged_data_payload(): block = BasicBlock.from_dict(block_dict) assert block.to_dict() == block_dict assert isinstance(block.payload, get_args(Payload)) - assert block.payload.type == PayloadType.Transaction + assert block.payload.type == PayloadType.SignedTransaction diff --git a/bindings/python/tests/test_output.py b/bindings/python/tests/test_output.py index 9964d70b68..dabdffb0c9 100644 --- a/bindings/python/tests/test_output.py +++ b/bindings/python/tests/test_output.py @@ -169,7 +169,7 @@ def test_output(): assert account_output.to_dict() == account_output_dict foundry_output_dict = { - "type": 2, + "type": 3, "amount": "54700", "serialNumber": 1, "tokenScheme": { @@ -198,7 +198,7 @@ def test_output(): assert foundry_output.to_dict() == foundry_output_dict nft_output_dict = { - "type": 3, + "type": 4, "mana": "47800", "amount": "47800", "nftId": "0x90e84936bd0cffd1595d2a58f63b1a8d0d3e333ed893950a5f3f0043c6e59ec1", diff --git a/bindings/python/tox.ini b/bindings/python/tox.ini index 6bde2729d1..e261e437c0 100644 --- a/bindings/python/tox.ini +++ b/bindings/python/tox.ini @@ -1,13 +1,13 @@ [tox] # can't install from sdist because local pyo3 repo can't be included in the sdist skipsdist = true -envlist = py{39,310,311} +envlist = py{310,311,312} [gh-actions] python = - 3.9: py39 3.10: py310 3.11: py311 + 3.12: py312 [testenv] description = Run the unit tests under {basepython} diff --git a/bindings/wasm/examples/node.js b/bindings/wasm/examples/node.js index 6a779abebb..57489f2303 100644 --- a/bindings/wasm/examples/node.js +++ b/bindings/wasm/examples/node.js @@ -3,7 +3,7 @@ const console = require('console'); const fs = require('fs'); -const { Wallet, CoinType, initLogger } = require('../node/lib'); +const { Wallet, CoinType, initLogger, SecretManager } = require('../node/lib'); async function run() { try { @@ -19,27 +19,36 @@ async function run() { colorEnabled: true, }); + const mnemonicSecretManager = { + mnemonic: + 'inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak', + }; + + const secretManager = new SecretManager(mnemonicSecretManager); + + const walletAddress = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + accountIndex: 0, + range: { + start: 0, + end: 1, + }, + bech32Hrp: 'tst', + }); + const wallet = new Wallet({ + address: walletAddress[0], storagePath: './alice-database', - coinType: CoinType.Shimmer, + bipPath: { + coinType: CoinType.IOTA, + }, clientOptions: { nodes: ['https://api.testnet.shimmer.network'], }, - secretManager: { - mnemonic: - 'inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak', - }, + secretManager: mnemonicSecretManager, }); - const account = await wallet.createAccount({ - alias: 'Alice', - bech32Hrp: 'rms', - }); - - console.log('Account created:', account); - account.setAlias('new alias'); - - const balance = await account.sync(); + const balance = await wallet.sync(); console.log(balance); } diff --git a/bindings/wasm/test/account.spec.ts b/bindings/wasm/test/account.spec.ts index 37ac3ef02b..56a206d467 100644 --- a/bindings/wasm/test/account.spec.ts +++ b/bindings/wasm/test/account.spec.ts @@ -29,7 +29,7 @@ async function run() { expect(account.getMetadata().alias).toBe('Alice'); - const balance: Balance = await account.sync(); + const balance: Balance = await wallet.sync(); expect(balance.baseCoin.available).not.toBeNaN(); await account.setAlias('new alias'); diff --git a/cli/src/account.rs b/cli/src/account.rs deleted file mode 100644 index 70c2bff6a8..0000000000 --- a/cli/src/account.rs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use clap::Parser; -use colored::Colorize; -use iota_sdk::wallet::{Account, Wallet}; -use rustyline::{error::ReadlineError, history::MemHistory, Config, Editor}; - -use crate::{ - command::{ - account::{ - addresses_command, balance_command, burn_native_token_command, burn_nft_command, claim_command, - claimable_outputs_command, consolidate_command, create_account_output_command, create_native_token_command, - decrease_voting_power_command, destroy_account_command, destroy_foundry_command, faucet_command, - increase_voting_power_command, melt_native_token_command, mint_native_token, mint_nft_command, - new_address_command, node_info_command, output_command, outputs_command, participation_overview_command, - send_command, send_native_token_command, send_nft_command, stop_participating_command, sync_command, - transaction_command, transactions_command, unspent_outputs_command, vote_command, voting_output_command, - voting_power_command, AccountCli, AccountCommand, - }, - account_completion::AccountPromptHelper, - }, - error::Error, - helper::bytes_from_hex_or_file, - println_log_error, -}; - -// loop on the account prompt -pub async fn account_prompt(wallet: &Wallet, mut account: Account) -> Result<(), Error> { - let config = Config::builder() - .auto_add_history(true) - .history_ignore_space(true) - .completion_type(rustyline::CompletionType::List) - .edit_mode(rustyline::EditMode::Emacs) - .build(); - - let mut rl = Editor::with_history(config, MemHistory::with_config(config))?; - rl.set_helper(Some(AccountPromptHelper::default())); - - loop { - match account_prompt_internal(wallet, &account, &mut rl).await { - Ok(res) => match res { - AccountPromptResponse::Reprompt => (), - AccountPromptResponse::Done => { - return Ok(()); - } - AccountPromptResponse::Switch(new_account) => { - account = new_account; - } - }, - Err(e) => { - println_log_error!("{e}"); - } - } - } -} - -pub enum AccountPromptResponse { - Reprompt, - Done, - Switch(Account), -} - -// loop on the account prompt -pub async fn account_prompt_internal( - wallet: &Wallet, - account: &Account, - rl: &mut Editor, -) -> Result { - let alias = account.details().await.alias().clone(); - let prompt = format!("Account \"{alias}\": "); - - if let Some(helper) = rl.helper_mut() { - helper.set_prompt(prompt.green().to_string()); - } - - let input = rl.readline(&prompt); - - match input { - Ok(command) => { - match command.trim() { - "" => {} - "h" | "help" => AccountCli::print_help()?, - "c" | "clear" => { - // Clear console - let _ = std::process::Command::new("clear").status(); - } - "accounts" => { - // List all accounts - let accounts = wallet.get_accounts().await?; - println!("INDEX\tALIAS"); - for account in accounts { - let details = &*account.details().await; - println!("{}\t{}", details.index(), details.alias()); - } - } - _ => { - // Prepend `Account: ` so the parsing will be correct - let command = format!("Account: {command}"); - let account_cli = match AccountCli::try_parse_from(command.split_whitespace()) { - Ok(account_cli) => account_cli, - Err(err) => { - println!("{err}"); - return Ok(AccountPromptResponse::Reprompt); - } - }; - match account_cli.command { - AccountCommand::Addresses => addresses_command(account).await, - AccountCommand::Balance { addresses } => balance_command(account, addresses).await, - AccountCommand::BurnNativeToken { token_id, amount } => { - burn_native_token_command(account, token_id, amount).await - } - AccountCommand::BurnNft { nft_id } => burn_nft_command(account, nft_id).await, - AccountCommand::Claim { output_id } => claim_command(account, output_id).await, - AccountCommand::ClaimableOutputs => claimable_outputs_command(account).await, - AccountCommand::Consolidate => consolidate_command(account).await, - AccountCommand::CreateAccountOutput => create_account_output_command(account).await, - AccountCommand::CreateNativeToken { - circulating_supply, - maximum_supply, - foundry_metadata_hex, - foundry_metadata_file, - } => { - create_native_token_command( - account, - circulating_supply, - maximum_supply, - bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file).await?, - ) - .await - } - AccountCommand::DestroyAccount { account_id } => { - destroy_account_command(account, account_id).await - } - AccountCommand::DestroyFoundry { foundry_id } => { - destroy_foundry_command(account, foundry_id).await - } - AccountCommand::Exit => { - return Ok(AccountPromptResponse::Done); - } - AccountCommand::Faucet { address, url } => faucet_command(account, address, url).await, - AccountCommand::MeltNativeToken { token_id, amount } => { - melt_native_token_command(account, token_id, amount).await - } - AccountCommand::MintNativeToken { token_id, amount } => { - mint_native_token(account, token_id, amount).await - } - AccountCommand::MintNft { - address, - immutable_metadata_hex, - immutable_metadata_file, - metadata_hex, - metadata_file, - tag, - sender, - issuer, - } => { - mint_nft_command( - account, - address, - bytes_from_hex_or_file(immutable_metadata_hex, immutable_metadata_file).await?, - bytes_from_hex_or_file(metadata_hex, metadata_file).await?, - tag, - sender, - issuer, - ) - .await - } - AccountCommand::NewAddress => new_address_command(account).await, - AccountCommand::NodeInfo => node_info_command(account).await, - AccountCommand::Output { selector } => output_command(account, selector).await, - AccountCommand::Outputs => outputs_command(account).await, - AccountCommand::Send { - address, - amount, - return_address, - expiration, - allow_micro_amount, - } => { - let allow_micro_amount = if return_address.is_some() || expiration.is_some() { - true - } else { - allow_micro_amount - }; - send_command(account, address, amount, return_address, expiration, allow_micro_amount).await - } - AccountCommand::SendNativeToken { - address, - token_id, - amount, - gift_storage_deposit, - } => send_native_token_command(account, address, token_id, amount, gift_storage_deposit).await, - AccountCommand::SendNft { address, nft_id } => send_nft_command(account, address, nft_id).await, - AccountCommand::Switch { account_id } => { - return Ok(AccountPromptResponse::Switch(wallet.get_account(account_id).await?)); - } - AccountCommand::Sync => sync_command(account).await, - AccountCommand::Transaction { selector } => transaction_command(account, selector).await, - AccountCommand::Transactions { show_details } => { - transactions_command(account, show_details).await - } - AccountCommand::UnspentOutputs => unspent_outputs_command(account).await, - AccountCommand::Vote { event_id, answers } => vote_command(account, event_id, answers).await, - AccountCommand::StopParticipating { event_id } => { - stop_participating_command(account, event_id).await - } - AccountCommand::ParticipationOverview { event_ids } => { - let event_ids = (!event_ids.is_empty()).then_some(event_ids); - participation_overview_command(account, event_ids).await - } - AccountCommand::VotingPower => voting_power_command(account).await, - AccountCommand::IncreaseVotingPower { amount } => { - increase_voting_power_command(account, amount).await - } - AccountCommand::DecreaseVotingPower { amount } => { - decrease_voting_power_command(account, amount).await - } - AccountCommand::VotingOutput => voting_output_command(account).await, - } - .unwrap_or_else(|err| { - println_log_error!("{err}"); - }); - } - } - } - Err(ReadlineError::Interrupted) => { - return Ok(AccountPromptResponse::Done); - } - Err(err) => { - println_log_error!("{err}"); - } - } - - Ok(AccountPromptResponse::Reprompt) -} diff --git a/cli/src/command/wallet.rs b/cli/src/cli.rs similarity index 64% rename from cli/src/command/wallet.rs rename to cli/src/cli.rs index 0ba4410799..49a40fc799 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/cli.rs @@ -1,7 +1,7 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::path::Path; +use std::{path::Path, str::FromStr}; use clap::{builder::BoolishValueParser, Args, CommandFactory, Parser, Subcommand}; use iota_sdk::{ @@ -11,50 +11,104 @@ use iota_sdk::{ stronghold::StrongholdAdapter, utils::Password, }, - wallet::{account::types::AccountIdentifier, ClientOptions, Wallet}, + crypto::keys::bip44::Bip44, + types::block::address::Bech32Address, + wallet::{ClientOptions, Wallet}, }; use log::LevelFilter; use crate::{ error::Error, - helper::{check_file_exists, enter_or_generate_mnemonic, generate_mnemonic, get_password, import_mnemonic}, + helper::{ + check_file_exists, enter_or_generate_mnemonic, generate_mnemonic, get_alias, get_decision, get_password, + import_mnemonic, + }, println_log_error, println_log_info, }; const DEFAULT_LOG_LEVEL: &str = "debug"; -const DEFAULT_NODE_URL: &str = "https://api.testnet.shimmer.network"; +const DEFAULT_NODE_URL: &str = "http://localhost:8080"; const DEFAULT_STRONGHOLD_SNAPSHOT_PATH: &str = "./stardust-cli-wallet.stronghold"; const DEFAULT_WALLET_DATABASE_PATH: &str = "./stardust-cli-wallet-db"; #[derive(Debug, Clone, Parser)] #[command(author, version, about, long_about = None, propagate_version = true)] -pub struct WalletCli { +pub struct Cli { /// Set the path to the wallet database. #[arg(long, value_name = "PATH", env = "WALLET_DATABASE_PATH", default_value = DEFAULT_WALLET_DATABASE_PATH)] pub wallet_db_path: String, /// Set the path to the stronghold snapshot file. #[arg(long, value_name = "PATH", env = "STRONGHOLD_SNAPSHOT_PATH", default_value = DEFAULT_STRONGHOLD_SNAPSHOT_PATH)] pub stronghold_snapshot_path: String, - /// Set the account to enter. - pub account: Option, /// Set the log level. #[arg(short, long, default_value = DEFAULT_LOG_LEVEL)] pub log_level: LevelFilter, #[command(subcommand)] - pub command: Option, + pub command: Option, } -impl WalletCli { +impl Cli { pub fn print_help() -> Result<(), Error> { Self::command().bin_name("wallet").print_help()?; Ok(()) } } +#[derive(Debug, Clone, Args)] +pub struct InitParameters { + /// Set the path to a file containing mnemonics. If empty, a mnemonic has to be entered or will be randomly + /// generated. + #[arg(short, long, value_name = "PATH")] + pub mnemonic_file_path: Option, + /// Set the node to connect to with this wallet. + #[arg(short, long, value_name = "URL", env = "NODE_URL", default_value = DEFAULT_NODE_URL)] + pub node_url: String, + /// Set the BIP path, `4219/0/0/0` if not provided. + #[arg(short, long, value_parser = parse_bip_path)] + pub bip_path: Option, + /// Set the Bech32-encoded wallet address. + #[arg(short, long)] + pub address: Option, +} + +impl Default for InitParameters { + fn default() -> Self { + Self { + mnemonic_file_path: None, + node_url: DEFAULT_NODE_URL.to_string(), + bip_path: Some(Bip44::new(SHIMMER_COIN_TYPE)), + address: None, + } + } +} + +fn parse_bip_path(arg: &str) -> Result { + let mut bip_path_enc = Vec::with_capacity(4); + for p in arg.split_terminator('/').map(|p| p.trim()) { + match p.parse::() { + Ok(value) => bip_path_enc.push(value), + Err(_) => { + return Err(format!("cannot parse BIP path: {p}")); + } + } + } + + if bip_path_enc.len() != 4 { + return Err( + "invalid BIP path format. Expected: `coin_type/account_index/change_address/address_index`".to_string(), + ); + } + + let bip_path = Bip44::new(bip_path_enc[0]) + .with_account(bip_path_enc[1]) + .with_change(bip_path_enc[2]) + .with_address_index(bip_path_enc[3]); + + Ok(bip_path) +} + #[derive(Debug, Clone, Subcommand)] -pub enum WalletCommand { - /// List all accounts. - Accounts, +pub enum CliCommand { /// Create a stronghold backup file. Backup { /// Path of the created stronghold backup file. @@ -78,11 +132,6 @@ pub enum WalletCommand { #[arg(long, num_args = 0..=1, default_missing_value = Some("true"), value_parser = BoolishValueParser::new())] output_stdout: Option, }, - /// Create a new account. - NewAccount { - /// Account alias, next available account index if not provided. - alias: Option, - }, /// Get information about currently set node. NodeInfo, /// Restore a stronghold backup file. @@ -95,46 +144,84 @@ pub enum WalletCommand { /// Node URL to use for all future operations. url: String, }, - /// Synchronize all accounts. + /// Synchronize wallet. Sync, } -#[derive(Debug, Clone, Args)] -pub struct InitParameters { - /// Set the path to a file containing mnemonics. If empty, a mnemonic has to be entered or will be randomly - /// generated. - #[arg(short, long, value_name = "PATH")] - pub mnemonic_file_path: Option, - /// Set the node to connect to with this wallet. - #[arg(short, long, value_name = "URL", env = "NODE_URL", default_value = DEFAULT_NODE_URL)] - pub node_url: String, - /// Coin type, SHIMMER_COIN_TYPE (4219) if not provided. - #[arg(short, long, default_value_t = SHIMMER_COIN_TYPE)] - pub coin_type: u32, -} - -impl Default for InitParameters { - fn default() -> Self { - Self { - mnemonic_file_path: None, - node_url: DEFAULT_NODE_URL.to_string(), - coin_type: SHIMMER_COIN_TYPE, +pub async fn new_wallet(cli: Cli) -> Result, Error> { + let storage_path = Path::new(&cli.wallet_db_path); + let snapshot_path = Path::new(&cli.stronghold_snapshot_path); + + Ok(if let Some(command) = cli.command { + match command { + CliCommand::Backup { backup_path } => { + backup_command(storage_path, snapshot_path, std::path::Path::new(&backup_path)).await?; + None + } + CliCommand::ChangePassword => { + let wallet = change_password_command(storage_path, snapshot_path).await?; + Some(wallet) + } + CliCommand::Init(init_parameters) => { + let wallet = init_command(storage_path, snapshot_path, init_parameters).await?; + Some(wallet) + } + CliCommand::MigrateStrongholdSnapshotV2ToV3 { path } => { + migrate_stronghold_snapshot_v2_to_v3_command(path).await?; + None + } + CliCommand::Mnemonic { + output_file_name, + output_stdout, + } => { + mnemonic_command(output_file_name, output_stdout).await?; + None + } + CliCommand::NodeInfo => { + node_info_command(storage_path).await?; + None + } + CliCommand::Restore { backup_path } => { + let wallet = restore_command(storage_path, snapshot_path, std::path::Path::new(&backup_path)).await?; + Some(wallet) + } + CliCommand::SetNodeUrl { url } => { + let wallet = set_node_url_command(storage_path, snapshot_path, url).await?; + Some(wallet) + } + CliCommand::Sync => { + let wallet = sync_command(storage_path, snapshot_path).await?; + Some(wallet) + } } - } -} - -pub async fn accounts_command(storage_path: &Path, snapshot_path: &Path) -> Result<(), Error> { - let password = get_password("Stronghold password", false)?; - let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; - let accounts = wallet.get_accounts().await?; - - println!("INDEX\tALIAS"); - for account in accounts { - let details = &*account.details().await; - println!("{}\t{}", details.index(), details.alias()); - } - - Ok(()) + } else { + // no command provided, i.e. `> ./wallet` + match (storage_path.exists(), snapshot_path.exists()) { + (true, true) => { + let password = get_password("Stronghold password", false)?; + let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; + Some(wallet) + } + (false, false) => { + if get_decision("Create a new wallet with default parameters?")? { + let wallet = init_command(storage_path, snapshot_path, InitParameters::default()).await?; + println_log_info!("Created new wallet."); + Some(wallet) + } else { + Cli::print_help()?; + None + } + } + (true, false) => { + println_log_error!("Stronghold snapshot not found at '{}'.", snapshot_path.display()); + None + } + (false, true) => { + println_log_error!("Wallet database not found at '{}'.", storage_path.display()); + None + } + } + }) } pub async fn backup_command(storage_path: &Path, snapshot_path: &Path, backup_path: &Path) -> Result<(), Error> { @@ -161,7 +248,7 @@ pub async fn change_password_command(storage_path: &Path, snapshot_path: &Path) pub async fn init_command( storage_path: &Path, snapshot_path: &Path, - parameters: InitParameters, + init_params: InitParameters, ) -> Result { if storage_path.exists() { return Err(Error::Miscellaneous(format!( @@ -176,7 +263,7 @@ pub async fn init_command( ))); } let password = get_password("Stronghold password", true)?; - let mnemonic = match parameters.mnemonic_file_path { + let mnemonic = match init_params.mnemonic_file_path { Some(path) => import_mnemonic(&path).await?, None => enter_or_generate_mnemonic().await?, }; @@ -187,11 +274,24 @@ pub async fn init_command( secret_manager.store_mnemonic(mnemonic).await?; let secret_manager = SecretManager::Stronghold(secret_manager); + let alias = if get_decision("Do you want to assign an alias to your wallet?")? { + Some(get_alias("New wallet alias").await?) + } else { + None + }; + + let address = init_params + .address + .map(|addr| Bech32Address::from_str(&addr)) + .transpose()?; + Ok(Wallet::builder() .with_secret_manager(secret_manager) - .with_client_options(ClientOptions::new().with_node(parameters.node_url.as_str())?) + .with_client_options(ClientOptions::new().with_node(init_params.node_url.as_str())?) .with_storage_path(storage_path.to_str().expect("invalid unicode")) - .with_coin_type(parameters.coin_type) + .with_bip_path(init_params.bip_path) + .with_address(address) + .with_alias(alias) .finish() .await?) } @@ -213,19 +313,6 @@ pub async fn mnemonic_command(output_file_name: Option, output_stdout: O Ok(()) } -pub async fn new_account_command( - storage_path: &Path, - snapshot_path: &Path, - alias: Option, -) -> Result<(Wallet, AccountIdentifier), Error> { - let password = get_password("Stronghold password", !snapshot_path.exists())?; - let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; - - let alias = add_account(&wallet, alias).await?; - - Ok((wallet, alias)) -} - pub async fn node_info_command(storage_path: &Path) -> Result { let wallet = unlock_wallet(storage_path, None, None).await?; let node_info = wallet.client().get_info().await?; @@ -258,7 +345,7 @@ pub async fn restore_command(storage_path: &Path, snapshot_path: &Path, backup_p .with_client_options(ClientOptions::new().with_node(DEFAULT_NODE_URL)?) .with_storage_path(storage_path.to_str().expect("invalid unicode")) // Will be overwritten by the backup's value. - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; @@ -286,7 +373,7 @@ pub async fn sync_command(storage_path: &Path, snapshot_path: &Path) -> Result) -> Result { - let mut account_builder = wallet.create_account(); - - if let Some(alias) = alias { - account_builder = account_builder.with_alias(alias); - } - - let account = account_builder.finish().await?; - let alias = AccountIdentifier::Alias(account.details().await.alias().clone()); - - println_log_info!("Created account \"{alias}\""); - - Ok(alias) -} diff --git a/cli/src/command/mod.rs b/cli/src/command/mod.rs deleted file mode 100644 index 503b26a487..0000000000 --- a/cli/src/command/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -pub mod account; -pub mod account_completion; -pub mod wallet; diff --git a/cli/src/error.rs b/cli/src/error.rs index 6c6887a293..ce716ff35b 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -10,6 +10,7 @@ use rustyline::error::ReadlineError; use serde_json::Error as SerdeJsonError; #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { #[error("block error: {0}")] Block(#[from] BlockError), @@ -23,8 +24,6 @@ pub enum Error { Logger(#[from] LoggerError), #[error("{0}")] Miscellaneous(String), - #[error("generate at least one address before using the faucet")] - NoAddressForFaucet, #[error("prompt error: {0}")] Prompt(#[from] ReadlineError), #[error("serde_json error: {0}")] diff --git a/cli/src/helper.rs b/cli/src/helper.rs index 5be5970080..bba612103c 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -8,7 +8,6 @@ use dialoguer::{console::Term, theme::ColorfulTheme, Input, Select}; use iota_sdk::{ client::{utils::Password, verify_mnemonic}, crypto::keys::bip39::Mnemonic, - wallet::{Account, Wallet}, }; use tokio::{ fs::{self, OpenOptions}, @@ -49,41 +48,17 @@ pub fn get_decision(prompt: &str) -> Result { } } -pub async fn get_account_alias(prompt: &str, wallet: &Wallet) -> Result { - let account_aliases = wallet.get_account_aliases().await?; +pub async fn get_alias(prompt: &str) -> Result { loop { let input = Input::::new().with_prompt(prompt).interact_text()?; if input.is_empty() || !input.is_ascii() { println_log_error!("Invalid input, please choose a non-empty alias consisting of ASCII characters."); - } else if account_aliases.iter().any(|alias| alias == &input) { - println_log_error!("Account '{input}' already exists, please choose another alias."); } else { return Ok(input); } } } -pub async fn pick_account(wallet: &Wallet) -> Result, Error> { - let mut accounts = wallet.get_accounts().await?; - - match accounts.len() { - 0 => Ok(None), - 1 => Ok(Some(accounts.swap_remove(0))), - _ => { - // fetch all available account aliases to display to the user - let account_aliases = wallet.get_account_aliases().await?; - - let index = Select::with_theme(&ColorfulTheme::default()) - .with_prompt("Select an account:") - .items(&account_aliases) - .default(0) - .interact_on(&Term::stderr())?; - - Ok(Some(accounts.swap_remove(index))) - } - } -} - pub async fn bytes_from_hex_or_file(hex: Option, file: Option) -> Result>, Error> { Ok(if let Some(hex) = hex { Some(prefix_hex::decode(hex).map_err(|e| Error::Miscellaneous(e.to_string()))?) @@ -152,10 +127,10 @@ pub async fn generate_mnemonic( println_log_info!("Mnemonic has been written to '{file_path}'."); } - println_log_info!("IMPORTANT:"); - println_log_info!("Store this mnemonic in a secure location!"); - println_log_info!( - "It is the only way to recover your account if you ever forget your password and/or lose the stronghold file." + println!("IMPORTANT:"); + println!("Store this mnemonic in a secure location!"); + println!( + "It is the only way to recover your wallet if you ever forget your password and/or lose the stronghold file." ); Ok(mnemonic) diff --git a/cli/src/main.rs b/cli/src/main.rs index ea0336e05b..df31b1d5c8 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,16 +1,18 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod account; -mod command; +mod cli; mod error; mod helper; -mod wallet; +mod wallet_cli; use clap::Parser; use fern_logger::{LoggerConfigBuilder, LoggerOutputConfigBuilder}; -use self::{command::wallet::WalletCli, error::Error, wallet::new_wallet}; +use self::{ + cli::{new_wallet, Cli}, + error::Error, +}; #[macro_export] macro_rules! println_log_info { @@ -28,7 +30,7 @@ macro_rules! println_log_error { }; } -fn logger_init(cli: &WalletCli) -> Result<(), Error> { +fn logger_init(cli: &Cli) -> Result<(), Error> { std::panic::set_hook(Box::new(move |panic_info| { println_log_error!("{panic_info}"); })); @@ -45,10 +47,9 @@ fn logger_init(cli: &WalletCli) -> Result<(), Error> { Ok(()) } -async fn run(cli: WalletCli) -> Result<(), Error> { - if let (Some(wallet), Some(account)) = new_wallet(cli).await? { - let account = wallet.get_account(account).await?; - account::account_prompt(&wallet, account).await?; +async fn run(cli: Cli) -> Result<(), Error> { + if let Some(wallet) = new_wallet(cli).await? { + wallet_cli::prompt(&wallet).await?; } Ok(()) @@ -58,7 +59,7 @@ async fn run(cli: WalletCli) -> Result<(), Error> { async fn main() { dotenvy::dotenv().ok(); - let cli = match WalletCli::try_parse() { + let cli = match Cli::try_parse() { Ok(cli) => cli, Err(e) => { println!("{e}"); diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs deleted file mode 100644 index e8ce5bdf4f..0000000000 --- a/cli/src/wallet.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2020-2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::path::Path; - -use iota_sdk::wallet::{account::types::AccountIdentifier, Wallet}; - -use crate::{ - command::wallet::{ - accounts_command, add_account, backup_command, change_password_command, init_command, - migrate_stronghold_snapshot_v2_to_v3_command, mnemonic_command, new_account_command, node_info_command, - restore_command, set_node_url_command, sync_command, unlock_wallet, InitParameters, WalletCli, WalletCommand, - }, - error::Error, - helper::{get_account_alias, get_decision, get_password, pick_account}, - println_log_error, println_log_info, -}; - -pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option), Error> { - let storage_path = Path::new(&cli.wallet_db_path); - let snapshot_path = Path::new(&cli.stronghold_snapshot_path); - - let (wallet, account_id) = if let Some(command) = cli.command { - match command { - WalletCommand::Accounts => { - accounts_command(storage_path, snapshot_path).await?; - return Ok((None, None)); - } - WalletCommand::Init(init_parameters) => { - let wallet = init_command(storage_path, snapshot_path, init_parameters).await?; - (Some(wallet), None) - } - WalletCommand::Restore { backup_path } => { - let wallet = restore_command(storage_path, snapshot_path, std::path::Path::new(&backup_path)).await?; - (Some(wallet), None) - } - WalletCommand::Backup { backup_path } => { - backup_command(storage_path, snapshot_path, std::path::Path::new(&backup_path)).await?; - return Ok((None, None)); - } - WalletCommand::ChangePassword => { - let wallet = change_password_command(storage_path, snapshot_path).await?; - (Some(wallet), None) - } - WalletCommand::MigrateStrongholdSnapshotV2ToV3 { path } => { - migrate_stronghold_snapshot_v2_to_v3_command(path).await?; - return Ok((None, None)); - } - WalletCommand::NewAccount { alias } => { - let (wallet, account) = new_account_command(storage_path, snapshot_path, alias).await?; - (Some(wallet), Some(account)) - } - WalletCommand::SetNodeUrl { url } => { - let wallet = set_node_url_command(storage_path, snapshot_path, url).await?; - (Some(wallet), None) - } - WalletCommand::Sync => { - let wallet = sync_command(storage_path, snapshot_path).await?; - (Some(wallet), None) - } - WalletCommand::Mnemonic { - output_file_name, - output_stdout, - } => { - mnemonic_command(output_file_name, output_stdout).await?; - return Ok((None, None)); - } - WalletCommand::NodeInfo => { - node_info_command(storage_path).await?; - return Ok((None, None)); - } - } - } else { - // no command provided, i.e. `> ./wallet` - match (storage_path.exists(), snapshot_path.exists()) { - (true, true) => { - let password = get_password("Stronghold password", false)?; - let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; - if wallet.get_accounts().await?.is_empty() { - create_initial_account(wallet).await? - } else if let Some(alias) = cli.account { - (Some(wallet), Some(alias)) - } else if let Some(account) = pick_account(&wallet).await? { - (Some(wallet), Some(account.alias().await.into())) - } else { - (Some(wallet), None) - } - } - (false, false) => { - if get_decision("Create a new wallet with default parameters?")? { - let wallet = init_command(storage_path, snapshot_path, InitParameters::default()).await?; - println_log_info!("Created new wallet."); - create_initial_account(wallet).await? - } else { - WalletCli::print_help()?; - (None, None) - } - } - (true, false) => { - println_log_error!("Stronghold snapshot not found at '{}'.", snapshot_path.display()); - (None, None) - } - (false, true) => { - println_log_error!("Wallet database not found at '{}'.", storage_path.display()); - (None, None) - } - } - }; - Ok((wallet, account_id)) -} - -async fn create_initial_account(wallet: Wallet) -> Result<(Option, Option), Error> { - // Ask the user whether an initial account should be created. - if get_decision("Create initial account?")? { - let alias = get_account_alias("New account alias", &wallet).await?; - let account_id = add_account(&wallet, Some(alias)).await?; - println_log_info!("Created initial account.\nType `help` to see all available account commands."); - Ok((Some(wallet), Some(account_id))) - } else { - Ok((Some(wallet), None)) - } -} diff --git a/cli/src/command/account_completion.rs b/cli/src/wallet_cli/completer.rs similarity index 82% rename from cli/src/command/account_completion.rs rename to cli/src/wallet_cli/completer.rs index 53a78c1261..140183b0b6 100644 --- a/cli/src/command/account_completion.rs +++ b/cli/src/wallet_cli/completer.rs @@ -8,12 +8,9 @@ use rustyline::{ completion::Completer, highlight::Highlighter, hint::HistoryHinter, Completer, Context, Helper, Hinter, Validator, }; -#[derive(Default)] -pub struct AccountCompleter; - -const ACCOUNT_COMMANDS: &[&str] = &[ +const WALLET_COMMANDS: &[&str] = &[ "accounts", - "addresses", + "address", "balance", "burn-native-token", "burn-nft", @@ -27,17 +24,17 @@ const ACCOUNT_COMMANDS: &[&str] = &[ "destroy-foundry", "exit", "faucet", + "implicit-account-creation-address", + "implicit-accounts", "melt-native-token", "mint-native-token", "mint-nft", - "new-address", "node-info", "output", "outputs", "send", "send-native-token", "send-nft", - "switch", "sync", "transaction", "transactions", @@ -54,7 +51,10 @@ const ACCOUNT_COMMANDS: &[&str] = &[ "help", ]; -impl Completer for AccountCompleter { +#[derive(Default)] +pub struct WalletCommandCompleter; + +impl Completer for WalletCommandCompleter { type Candidate = &'static str; fn complete( @@ -65,7 +65,7 @@ impl Completer for AccountCompleter { ) -> rustyline::Result<(usize, Vec)> { Ok(( 0, - ACCOUNT_COMMANDS + WALLET_COMMANDS .iter() .filter_map(|cmd| cmd.starts_with(input).then_some(*cmd)) .collect(), @@ -74,21 +74,21 @@ impl Completer for AccountCompleter { } #[derive(Helper, Completer, Hinter, Validator)] -pub struct AccountPromptHelper { +pub struct WalletCommandHelper { #[rustyline(Completer)] - completer: AccountCompleter, + completer: WalletCommandCompleter, #[rustyline(Hinter)] hinter: HistoryHinter, prompt: String, } -impl AccountPromptHelper { +impl WalletCommandHelper { pub fn set_prompt(&mut self, prompt: String) { self.prompt = prompt; } } -impl Highlighter for AccountPromptHelper { +impl Highlighter for WalletCommandHelper { fn highlight_prompt<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str, default: bool) -> Cow<'b, str> { if default { Cow::Borrowed(&self.prompt) @@ -102,10 +102,10 @@ impl Highlighter for AccountPromptHelper { } } -impl Default for AccountPromptHelper { +impl Default for WalletCommandHelper { fn default() -> Self { Self { - completer: AccountCompleter, + completer: WalletCommandCompleter, hinter: HistoryHinter {}, prompt: String::new(), } diff --git a/cli/src/command/account.rs b/cli/src/wallet_cli/mod.rs similarity index 57% rename from cli/src/command/account.rs rename to cli/src/wallet_cli/mod.rs index ed4b615f92..ac8c5a4b67 100644 --- a/cli/src/command/account.rs +++ b/cli/src/wallet_cli/mod.rs @@ -1,9 +1,12 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod completer; + use std::str::FromStr; use clap::{CommandFactory, Parser, Subcommand}; +use colored::Colorize; use iota_sdk::{ client::request_funds_from_faucet, types::{ @@ -16,45 +19,49 @@ use iota_sdk::{ }, payload::signed_transaction::TransactionId, slot::SlotIndex, - ConvertTo, }, }, + utils::ConvertTo, wallet::{ - account::{ - types::{AccountIdentifier, Bip44Address, OutputData, TransactionWithMetadata}, - Account, ConsolidationParams, OutputsToClaim, SyncOptions, TransactionOptions, - }, - CreateNativeTokenParams, MintNftParams, SendNativeTokenParams, SendNftParams, SendParams, + types::{OutputData, TransactionWithMetadata}, + ConsolidationParams, CreateNativeTokenParams, MintNftParams, OutputsToClaim, SendNativeTokensParams, + SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet, }, U256, }; +use rustyline::{error::ReadlineError, history::MemHistory, Config, Editor}; -use crate::{error::Error, helper::to_utc_date_time, println_log_info}; +use self::completer::WalletCommandHelper; +use crate::{ + error::Error, + helper::{bytes_from_hex_or_file, to_utc_date_time}, + println_log_error, println_log_info, +}; #[derive(Debug, Parser)] #[command(author, version, about, long_about = None, propagate_version = true)] -pub struct AccountCli { +pub struct WalletCli { #[command(subcommand)] - pub command: AccountCommand, + pub command: WalletCommand, } -impl AccountCli { +impl WalletCli { pub fn print_help() -> Result<(), Error> { - Self::command().bin_name("Account:").print_help()?; + Self::command().bin_name("Wallet:").print_help()?; Ok(()) } } +/// Commands #[derive(Debug, Subcommand)] #[allow(clippy::large_enum_variant)] -pub enum AccountCommand { - /// List the account addresses. - Addresses, - /// Print the account balance. - Balance { - /// Addresses to compute the balance for. - addresses: Option>, - }, +pub enum WalletCommand { + /// Lists the accounts of the wallet. + Accounts, + /// Print the wallet address. + Address, + /// Print the wallet balance. + Balance, /// Burn an amount of native token. BurnNativeToken { /// Token ID to be burnt, e.g. 0x087d205988b733d97fb145ae340e27a8b19554d1ceee64574d7e5ff66c45f69e7a0100000000. @@ -106,11 +113,15 @@ pub enum AccountCommand { Exit, /// Request funds from the faucet. Faucet { - /// Address the faucet sends the funds to, defaults to the latest address. + /// Address the faucet sends the funds to, defaults to the wallet address. address: Option, - /// URL of the faucet, default to . + /// URL of the faucet, default to . url: Option, }, + /// Returns the implicit account creation address of the wallet if it is Ed25519 based. + ImplicitAccountCreationAddress, + /// Lists the implicit accounts of the wallet. + ImplicitAccounts, /// Mint additional native tokens. MintNativeToken { /// Token ID to be minted, e.g. 0x087d205988b733d97fb145ae340e27a8b19554d1ceee64574d7e5ff66c45f69e7a0100000000. @@ -152,8 +163,6 @@ pub enum AccountCommand { /// Amount to be melted, e.g. 100. amount: String, }, - /// Generate a new address. - NewAddress, /// Get information about currently set node. NodeInfo, /// Display an output. @@ -172,7 +181,7 @@ pub enum AccountCommand { amount: u64, /// Bech32 encoded return address, to which the storage deposit will be returned if one is necessary /// given the provided amount. If a storage deposit is needed and a return address is not provided, it will - /// default to the first address of the account. + /// default to the wallet address. #[arg(long)] return_address: Option, /// Expiration in slot indices, after which the output will be available for the sender again, if not spent by @@ -205,12 +214,7 @@ pub enum AccountCommand { /// NFT ID to be sent, e.g. 0xecadf10e6545aa82da4df2dfd2a496b457c8850d2cab49b7464cb273d3dffb07. nft_id: String, }, - /// Switch to a different account. - Switch { - /// The identifier (alias or index) of the account you want to switch to. - account_id: AccountIdentifier, - }, - /// Synchronize the account. + /// Synchronize the wallet. Sync, /// Show the details of a transaction. #[clap(visible_alias = "tx")] @@ -219,14 +223,14 @@ pub enum AccountCommand { /// Either by ID (e.g. 0x84fe6b1796bddc022c9bc40206f0a692f4536b02aa8c13140264e2e01a3b7e4b) or index. selector: TransactionSelector, }, - /// List the account transactions. + /// List the wallet transactions. #[clap(visible_alias = "txs")] Transactions { - /// List account transactions with all details. + /// List wallet transactions with all details. #[arg(long, default_value_t = false)] show_details: bool, }, - /// List the account unspent outputs. + /// List the unspent outputs. UnspentOutputs, /// Cast votes for an event. Vote { @@ -241,26 +245,26 @@ pub enum AccountCommand { /// 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2. event_id: ParticipationEventId, }, - /// Get the participation overview of the account. + /// Get the participation overview of the wallet. ParticipationOverview { /// Event IDs for which to get the participation overview, e.g. /// 0xdc049a721dc65ec342f836c876ec15631ed915cd55213cee39e8d1c821c751f2... #[arg(short, long, num_args = 1.., value_delimiter = ' ')] event_ids: Vec, }, - /// Get the voting power of the account. + /// Get the voting power of the wallet. VotingPower, - /// Increase the voting power of the account. + /// Increase the voting power of the wallet. IncreaseVotingPower { /// Amount to increase the voting power by, e.g. 100. amount: u64, }, - /// Decrease the voting power of the account. + /// Decrease the voting power of the wallet. DecreaseVotingPower { /// Amount to decrease the voting power by, e.g. 100. amount: u64, }, - /// Get the voting output of the account. + /// Get the voting output of the wallet. VotingOutput, } @@ -302,38 +306,31 @@ impl FromStr for OutputSelector { } } -/// `addresses` command -pub async fn addresses_command(account: &Account) -> Result<(), Error> { - let addresses = account.addresses().await; +// `accounts` command +pub async fn accounts_command(wallet: &Wallet) -> Result<(), Error> { + print_outputs(wallet.accounts().await, "Accounts:").await +} - if addresses.is_empty() { - println_log_info!("No addresses found"); - } else { - for address in addresses { - print_address(account, &address).await?; - } - } +// `address` command +pub async fn address_command(wallet: &Wallet) -> Result<(), Error> { + print_wallet_address(wallet).await?; Ok(()) } // `balance` command -pub async fn balance_command(account: &Account, addresses: Option>) -> Result<(), Error> { - let balance = if let Some(addresses) = addresses { - account.addresses_balance(addresses).await? - } else { - account.balance().await? - }; +pub async fn balance_command(wallet: &Wallet) -> Result<(), Error> { + let balance = wallet.balance().await?; println_log_info!("{balance:#?}"); Ok(()) } // `burn-native-token` command -pub async fn burn_native_token_command(account: &Account, token_id: String, amount: String) -> Result<(), Error> { +pub async fn burn_native_token_command(wallet: &Wallet, token_id: String, amount: String) -> Result<(), Error> { println_log_info!("Burning native token {token_id} {amount}."); - let transaction = account + let transaction = wallet .burn( NativeToken::new( TokenId::from_str(&token_id)?, @@ -353,10 +350,10 @@ pub async fn burn_native_token_command(account: &Account, token_id: String, amou } // `burn-nft` command -pub async fn burn_nft_command(account: &Account, nft_id: String) -> Result<(), Error> { +pub async fn burn_nft_command(wallet: &Wallet, nft_id: String) -> Result<(), Error> { println_log_info!("Burning nft {nft_id}."); - let transaction = account.burn(NftId::from_str(&nft_id)?, None).await?; + let transaction = wallet.burn(NftId::from_str(&nft_id)?, None).await?; println_log_info!( "Burning transaction sent:\n{:?}\n{:?}", @@ -368,11 +365,11 @@ pub async fn burn_nft_command(account: &Account, nft_id: String) -> Result<(), E } // `claim` command -pub async fn claim_command(account: &Account, output_id: Option) -> Result<(), Error> { +pub async fn claim_command(wallet: &Wallet, output_id: Option) -> Result<(), Error> { if let Some(output_id) = output_id { println_log_info!("Claiming output {output_id}"); - let transaction = account.claim_outputs([OutputId::from_str(&output_id)?]).await?; + let transaction = wallet.claim_outputs([OutputId::from_str(&output_id)?]).await?; println_log_info!( "Claiming transaction sent:\n{:?}\n{:?}", @@ -382,7 +379,7 @@ pub async fn claim_command(account: &Account, output_id: Option) -> Resu } else { println_log_info!("Claiming outputs."); - let output_ids = account.claimable_outputs(OutputsToClaim::All).await?; + let output_ids = wallet.claimable_outputs(OutputsToClaim::All).await?; if output_ids.is_empty() { println_log_info!("No outputs available to claim."); @@ -391,7 +388,7 @@ pub async fn claim_command(account: &Account, output_id: Option) -> Resu // Doing chunks of only 60, because we might need to create the double amount of outputs, because of potential // storage deposit return unlock conditions and also consider the remainder output. for output_ids_chunk in output_ids.chunks(60) { - let transaction = account.claim_outputs(output_ids_chunk.to_vec()).await?; + let transaction = wallet.claim_outputs(output_ids_chunk.to_vec()).await?; println_log_info!( "Claiming transaction sent:\n{:?}\n{:?}", transaction.transaction_id, @@ -404,15 +401,15 @@ pub async fn claim_command(account: &Account, output_id: Option) -> Resu } /// `claimable-outputs` command -pub async fn claimable_outputs_command(account: &Account) -> Result<(), Error> { - let balance = account.balance().await?; +pub async fn claimable_outputs_command(wallet: &Wallet) -> Result<(), Error> { + let balance = wallet.balance().await?; for output_id in balance .potentially_locked_outputs() .iter() .filter_map(|(output_id, unlockable)| unlockable.then_some(output_id)) { // Unwrap: for the iterated `OutputId`s this call will always return `Some(...)`. - let output_data = account.get_output(output_id).await.unwrap(); + let output_data = wallet.get_output(output_id).await.unwrap(); let output = output_data.output; let kind = match output { Output::Nft(_) => "Nft", @@ -439,7 +436,7 @@ pub async fn claimable_outputs_command(account: &Account) -> Result<(), Error> { println_log_info!(" - base coin amount: {}", amount); if let Some(expiration) = unlock_conditions.expiration() { - let slot_index = account.client().get_slot_index().await?; + let slot_index = wallet.client().get_slot_index().await?; if *expiration.slot_index() > *slot_index { println_log_info!(" - expires in {} slot indices", *expiration.slot_index() - *slot_index); @@ -457,10 +454,10 @@ pub async fn claimable_outputs_command(account: &Account) -> Result<(), Error> { } // `consolidate` command -pub async fn consolidate_command(account: &Account) -> Result<(), Error> { +pub async fn consolidate_command(wallet: &Wallet) -> Result<(), Error> { println_log_info!("Consolidating outputs."); - let transaction = account + let transaction = wallet .consolidate_outputs(ConsolidationParams::new().with_force(true)) .await?; @@ -474,10 +471,10 @@ pub async fn consolidate_command(account: &Account) -> Result<(), Error> { } // `create-account-output` command -pub async fn create_account_output_command(account: &Account) -> Result<(), Error> { +pub async fn create_account_output_command(wallet: &Wallet) -> Result<(), Error> { println_log_info!("Creating account output."); - let transaction = account.create_account_output(None, None).await?; + let transaction = wallet.create_account_output(None, None).await?; println_log_info!( "Account output creation transaction sent:\n{:?}\n{:?}", @@ -490,24 +487,24 @@ pub async fn create_account_output_command(account: &Account) -> Result<(), Erro // `create-native-token` command pub async fn create_native_token_command( - account: &Account, + wallet: &Wallet, circulating_supply: String, maximum_supply: String, foundry_metadata: Option>, ) -> Result<(), Error> { // If no account output exists, create one first - if account.balance().await?.accounts().is_empty() { - let transaction = account.create_account_output(None, None).await?; + if wallet.balance().await?.accounts().is_empty() { + let transaction = wallet.create_account_output(None, None).await?; println_log_info!( "Account output minting transaction sent:\n{:?}\n{:?}", transaction.transaction_id, transaction.block_id ); - account + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - // Sync account after the transaction got confirmed, so the account output is available - account.sync(None).await?; + // Sync wallet after the transaction got confirmed, so the account output is available + wallet.sync(None).await?; } let params = CreateNativeTokenParams { @@ -517,7 +514,7 @@ pub async fn create_native_token_command( foundry_metadata, }; - let create_transaction = account.create_native_token(params, None).await?; + let create_transaction = wallet.create_native_token(params, None).await?; println_log_info!( "Transaction to create native token sent:\n{:?}\n{:?}", @@ -529,10 +526,10 @@ pub async fn create_native_token_command( } // `destroy-account` command -pub async fn destroy_account_command(account: &Account, account_id: String) -> Result<(), Error> { +pub async fn destroy_account_command(wallet: &Wallet, account_id: String) -> Result<(), Error> { println_log_info!("Destroying account {account_id}."); - let transaction = account.burn(AccountId::from_str(&account_id)?, None).await?; + let transaction = wallet.burn(AccountId::from_str(&account_id)?, None).await?; println_log_info!( "Destroying account transaction sent:\n{:?}\n{:?}", @@ -544,10 +541,10 @@ pub async fn destroy_account_command(account: &Account, account_id: String) -> R } // `destroy-foundry` command -pub async fn destroy_foundry_command(account: &Account, foundry_id: String) -> Result<(), Error> { +pub async fn destroy_foundry_command(wallet: &Wallet, foundry_id: String) -> Result<(), Error> { println_log_info!("Destroying foundry {foundry_id}."); - let transaction = account.burn(FoundryId::from_str(&foundry_id)?, None).await?; + let transaction = wallet.burn(FoundryId::from_str(&foundry_id)?, None).await?; println_log_info!( "Destroying foundry transaction sent:\n{:?}\n{:?}", @@ -559,31 +556,35 @@ pub async fn destroy_foundry_command(account: &Account, foundry_id: String) -> R } // `faucet` command -pub async fn faucet_command( - account: &Account, - address: Option, - url: Option, -) -> Result<(), Error> { +pub async fn faucet_command(wallet: &Wallet, address: Option, url: Option) -> Result<(), Error> { let address = if let Some(address) = address { address } else { - match account.addresses().await.into_iter().rev().next() { - Some(address) => address.into_bech32(), - None => return Err(Error::NoAddressForFaucet), - } + wallet.address().await }; - let faucet_url = url - .as_deref() - .unwrap_or("https://faucet.testnet.shimmer.network/api/enqueue"); + + let faucet_url = url.as_deref().unwrap_or("http://localhost:8088/api/enqueue"); println_log_info!("{}", request_funds_from_faucet(faucet_url, &address).await?); Ok(()) } +// `implicit-account-creation-address` command +pub async fn implicit_account_creation_address_command(wallet: &Wallet) -> Result<(), Error> { + println_log_info!("{}", wallet.implicit_account_creation_address().await?); + + Ok(()) +} + +// `implicit-accounts` command +pub async fn implicit_accounts_command(wallet: &Wallet) -> Result<(), Error> { + print_outputs(wallet.implicit_accounts().await, "Implicit accounts:").await +} + // `melt-native-token` command -pub async fn melt_native_token_command(account: &Account, token_id: String, amount: String) -> Result<(), Error> { - let transaction = account +pub async fn melt_native_token_command(wallet: &Wallet, token_id: String, amount: String) -> Result<(), Error> { + let transaction = wallet .melt_native_token( TokenId::from_str(&token_id)?, U256::from_dec_str(&amount).map_err(|e| Error::Miscellaneous(e.to_string()))?, @@ -601,8 +602,8 @@ pub async fn melt_native_token_command(account: &Account, token_id: String, amou } // `mint-native-token` command -pub async fn mint_native_token(account: &Account, token_id: String, amount: String) -> Result<(), Error> { - let mint_transaction = account +pub async fn mint_native_token_command(wallet: &Wallet, token_id: String, amount: String) -> Result<(), Error> { + let mint_transaction = wallet .mint_native_token( TokenId::from_str(&token_id)?, U256::from_dec_str(&amount).map_err(|e| Error::Miscellaneous(e.to_string()))?, @@ -621,7 +622,7 @@ pub async fn mint_native_token(account: &Account, token_id: String, amount: Stri // `mint-nft` command pub async fn mint_nft_command( - account: &Account, + wallet: &Wallet, address: Option, immutable_metadata: Option>, metadata: Option>, @@ -642,7 +643,7 @@ pub async fn mint_nft_command( .with_tag(tag) .with_sender(sender) .with_issuer(issuer); - let transaction = account.mint_nfts([nft_options], None).await?; + let transaction = wallet.mint_nfts([nft_options], None).await?; println_log_info!( "NFT minting transaction sent:\n{:?}\n{:?}", @@ -653,18 +654,9 @@ pub async fn mint_nft_command( Ok(()) } -// `new-address` command -pub async fn new_address_command(account: &Account) -> Result<(), Error> { - let address = account.generate_ed25519_addresses(1, None).await?; - - print_address(account, &address[0]).await?; - - Ok(()) -} - // `node-info` command -pub async fn node_info_command(account: &Account) -> Result<(), Error> { - let node_info = account.client().get_info().await?; +pub async fn node_info_command(wallet: &Wallet) -> Result<(), Error> { + let node_info = wallet.client().get_info().await?; println_log_info!("Current node info: {}", serde_json::to_string_pretty(&node_info)?); @@ -672,11 +664,11 @@ pub async fn node_info_command(account: &Account) -> Result<(), Error> { } /// `output` command -pub async fn output_command(account: &Account, selector: OutputSelector) -> Result<(), Error> { +pub async fn output_command(wallet: &Wallet, selector: OutputSelector) -> Result<(), Error> { let output = match selector { - OutputSelector::Id(id) => account.get_output(&id).await, + OutputSelector::Id(id) => wallet.get_output(&id).await, OutputSelector::Index(index) => { - let mut outputs = account.outputs(None).await?; + let mut outputs = wallet.outputs(None).await; outputs.sort_unstable_by(outputs_ordering); outputs.into_iter().nth(index) } @@ -692,13 +684,13 @@ pub async fn output_command(account: &Account, selector: OutputSelector) -> Resu } /// `outputs` command -pub async fn outputs_command(account: &Account) -> Result<(), Error> { - print_outputs(account.outputs(None).await?, "Outputs:").await +pub async fn outputs_command(wallet: &Wallet) -> Result<(), Error> { + print_outputs(wallet.outputs(None).await, "Outputs:").await } // `send` command pub async fn send_command( - account: &Account, + wallet: &Wallet, address: impl ConvertTo, amount: u64, return_address: Option>, @@ -708,7 +700,7 @@ pub async fn send_command( let params = [SendParams::new(amount, address)? .with_return_address(return_address.map(ConvertTo::convert).transpose()?) .with_expiration(expiration)]; - let transaction = account + let transaction = wallet .send_with_params( params, TransactionOptions { @@ -729,7 +721,7 @@ pub async fn send_command( // `send-native-token` command pub async fn send_native_token_command( - account: &Account, + wallet: &Wallet, address: impl ConvertTo, token_id: String, amount: String, @@ -738,20 +730,19 @@ pub async fn send_native_token_command( let address = address.convert()?; let transaction = if gift_storage_deposit.unwrap_or(false) { // Send native tokens together with the required storage deposit - let rent_structure = account.client().get_rent_structure().await?; - let token_supply = account.client().get_token_supply().await?; + let storage_params = wallet.client().get_storage_score_parameters().await?; - account.client().bech32_hrp_matches(address.hrp()).await?; + wallet.client().bech32_hrp_matches(address.hrp()).await?; - let outputs = [BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) + let outputs = [BasicOutputBuilder::new_with_minimum_amount(storage_params) .add_unlock_condition(AddressUnlockCondition::new(address)) .with_native_tokens([NativeToken::new( TokenId::from_str(&token_id)?, U256::from_dec_str(&amount).map_err(|e| Error::Miscellaneous(e.to_string()))?, )?]) - .finish_output(token_supply)?]; + .finish_output()?]; - account.send_outputs(outputs, None).await? + wallet.send_outputs(outputs, None).await? } else { // Send native tokens with storage deposit return and expiration let outputs = [SendNativeTokenParams::new( @@ -761,7 +752,7 @@ pub async fn send_native_token_command( U256::from_dec_str(&amount).map_err(|e| Error::Miscellaneous(e.to_string()))?, )], )?]; - account.send_native_tokens(outputs, None).await? + wallet.send_native_tokens(outputs, None).await? }; println_log_info!( @@ -775,12 +766,12 @@ pub async fn send_native_token_command( // `send-nft` command pub async fn send_nft_command( - account: &Account, + wallet: &Wallet, address: impl ConvertTo, nft_id: String, ) -> Result<(), Error> { let outputs = [SendNftParams::new(address.convert()?, &nft_id)?]; - let transaction = account.send_nft(outputs, None).await?; + let transaction = wallet.send_nft(outputs, None).await?; println_log_info!( "Nft transaction sent:\n{:?}\n{:?}", @@ -792,8 +783,8 @@ pub async fn send_nft_command( } // `sync` command -pub async fn sync_command(account: &Account) -> Result<(), Error> { - let balance = account +pub async fn sync_command(wallet: &Wallet) -> Result<(), Error> { + let balance = wallet .sync(Some(SyncOptions { sync_native_token_foundries: true, ..Default::default() @@ -806,8 +797,8 @@ pub async fn sync_command(account: &Account) -> Result<(), Error> { } /// `transaction` command -pub async fn transaction_command(account: &Account, selector: TransactionSelector) -> Result<(), Error> { - let mut transactions = account.transactions().await; +pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) -> Result<(), Error> { + let mut transactions = wallet.transactions().await; let transaction = match selector { TransactionSelector::Id(id) => transactions.into_iter().find(|tx| tx.transaction_id == id), TransactionSelector::Index(index) => { @@ -826,8 +817,8 @@ pub async fn transaction_command(account: &Account, selector: TransactionSelecto } /// `transactions` command -pub async fn transactions_command(account: &Account, show_details: bool) -> Result<(), Error> { - let mut transactions = account.transactions().await; +pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result<(), Error> { + let mut transactions = wallet.transactions().await; transactions.sort_unstable_by(transactions_ordering); if transactions.is_empty() { @@ -849,12 +840,12 @@ pub async fn transactions_command(account: &Account, show_details: bool) -> Resu } /// `unspent-outputs` command -pub async fn unspent_outputs_command(account: &Account) -> Result<(), Error> { - print_outputs(account.unspent_outputs(None).await?, "Unspent outputs:").await +pub async fn unspent_outputs_command(wallet: &Wallet) -> Result<(), Error> { + print_outputs(wallet.unspent_outputs(None).await, "Unspent outputs:").await } -pub async fn vote_command(account: &Account, event_id: ParticipationEventId, answers: Vec) -> Result<(), Error> { - let transaction = account.vote(Some(event_id), Some(answers)).await?; +pub async fn vote_command(wallet: &Wallet, event_id: ParticipationEventId, answers: Vec) -> Result<(), Error> { + let transaction = wallet.vote(Some(event_id), Some(answers)).await?; println_log_info!( "Voting transaction sent:\n{:?}\n{:?}", @@ -865,8 +856,8 @@ pub async fn vote_command(account: &Account, event_id: ParticipationEventId, ans Ok(()) } -pub async fn stop_participating_command(account: &Account, event_id: ParticipationEventId) -> Result<(), Error> { - let transaction = account.stop_participating(event_id).await?; +pub async fn stop_participating_command(wallet: &Wallet, event_id: ParticipationEventId) -> Result<(), Error> { + let transaction = wallet.stop_participating(event_id).await?; println_log_info!( "Stop participating transaction sent:\n{:?}\n{:?}", @@ -878,26 +869,26 @@ pub async fn stop_participating_command(account: &Account, event_id: Participati } pub async fn participation_overview_command( - account: &Account, + wallet: &Wallet, event_ids: Option>, ) -> Result<(), Error> { - let participation_overview = account.get_participation_overview(event_ids).await?; + let participation_overview = wallet.get_participation_overview(event_ids).await?; println_log_info!("Participation overview: {participation_overview:?}"); Ok(()) } -pub async fn voting_power_command(account: &Account) -> Result<(), Error> { - let voting_power = account.get_voting_power().await?; +pub async fn voting_power_command(wallet: &Wallet) -> Result<(), Error> { + let voting_power = wallet.get_voting_power().await?; println_log_info!("Voting power: {voting_power}"); Ok(()) } -pub async fn increase_voting_power_command(account: &Account, amount: u64) -> Result<(), Error> { - let transaction = account.increase_voting_power(amount).await?; +pub async fn increase_voting_power_command(wallet: &Wallet, amount: u64) -> Result<(), Error> { + let transaction = wallet.increase_voting_power(amount).await?; println_log_info!( "Increase voting power transaction sent:\n{:?}\n{:?}", @@ -908,8 +899,8 @@ pub async fn increase_voting_power_command(account: &Account, amount: u64) -> Re Ok(()) } -pub async fn decrease_voting_power_command(account: &Account, amount: u64) -> Result<(), Error> { - let transaction = account.decrease_voting_power(amount).await?; +pub async fn decrease_voting_power_command(wallet: &Wallet, amount: u64) -> Result<(), Error> { + let transaction = wallet.decrease_voting_power(amount).await?; println_log_info!( "Decrease voting power transaction sent:\n{:?}\n{:?}", @@ -920,32 +911,29 @@ pub async fn decrease_voting_power_command(account: &Account, amount: u64) -> Re Ok(()) } -pub async fn voting_output_command(account: &Account) -> Result<(), Error> { - let output = account.get_voting_output().await?; +pub async fn voting_output_command(wallet: &Wallet) -> Result<(), Error> { + let output = wallet.get_voting_output().await?; println_log_info!("Voting output: {output:?}"); Ok(()) } -async fn print_address(account: &Account, address: &Bip44Address) -> Result<(), Error> { +async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { + let address = wallet.address().await; + let mut log = format!( - "Address: {}\n{:<9}{}\n{:<9}{:?}", - address.key_index(), + "Address:\n{:<9}{}\n{:<9}{:?}", "Bech32:", - address.address(), + address, "Hex:", - address.address().inner() + address.inner() ); - if *address.internal() { - log = format!("{log}\nChange address"); - } - - let addresses = account.addresses_with_unspent_outputs().await?; - let slot_index = account.client().get_slot_index().await?; + let unspent_outputs = wallet.unspent_outputs(None).await; + let slot_index = wallet.client().get_slot_index().await?; - let mut output_ids: &[OutputId] = &[]; + let mut output_ids = Vec::new(); let mut amount = 0; let mut native_tokens = NativeTokensBuilder::new(); let mut accounts = Vec::new(); @@ -954,48 +942,43 @@ async fn print_address(account: &Account, address: &Bip44Address) -> Result<(), let mut delegations = Vec::new(); let mut anchors = Vec::new(); - if let Some(address) = addresses - .iter() - .find(|a| a.key_index() == address.key_index() && a.internal() == address.internal()) - { - output_ids = address.output_ids().as_slice(); - - for output_id in output_ids { - if let Some(output_data) = account.get_output(output_id).await { - // Output might be associated with the address, but can't be unlocked by it, so we check that here. - let (required_address, _) = output_data - .output - .required_and_unlocked_address(slot_index, output_id)?; - - if address.address().as_ref() == &required_address { - if let Some(nts) = output_data.output.native_tokens() { - native_tokens.add_native_tokens(nts.clone())?; - } - match &output_data.output { - Output::Basic(_) => {} - Output::Account(account) => accounts.push(account.account_id_non_null(output_id)), - Output::Foundry(foundry) => foundries.push(foundry.id()), - Output::Nft(nft) => nfts.push(nft.nft_id_non_null(output_id)), - Output::Delegation(delegation) => { - delegations.push(delegation.delegation_id_non_null(output_id)) - } - Output::Anchor(anchor) => anchors.push(anchor.anchor_id_non_null(output_id)), - } - let unlock_conditions = output_data - .output - .unlock_conditions() - .expect("output must have unlock conditions"); - let sdr_amount = unlock_conditions - .storage_deposit_return() - .map(|sdr| sdr.amount()) - .unwrap_or(0); - - amount += output_data.output.amount() - sdr_amount; - } + for output_data in unspent_outputs { + let output_id = output_data.output_id; + output_ids.push(output_id); + + // Output might be associated with the address, but can't be unlocked by it, so we check that here. + let (required_address, _) = &output_data + .output + .required_and_unlocked_address(slot_index, &output_id)?; + + if address.inner() == required_address { + if let Some(nts) = output_data.output.native_tokens() { + native_tokens.add_native_tokens(nts.clone())?; } + match &output_data.output { + Output::Basic(_) => {} + Output::Account(account) => accounts.push(account.account_id_non_null(&output_id)), + Output::Foundry(foundry) => foundries.push(foundry.id()), + Output::Nft(nft) => nfts.push(nft.nft_id_non_null(&output_id)), + Output::Delegation(delegation) => delegations.push(delegation.delegation_id_non_null(&output_id)), + Output::Anchor(anchor) => anchors.push(anchor.anchor_id_non_null(&output_id)), + } + let unlock_conditions = output_data + .output + .unlock_conditions() + .expect("output must have unlock conditions"); + let sdr_amount = unlock_conditions + .storage_deposit_return() + .map(|sdr| sdr.amount()) + .unwrap_or(0); + + amount += output_data.output.amount() - sdr_amount; } } + let bip_path = wallet.bip_path().await; + log = format!("{log}\nBIP path: {bip_path:?}"); + log = format!( "{log}\nOutputs: {:#?}\nBase coin amount: {}\nNative Tokens: {:?}\nAccounts: {:?}\nFoundries: {:?}\nNFTs: {:?}\nDelegations: {:?}\nAnchors: {:?}\n", output_ids, @@ -1013,6 +996,203 @@ async fn print_address(account: &Account, address: &Bip44Address) -> Result<(), Ok(()) } +// loop on the wallet prompt +pub async fn prompt(wallet: &Wallet) -> Result<(), Error> { + let config = Config::builder() + .auto_add_history(true) + .history_ignore_space(true) + .completion_type(rustyline::CompletionType::List) + .edit_mode(rustyline::EditMode::Emacs) + .build(); + + let mut rl = Editor::with_history(config, MemHistory::with_config(config))?; + rl.set_helper(Some(WalletCommandHelper::default())); + + loop { + match prompt_internal(wallet, &mut rl).await { + Ok(res) => match res { + PromptResponse::Reprompt => (), + PromptResponse::Done => { + return Ok(()); + } + }, + Err(e) => { + println_log_error!("{e}"); + } + } + } +} + +pub enum PromptResponse { + Reprompt, + Done, +} + +pub async fn prompt_internal( + wallet: &Wallet, + rl: &mut Editor, +) -> Result { + let prompt = if let Some(alias) = wallet.alias().await { + format!("Wallet \"{alias}\": ") + } else { + format!("Wallet: ") + }; + + if let Some(helper) = rl.helper_mut() { + helper.set_prompt(prompt.green().to_string()); + } + + let input = rl.readline(&prompt); + match input { + Ok(command) => { + match command.trim() { + "" => {} + "h" | "help" => WalletCli::print_help()?, + "c" | "clear" => { + // Clear console + let _ = std::process::Command::new("clear").status(); + } + _ => { + // Prepend `Wallet: ` so the parsing will be correct + let command = format!("Wallet: {command}"); + let protocol_cli = match WalletCli::try_parse_from(command.split_whitespace()) { + Ok(protocol_cli) => protocol_cli, + Err(err) => { + println!("{err}"); + return Ok(PromptResponse::Reprompt); + } + }; + match protocol_cli.command { + WalletCommand::Accounts => accounts_command(wallet).await, + WalletCommand::Address => address_command(wallet).await, + WalletCommand::Balance => balance_command(wallet).await, + WalletCommand::BurnNativeToken { token_id, amount } => { + burn_native_token_command(wallet, token_id, amount).await + } + WalletCommand::BurnNft { nft_id } => burn_nft_command(wallet, nft_id).await, + WalletCommand::Claim { output_id } => claim_command(wallet, output_id).await, + WalletCommand::ClaimableOutputs => claimable_outputs_command(wallet).await, + WalletCommand::Consolidate => consolidate_command(wallet).await, + WalletCommand::CreateAccountOutput => create_account_output_command(wallet).await, + WalletCommand::CreateNativeToken { + circulating_supply, + maximum_supply, + foundry_metadata_hex, + foundry_metadata_file, + } => { + create_native_token_command( + wallet, + circulating_supply, + maximum_supply, + bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file).await?, + ) + .await + } + WalletCommand::DestroyAccount { account_id } => { + destroy_account_command(wallet, account_id).await + } + WalletCommand::DestroyFoundry { foundry_id } => { + destroy_foundry_command(wallet, foundry_id).await + } + WalletCommand::Exit => { + return Ok(PromptResponse::Done); + } + WalletCommand::Faucet { address, url } => faucet_command(wallet, address, url).await, + WalletCommand::ImplicitAccountCreationAddress => { + implicit_account_creation_address_command(wallet).await + } + WalletCommand::ImplicitAccounts => implicit_accounts_command(wallet).await, + WalletCommand::MeltNativeToken { token_id, amount } => { + melt_native_token_command(wallet, token_id, amount).await + } + WalletCommand::MintNativeToken { token_id, amount } => { + mint_native_token_command(wallet, token_id, amount).await + } + WalletCommand::MintNft { + address, + immutable_metadata_hex, + immutable_metadata_file, + metadata_hex, + metadata_file, + tag, + sender, + issuer, + } => { + mint_nft_command( + wallet, + address, + bytes_from_hex_or_file(immutable_metadata_hex, immutable_metadata_file).await?, + bytes_from_hex_or_file(metadata_hex, metadata_file).await?, + tag, + sender, + issuer, + ) + .await + } + WalletCommand::NodeInfo => node_info_command(wallet).await, + WalletCommand::Output { selector } => output_command(wallet, selector).await, + WalletCommand::Outputs => outputs_command(wallet).await, + WalletCommand::Send { + address, + amount, + return_address, + expiration, + allow_micro_amount, + } => { + let allow_micro_amount = if return_address.is_some() || expiration.is_some() { + true + } else { + allow_micro_amount + }; + send_command(wallet, address, amount, return_address, expiration, allow_micro_amount).await + } + WalletCommand::SendNativeToken { + address, + token_id, + amount, + gift_storage_deposit, + } => send_native_token_command(wallet, address, token_id, amount, gift_storage_deposit).await, + WalletCommand::SendNft { address, nft_id } => send_nft_command(wallet, address, nft_id).await, + WalletCommand::Sync => sync_command(wallet).await, + WalletCommand::Transaction { selector } => transaction_command(wallet, selector).await, + WalletCommand::Transactions { show_details } => { + transactions_command(wallet, show_details).await + } + WalletCommand::UnspentOutputs => unspent_outputs_command(wallet).await, + WalletCommand::Vote { event_id, answers } => vote_command(wallet, event_id, answers).await, + WalletCommand::StopParticipating { event_id } => { + stop_participating_command(wallet, event_id).await + } + WalletCommand::ParticipationOverview { event_ids } => { + let event_ids = (!event_ids.is_empty()).then_some(event_ids); + participation_overview_command(wallet, event_ids).await + } + WalletCommand::VotingPower => voting_power_command(wallet).await, + WalletCommand::IncreaseVotingPower { amount } => { + increase_voting_power_command(wallet, amount).await + } + WalletCommand::DecreaseVotingPower { amount } => { + decrease_voting_power_command(wallet, amount).await + } + WalletCommand::VotingOutput => voting_output_command(wallet).await, + } + .unwrap_or_else(|err| { + println_log_error!("{err}"); + }); + } + } + } + Err(ReadlineError::Interrupted) => { + return Ok(PromptResponse::Done); + } + Err(err) => { + println_log_error!("{err}"); + } + } + + Ok(PromptResponse::Reprompt) +} + async fn print_outputs(mut outputs: Vec, title: &str) -> Result<(), Error> { if outputs.is_empty() { println_log_info!("No outputs found"); diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index d0b13c8023..490af55ed3 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -45,7 +45,7 @@ iota-crypto = { version = "0.23.0", default-features = false, features = [ "secp256k1", ] } iterator-sorted = { version = "0.1.0", default-features = false } -packable = { version = "0.8.3", default-features = false, features = [ +packable = { version = "0.9.0", default-features = false, features = [ "primitive-types", ] } paste = { version = "1.0.14", default-features = false } @@ -126,7 +126,9 @@ wasm-bindgen-futures = { version = "0.4.37", default-features = false, optional [dev-dependencies] iota-sdk = { path = ".", default-features = false, features = ["rand"] } -pretty_assertions = { version = "1.4.0", default-features = false, features = [ "alloc" ] } +pretty_assertions = { version = "1.4.0", default-features = false, features = [ + "alloc", +] } dotenvy = { version = "0.15.7", default-features = false } fern-logger = { version = "0.5.0", default-features = false } @@ -221,13 +223,8 @@ path = "examples/how_tos/accounts_and_addresses/create_mnemonic.rs" required-features = ["client"] [[example]] -name = "create_account" -path = "examples/how_tos/accounts_and_addresses/create_account.rs" -required-features = ["rocksdb", "stronghold"] - -[[example]] -name = "list_accounts" -path = "examples/how_tos/accounts_and_addresses/list_accounts.rs" +name = "create_wallet" +path = "examples/how_tos/accounts_and_addresses/create_wallet.rs" required-features = ["rocksdb", "stronghold"] [[example]] @@ -235,16 +232,6 @@ name = "check_balance" path = "examples/how_tos/accounts_and_addresses/check_balance.rs" required-features = ["rocksdb", "stronghold"] -[[example]] -name = "list_addresses" -path = "examples/how_tos/accounts_and_addresses/list_addresses.rs" -required-features = ["rocksdb", "stronghold"] - -[[example]] -name = "create_address" -path = "examples/how_tos/accounts_and_addresses/create_address.rs" -required-features = ["rocksdb", "stronghold"] - [[example]] name = "list_transactions" path = "examples/how_tos/accounts_and_addresses/list_transactions.rs" @@ -355,6 +342,11 @@ name = "destroy_account_output" path = "examples/how_tos/account/destroy.rs" required-features = ["wallet", "stronghold"] +[[example]] +name = "implicit_account_creation" +path = "examples/how_tos/account/implicit_account_creation.rs" +required-features = ["wallet"] + # Outputs [[example]] @@ -588,8 +580,8 @@ path = "examples/wallet/getting_started.rs" required-features = ["stronghold"] [[example]] -name = "0_generate_addresses" -path = "examples/wallet/offline_signing/0_generate_addresses.rs" +name = "0_generate_address" +path = "examples/wallet/offline_signing/0_generate_address.rs" required-features = ["wallet", "storage", "stronghold"] [[example]] @@ -627,11 +619,6 @@ name = "check_unlock_conditions" path = "examples/wallet/17_check_unlock_conditions.rs" required-features = ["wallet", "storage"] -[[example]] -name = "accounts" -path = "examples/wallet/accounts.rs" -required-features = ["wallet", "storage"] - [[example]] name = "background_syncing" path = "examples/wallet/background_syncing.rs" @@ -657,21 +644,11 @@ name = "logger" path = "examples/wallet/logger.rs" required-features = ["wallet", "storage"] -[[example]] -name = "recover_accounts" -path = "examples/wallet/recover_accounts.rs" -required-features = ["wallet", "storage"] - [[example]] name = "spammer" path = "examples/wallet/spammer.rs" required-features = ["wallet"] -[[example]] -name = "split_funds" -path = "examples/wallet/split_funds.rs" -required-features = ["wallet", "storage"] - [[example]] name = "storage" path = "examples/wallet/storage.rs" diff --git a/sdk/examples/.env.example b/sdk/examples/.env.example index d51040423a..7366ed10d1 100644 --- a/sdk/examples/.env.example +++ b/sdk/examples/.env.example @@ -6,7 +6,7 @@ # Mnemonics (Don't ever use them to manage real funds!) MNEMONIC="endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river" MNEMONIC_2="width scatter jaguar sponsor erosion enable cave since ancient first garden royal luggage exchange ritual exotic play wall clinic ride autumn divert spin exchange" -# The Wallet database folder used to store account data +# The Wallet database folder used to store wallet data WALLET_DB_PATH="./example-walletdb" # The Stronghold snapshot file location used to store secrets STRONGHOLD_SNAPSHOT_PATH="./example.stronghold" diff --git a/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs b/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs index d708ee22e3..a66e1beb2c 100644 --- a/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs +++ b/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs @@ -41,7 +41,7 @@ async fn main() -> Result<()> { // Get output IDs of account outputs that can be controlled by this address. let output_ids_response = client - .account_output_ids(AccountOutputQueryParameters::new().unlockable_by_address(address)) + .account_output_ids(AccountOutputQueryParameters::new().address(address)) .await?; println!("Account output IDs: {output_ids_response:#?}"); diff --git a/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs b/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs index dc7b853744..1e81619103 100644 --- a/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs +++ b/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs @@ -41,7 +41,7 @@ async fn main() -> Result<()> { // Get output IDs of foundry outputs that can be controlled by this address. let output_ids_response = client - .foundry_output_ids(FoundryOutputQueryParameters::new().account_address(address)) + .foundry_output_ids(FoundryOutputQueryParameters::new().account(address)) .await?; println!("Foundry output IDs: {output_ids_response:#?}"); diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index 3f6631f41a..b29486db14 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -34,8 +34,7 @@ async fn main() -> Result<()> { .finish() .await?; - let token_supply = client.get_token_supply().await?; - let rent_structure = client.get_rent_structure().await?; + let storage_score_params = client.get_storage_score_parameters().await?; let address = std::env::args() .nth(1) @@ -43,13 +42,13 @@ async fn main() -> Result<()> { let address = Address::try_from_bech32(address)?; // Account id needs to be null the first time - let account_output = AccountOutputBuilder::new_with_minimum_storage_deposit(rent_structure, AccountId::null()) + let account_output = AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .add_feature(SenderFeature::new(address.clone())) .add_feature(MetadataFeature::new(metadata)?) .add_immutable_feature(IssuerFeature::new(address.clone())) .add_immutable_feature(MetadataFeature::new(metadata)?) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(token_supply)?; + .finish_output()?; println!("{account_output:#?}"); diff --git a/sdk/examples/client/output/build_basic_output.rs b/sdk/examples/client/output/build_basic_output.rs index 489ae2bbe6..c7ef6391ac 100644 --- a/sdk/examples/client/output/build_basic_output.rs +++ b/sdk/examples/client/output/build_basic_output.rs @@ -9,7 +9,7 @@ //! ``` use iota_sdk::{ - client::{Client, Result}, + client::Result, types::block::{ address::Address, output::{ @@ -30,14 +30,6 @@ async fn main() -> Result<()> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let token_supply = client.get_token_supply().await?; - let address = std::env::args() .nth(1) .unwrap_or("rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy".to_string()); @@ -48,40 +40,36 @@ async fn main() -> Result<()> { let outputs = [ // most simple output - basic_output_builder.clone().finish_output(token_supply)?, + basic_output_builder.clone().finish_output()?, // with metadata feature block basic_output_builder .clone() .add_feature(MetadataFeature::new(METADATA)?) - .finish_output(token_supply)?, + .finish_output()?, // with storage deposit return basic_output_builder .clone() - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - address.clone(), - 1_000_000, - token_supply, - )?) - .finish_output(token_supply)?, + .add_unlock_condition(StorageDepositReturnUnlockCondition::new(address.clone(), 1_000_000)) + .finish_output()?, // with expiration basic_output_builder .clone() .add_unlock_condition(ExpirationUnlockCondition::new(address.clone(), 1)?) - .finish_output(token_supply)?, + .finish_output()?, // with timelock basic_output_builder .clone() .add_unlock_condition(TimelockUnlockCondition::new(1)?) - .finish_output(token_supply)?, + .finish_output()?, // with tag feature basic_output_builder .clone() .add_feature(TagFeature::new(METADATA)?) - .finish_output(token_supply)?, + .finish_output()?, // with sender feature basic_output_builder .add_feature(SenderFeature::new(address)) - .finish_output(token_supply)?, + .finish_output()?, ]; println!("{outputs:#?}"); diff --git a/sdk/examples/client/output/build_nft_output.rs b/sdk/examples/client/output/build_nft_output.rs index 5dfafd8772..208f662a09 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -34,8 +34,7 @@ async fn main() -> Result<()> { .finish() .await?; - let token_supply = client.get_token_supply().await?; - let rent_structure = client.get_rent_structure().await?; + let storage_score_params = client.get_storage_score_parameters().await?; let address = std::env::args() .nth(1) @@ -56,14 +55,14 @@ async fn main() -> Result<()> { .to_string(); // NftId needs to be null the first time - let nft_output = NftOutputBuilder::new_with_minimum_storage_deposit(rent_structure, NftId::null()) + let nft_output = NftOutputBuilder::new_with_minimum_amount(storage_score_params, NftId::null()) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) .add_feature(SenderFeature::new(address.clone())) .add_feature(MetadataFeature::new(MUTABLE_METADATA)?) .add_feature(TagFeature::new(TAG)?) .add_immutable_feature(IssuerFeature::new(address)) .add_immutable_feature(MetadataFeature::new(tip_27_immutable_metadata)?) - .finish_output(token_supply)?; + .finish_output()?; println!("{nft_output:#?}"); diff --git a/sdk/examples/how_tos/account/create.rs b/sdk/examples/how_tos/account/create.rs index a0b2263b7b..78ad399c7f 100644 --- a/sdk/examples/how_tos/account/create.rs +++ b/sdk/examples/how_tos/account/create.rs @@ -4,7 +4,7 @@ //! In this example we will create an account output. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example and that funds are available by running +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example and that funds are available by running //! the `get_funds` example! //! //! Rename `.env.example` to `.env` first, then run the command: @@ -23,10 +23,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; println!("Accounts BEFORE:\n{:#?}", balance.accounts()); // Set the stronghold password @@ -37,10 +36,10 @@ async fn main() -> Result<()> { println!("Sending the create-account transaction..."); // Create an account output - let transaction = account.create_account_output(None, None).await?; + let transaction = wallet.create_account_output(None, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -49,7 +48,7 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; println!("Accounts AFTER:\n{:#?}", balance.accounts()); Ok(()) diff --git a/sdk/examples/how_tos/account/destroy.rs b/sdk/examples/how_tos/account/destroy.rs index 1c9388e361..20930776f6 100644 --- a/sdk/examples/how_tos/account/destroy.rs +++ b/sdk/examples/how_tos/account/destroy.rs @@ -1,10 +1,10 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will try to destroy the first account output there is in the account. +//! In this example we will try to destroy the first account output there is in the wallet. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -22,11 +22,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let alias = "Alice"; - let account = wallet.get_account(alias).await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; // Get the first account if let Some(account_id) = balance.accounts().first() { @@ -40,10 +38,10 @@ async fn main() -> Result<()> { println!("Sending account burn transaction..."); - let transaction = account.burn(*account_id, None).await?; + let transaction = wallet.burn(*account_id, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; @@ -55,11 +53,12 @@ async fn main() -> Result<()> { println!("Burned Account '{}'", account_id); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; + let accounts_after = balance.accounts(); println!("Accounts AFTER destroying:\n{accounts_after:#?}",); } else { - println!("No Account available in account '{alias}'"); + println!("No Account available"); } Ok(()) diff --git a/sdk/examples/how_tos/account/implicit_account_creation.rs b/sdk/examples/how_tos/account/implicit_account_creation.rs new file mode 100644 index 0000000000..9f565da0b8 --- /dev/null +++ b/sdk/examples/how_tos/account/implicit_account_creation.rs @@ -0,0 +1,38 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! In this example, we create an implicit account creation address. +//! +//! Rename `.env.example` to `.env` first, then run the command: +//! ```sh +//! cargo run --release --all-features --example implicit_account_creation +//! ``` + +use iota_sdk::{ + client::{constants::SHIMMER_COIN_TYPE, secret::SecretManager}, + crypto::keys::bip44::Bip44, + wallet::{ClientOptions, Result, Wallet}, +}; + +#[tokio::main] +async fn main() -> Result<()> { + //  This example uses secrets in environment variables for simplicity which should not be done in production. + dotenvy::dotenv().ok(); + + let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; + let client_options = ClientOptions::new().with_node("https://api.testnet.shimmer.network")?; + + let wallet = Wallet::builder() + .with_secret_manager(secret_manager) + .with_client_options(client_options) + .with_storage_path("implicit_account_creation") + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) + .finish() + .await?; + + let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; + + println!("{implicit_account_creation_address}"); + + Ok(()) +} diff --git a/sdk/examples/how_tos/account_wallet/request_funds.rs b/sdk/examples/how_tos/account_wallet/request_funds.rs index 9691313432..1975a09391 100644 --- a/sdk/examples/how_tos/account_wallet/request_funds.rs +++ b/sdk/examples/how_tos/account_wallet/request_funds.rs @@ -9,10 +9,7 @@ use iota_sdk::{ client::request_funds_from_faucet, types::block::address::{AccountAddress, ToBech32Ext}, - wallet::{ - account::{AliasSyncOptions, SyncOptions}, - Result, - }, + wallet::{AccountSyncOptions, Result, SyncOptions}, Wallet, }; @@ -29,18 +26,16 @@ async fn main() -> Result<()> { .finish() .await?; - // Get the account - let account = wallet.get_account("Alice").await?; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let total_base_token_balance = balance.base_coin().total(); - println!("Balance before requesting funds on account address: {total_base_token_balance:#?}"); + println!("Balance before requesting funds on wallet address: {total_base_token_balance:#?}"); let account_id = balance.accounts().first().unwrap(); println!("Account Id: {account_id}"); // Get account address - let account_address = AccountAddress::new(*account_id).to_bech32(account.client().get_bech32_hrp().await.unwrap()); + let account_address = AccountAddress::new(*account_id).to_bech32(wallet.client().get_bech32_hrp().await.unwrap()); let faucet_response = request_funds_from_faucet(&faucet_url, &account_address).await?; println!("{faucet_response}"); @@ -48,13 +43,13 @@ async fn main() -> Result<()> { tokio::time::sleep(std::time::Duration::from_secs(10)).await; let sync_options = SyncOptions { - alias: AliasSyncOptions { + account: AccountSyncOptions { basic_outputs: true, ..Default::default() }, ..Default::default() }; - let total_base_token_balance = account.sync(Some(sync_options)).await?.base_coin().total(); + let total_base_token_balance = wallet.sync(Some(sync_options)).await?.base_coin().total(); println!("Balance after requesting funds on account address: {total_base_token_balance:#?}"); Ok(()) diff --git a/sdk/examples/how_tos/account_wallet/transaction.rs b/sdk/examples/how_tos/account_wallet/transaction.rs index 946a14e297..5087b82e19 100644 --- a/sdk/examples/how_tos/account_wallet/transaction.rs +++ b/sdk/examples/how_tos/account_wallet/transaction.rs @@ -9,10 +9,7 @@ use iota_sdk::{ client::node_api::indexer::query_parameters::BasicOutputQueryParameters, types::block::address::{AccountAddress, ToBech32Ext}, - wallet::{ - account::{AliasSyncOptions, SyncOptions, TransactionOptions}, - Result, - }, + wallet::{AccountSyncOptions, Result, SyncOptions, TransactionOptions}, Wallet, }; @@ -22,7 +19,7 @@ async fn main() -> Result<()> { dotenvy::dotenv().ok(); let sync_options = SyncOptions { - alias: AliasSyncOptions { + account: AccountSyncOptions { basic_outputs: true, ..Default::default() }, @@ -38,9 +35,7 @@ async fn main() -> Result<()> { .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - // Get the account - let account = wallet.get_account("Alice").await?; - let balance = account.sync(Some(sync_options.clone())).await?; + let balance = wallet.sync(Some(sync_options.clone())).await?; let total_base_token_balance = balance.base_coin().total(); println!("Balance before sending funds from account: {total_base_token_balance:#?}"); @@ -49,10 +44,10 @@ async fn main() -> Result<()> { println!("Account Id: {account_id}"); // Get account address - let account_address = AccountAddress::new(*account_id).to_bech32(account.client().get_bech32_hrp().await.unwrap()); + let account_address = AccountAddress::new(*account_id).to_bech32(wallet.client().get_bech32_hrp().await.unwrap()); // Find first output unlockable by the account address - let input = *account + let input = *wallet .client() .basic_output_ids(BasicOutputQueryParameters::only_address_unlock_condition( account_address, @@ -62,7 +57,7 @@ async fn main() -> Result<()> { .first() .unwrap(); - let transaction = account + let transaction = wallet .send( 1_000_000, "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu", @@ -72,7 +67,7 @@ async fn main() -> Result<()> { }, ) .await?; - account + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -80,7 +75,7 @@ async fn main() -> Result<()> { transaction.transaction_id ); - let total_base_token_balance = account.sync(Some(sync_options)).await?.base_coin().total(); + let total_base_token_balance = wallet.sync(Some(sync_options)).await?.base_coin().total(); println!("Balance after sending funds from account: {total_base_token_balance:#?}"); Ok(()) diff --git a/sdk/examples/how_tos/accounts_and_addresses/check_balance.rs b/sdk/examples/how_tos/accounts_and_addresses/check_balance.rs index 87b28a24f0..ab200fa677 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/check_balance.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/check_balance.rs @@ -1,10 +1,10 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we sync the account and get the balance. +//! In this example we sync the wallet and get the balance. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -22,18 +22,14 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; // Sync and get the balance - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; println!("{balance:#?}"); - println!("ADDRESSES:"); let explorer_url = std::env::var("EXPLORER_URL").ok(); - let prepended = explorer_url.map(|url| format!("{url}/addr/")).unwrap_or_default(); - for address in account.addresses().await { - println!(" - {prepended}{}", address.address()); - } + let addr_base_url = explorer_url.map(|url| format!("{url}/addr/")).unwrap_or_default(); + println!("{addr_base_url}{}", wallet.address().await); Ok(()) } diff --git a/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs b/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs index ae79dea54f..bb08f13690 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs @@ -1,11 +1,11 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will consolidate basic outputs from an account with only an AddressUnlockCondition by sending +//! In this example we will consolidate basic outputs from a wallet with only an AddressUnlockCondition by sending //! them to the same address again. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -14,7 +14,7 @@ use iota_sdk::{ types::block::address::ToBech32Ext, - wallet::{account::ConsolidationParams, Result}, + wallet::{ConsolidationParams, Result}, Wallet, }; @@ -27,23 +27,22 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - // Sync account to make sure account is updated with outputs from previous examples - account.sync(None).await?; - println!("Account synced"); + // Sync wallet to make sure it is updated with outputs from previous examples + wallet.sync(None).await?; + println!("Wallet synced"); // List unspent outputs before consolidation. // The output we created with example `03_get_funds` and the basic output from `09_mint_native_tokens` have only one // unlock condition and it is an `AddressUnlockCondition`, and so they are valid for consolidation. They have the - // same `AddressUnlockCondition`(the first address of the account), so they will be consolidated into one + // same `AddressUnlockCondition`(the address of the wallet), so they will be consolidated into one // output. - let outputs = account.unspent_outputs(None).await?; + let outputs = wallet.unspent_outputs(None).await; println!("Outputs BEFORE consolidation:"); outputs.iter().enumerate().for_each(|(i, output_data)| { println!("OUTPUT #{i}"); @@ -59,13 +58,13 @@ async fn main() -> Result<()> { // Consolidate unspent outputs and print the consolidation transaction ID // Set `force` to true to force the consolidation even though the `output_threshold` isn't reached - let transaction = account + let transaction = wallet .consolidate_outputs(ConsolidationParams::new().with_force(true)) .await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for the consolidation transaction to get confirmed - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -74,12 +73,12 @@ async fn main() -> Result<()> { block_id ); - // Sync account - account.sync(None).await?; - println!("Account synced"); + // Sync wallet + wallet.sync(None).await?; + println!("Wallet synced"); // Outputs after consolidation - let outputs = account.unspent_outputs(None).await?; + let outputs = wallet.unspent_outputs(None).await; println!("Outputs AFTER consolidation:"); outputs.iter().enumerate().for_each(|(i, output_data)| { println!("OUTPUT #{i}"); diff --git a/sdk/examples/how_tos/accounts_and_addresses/create_address.rs b/sdk/examples/how_tos/accounts_and_addresses/create_address.rs deleted file mode 100644 index 3cd40dac03..0000000000 --- a/sdk/examples/how_tos/accounts_and_addresses/create_address.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will generate addresses for an already existing wallet. -//! -//! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --all-features --example create_address` -//! ``` - -use iota_sdk::{wallet::Result, Wallet}; - -// The number of addresses to generate -const NUM_ADDRESSES_TO_GENERATE: u32 = 5; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let wallet = Wallet::builder() - .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) - .finish() - .await?; - let account = wallet.get_account("Alice").await?; - - // Provide the stronghold password - wallet - .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) - .await?; - - let explorer_url = std::env::var("EXPLORER_URL").ok(); - let address_url = explorer_url.map(|url| format!("{url}/addr/")).unwrap_or_default(); - - println!("Current addresses:"); - for address in account.addresses().await { - println!(" - {address_url}{}", address.address()); - } - - // Generate some addresses - let new_addresses = account - .generate_ed25519_addresses(NUM_ADDRESSES_TO_GENERATE, None) - .await?; - println!("Generated {} new addresses:", new_addresses.len()); - let account_addresses = account.addresses().await; - for new_address in new_addresses.iter() { - assert!(account_addresses.contains(new_address)); - println!(" - {address_url}{}", new_address.address()); - } - Ok(()) -} diff --git a/sdk/examples/how_tos/accounts_and_addresses/create_account.rs b/sdk/examples/how_tos/accounts_and_addresses/create_wallet.rs similarity index 83% rename from sdk/examples/how_tos/accounts_and_addresses/create_account.rs rename to sdk/examples/how_tos/accounts_and_addresses/create_wallet.rs index af62241b55..be4d113b1d 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/create_account.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/create_wallet.rs @@ -7,7 +7,7 @@ //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh -//! cargo run --release --all-features --example create_account +//! cargo run --release --all-features --example create_wallet //! ``` use iota_sdk::{ @@ -15,7 +15,7 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::{stronghold::StrongholdSecretManager, SecretManager}, }, - crypto::keys::bip39::Mnemonic, + crypto::keys::{bip39::Mnemonic, bip44::Bip44}, wallet::{ClientOptions, Result, Wallet}, }; @@ -42,14 +42,11 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Stronghold(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - // Create a new account - let account = wallet.create_account().with_alias("Alice").finish().await?; - - println!("Generated new account: '{}'", account.alias().await); + println!("Generated new wallet"); Ok(()) } diff --git a/sdk/examples/how_tos/accounts_and_addresses/list_accounts.rs b/sdk/examples/how_tos/accounts_and_addresses/list_accounts.rs deleted file mode 100644 index ffc57e6e45..0000000000 --- a/sdk/examples/how_tos/accounts_and_addresses/list_accounts.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will list all accounts in the wallet. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --all-features --example list_accounts -//! ``` - -use iota_sdk::{wallet::Result, Wallet}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let wallet = Wallet::builder() - .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) - .finish() - .await?; - - // Get the accounts and print the alias of each account - for account in wallet.get_accounts().await? { - println!("{}", account.alias().await); - } - - Ok(()) -} diff --git a/sdk/examples/how_tos/accounts_and_addresses/list_addresses.rs b/sdk/examples/how_tos/accounts_and_addresses/list_addresses.rs deleted file mode 100644 index ebcc539c71..0000000000 --- a/sdk/examples/how_tos/accounts_and_addresses/list_addresses.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will list all addresses of an account. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --all-features --example list_addresses -//! ``` - -use iota_sdk::{wallet::Result, Wallet}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let wallet = Wallet::builder() - .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) - .finish() - .await?; - let account = wallet.get_account("Alice").await?; - - for address in account.addresses().await { - println!("{}", address.address()); - } - - Ok(()) -} diff --git a/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs b/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs index 3593cbd5ee..9c732c4831 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will list all outputs of an account. +//! In this example we will list all outputs of a wallet. //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -19,20 +19,19 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // Sync account - account.sync(None).await?; + // Sync wallet + wallet.sync(None).await?; // Print output ids println!("Output ids:"); - for output in account.outputs(None).await? { + for output in wallet.outputs(None).await { println!("{}", output.output_id); } // Print unspent output ids println!("Unspent output ids:"); - for output in account.unspent_outputs(None).await? { + for output in wallet.unspent_outputs(None).await { println!("{}", output.output_id); } diff --git a/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs b/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs index 59b46f5044..6d07e6cb7c 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will list all transaction of an account. +//! In this example we will list all transaction of a wallet. //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -9,7 +9,7 @@ //! ``` use iota_sdk::{ - wallet::{account::SyncOptions, Result}, + wallet::{Result, SyncOptions}, Wallet, }; @@ -22,10 +22,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // Sync account - account + // Sync wallet + wallet .sync(Some(SyncOptions { sync_incoming_transactions: true, ..Default::default() @@ -34,13 +33,13 @@ async fn main() -> Result<()> { // Print transaction ids println!("Sent transactions:"); - for transaction in account.transactions().await { + for transaction in wallet.transactions().await { println!("{}", transaction.transaction_id); } // Print received transaction ids println!("Received transactions:"); - for transaction in account.incoming_transactions().await { + for transaction in wallet.incoming_transactions().await { println!("{}", transaction.transaction_id); } diff --git a/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs b/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs index e79eb8a300..7c9ba9c99a 100644 --- a/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs +++ b/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 //! In this example we will send a transaction. +//! //! Rename `.env.example` to `.env` first. //! //! `cargo run --release --all-features --example advanced_transaction` @@ -24,12 +25,13 @@ async fn main() -> Result<()> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); - // Create the wallet - let wallet = Wallet::builder().finish().await?; + // Get the wallet we generated with `create_wallet`. + let wallet = Wallet::builder() + .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) + .finish() + .await?; - // Get the account we generated with `create_account` - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. + // May want to ensure the wallet is synced before sending a transaction. let balance = wallet.sync(None).await?; if balance.base_coin().available() >= 1_000_000 { @@ -46,13 +48,13 @@ async fn main() -> Result<()> { "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu", )?)) .add_unlock_condition(TimelockUnlockCondition::new(slot_index)?) - .finish_output(account.client().get_token_supply().await?)?; + .finish_output()?; - let transaction = account.send_outputs(vec![basic_output], None).await?; + let transaction = wallet.send_outputs(vec![basic_output], None).await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( diff --git a/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs b/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs index 723d12d877..68319df0a9 100644 --- a/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs +++ b/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs @@ -7,7 +7,7 @@ //! `cargo run --release --all-features --example claim_transaction` use iota_sdk::{ - wallet::{account::OutputsToClaim, Result}, + wallet::{OutputsToClaim, Result}, Wallet, }; @@ -16,31 +16,31 @@ async fn main() -> Result<()> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); - // Create the wallet - let wallet = Wallet::builder().finish().await?; - - // Get the account we generated with `create_account` - let account = wallet.get_account("Alice").await?; + // Get the wallet we generated with `create_wallet`. + let wallet = Wallet::builder() + .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) + .finish() + .await?; - // May want to ensure the account is synced before sending a transaction. - account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + wallet.sync(None).await?; // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - let output_ids = account.claimable_outputs(OutputsToClaim::All).await?; + let output_ids = wallet.claimable_outputs(OutputsToClaim::All).await?; println!("Available outputs to claim:"); for output_id in &output_ids { println!("{}", output_id); } - let transaction = account.claim_outputs(output_ids).await?; + let transaction = wallet.claim_outputs(output_ids).await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( diff --git a/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs b/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs index 5203d187dc..a4ddb1db9e 100644 --- a/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs +++ b/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs @@ -1,10 +1,10 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will send an amount below the minimum storage deposit. +//! In this example we will send an amount below the minimum amount. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -12,7 +12,7 @@ //! ``` use iota_sdk::{ - wallet::{account::TransactionOptions, Result}, + wallet::{Result, TransactionOptions}, Wallet, }; @@ -26,14 +26,14 @@ async fn main() -> Result<()> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); + // Get the wallet we generated with `create_wallet`. let wallet = Wallet::builder() .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. - account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + wallet.sync(None).await?; // Set the stronghold password wallet @@ -43,7 +43,7 @@ async fn main() -> Result<()> { println!("Sending '{}' coin(s) to '{}'...", SEND_MICRO_AMOUNT, RECV_ADDRESS); // Send a micro transaction - let transaction = account + let transaction = wallet .send( SEND_MICRO_AMOUNT, RECV_ADDRESS, @@ -56,7 +56,7 @@ async fn main() -> Result<()> { println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; diff --git a/sdk/examples/how_tos/native_tokens/burn.rs b/sdk/examples/how_tos/native_tokens/burn.rs index 887c47a8ce..15d0985236 100644 --- a/sdk/examples/how_tos/native_tokens/burn.rs +++ b/sdk/examples/how_tos/native_tokens/burn.rs @@ -6,9 +6,9 @@ //! output that minted it. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! -//! You may provide a TOKEN_ID that is available in the account. You can check this by running the +//! You may provide a TOKEN_ID that is available in the wallet. You can check this by running the //! `get_balance` example. You can create a new native token by running the `create_native_token` example. //! //! Rename `.env.example` to `.env` first, then run the command: @@ -22,7 +22,7 @@ use iota_sdk::{ Wallet, U256, }; -// The minimum available native token amount to search for in the account +// The minimum available native token amount to search for in the wallet const MIN_AVAILABLE_AMOUNT: u64 = 11; // The amount of the native token to burn const BURN_AMOUNT: u64 = 1; @@ -36,11 +36,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let alias = "Alice"; - let account = wallet.get_account(alias.to_string()).await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; // Take the given token id, or use a default. let token_id = std::env::args() @@ -59,10 +57,10 @@ async fn main() -> Result<()> { .await?; // Burn a native token - let transaction = account.burn(NativeToken::new(token_id, BURN_AMOUNT)?, None).await?; + let transaction = wallet.burn(NativeToken::new(token_id, BURN_AMOUNT)?, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -71,7 +69,7 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; print!("Balance after burning: "); if let Some(native_token_balance) = balance @@ -85,7 +83,7 @@ async fn main() -> Result<()> { } } else { println!( - "Native token '{token_id}' doesn't exist or there's not at least '{MIN_AVAILABLE_AMOUNT}' tokens of it in account '{alias}'" + "Native token '{token_id}' doesn't exist or there's not at least '{MIN_AVAILABLE_AMOUNT}' tokens of it in the wallet" ); } diff --git a/sdk/examples/how_tos/native_tokens/create.rs b/sdk/examples/how_tos/native_tokens/create.rs index 0a3b8a88f3..c47b7f8275 100644 --- a/sdk/examples/how_tos/native_tokens/create.rs +++ b/sdk/examples/how_tos/native_tokens/create.rs @@ -4,7 +4,7 @@ //! In this example we will create a native token. //! //! Make sure that `example.stronghold` and `example.walletdb` already exist by -//! running the `create_account` example! +//! running the `create_wallet` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -31,23 +31,23 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - let balance = account.sync(None).await?; + + let balance = wallet.sync(None).await?; // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - // We can first check if we already have an account output in our account, because an account can have many foundry + // We can first check if we already have an account output in our wallet, because an account can have many foundry // outputs and therefore we can reuse an existing one if balance.accounts().is_empty() { // If we don't have an account, we need to create one - let transaction = account.create_account_output(None, None).await?; + let transaction = wallet.create_account_output(None, None).await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -56,8 +56,8 @@ async fn main() -> Result<()> { block_id ); - account.sync(None).await?; - println!("Account synced"); + wallet.sync(None).await?; + println!("Wallet synced"); } let metadata = @@ -72,11 +72,11 @@ async fn main() -> Result<()> { foundry_metadata: Some(metadata.to_bytes()), }; - let transaction = account.create_native_token(params, None).await?; + let transaction = wallet.create_native_token(params, None).await?; println!("Transaction sent: {}", transaction.transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction.transaction_id, None, None) .await?; println!( @@ -86,9 +86,9 @@ async fn main() -> Result<()> { ); println!("Created token: {}", transaction.token_id); - // Ensure the account is synced after creating the native token. - account.sync(None).await?; - println!("Account synced"); + // Ensure the wallet is synced after creating the native token. + wallet.sync(None).await?; + println!("Wallet synced"); Ok(()) } diff --git a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs index 04ff86e03b..a324f366db 100644 --- a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs +++ b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs @@ -1,11 +1,11 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will try to destroy the first foundry there is in the account. This is only possible if its +//! In this example we will try to destroy the first foundry there is in the wallet. This is only possible if its //! circulating supply is 0 and no native tokens were burned. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -23,16 +23,14 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let alias = "Alice"; - let account = wallet.get_account(alias).await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; let foundry_count = balance.foundries().len(); println!("Foundries before destroying: {foundry_count}"); - // We try to destroy the first foundry in the account + // We try to destroy the first foundry in the wallet if let Some(foundry_id) = balance.foundries().first() { let token_id = TokenId::from(*foundry_id); @@ -47,7 +45,7 @@ async fn main() -> Result<()> { .iter() .find(|native_token| *native_token.token_id() == token_id); if let Some(native_token) = native_tokens { - let output = account.get_foundry_output(token_id).await?; + let output = wallet.get_foundry_output(token_id).await?; // Check if all tokens are melted. if native_token.available() != output.as_foundry().token_scheme().as_simple().circulating_supply() { // We are not able to melt all tokens, because we don't own them or they are not unlocked. @@ -57,12 +55,12 @@ async fn main() -> Result<()> { println!("Melting remaining tokens.."); // Melt all tokens so we can destroy the foundry. - let transaction = account + let transaction = wallet .melt_native_token(token_id, native_token.available(), None) .await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -72,15 +70,15 @@ async fn main() -> Result<()> { ); // Sync to make the foundry output available again, because it was used in the melting transaction. - account.sync(None).await?; + wallet.sync(None).await?; } println!("Destroying foundry.."); - let transaction = account.burn(*foundry_id, None).await?; + let transaction = wallet.burn(*foundry_id, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -90,12 +88,12 @@ async fn main() -> Result<()> { ); // Resync to update the foundries list. - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let foundry_count = balance.foundries().len(); println!("Foundries after destroying: {foundry_count}"); } else { - println!("No Foundry available in account '{alias}'"); + println!("No Foundry available in the wallet"); } Ok(()) diff --git a/sdk/examples/how_tos/native_tokens/melt.rs b/sdk/examples/how_tos/native_tokens/melt.rs index 8abab66817..90bdcc34d0 100644 --- a/sdk/examples/how_tos/native_tokens/melt.rs +++ b/sdk/examples/how_tos/native_tokens/melt.rs @@ -4,9 +4,9 @@ //! In this example we will melt an existing native token with its foundry. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! -//! You may provide a TOKEN_ID that is available in the account. The foundry +//! You may provide a TOKEN_ID that is available in the wallet. The foundry //! output which minted it needs to be available as well. You can check this by //! running the `get_balance` example. You can create a new native token by running //! the `create_native_token` example. @@ -30,10 +30,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; // Find first foundry and corresponding token id let token_id = std::env::args() @@ -49,7 +48,7 @@ async fn main() -> Result<()> { let available_balance = native_token_balance.available(); println!("Balance before melting: {available_balance}"); } else { - println!("Couldn't find native token '{token_id}' in the account"); + println!("Couldn't find native token '{token_id}' in the wallet"); return Ok(()); } @@ -60,10 +59,10 @@ async fn main() -> Result<()> { // Melt some of the circulating supply - let transaction = account.melt_native_token(token_id, MELT_AMOUNT, None).await?; + let transaction = wallet.melt_native_token(token_id, MELT_AMOUNT, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; @@ -73,7 +72,7 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let available_balance = balance .native_tokens() .iter() diff --git a/sdk/examples/how_tos/native_tokens/mint.rs b/sdk/examples/how_tos/native_tokens/mint.rs index 4aba7d2ea4..46a6ddfce9 100644 --- a/sdk/examples/how_tos/native_tokens/mint.rs +++ b/sdk/examples/how_tos/native_tokens/mint.rs @@ -4,9 +4,9 @@ //! In this example we will mint an existing native token with its foundry. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! -//! You may provide a TOKEN_ID that is available in the account. The foundry +//! You may provide a TOKEN_ID that is available in the wallet. The foundry //! output which minted it needs to be available as well. You can check this by //! running the `get_balance` example. You can create a new native token by running //! the `create_native_token` example. @@ -30,10 +30,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; // Find first foundry and corresponding token id let token_id = std::env::args() @@ -49,7 +48,7 @@ async fn main() -> Result<()> { let available_balance = native_token_balance.available(); println!("Balance before minting: {available_balance}"); } else { - println!("Couldn't find native token '{token_id}' in the account"); + println!("Couldn't find native token '{token_id}' in the wallet"); return Ok(()); } @@ -59,10 +58,10 @@ async fn main() -> Result<()> { .await?; // Mint some more native tokens - let transaction = account.mint_native_token(token_id, MINT_AMOUNT, None).await?; + let transaction = wallet.mint_native_token(token_id, MINT_AMOUNT, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; @@ -72,7 +71,7 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let available_balance = balance .native_tokens() .iter() diff --git a/sdk/examples/how_tos/native_tokens/send.rs b/sdk/examples/how_tos/native_tokens/send.rs index 9a0d56a6b1..3aa1c59b79 100644 --- a/sdk/examples/how_tos/native_tokens/send.rs +++ b/sdk/examples/how_tos/native_tokens/send.rs @@ -4,7 +4,7 @@ //! In this example we will send native tokens. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -32,10 +32,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; // Get a token with sufficient balance if let Some(token_id) = balance @@ -64,11 +63,11 @@ async fn main() -> Result<()> { [(*token_id, U256::from(SEND_NATIVE_TOKEN_AMOUNT))], )?]; - let transaction = account.send_native_tokens(outputs, None).await?; + let transaction = wallet.send_native_tokens(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -77,7 +76,7 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let available_balance = balance .native_tokens() diff --git a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs index cb934124ea..d49d6d177a 100644 --- a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs +++ b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs @@ -4,9 +4,9 @@ //! In this example we will mint the issuer NFT for the NFT collection. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! -//! Make sure that Account Alice already has some funds by running the +//! Make sure that the wallet already has some funds by running the //! `./how_tos/simple_transaction/request_funds.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: @@ -19,7 +19,7 @@ use iota_sdk::{ output::{NftId, Output, OutputId}, payload::signed_transaction::TransactionId, }, - wallet::{Account, MintNftParams, Result}, + wallet::{MintNftParams, Result}, Wallet, }; @@ -32,10 +32,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - account.sync(None).await?; - println!("Account synced!"); + wallet.sync(None).await?; + println!("Wallet synced!"); // Set the stronghold password wallet @@ -46,9 +45,9 @@ async fn main() -> Result<()> { println!("Sending NFT minting transaction..."); let nft_mint_params = [MintNftParams::new() .with_immutable_metadata(b"This NFT will be the issuer from the awesome NFT collection".to_vec())]; - let transaction = dbg!(account.mint_nfts(nft_mint_params, None).await)?; + let transaction = dbg!(wallet.mint_nfts(nft_mint_params, None).await)?; - wait_for_inclusion(&transaction.transaction_id, &account).await?; + wait_for_inclusion(&transaction.transaction_id, &wallet).await?; for (output_index, output) in transaction.payload.transaction().outputs().iter().enumerate() { if let Output::Nft(nft_output) = output { @@ -64,14 +63,14 @@ async fn main() -> Result<()> { Ok(()) } -async fn wait_for_inclusion(transaction_id: &TransactionId, account: &Account) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(transaction_id, None, None) .await?; println!( diff --git a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs index 09a4b0386e..b71ff05350 100644 --- a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs +++ b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs @@ -4,7 +4,7 @@ //! In this example we will mint some collection NFTs with issuer feature. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example. +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example. //! //! You have to provide the ISSUER_NFT_ID that was created by first running the //! `mint_issuer_nft` example! @@ -20,7 +20,7 @@ use iota_sdk::{ output::{feature::Irc27Metadata, NftId}, payload::signed_transaction::TransactionId, }, - wallet::{Account, MintNftParams, Result}, + wallet::{MintNftParams, Result}, Wallet, }; @@ -43,17 +43,16 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - account.sync(None).await?; - println!("Account synced!"); + wallet.sync(None).await?; + println!("Wallet synced!"); // Set the stronghold password wallet .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - let bech32_hrp = account.client().get_bech32_hrp().await?; + let bech32_hrp = wallet.client().get_bech32_hrp().await?; let issuer = Bech32Address::new(bech32_hrp, NftAddress::new(issuer_nft_id)); // Create the metadata with another index for each @@ -73,11 +72,11 @@ async fn main() -> Result<()> { index * NUM_NFTS_MINTED_PER_TRANSACTION + nft_mint_params.len(), NFT_COLLECTION_SIZE ); - let transaction = account.mint_nfts(nft_mint_params.to_vec(), None).await?; - wait_for_inclusion(&transaction.transaction_id, &account).await?; + let transaction = wallet.mint_nfts(nft_mint_params.to_vec(), None).await?; + wait_for_inclusion(&transaction.transaction_id, &wallet).await?; // Sync so the new outputs are available again for new transactions - account.sync(None).await?; + wallet.sync(None).await?; } // After the NFTs are minted, the issuer nft can be sent to the so called "null address" @@ -105,14 +104,14 @@ fn get_immutable_metadata(index: usize) -> Irc27Metadata { .with_collection_name("Shimmer OG") } -async fn wait_for_inclusion(transaction_id: &TransactionId, account: &Account) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(transaction_id, None, None) .await?; println!( diff --git a/sdk/examples/how_tos/nfts/burn_nft.rs b/sdk/examples/how_tos/nfts/burn_nft.rs index d6f55adce4..84a4ab2df5 100644 --- a/sdk/examples/how_tos/nfts/burn_nft.rs +++ b/sdk/examples/how_tos/nfts/burn_nft.rs @@ -4,7 +4,7 @@ //! In this example we will burn an existing nft output. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -23,11 +23,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let alias = "Alice"; - let account = wallet.get_account(alias).await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; // Get the first nft if let Some(nft_id) = balance.nfts().first() { @@ -39,10 +37,10 @@ async fn main() -> Result<()> { .set_stronghold_password(std::env::var("STRONGHOLD_PASSWORD").unwrap()) .await?; - let transaction = account.burn(*nft_id, None).await?; + let transaction = wallet.burn(*nft_id, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; @@ -54,11 +52,11 @@ async fn main() -> Result<()> { println!("Burned NFT '{}'", nft_id); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let nfts_after = balance.nfts(); println!("Balance after burning:\n{nfts_after:#?}",); } else { - println!("No NFT available in account '{alias}'"); + println!("No NFT available in the wallet"); } Ok(()) diff --git a/sdk/examples/how_tos/nfts/mint_nft.rs b/sdk/examples/how_tos/nfts/mint_nft.rs index 91b7c9cbaf..b1af051b4c 100644 --- a/sdk/examples/how_tos/nfts/mint_nft.rs +++ b/sdk/examples/how_tos/nfts/mint_nft.rs @@ -4,7 +4,7 @@ //! In this example we will mint some NFTs. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -35,20 +35,17 @@ async fn main() -> Result<()> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); - // Create the wallet + // Get the wallet we generated with `create_wallet`. let wallet = Wallet::builder() .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - // Get the account we generated with `create_account` - let account = wallet.get_account("Alice").await?; + // Ensure the wallet is synced after minting. + wallet.sync(None).await?; - // Ensure the account is synced after minting. - account.sync(None).await?; - - // We send from the first address in the account. - let sender_address = account.first_address_bech32().await; + // We send from the wallet address. + let sender_address = wallet.address().await; // Set the stronghold password wallet @@ -72,11 +69,11 @@ async fn main() -> Result<()> { .try_with_issuer(sender_address.clone())? .with_immutable_metadata(metadata.to_bytes())]; - let transaction = account.mint_nfts(nft_params, None).await?; + let transaction = wallet.mint_nfts(nft_params, None).await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -87,21 +84,20 @@ async fn main() -> Result<()> { println!("Minted NFT 1"); // Build an NFT manually by using the `NftOutputBuilder` - let token_supply = account.client().get_token_supply().await?; let outputs = [ // address of the owner of the NFT NftOutputBuilder::new_with_amount(NFT2_AMOUNT, NftId::null()) .add_unlock_condition(AddressUnlockCondition::new(sender_address.clone())) .add_feature(SenderFeature::new(sender_address.clone())) .add_immutable_feature(IssuerFeature::new(sender_address)) - .finish_output(token_supply)?, + .finish_output()?, ]; - let transaction = account.send_outputs(outputs, None).await?; + let transaction = wallet.send_outputs(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -111,8 +107,8 @@ async fn main() -> Result<()> { ); println!("Minted NFT 2"); - // Ensure the account is synced after minting. - account.sync(None).await?; + // Ensure the wallet is synced after minting. + wallet.sync(None).await?; Ok(()) } diff --git a/sdk/examples/how_tos/nfts/send_nft.rs b/sdk/examples/how_tos/nfts/send_nft.rs index 070c24731a..4c24e104ff 100644 --- a/sdk/examples/how_tos/nfts/send_nft.rs +++ b/sdk/examples/how_tos/nfts/send_nft.rs @@ -4,7 +4,7 @@ //! In this example we will send an NFT. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -29,10 +29,9 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. - let balance = account.sync(None).await?; + // May want to ensure the wallet is synced before sending a transaction. + let balance = wallet.sync(None).await?; // Get the first nft if let Some(nft_id) = balance.nfts().first() { @@ -45,11 +44,11 @@ async fn main() -> Result<()> { println!("Sending NFT '{}' to '{}'...", nft_id, RECV_ADDRESS); - let transaction = account.send_nft(outputs, None).await?; + let transaction = wallet.send_nft(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; diff --git a/sdk/examples/how_tos/outputs/features.rs b/sdk/examples/how_tos/outputs/features.rs index 97ae44ceb9..414ffe092e 100644 --- a/sdk/examples/how_tos/outputs/features.rs +++ b/sdk/examples/how_tos/outputs/features.rs @@ -31,12 +31,11 @@ async fn main() -> Result<()> { // Create a client instance. let client = Client::builder().with_node(&node_url)?.finish().await?; - let token_supply = client.get_token_supply().await?; - let rent_structure = client.get_rent_structure().await?; + let storage_score_params = client.get_storage_score_parameters().await?; let address = Address::try_from_bech32("rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy")?; - let nft_output_builder = NftOutputBuilder::new_with_minimum_storage_deposit(rent_structure, NftId::null()) + let nft_output_builder = NftOutputBuilder::new_with_minimum_amount(storage_score_params, NftId::null()) .add_unlock_condition(AddressUnlockCondition::new(address.clone())); let outputs = [ @@ -44,26 +43,26 @@ async fn main() -> Result<()> { nft_output_builder .clone() .add_feature(SenderFeature::new(address.clone())) - .finish_output(token_supply)?, + .finish_output()?, // with issuer feature nft_output_builder .clone() .add_immutable_feature(IssuerFeature::new(address)) - .finish_output(token_supply)?, + .finish_output()?, // with metadata feature block nft_output_builder .clone() .add_feature(MetadataFeature::new("Hello, World!")?) - .finish_output(token_supply)?, + .finish_output()?, // with immutable metadata feature block nft_output_builder .clone() .add_immutable_feature(MetadataFeature::new("Hello, World!")?) - .finish_output(token_supply)?, + .finish_output()?, // with tag feature nft_output_builder .add_feature(TagFeature::new("Hello, World!")?) - .finish_output(token_supply)?, + .finish_output()?, ]; // Convert ouput array to json array diff --git a/sdk/examples/how_tos/outputs/unlock_conditions.rs b/sdk/examples/how_tos/outputs/unlock_conditions.rs index ae74660100..94cb8ce779 100644 --- a/sdk/examples/how_tos/outputs/unlock_conditions.rs +++ b/sdk/examples/how_tos/outputs/unlock_conditions.rs @@ -33,46 +33,40 @@ async fn main() -> Result<()> { // Create a client instance. let client = Client::builder().with_node(&node_url)?.finish().await?; - let token_supply = client.get_token_supply().await?; - let rent_structure = client.get_rent_structure().await?; + let storage_score_params = client.get_storage_score_parameters().await?; let address = Address::try_from_bech32("rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy")?; let account_address = Address::try_from_bech32("rms1pr59qm43mjtvhcajfmupqf23x29llam88yecn6pyul80rx099krmv2fnnux")?; let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(50, 0, 100)?); - let basic_output_builder = BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) + let basic_output_builder = BasicOutputBuilder::new_with_minimum_amount(storage_score_params) .add_unlock_condition(AddressUnlockCondition::new(address.clone())); - let foundry_output_builder = - FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, 1, token_scheme); + let foundry_output_builder = FoundryOutputBuilder::new_with_minimum_amount(storage_score_params, 1, token_scheme); let outputs = [ //// most simple output - basic_output_builder.clone().finish_output(token_supply)?, + basic_output_builder.clone().finish_output()?, // with storage deposit return unlock condition basic_output_builder .clone() - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - address.clone(), - 1000000, - token_supply, - )?) - .finish_output(token_supply)?, + .add_unlock_condition(StorageDepositReturnUnlockCondition::new(address.clone(), 1000000)) + .finish_output()?, // with timeout unlock condition basic_output_builder .clone() .add_unlock_condition(TimelockUnlockCondition::new(1)?) - .finish_output(token_supply)?, + .finish_output()?, // with expiration unlock condition basic_output_builder .add_unlock_condition(ExpirationUnlockCondition::new(address.clone(), 1)?) - .finish_output(token_supply)?, + .finish_output()?, // with immutable account unlock condition foundry_output_builder .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new( *account_address.as_account(), )) - .finish_output(token_supply)?, + .finish_output()?, ]; // Convert ouput array to json array diff --git a/sdk/examples/how_tos/simple_transaction/request_funds.rs b/sdk/examples/how_tos/simple_transaction/request_funds.rs index 6203f8829b..2b19ae1d8f 100644 --- a/sdk/examples/how_tos/simple_transaction/request_funds.rs +++ b/sdk/examples/how_tos/simple_transaction/request_funds.rs @@ -1,10 +1,10 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we request funds from the faucet to the first address in the account. +//! In this example we request funds from the faucet to the first address in the wallet. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -22,19 +22,17 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - let balance = account.sync(None).await?; - println!("Account synced"); + let balance = wallet.sync(None).await?; + println!("Wallet synced"); - let addresses = account.addresses().await; + let wallet_address = wallet.address().await; let funds_before = balance.base_coin().available(); println!("Current available funds: {funds_before}"); println!("Requesting funds from faucet..."); - let faucet_response = - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), addresses[0].address()).await?; + let faucet_response = request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &wallet_address).await?; println!("Response from faucet: {}", faucet_response.trim_end()); @@ -46,7 +44,7 @@ async fn main() -> Result<()> { println!("Timeout: waiting for funds took too long"); return Ok(()); }; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let funds_after = balance.base_coin().available(); if funds_after > funds_before { break funds_after; diff --git a/sdk/examples/how_tos/simple_transaction/simple_transaction.rs b/sdk/examples/how_tos/simple_transaction/simple_transaction.rs index f1024a5bd6..16915f60b0 100644 --- a/sdk/examples/how_tos/simple_transaction/simple_transaction.rs +++ b/sdk/examples/how_tos/simple_transaction/simple_transaction.rs @@ -4,7 +4,7 @@ //! In this example we will issue a simple base coin transaction. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -27,9 +27,8 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - // May want to ensure the account is synced before sending a transaction. + // May want to ensure the wallet is synced before sending a transaction. let _ = wallet.sync(None).await?; // Set the stronghold password @@ -38,10 +37,10 @@ async fn main() -> Result<()> { .await?; println!("Trying to send '{}' coins to '{}'...", SEND_AMOUNT, RECV_ADDRESS); - let transaction = account.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; + let transaction = wallet.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; diff --git a/sdk/examples/wallet/17_check_unlock_conditions.rs b/sdk/examples/wallet/17_check_unlock_conditions.rs index 4c26147502..f961427fb5 100644 --- a/sdk/examples/wallet/17_check_unlock_conditions.rs +++ b/sdk/examples/wallet/17_check_unlock_conditions.rs @@ -1,20 +1,17 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we check if an output has only an address unlock condition and that the address is from the account. +//! In this example we check if an output has only an address unlock condition and that the address is from the wallet. //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example! +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example! //! //! ```sh //! cargo run --release --all-features --example check_unlock_conditions //! ``` use iota_sdk::{ - types::block::{ - address::Bech32Address, - output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, UnlockCondition}, - }, + types::block::output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, UnlockCondition}, wallet::Result, Wallet, }; @@ -31,30 +28,22 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; - let account_addresses = account - .addresses() - .await - .into_iter() - .map(|a| a.into_bech32()) - .collect::>(); + let wallet_address = wallet.address().await; - println!("ADDRESSES:\n{:#?}", account_addresses); + println!("Wallet address:\n{:#?}", wallet_address); let output = BasicOutputBuilder::new_with_amount(AMOUNT) - .add_unlock_condition(AddressUnlockCondition::new(account_addresses[0].as_ref().clone())) - .finish_output(account.client().get_token_supply().await?)?; + .add_unlock_condition(AddressUnlockCondition::new(wallet_address.clone())) + .finish_output()?; let controlled_by_account = if let [UnlockCondition::Address(address_unlock_condition)] = output .unlock_conditions() .expect("output needs to have unlock conditions") .as_ref() { - // Check that address in the unlock condition belongs to the account - account_addresses - .iter() - .any(|address| address.as_ref() == address_unlock_condition.address()) + // Check that the address in the unlock condition belongs to the wallet + wallet_address.inner() == address_unlock_condition.address() } else { false }; diff --git a/sdk/examples/wallet/accounts.rs b/sdk/examples/wallet/accounts.rs deleted file mode 100644 index 28230f697b..0000000000 --- a/sdk/examples/wallet/accounts.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will print the details of two accounts in the wallet. If an account doesn't exist yet it will be -//! created. For the second account it will generate as many addresses as defined in the constant. -//! -//! Make sure there's no `STRONGHOLD_SNAPSHOT_PATH` file and no `WALLET_DB_PATH` folder yet! -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --all-features --example accounts -//! ``` - -use iota_sdk::{ - client::{ - constants::SHIMMER_COIN_TYPE, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, - utils::request_funds_from_faucet, - }, - wallet::{ClientOptions, Result, Wallet}, -}; - -// The number of addresses to generate -const NUM_ADDRESSES_TO_GENERATE: u32 = 5; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; - - let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) - .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) - .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) - .finish() - .await?; - - // Get or create first account - let _ = wallet.get_or_create_account("Alice").await?; - - // Get or create second account - let alias2 = "Bob"; - let account2 = wallet.get_or_create_account(alias2).await?; - - let accounts = wallet.get_accounts().await?; - println!("WALLET ACCOUNTS:"); - for account in accounts { - let account = account.details().await; - println!("- {}", account.alias()); - } - - println!("Generating {NUM_ADDRESSES_TO_GENERATE} addresses for account '{alias2}'..."); - let addresses = account2 - .generate_ed25519_addresses(NUM_ADDRESSES_TO_GENERATE, None) - .await?; - - let balance = account2.sync(None).await?; - let funds_before = balance.base_coin().available(); - println!("Current available funds: {funds_before}"); - - println!("Requesting funds from faucet..."); - let faucet_response = - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), addresses[0].address()).await?; - println!("Response from faucet: {}", faucet_response.trim_end()); - - println!("Waiting for funds (timeout=60s)..."); - // Check for changes to the balance - let start = std::time::Instant::now(); - let balance = loop { - if start.elapsed().as_secs() > 60 { - println!("Timeout: waiting for funds took too long"); - return Ok(()); - }; - let now = tokio::time::Instant::now(); - let balance = account2.sync(None).await?; - if balance.base_coin().available() > funds_before { - println!("Account synced in: {:.2?}", now.elapsed()); - break balance; - } else { - tokio::time::sleep(instant::Duration::from_secs(2)).await; - } - }; - - println!("New available funds: {}", balance.base_coin().available()); - - let addresses = account2.addresses().await; - println!("Number of addresses in {alias2}'s account: {}", addresses.len()); - println!("{alias2}'s base coin balance:\n{:#?}", balance.base_coin()); - - Ok(()) -} diff --git a/sdk/examples/wallet/background_syncing.rs b/sdk/examples/wallet/background_syncing.rs index 0f8217feb8..9cb953ebca 100644 --- a/sdk/examples/wallet/background_syncing.rs +++ b/sdk/examples/wallet/background_syncing.rs @@ -1,7 +1,7 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will sync an account in the background. +//! In this example we will sync a wallet in the background. //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -14,6 +14,7 @@ use iota_sdk::{ request_funds_from_faucet, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, + crypto::keys::bip44::Bip44, wallet::{ClientOptions, Result, Wallet}, }; @@ -29,16 +30,14 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - // Get or create new account - let account = wallet.get_or_create_account("Alice").await?; - let addresses = account.addresses().await; + let wallet_address = wallet.address().await; // Manually sync to ensure we have the correct funds to start with - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let funds_before = balance.base_coin().available(); println!("Current available funds: {funds_before}"); @@ -46,8 +45,7 @@ async fn main() -> Result<()> { println!("Started background syncing"); println!("Requesting funds from faucet..."); - let faucet_response = - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), addresses[0].address()).await?; + let faucet_response = request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &wallet_address).await?; println!("Response from faucet: {}", faucet_response.trim_end()); println!("Waiting for funds (timeout=60s)..."); @@ -59,7 +57,7 @@ async fn main() -> Result<()> { return Ok(()); }; // We just query the balance and don't manually sync - let balance = account.balance().await?; + let balance = wallet.balance().await?; let funds_after = balance.base_coin().available(); if funds_after > funds_before { break funds_after; diff --git a/sdk/examples/wallet/events.rs b/sdk/examples/wallet/events.rs index c883e0548a..c2e2d99ec5 100644 --- a/sdk/examples/wallet/events.rs +++ b/sdk/examples/wallet/events.rs @@ -14,6 +14,7 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, + crypto::keys::bip44::Bip44, types::block::{ address::Address, output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder}, @@ -39,31 +40,28 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; wallet .listen([], move |event| { - println!("RECEIVED AN EVENT:\n{:?}", event.event); + println!("RECEIVED AN EVENT:\n{:?}", event); }) .await; - // Get or create an account - let account = wallet.get_or_create_account("Alice").await?; - - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; println!("Balance BEFORE:\n{:#?}", balance.base_coin()); // send transaction let outputs = [BasicOutputBuilder::new_with_amount(SEND_AMOUNT) .add_unlock_condition(AddressUnlockCondition::new(Address::try_from_bech32(RECV_ADDRESS)?)) - .finish_output(account.client().get_token_supply().await?)?]; + .finish_output()?]; - let transaction = account.send_outputs(outputs, None).await?; + let transaction = wallet.send_outputs(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; @@ -73,7 +71,7 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; println!("Balance AFTER:\n{:#?}", balance.base_coin()); Ok(()) diff --git a/sdk/examples/wallet/getting_started.rs b/sdk/examples/wallet/getting_started.rs index 1ec1488ebf..35b1431d23 100644 --- a/sdk/examples/wallet/getting_started.rs +++ b/sdk/examples/wallet/getting_started.rs @@ -1,8 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! In this example we will create a new wallet, a mnemonic, and an initial account. Then, we'll print the first address -//! of that account. +//! In this example we will first create a new wallet and a mnemonic, and then, print the wallet's address. //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh @@ -14,6 +13,7 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::{stronghold::StrongholdSecretManager, SecretManager}, }, + crypto::keys::bip44::Bip44, wallet::{ClientOptions, Result, Wallet}, }; @@ -23,7 +23,7 @@ async fn main() -> Result<()> { // WARNING: Never hardcode passwords in production code. let secret_manager = StrongholdSecretManager::builder() .password("password".to_owned()) // A password to encrypt the stored data. - .build("vault.stronghold")?; // The path to store the account snapshot. + .build("vault.stronghold")?; // The path to store the wallet snapshot. let client_options = ClientOptions::new().with_node("https://api.testnet.shimmer.network")?; @@ -31,8 +31,9 @@ async fn main() -> Result<()> { let wallet = Wallet::builder() .with_secret_manager(SecretManager::Stronghold(secret_manager)) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) .with_storage_path("getting-started-db") + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) + .with_alias("Alice".to_string()) .finish() .await?; @@ -42,15 +43,8 @@ async fn main() -> Result<()> { println!("Mnemonic: {}", mnemonic.as_ref()); wallet.store_mnemonic(mnemonic).await?; - // Create an account. - let account = wallet - .create_account() - .with_alias("Alice") // A name to associate with the created account. - .finish() - .await?; - - let first_address = &account.addresses().await[0]; - println!("{}", first_address.address()); + let wallet_address = wallet.address().await; + println!("{}", wallet_address); Ok(()) } diff --git a/sdk/examples/wallet/ledger_nano.rs b/sdk/examples/wallet/ledger_nano.rs index 75a65a0ae8..e428af78cb 100644 --- a/sdk/examples/wallet/ledger_nano.rs +++ b/sdk/examples/wallet/ledger_nano.rs @@ -19,13 +19,10 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::{ledger_nano::LedgerSecretManager, SecretManager}, }, + crypto::keys::bip44::Bip44, wallet::{ClientOptions, Result, Wallet}, }; -// The account alias used in this example -const ACCOUNT_ALIAS: &str = "ledger"; -// The number of addresses to generate -const NUM_ADDRESSES_TO_GENERATE: u32 = 1; // The address to send coins to const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; // The amount of base coins we'll send @@ -42,35 +39,30 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::LedgerNano(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; println!("{:?}", wallet.get_ledger_nano_status().await?); - // Get or create a new account - let account = wallet.get_or_create_account(ACCOUNT_ALIAS).await?; - - println!("Generating {NUM_ADDRESSES_TO_GENERATE} addresses..."); + println!("Generating address..."); let now = tokio::time::Instant::now(); - let addresses = account - .generate_ed25519_addresses(NUM_ADDRESSES_TO_GENERATE, None) - .await?; + let address = wallet.generate_ed25519_address(0, 0, None).await?; println!("took: {:.2?}", now.elapsed()); - println!("ADDRESSES:\n{addresses:#?}"); + println!("ADDRESS:\n{address:#?}"); let now = tokio::time::Instant::now(); - let balance = account.sync(None).await?; - println!("Account synced in: {:.2?}", now.elapsed()); + let balance = wallet.sync(None).await?; + println!("Wallet synced in: {:.2?}", now.elapsed()); println!("Balance BEFORE:\n{:?}", balance.base_coin()); println!("Sending the coin-transfer transaction..."); - let transaction = account.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; + let transaction = wallet.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -80,8 +72,8 @@ async fn main() -> Result<()> { ); let now = tokio::time::Instant::now(); - let balance = account.sync(None).await?; - println!("Account synced in: {:.2?}", now.elapsed()); + let balance = wallet.sync(None).await?; + println!("Wallet synced in: {:.2?}", now.elapsed()); println!("Balance AFTER:\n{:?}", balance.base_coin()); diff --git a/sdk/examples/wallet/logger.rs b/sdk/examples/wallet/logger.rs index 4cb69e54ca..43033a3bef 100644 --- a/sdk/examples/wallet/logger.rs +++ b/sdk/examples/wallet/logger.rs @@ -13,12 +13,10 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, + crypto::keys::bip44::Bip44, wallet::{ClientOptions, Result, Wallet}, }; -// The number of addresses to generate -const NUM_ADDRESSES_TO_GENERATE: u32 = 5; - #[tokio::main] async fn main() -> Result<()> { // This example uses secrets in environment variables for simplicity which should not be done in production. @@ -41,20 +39,15 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - // Get or create a new account - let account = wallet.get_or_create_account("Alice").await?; - - println!("Generating {NUM_ADDRESSES_TO_GENERATE} addresses..."); - let _ = account - .generate_ed25519_addresses(NUM_ADDRESSES_TO_GENERATE, None) - .await?; + println!("Generating address..."); + let _ = wallet.generate_ed25519_address(0, 0, None).await?; - println!("Syncing account"); - account.sync(None).await?; + println!("Syncing wallet"); + wallet.sync(None).await?; println!("Example finished successfully"); Ok(()) diff --git a/sdk/examples/wallet/offline_signing/0_generate_addresses.rs b/sdk/examples/wallet/offline_signing/0_generate_address.rs similarity index 67% rename from sdk/examples/wallet/offline_signing/0_generate_addresses.rs rename to sdk/examples/wallet/offline_signing/0_generate_address.rs index 30357e2819..fa14f84112 100644 --- a/sdk/examples/wallet/offline_signing/0_generate_addresses.rs +++ b/sdk/examples/wallet/offline_signing/0_generate_address.rs @@ -5,21 +5,21 @@ //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh -//! cargo run --release --all-features --example 0_generate_addresses +//! cargo run --release --all-features --example 0_generate_address //! ``` use iota_sdk::{ client::{ - constants::{SHIMMER_BECH32_HRP, SHIMMER_COIN_TYPE}, + constants::SHIMMER_COIN_TYPE, secret::{stronghold::StrongholdSecretManager, SecretManager}, }, - crypto::keys::bip39::Mnemonic, - wallet::{Account, ClientOptions, Result, Wallet}, + crypto::keys::{bip39::Mnemonic, bip44::Bip44}, + wallet::{ClientOptions, Result, Wallet}, }; const OFFLINE_WALLET_DB_PATH: &str = "./examples/wallet/offline_signing/example-offline-walletdb"; const STRONGHOLD_SNAPSHOT_PATH: &str = "./examples/wallet/offline_signing/example.stronghold"; -const ADDRESSES_FILE_PATH: &str = "./examples/wallet/offline_signing/example.addresses.json"; +const ADDRESS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.address.json"; #[tokio::main] async fn main() -> Result<()> { @@ -43,30 +43,22 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Stronghold(secret_manager)) .with_storage_path(OFFLINE_WALLET_DB_PATH) .with_client_options(offline_client) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - // Create a new account - let account = wallet - .create_account() - .with_alias("Alice") - .with_bech32_hrp(SHIMMER_BECH32_HRP) - .finish() - .await?; - - println!("Generated a new account '{}'", account.alias().await); + println!("Generated a new wallet"); - write_addresses_to_file(&account).await + write_wallet_address_to_file(&wallet).await } -async fn write_addresses_to_file(account: &Account) -> Result<()> { +async fn write_wallet_address_to_file(wallet: &Wallet) -> Result<()> { use tokio::io::AsyncWriteExt; - let addresses = account.addresses().await; - let json = serde_json::to_string_pretty(&addresses)?; - let mut file = tokio::io::BufWriter::new(tokio::fs::File::create(ADDRESSES_FILE_PATH).await?); - println!("example.addresses.json:\n{json}"); + let wallet_address = wallet.address().await; + let json = serde_json::to_string_pretty(&wallet_address)?; + let mut file = tokio::io::BufWriter::new(tokio::fs::File::create(ADDRESS_FILE_PATH).await?); + println!("example.address.json:\n{json}"); file.write_all(json.as_bytes()).await?; file.flush().await?; Ok(()) diff --git a/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs b/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs index ea9c3a0564..d9eee607cc 100644 --- a/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs +++ b/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs @@ -14,11 +14,12 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::SecretManager, }, - wallet::{account::types::Bip44Address, ClientOptions, Result, SendParams, Wallet}, + crypto::keys::bip44::Bip44, + wallet::{types::Bip44Address, ClientOptions, Result, SendParams, Wallet}, }; const ONLINE_WALLET_DB_PATH: &str = "./examples/wallet/offline_signing/example-online-walletdb"; -const ADDRESSES_FILE_PATH: &str = "./examples/wallet/offline_signing/example.addresses.json"; +const ADDRESS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.address.json"; const PREPARED_TRANSACTION_FILE_PATH: &str = "./examples/wallet/offline_signing/example.prepared_transaction.json"; // Address to which we want to send the amount. const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; @@ -33,7 +34,7 @@ async fn main() -> Result<()> { let params = [SendParams::new(SEND_AMOUNT, RECV_ADDRESS)?]; // Recovers addresses from example `0_address_generation`. - let addresses = read_addresses_from_file().await?; + let address = read_address_from_file().await?.into_bech32(); let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; @@ -42,22 +43,15 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Placeholder) .with_storage_path(ONLINE_WALLET_DB_PATH) .with_client_options(client_options.clone()) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) + .with_address(address) .finish() .await?; - // Create a new account - let account = wallet - .create_account() - .with_alias("Alice") - .with_addresses(addresses) - .finish() - .await?; - - // Sync the account to get the outputs for the addresses - account.sync(None).await?; + // Sync the wallet to get the outputs for the addresses + wallet.sync(None).await?; - let prepared_transaction = account.prepare_send(params.clone(), None).await?; + let prepared_transaction = wallet.prepare_send(params.clone(), None).await?; println!("Prepared transaction sending {params:?}"); @@ -66,10 +60,10 @@ async fn main() -> Result<()> { Ok(()) } -async fn read_addresses_from_file() -> Result> { +async fn read_address_from_file() -> Result { use tokio::io::AsyncReadExt; - let mut file = tokio::io::BufReader::new(tokio::fs::File::open(ADDRESSES_FILE_PATH).await?); + let mut file = tokio::io::BufReader::new(tokio::fs::File::open(ADDRESS_FILE_PATH).await?); let mut json = String::new(); file.read_to_string(&mut json).await?; diff --git a/sdk/examples/wallet/offline_signing/3_send_transaction.rs b/sdk/examples/wallet/offline_signing/3_send_transaction.rs index eeace0f994..8d1c91f13e 100644 --- a/sdk/examples/wallet/offline_signing/3_send_transaction.rs +++ b/sdk/examples/wallet/offline_signing/3_send_transaction.rs @@ -15,7 +15,7 @@ use iota_sdk::{ Client, }, types::{block::payload::signed_transaction::TransactionId, TryFromDto}, - wallet::{Account, Result}, + wallet::Result, Wallet, }; @@ -34,16 +34,13 @@ async fn main() -> Result<()> { .finish() .await?; - // Create a new account - let account = wallet.get_account("Alice").await?; - - let signed_transaction_data = read_signed_transaction_from_file(account.client()).await?; + let signed_transaction_data = read_signed_transaction_from_file(wallet.client()).await?; // Sends offline signed transaction online. - let transaction = account + let transaction = wallet .submit_and_store_transaction(signed_transaction_data, None) .await?; - wait_for_inclusion(&transaction.transaction_id, &account).await?; + wait_for_inclusion(&transaction.transaction_id, &wallet).await?; Ok(()) } @@ -63,14 +60,14 @@ async fn read_signed_transaction_from_file(client: &Client) -> Result Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(transaction_id, None, None) .await?; println!( diff --git a/sdk/examples/wallet/participation.rs b/sdk/examples/wallet/participation.rs index bfa7129fe3..d55f4059ea 100644 --- a/sdk/examples/wallet/participation.rs +++ b/sdk/examples/wallet/participation.rs @@ -8,7 +8,7 @@ //! * if a voting occurred, stops the voting and destroys the voting output //! //! Make sure that `STRONGHOLD_SNAPSHOT_PATH` and `WALLET_DB_PATH` already exist by -//! running the `./how_tos/accounts_and_addresses/create_account.rs` example and there are funds on the first address +//! running the `./how_tos/accounts_and_addresses/create_wallet.rs` example and there are funds on the first address //! by running the `get_funds` example! //! //! Rename `.env.example` to `.env` first, then run the command: @@ -18,7 +18,7 @@ use iota_sdk::{ client::node_manager::node::Node, - wallet::{account::types::participation::ParticipationEventRegistrationOptions, Result}, + wallet::{types::participation::ParticipationEventRegistrationOptions, Result}, Wallet, }; use url::Url; @@ -46,7 +46,6 @@ async fn main() -> Result<()> { .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .finish() .await?; - let account = wallet.get_account("Alice").await?; // Provide the stronghold password wallet @@ -59,7 +58,7 @@ async fn main() -> Result<()> { auth: None, disabled: false, }; - let _ = account + let _ = wallet .register_participation_events(&ParticipationEventRegistrationOptions { node, // We ignore this particular event @@ -72,7 +71,7 @@ async fn main() -> Result<()> { .await?; println!("Registered events:"); - let registered_participation_events = account.get_participation_events().await?; + let registered_participation_events = wallet.get_participation_events().await?; for (i, (id, event)) in registered_participation_events.iter().enumerate() { println!("EVENT #{i}"); println!( @@ -86,11 +85,11 @@ async fn main() -> Result<()> { } println!("Checking for participation event '{PARTICIPATION_EVENT_ID_1}'"); - if let Ok(Some(event)) = account.get_participation_event(event_id).await { + if let Ok(Some(event)) = wallet.get_participation_event(event_id).await { println!("{event:#?}"); println!("Getting event status for '{PARTICIPATION_EVENT_ID_1}'"); - let event_status = account.get_participation_event_status(&event_id).await?; + let event_status = wallet.get_participation_event_status(&event_id).await?; println!("{event_status:#?}"); } else { println!("Event not found"); @@ -100,12 +99,12 @@ async fn main() -> Result<()> { // deregister an event //////////////////////////////////////////////// if !DEREGISTERED_PARTICIPATION_EVENT.is_empty() { - account + wallet .deregister_participation_event(&DEREGISTERED_PARTICIPATION_EVENT.parse()?) .await?; println!("Registered events (updated):"); - let registered_participation_events = account.get_participation_events().await?; + let registered_participation_events = wallet.get_participation_events().await?; for (i, (id, event)) in registered_participation_events.iter().enumerate() { println!("EVENT #{i}"); println!( @@ -119,8 +118,8 @@ async fn main() -> Result<()> { } } - let balance = account.sync(None).await?; - println!("Account synced"); + let balance = wallet.sync(None).await?; + println!("Wallet synced"); //////////////////////////////////////////////// // create voting output or increase voting power @@ -129,11 +128,11 @@ async fn main() -> Result<()> { println!("Current voting power: {}", balance.base_coin().voting_power()); println!("Sending transaction to increase voting power..."); - let transaction = account.increase_voting_power(INCREASE_VOTING_POWER_AMOUNT).await?; + let transaction = wallet.increase_voting_power(INCREASE_VOTING_POWER_AMOUNT).await?; println!("Transaction sent: {}", transaction.transaction_id); println!("Waiting for `increase voting power` transaction to be included..."); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -142,11 +141,11 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; - println!("Account synced"); + let balance = wallet.sync(None).await?; + println!("Wallet synced"); println!("New voting power: {}", balance.base_coin().voting_power()); - let voting_output = account.get_voting_output().await?.unwrap(); + let voting_output = wallet.get_voting_output().await?.unwrap(); println!("Voting output:\n{:#?}", voting_output.output); //////////////////////////////////////////////// @@ -154,11 +153,11 @@ async fn main() -> Result<()> { //////////////////////////////////////////////// println!("Sending transaction to decrease voting power..."); - let transaction = account.decrease_voting_power(DECREASE_VOTING_POWER_AMOUNT).await?; + let transaction = wallet.decrease_voting_power(DECREASE_VOTING_POWER_AMOUNT).await?; println!("Transaction sent: {}", transaction.transaction_id); println!("Waiting for `decrease voting power` transaction to be included..."); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -167,8 +166,8 @@ async fn main() -> Result<()> { block_id ); - let balance = account.sync(None).await?; - println!("Account synced"); + let balance = wallet.sync(None).await?; + println!("Wallet synced"); println!("New voting power: {}", balance.base_coin().voting_power()); //////////////////////////////////////////////// @@ -176,14 +175,14 @@ async fn main() -> Result<()> { //////////////////////////////////////////////// println!("Sending transaction to vote..."); - let transaction = account.vote(Some(event_id), Some(vec![0])).await?; + let transaction = wallet.vote(Some(event_id), Some(vec![0])).await?; // NOTE!!! // from here on out, the example will only proceed if you've set up your own participation event and // changed the constants above with a valid (i.e. ongoing) event id for println!("Transaction sent: {}", transaction.transaction_id); println!("Waiting for `vote` transaction to be included..."); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -192,25 +191,25 @@ async fn main() -> Result<()> { block_id ); - account.sync(None).await?; - println!("Account synced"); + wallet.sync(None).await?; + println!("Wallet synced"); //////////////////////////////////////////////// // get voting overview //////////////////////////////////////////////// - let overview = account.get_participation_overview(None).await?; + let overview = wallet.get_participation_overview(None).await?; println!("Particpation overview:\n{overview:?}"); //////////////////////////////////////////////// // stop vote //////////////////////////////////////////////// - let transaction = account.stop_participating(event_id).await?; + let transaction = wallet.stop_participating(event_id).await?; println!("Transaction sent: {}", transaction.transaction_id); println!("Waiting for `stop participating` transaction to be included..."); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -219,22 +218,22 @@ async fn main() -> Result<()> { block_id ); - account.sync(None).await?; - println!("Account synced"); + wallet.sync(None).await?; + println!("Wallet synced"); //////////////////////////////////////////////// // destroy voting output //////////////////////////////////////////////// - let voting_output = account.get_voting_output().await?.unwrap(); + let voting_output = wallet.get_voting_output().await?.unwrap(); println!("Voting output: {:?}", voting_output.output); // Decrease full amount, there should be no voting output afterwards - let transaction = account.decrease_voting_power(voting_output.output.amount()).await?; + let transaction = wallet.decrease_voting_power(voting_output.output.amount()).await?; println!("Transaction sent: {}", transaction.transaction_id); println!("Waiting for `decrease voting power` transaction to be included..."); - let block_id = account + let block_id = wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; println!( @@ -243,10 +242,10 @@ async fn main() -> Result<()> { block_id ); - account.sync(None).await?; - println!("Account synced"); + wallet.sync(None).await?; + println!("Wallet synced"); - assert!(account.get_voting_output().await.is_err()); + assert!(wallet.get_voting_output().await.is_err()); Ok(()) } diff --git a/sdk/examples/wallet/recover_accounts.rs b/sdk/examples/wallet/recover_accounts.rs deleted file mode 100644 index 61a05dfb13..0000000000 --- a/sdk/examples/wallet/recover_accounts.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will recover a wallet from a given mnemonic. -//! -//! Make sure there's no folder yet at `WALLET_DB_PATH`. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --all-features --example recover_accounts -//! ``` - -use iota_sdk::{ - client::{ - constants::SHIMMER_COIN_TYPE, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, - }, - wallet::{ClientOptions, Result, Wallet}, -}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; - - let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) - .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) - .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) - .finish() - .await?; - - let accounts = wallet.recover_accounts(0, 2, 2, None).await?; - - println!("Recovered {} accounts", accounts.len()); - for account in accounts.iter() { - println!("ACCOUNT #{}:", account.details().await.index()); - let now = tokio::time::Instant::now(); - let balance = account.sync(None).await?; - println!("Account synced in: {:.2?}", now.elapsed()); - println!("Balance: {balance:#?}"); - } - - Ok(()) -} diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index 52e33d43cb..9387836fd1 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -12,14 +12,17 @@ use iota_sdk::{ client::{ constants::SHIMMER_COIN_TYPE, request_funds_from_faucet, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, + secret::{mnemonic::MnemonicSecretManager, SecretManage, SecretManager}, }, - types::block::{address::Bech32Address, output::BasicOutput, payload::signed_transaction::TransactionId}, - wallet::{account::FilterOptions, Account, ClientOptions, Result, SendParams, Wallet}, + crypto::keys::bip44::Bip44, + types::block::{ + address::{Address, Bech32Address, Hrp}, + output::BasicOutput, + payload::signed_transaction::TransactionId, + }, + wallet::{ClientOptions, FilterOptions, Result, SendParams, Wallet}, }; -// The account alias used in this example. -const ACCOUNT_ALIAS: &str = "spammer"; // The number of spamming rounds. const NUM_ROUNDS: usize = 1000; // The amount to send in each transaction @@ -39,28 +42,39 @@ async fn main() -> Result<()> { // Restore wallet from a mnemonic phrase. let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; + + let bip_path = Bip44::new(SHIMMER_COIN_TYPE); + let address = Bech32Address::new( + Hrp::from_str_unchecked("smr"), + Address::from( + secret_manager + .generate_ed25519_addresses(bip_path.coin_type, bip_path.account, 0..1, None) + .await?[0], + ), + ); + let wallet = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(bip_path) + .with_address(address) .finish() .await?; - let account = wallet.get_or_create_account(ACCOUNT_ALIAS).await?; - let recv_address = account.first_address_bech32().await; + let recv_address = wallet.address().await; println!("Recv address: {}", recv_address); // Ensure there are enough available funds for spamming. - ensure_enough_funds(&account, &recv_address).await?; + ensure_enough_funds(&wallet, &recv_address).await?; // We make sure that for all threads there are always inputs available to // fund the transaction, otherwise we create enough unspent outputs. - let num_unspent_basic_outputs_with_send_amount = account + let num_unspent_basic_outputs_with_send_amount = wallet .unspent_outputs(FilterOptions { output_types: Some(vec![BasicOutput::KIND]), ..Default::default() }) - .await? + .await .iter() .filter(|data| data.output.amount() >= SEND_AMOUNT) .count(); @@ -70,12 +84,12 @@ async fn main() -> Result<()> { if num_unspent_basic_outputs_with_send_amount < 127 { println!("Creating unspent outputs..."); - let transaction = account + let transaction = wallet .send_with_params(vec![SendParams::new(SEND_AMOUNT, recv_address.clone())?; 127], None) .await?; - wait_for_inclusion(&transaction.transaction_id, &account).await?; + wait_for_inclusion(&transaction.transaction_id, &wallet).await?; - account.sync(None).await?; + wallet.sync(None).await?; } println!("Spamming transactions..."); @@ -86,14 +100,14 @@ async fn main() -> Result<()> { let mut tasks = tokio::task::JoinSet::>::new(); for n in 0..num_simultaneous_txs { - let account_clone = account.clone(); let recv_address = recv_address.clone(); + let wallet = wallet.clone(); tasks.spawn(async move { println!("Thread {n}: sending {SEND_AMOUNT} coins to own address"); let thread_timer = tokio::time::Instant::now(); - let transaction = account_clone + let transaction = wallet .send(SEND_AMOUNT, recv_address, None) .await .map_err(|err| (n, err))?; @@ -123,14 +137,14 @@ async fn main() -> Result<()> { if error_state.is_err() { // Sync when getting an error, because that's probably when no outputs are available anymore - let mut balance = account.sync(None).await?; - println!("Account synced"); + let mut balance = wallet.sync(None).await?; + println!("Wallet synced"); while balance.base_coin().available() == 0 { println!("No funds available"); tokio::time::sleep(std::time::Duration::from_secs(2)).await; - balance = account.sync(None).await?; - println!("Account synced"); + balance = wallet.sync(None).await?; + println!("Wallet synced"); } } @@ -142,8 +156,8 @@ async fn main() -> Result<()> { Ok(()) } -async fn ensure_enough_funds(account: &Account, bech32_address: &Bech32Address) -> Result<()> { - let balance = account.sync(None).await?; +async fn ensure_enough_funds(wallet: &Wallet, bech32_address: &Bech32Address) -> Result<()> { + let balance = wallet.sync(None).await?; let available_funds = balance.base_coin().available(); println!("Available funds: {available_funds}"); let min_required_funds = (1.1f64 * (127u64 * SEND_AMOUNT) as f64) as u64; @@ -163,7 +177,7 @@ async fn ensure_enough_funds(account: &Account, bech32_address: &Bech32Address) if start.elapsed().as_secs() > 60 { panic!("Requesting funds failed (timeout)"); }; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let available_funds_after = balance.base_coin().available(); if available_funds_after > available_funds { break available_funds_after; @@ -183,14 +197,14 @@ async fn ensure_enough_funds(account: &Account, bech32_address: &Bech32Address) } } -async fn wait_for_inclusion(transaction_id: &TransactionId, account: &Account) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(transaction_id, None, None) .await?; println!( diff --git a/sdk/examples/wallet/split_funds.rs b/sdk/examples/wallet/split_funds.rs deleted file mode 100644 index 49f3be624d..0000000000 --- a/sdk/examples/wallet/split_funds.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will split funds among a pre-defined number of addresses. -//! -//! Make sure there's no folder yet at `WALLET_DB_PATH`. -//! For this example it's best to use a fresh mnemonic and start with a balance on the first address only. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --all-features --example split_funds -//! ``` - -use iota_sdk::{ - client::{ - constants::SHIMMER_COIN_TYPE, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, - }, - types::block::output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder}, - wallet::{account::types::Bip44Address, Account, ClientOptions, Result, Wallet}, -}; - -// The base coin amount to send -const SEND_AMOUNT: u64 = 1_000_000; -// The number of addresses funds are distributed to -const ADDRESSES_TO_SPLIT_FUNDS: usize = 15; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; - - let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) - .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) - .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) - .finish() - .await?; - - // Get account or create a new one - let account = wallet.get_or_create_account("Alice").await?; - - let _ = ensure_enough_addresses(&account, ADDRESSES_TO_SPLIT_FUNDS).await?; - - let addresses = account.addresses().await; - println!("Total address count: {}", addresses.len()); - - sync_print_balance(&account).await?; - - let addresses_with_unspent_outputs = account.addresses_with_unspent_outputs().await?; - println!( - "Addresses with balance count (before): {}", - addresses_with_unspent_outputs.len() - ); - - let token_supply = account.client().get_token_supply().await?; - - // Send split transactions - for addresses_chunk in addresses.chunks(2).map(|chunk| chunk.to_vec()) { - let outputs_per_transaction = addresses_chunk - .into_iter() - .map(|a| { - BasicOutputBuilder::new_with_amount(SEND_AMOUNT) - .add_unlock_condition(AddressUnlockCondition::new(a.address())) - .finish_output(token_supply) - .unwrap() - }) - .collect::>(); - - println!( - "Sending '{}' coins in {} outputs...", - SEND_AMOUNT, - outputs_per_transaction.len() - ); - let transaction = account.send_outputs(outputs_per_transaction, None).await?; - println!( - "Transaction sent: {}/transaction/{}", - std::env::var("EXPLORER_URL").unwrap(), - transaction.transaction_id - ); - - // Wait for transaction to get included - let block_id = account - .reissue_transaction_until_included(&transaction.transaction_id, None, None) - .await?; - - println!( - "Block included: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_id - ); - } - - sync_print_balance(&account).await?; - - let addresses_with_unspent_outputs = account.addresses_with_unspent_outputs().await?; - println!( - "Addresses with balance count (after): {}", - addresses_with_unspent_outputs.len() - ); - - println!("Example finished successfully"); - - Ok(()) -} - -async fn sync_print_balance(account: &Account) -> Result<()> { - let alias = account.alias().await; - let now = tokio::time::Instant::now(); - let balance = account.sync(None).await?; - println!("{alias}'s account synced in: {:.2?}", now.elapsed()); - println!("{alias}'s balance:\n{:#?}", balance.base_coin()); - Ok(()) -} - -async fn ensure_enough_addresses(account: &Account, limit: usize) -> Result> { - let alias = account.alias().await; - if account.addresses().await.len() < limit { - let num_addresses_to_generate = limit - account.addresses().await.len(); - println!("Generating {num_addresses_to_generate} addresses for account '{alias}'..."); - account - .generate_ed25519_addresses(num_addresses_to_generate as u32, None) - .await?; - } - Ok(account.addresses().await) -} diff --git a/sdk/examples/wallet/storage.rs b/sdk/examples/wallet/storage.rs index 98c3bcdf66..fa1b3b571d 100644 --- a/sdk/examples/wallet/storage.rs +++ b/sdk/examples/wallet/storage.rs @@ -13,12 +13,10 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, - wallet::{account::types::Bip44Address, Account, ClientOptions, Result, Wallet}, + crypto::keys::bip44::Bip44, + wallet::{types::Bip44Address, ClientOptions, Result, Wallet}, }; -// The maximum number of addresses to generate -const MAX_ADDRESSES_TO_GENERATE: usize = 3; - #[tokio::main] async fn main() -> Result<()> { // This example uses secrets in environment variables for simplicity which should not be done in production. @@ -32,48 +30,25 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - // Get account or create a new one - let account = wallet.get_or_create_account("Alice").await?; - - let addresses = generate_max_addresses(&account, MAX_ADDRESSES_TO_GENERATE).await?; - let bech32_addresses = addresses - .into_iter() - .map(|address| address.into_bech32()) - .collect::>(); - - println!("Total address count:\n{:?}", account.addresses().await.len()); - println!("ADDRESSES:\n{bech32_addresses:#?}"); + let bech32_address = wallet.address().await; - sync_print_balance(&account).await?; + println!("ADDRESS:\n{bech32_address}"); - #[cfg(debug_assertions)] - wallet.verify_integrity().await?; + sync_print_balance(&wallet).await?; println!("Example finished successfully"); Ok(()) } -async fn generate_max_addresses(account: &Account, max: usize) -> Result> { - let alias = account.alias().await; - if account.addresses().await.len() < max { - let num_addresses_to_generate = max - account.addresses().await.len(); - println!("Generating {num_addresses_to_generate} addresses for account '{alias}'..."); - account - .generate_ed25519_addresses(num_addresses_to_generate as u32, None) - .await?; - } - Ok(account.addresses().await) -} - -async fn sync_print_balance(account: &Account) -> Result<()> { - let alias = account.alias().await; +async fn sync_print_balance(wallet: &Wallet) -> Result<()> { + let alias = wallet.alias().await; let now = tokio::time::Instant::now(); - let balance = account.sync(None).await?; - println!("{alias}'s account synced in: {:.2?}", now.elapsed()); - println!("{alias}'s balance:\n{:#?}", balance.base_coin()); + let balance = wallet.sync(None).await?; + println!("Wallet synced in: {:.2?}", now.elapsed()); + println!("Balance:\n{:#?}", balance.base_coin()); Ok(()) } diff --git a/sdk/examples/wallet/wallet.rs b/sdk/examples/wallet/wallet.rs index ab0e45222c..12f8b2ec33 100644 --- a/sdk/examples/wallet/wallet.rs +++ b/sdk/examples/wallet/wallet.rs @@ -3,11 +3,9 @@ //! In this example we will: //! * create a wallet from a mnemonic phrase -//! * create an account if it does not exist yet -//! * generate some addresses for that account - if necessary -//! * print all addresses in the account -//! * print all addresses with funds in the account -//! * make a coin transaction +//! * print the wallet address (as Bech32) +//! * print funds on the wallet address +//! * issue a coin transaction //! //! Make sure there's no `STRONGHOLD_SNAPSHOT_PATH` file and no `WALLET_DB_PATH` folder yet! //! @@ -16,17 +14,16 @@ //! cargo run --release --all-features --example wallet //! ``` +use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ constants::SHIMMER_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, types::block::payload::signed_transaction::TransactionId, - wallet::{Account, ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Result, Wallet}, }; -// The number of addresses to generate in this account -const MAX_ADDRESSES_TO_GENERATE: usize = 10; // The amount of coins to send const SEND_AMOUNT: u64 = 1_000_000; // The address to send the coins to @@ -39,22 +36,14 @@ async fn main() -> Result<()> { let wallet = create_wallet().await?; - let account = wallet.get_or_create_account("Alice").await?; - print_accounts(&wallet).await?; - - generate_addresses(&account, MAX_ADDRESSES_TO_GENERATE).await?; - print_addresses(&account).await?; - // Change to `true` to print the full balance report - sync_print_balance(&account, false).await?; - - print_addresses_with_funds(&account).await?; + sync_print_balance(&wallet, false).await?; println!("Sending '{}' coins to '{}'...", SEND_AMOUNT, RECV_ADDRESS); - let transaction = account.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; - wait_for_inclusion(&transaction.transaction_id, &account).await?; + let transaction = wallet.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; + wait_for_inclusion(&transaction.transaction_id, &wallet).await?; - sync_print_balance(&account, false).await?; + sync_print_balance(&wallet, false).await?; println!("Example finished successfully"); Ok(()) @@ -67,81 +56,36 @@ async fn create_wallet() -> Result { .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await } -async fn print_accounts(wallet: &Wallet) -> Result<()> { - let accounts = wallet.get_accounts().await?; - println!("Accounts:"); - for account in accounts { - let details = account.details().await; - println!("- {}", details.alias()); - } - Ok(()) -} - -async fn generate_addresses(account: &Account, max: usize) -> Result<()> { - if account.addresses().await.len() < max { - let num_addresses_to_generate = max - account.addresses().await.len(); - println!("Generating {num_addresses_to_generate} addresses ..."); - let now = tokio::time::Instant::now(); - account - .generate_ed25519_addresses(num_addresses_to_generate as u32, None) - .await?; - println!("Finished in: {:.2?}", now.elapsed()); - } - Ok(()) -} - -async fn print_addresses(account: &Account) -> Result<()> { - let addresses = account.addresses().await; - println!("{}'s addresses:", account.alias().await); - for address in addresses { - println!("- {}", address.address()); - } +async fn print_address(wallet: &Wallet) -> Result<()> { + println!("Wallet address: {}", wallet.address().await); Ok(()) } -async fn sync_print_balance(account: &Account, full_report: bool) -> Result<()> { - let alias = account.alias().await; +async fn sync_print_balance(wallet: &Wallet, full_report: bool) -> Result<()> { let now = tokio::time::Instant::now(); - let balance = account.sync(None).await?; - println!("{alias}'s account synced in: {:.2?}", now.elapsed()); + let balance = wallet.sync(None).await?; + println!("Wallet synced in: {:.2?}", now.elapsed()); if full_report { - println!("{alias}'s balance:\n{balance:#?}"); + println!("Balance:\n{balance:#?}"); } else { - println!("{alias}'s coin balance:\n{:#?}", balance.base_coin()); - } - Ok(()) -} - -async fn print_addresses_with_funds(account: &Account) -> Result<()> { - let addresses_with_unspent_outputs = account.addresses_with_unspent_outputs().await?; - println!( - "{}'s addresses with funds/assets: {}", - account.alias().await, - addresses_with_unspent_outputs.len() - ); - for address_with_unspent_outputs in addresses_with_unspent_outputs { - println!("- {}", address_with_unspent_outputs.address()); - println!(" Output Ids:"); - for output_id in address_with_unspent_outputs.output_ids() { - println!(" {}", output_id); - } + println!("Coin balance:\n{:#?}", balance.base_coin()); } Ok(()) } -async fn wait_for_inclusion(transaction_id: &TransactionId, account: &Account) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); // Wait for transaction to get included - let block_id = account + let block_id = wallet .reissue_transaction_until_included(transaction_id, None, None) .await?; println!( diff --git a/sdk/src/client/api/address.rs b/sdk/src/client/api/address.rs index 173de7289f..6d5501d4d7 100644 --- a/sdk/src/client/api/address.rs +++ b/sdk/src/client/api/address.rs @@ -12,10 +12,8 @@ use crate::{ secret::{GenerateAddressOptions, SecretManage, SecretManager}, Client, Result, }, - types::block::{ - address::{Address, Bech32Address, Hrp, ToBech32Ext}, - ConvertTo, - }, + types::block::address::{Address, Bech32Address, Hrp, ToBech32Ext}, + utils::ConvertTo, }; #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/sdk/src/client/api/block_builder/input_selection/error.rs b/sdk/src/client/api/block_builder/input_selection/error.rs index 14e3303216..9001e8adc0 100644 --- a/sdk/src/client/api/block_builder/input_selection/error.rs +++ b/sdk/src/client/api/block_builder/input_selection/error.rs @@ -12,6 +12,7 @@ use crate::types::block::output::{ChainId, OutputId, TokenId}; /// Errors related to input selection. #[derive(Debug, Eq, PartialEq, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// Block error. #[error("{0}")] diff --git a/sdk/src/client/api/block_builder/input_selection/remainder.rs b/sdk/src/client/api/block_builder/input_selection/remainder.rs index 17d6679fe1..8ccd4b5058 100644 --- a/sdk/src/client/api/block_builder/input_selection/remainder.rs +++ b/sdk/src/client/api/block_builder/input_selection/remainder.rs @@ -66,7 +66,7 @@ impl InputSelection { let native_tokens_remainder = native_tokens_diff.is_some(); let mut remainder_builder = - BasicOutputBuilder::new_with_minimum_storage_deposit(self.protocol_parameters.rent_structure()) + BasicOutputBuilder::new_with_minimum_amount(self.protocol_parameters.storage_score_parameters()) .add_unlock_condition(AddressUnlockCondition::new(Address::from(Ed25519Address::from( [0; 32], )))); @@ -75,12 +75,7 @@ impl InputSelection { remainder_builder = remainder_builder.with_native_tokens(native_tokens); } - Ok(( - remainder_builder - .finish_output(self.protocol_parameters.token_supply())? - .amount(), - native_tokens_remainder, - )) + Ok((remainder_builder.finish_output()?.amount(), native_tokens_remainder)) } // TODO return many remainder, :sadcat: @@ -99,7 +94,7 @@ impl InputSelection { let diff = amount - output_sdr_amount; let srd_output = BasicOutputBuilder::new_with_amount(diff) .with_unlock_conditions([AddressUnlockCondition::new(address.clone())]) - .finish_output(self.protocol_parameters.token_supply())?; + .finish_output()?; // TODO verify_storage_deposit ? @@ -143,14 +138,11 @@ impl InputSelection { remainder_builder = remainder_builder.with_native_tokens(native_tokens); } - let remainder = remainder_builder.finish_output(self.protocol_parameters.token_supply())?; + let remainder = remainder_builder.finish_output()?; log::debug!("Created remainder output of {diff} for {remainder_address:?}"); - remainder.verify_storage_deposit( - self.protocol_parameters.rent_structure(), - self.protocol_parameters.token_supply(), - )?; + remainder.verify_storage_deposit(self.protocol_parameters.storage_score_parameters())?; Ok(( Some(RemainderData { diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs b/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs index fbe3bc34c5..e0822901dc 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs +++ b/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs @@ -11,7 +11,7 @@ use crate::{ input::INPUT_COUNT_MAX, output::{ unlock_condition::StorageDepositReturnUnlockCondition, AccountOutputBuilder, FoundryOutputBuilder, - NftOutputBuilder, Output, OutputId, Rent, + MinimumOutputAmount, NftOutputBuilder, Output, OutputId, }, slot::SlotIndex, }, @@ -229,11 +229,15 @@ impl InputSelection { for output in outputs { let diff = amount_selection.missing_amount(); let amount = output.amount(); - let rent = output.rent_cost(self.protocol_parameters.rent_structure()); + let minimum_amount = output.minimum_amount(self.protocol_parameters.storage_score_parameters()); - let new_amount = if amount >= diff + rent { amount - diff } else { rent }; + let new_amount = if amount >= diff + minimum_amount { + amount - diff + } else { + minimum_amount + }; - // TODO check that new_amount is enough for the rent + // TODO check that new_amount is enough for the storage cost // PANIC: unwrap is fine as non-chain outputs have been filtered out already. log::debug!( @@ -245,13 +249,13 @@ impl InputSelection { let new_output = match output { Output::Account(output) => AccountOutputBuilder::from(&*output) .with_amount(new_amount) - .finish_output(self.protocol_parameters.token_supply())?, - Output::Nft(output) => NftOutputBuilder::from(&*output) - .with_amount(new_amount) - .finish_output(self.protocol_parameters.token_supply())?, + .finish_output()?, Output::Foundry(output) => FoundryOutputBuilder::from(&*output) .with_amount(new_amount) - .finish_output(self.protocol_parameters.token_supply())?, + .finish_output()?, + Output::Nft(output) => NftOutputBuilder::from(&*output) + .with_amount(new_amount) + .finish_output()?, _ => panic!("only account, nft and foundry can be automatically created"), }; diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/mod.rs b/sdk/src/client/api/block_builder/input_selection/requirement/mod.rs index 599e713b22..9da2517062 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/mod.rs +++ b/sdk/src/client/api/block_builder/input_selection/requirement/mod.rs @@ -78,18 +78,6 @@ impl InputSelection { is_created } - // Add an nft requirement if the nft output is transitioning and then required in the inputs. - Output::Nft(nft_output) => { - let is_created = nft_output.nft_id().is_null(); - - if !is_created { - let requirement = Requirement::Nft(*nft_output.nft_id()); - log::debug!("Adding {requirement:?} from output"); - self.requirements.push(requirement); - } - - is_created - } // Add a foundry requirement if the foundry output is transitioning and then required in the inputs. // Also add an account requirement since the associated account output needs to be transitioned. Output::Foundry(foundry_output) => { @@ -114,6 +102,18 @@ impl InputSelection { is_created } + // Add an nft requirement if the nft output is transitioning and then required in the inputs. + Output::Nft(nft_output) => { + let is_created = nft_output.nft_id().is_null(); + + if !is_created { + let requirement = Requirement::Nft(*nft_output.nft_id()); + log::debug!("Adding {requirement:?} from output"); + self.requirements.push(requirement); + } + + is_created + } _ => false, }; diff --git a/sdk/src/client/api/block_builder/input_selection/transition.rs b/sdk/src/client/api/block_builder/input_selection/transition.rs index 84213bb246..18344028c6 100644 --- a/sdk/src/client/api/block_builder/input_selection/transition.rs +++ b/sdk/src/client/api/block_builder/input_selection/transition.rs @@ -60,7 +60,7 @@ impl InputSelection { .with_foundry_counter(u32::max(highest_foundry_serial_number, input.foundry_counter())) .with_features(features); - let output = builder.finish_output(self.protocol_parameters.token_supply())?; + let output = builder.finish_output()?; self.automatically_transitioned.insert(ChainId::from(account_id)); @@ -100,7 +100,7 @@ impl InputSelection { let output = NftOutputBuilder::from(input) .with_nft_id(nft_id) .with_features(features) - .finish_output(self.protocol_parameters.token_supply())?; + .finish_output()?; self.automatically_transitioned.insert(ChainId::from(nft_id)); @@ -138,7 +138,7 @@ impl InputSelection { return Ok(None); } - let output = FoundryOutputBuilder::from(input).finish_output(self.protocol_parameters.token_supply())?; + let output = FoundryOutputBuilder::from(input).finish_output()?; self.automatically_transitioned.insert(ChainId::from(foundry_id)); @@ -152,8 +152,8 @@ impl InputSelection { pub(crate) fn transition_input(&mut self, input: &InputSigningData) -> Result, Error> { match &input.output { Output::Account(account_input) => self.transition_account_input(account_input, input.output_id()), - Output::Nft(nft_input) => self.transition_nft_input(nft_input, input.output_id()), Output::Foundry(foundry_input) => self.transition_foundry_input(foundry_input, input.output_id()), + Output::Nft(nft_input) => self.transition_nft_input(nft_input, input.output_id()), _ => Ok(None), } } diff --git a/sdk/src/client/api/types.rs b/sdk/src/client/api/types.rs index 3e73780289..c81dc1b691 100644 --- a/sdk/src/client/api/types.rs +++ b/sdk/src/client/api/types.rs @@ -68,14 +68,13 @@ impl TryFromDto for PreparedTransactionData { inputs_data: dto .inputs_data .into_iter() - .map(|i| InputSigningData::try_from_dto_with_params(i, ¶ms)) + .map(|i| InputSigningData::try_from(i)) .collect::>>() .map_err(|_| Error::InvalidField("input_data"))?, remainder: match dto.remainder { - Some(remainder) => Some( - RemainderData::try_from_dto_with_params(remainder, ¶ms) - .map_err(|_| Error::InvalidField("remainder"))?, - ), + Some(remainder) => { + Some(RemainderData::try_from(remainder).map_err(|_| Error::InvalidField("remainder"))?) + } None => None, }, }) @@ -121,7 +120,7 @@ impl TryFromDto for SignedTransactionData { inputs_data: dto .inputs_data .into_iter() - .map(|i| InputSigningData::try_from_dto_with_params(i, ¶ms)) + .map(|i| InputSigningData::try_from(i)) .collect::>>() .map_err(|_| Error::InvalidField("inputs_data"))?, }) @@ -151,13 +150,12 @@ pub struct RemainderDataDto { pub address: Address, } -impl TryFromDto for RemainderData { - type Dto = RemainderDataDto; +impl TryFrom for RemainderData { type Error = Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { + fn try_from(dto: RemainderDataDto) -> Result { Ok(Self { - output: Output::try_from_dto_with_params_inner(dto.output, params)?, + output: Output::try_from(dto.output)?, chain: dto.chain, address: dto.address, }) diff --git a/sdk/src/client/core.rs b/sdk/src/client/core.rs index e2ca71c2f4..e1e623e7d8 100644 --- a/sdk/src/client/core.rs +++ b/sdk/src/client/core.rs @@ -24,7 +24,7 @@ use crate::{ node_manager::NodeManager, Error, }, - types::block::{address::Hrp, output::RentStructure, protocol::ProtocolParameters}, + types::block::{address::Hrp, output::StorageScoreParameters, protocol::ProtocolParameters}, }; /// An IOTA node client. @@ -152,9 +152,13 @@ impl ClientInner { Ok(self.get_network_info().await?.protocol_parameters.bech32_hrp()) } - /// Gets the rent structure of the node we're connecting to. - pub async fn get_rent_structure(&self) -> Result { - Ok(self.get_network_info().await?.protocol_parameters.rent_structure()) + /// Gets the storage score parameters of the node we're connecting to. + pub async fn get_storage_score_parameters(&self) -> Result { + Ok(self + .get_network_info() + .await? + .protocol_parameters + .storage_score_parameters()) } /// Gets the token supply of the node we're connecting to. diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index e027b7fb04..6d99b5ded1 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -20,6 +20,7 @@ pub type Result = std::result::Result; /// Error type of the iota client crate. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// Invalid bech32 HRP, should match the one from the used network #[error("invalid bech32 hrp for the connected network: {provided}, expected: {expected}")] @@ -35,8 +36,8 @@ pub enum Error { /// Block types error #[error("{0}")] Block(#[from] crate::types::block::Error), - /// The wallet account has enough funds, but split on too many outputs - #[error("the wallet account has enough funds, but split on too many outputs: {0}, max. is 128, consolidate them")] + /// The wallet has enough funds, but split on too many outputs + #[error("the wallet has enough funds, but split on too many outputs: {0}, max. is 128, consolidate them")] ConsolidationRequired(usize), /// Crypto.rs error #[error("{0}")] diff --git a/sdk/src/client/mod.rs b/sdk/src/client/mod.rs index 7f6d79f8a3..e7ec504a6b 100644 --- a/sdk/src/client/mod.rs +++ b/sdk/src/client/mod.rs @@ -7,21 +7,27 @@ //! //! ## Sending a block without a payload //! ```no_run -//! # use iota_sdk::client::{Client, Result}; +//! # use iota_sdk::{ +//! # client::{Client, secret::{mnemonic::MnemonicSecretManager, SignBlock}, constants::IOTA_COIN_TYPE}, +//! # types::block::IssuerId, crypto::keys::bip44::Bip44 +//! # }; //! # #[tokio::main] -//! # async fn main() -> Result<()> { +//! # async fn main() -> Result<(), Box> { //! let client = Client::builder() //! .with_node("http://localhost:14265")? //! .finish() //! .await?; -//! -//! let block = client -//! .block() -//! .finish() -//! .await?; -//! -//! println!("Block sent {}", block.id()); -//! # Ok(())} +//! let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC")?)?; +//! let protocol_params = client.get_protocol_parameters().await?; +//! let block_id = client +//! .build_basic_block(IssuerId::null(), None) +//! .await? +//! .sign_ed25519(&secret_manager, Bip44::new(IOTA_COIN_TYPE)) +//! .await? +//! .id(&protocol_params); +//! println!("Block sent {}", block_id); +//! # Ok(()) +//! # } //! ``` #[cfg(feature = "mqtt")] diff --git a/sdk/src/client/node_api/core/routes.rs b/sdk/src/client/node_api/core/routes.rs index 462338bdd9..191ec80130 100644 --- a/sdk/src/client/node_api/core/routes.rs +++ b/sdk/src/client/node_api/core/routes.rs @@ -102,10 +102,16 @@ impl ClientInner { /// future rewards for those epochs. `epochStart` and `epochEnd` indicates the actual range for which reward value /// is returned and decayed for. /// GET /api/core/v3/rewards/{outputId} - pub async fn get_output_mana_rewards(&self, output_id: &OutputId) -> Result { + pub async fn get_output_mana_rewards( + &self, + output_id: &OutputId, + slot_index: impl Into> + Send, + ) -> Result { let path = &format!("api/core/v3/rewards/{output_id}"); - self.get_request(path, None, false, false).await + let query = query_tuples_to_query_string([slot_index.into().map(|i| ("slotIndex", i.to_string()))]); + + self.get_request(path, query.as_deref(), false, false).await } // Committee routes. @@ -220,10 +226,9 @@ impl ClientInner { pub async fn get_output(&self, output_id: &OutputId) -> Result { let path = &format!("api/core/v3/outputs/{output_id}"); - let output = self.get_request::(path, None, false, true).await?; - let token_supply = self.get_token_supply().await?; - - Ok(Output::try_from_dto_with_params(output, token_supply)?) + Ok(Output::try_from( + self.get_request::(path, None, false, true).await?, + )?) } /// Finds an output by its ID and returns it as raw bytes. diff --git a/sdk/src/client/node_api/error.rs b/sdk/src/client/node_api/error.rs index fa51045934..15dc9267eb 100644 --- a/sdk/src/client/node_api/error.rs +++ b/sdk/src/client/node_api/error.rs @@ -6,6 +6,7 @@ pub type Result = std::result::Result; /// Node errors. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// The requested data was not found. (404) #[error("The requested data {0} was not found.")] diff --git a/sdk/src/client/node_api/indexer/mod.rs b/sdk/src/client/node_api/indexer/mod.rs index 24d4c9830e..1357068173 100644 --- a/sdk/src/client/node_api/indexer/mod.rs +++ b/sdk/src/client/node_api/indexer/mod.rs @@ -1,7 +1,7 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! Node indexer API. +//! [REST API for IOTA UTXO indexers](https://editor.swagger.io/?url=https://raw.githubusercontent.com/iotaledger/tips/tip48/tips/TIP-0048/openapi3-indexer.yaml) pub mod query_parameters; pub mod routes; diff --git a/sdk/src/client/node_api/indexer/query_parameters.rs b/sdk/src/client/node_api/indexer/query_parameters.rs index 665e210d88..8027cea872 100644 --- a/sdk/src/client/node_api/indexer/query_parameters.rs +++ b/sdk/src/client/node_api/indexer/query_parameters.rs @@ -8,8 +8,6 @@ use serde::{Deserialize, Serialize}; use crate::types::block::{address::Bech32Address, output::TokenId, slot::SlotIndex}; -// https://github.com/iotaledger/inx-indexer/tree/develop/pkg/indexer - pub trait QueryParameter: Serialize + Send + Sync { /// Converts parameters to a single String. fn to_query_string(&self) -> Option { @@ -64,21 +62,22 @@ macro_rules! impl_query_parameters_methods { #[setters(strip_option)] #[serde(rename_all = "camelCase")] pub struct OutputQueryParameters { - /// Returns outputs that were created after a certain slot index. - created_after: Option, - /// Returns outputs that were created before a certain slot index. - created_before: Option, - /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). - cursor: Option, /// Filters outputs based on the presence of a native token. has_native_token: Option, /// Filters outputs based on the presence of a specific native token. native_token: Option, + /// Returns outputs that are unlockable by the bech32 address. + unlockable_by_address: Option, /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. page_size: Option, - /// Returns outputs that are unlockable by the bech32 address. - unlockable_by_address: Option, + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty String is provided, only + /// the first page is returned. + cursor: Option, + /// Returns outputs that were created before a certain slot index. + created_before: Option, + /// Returns outputs that were created after a certain slot index. + created_after: Option, } impl_query_parameters_methods!(OutputQueryParameters); @@ -88,47 +87,48 @@ impl_query_parameters_methods!(OutputQueryParameters); #[setters(strip_option)] #[serde(rename_all = "camelCase")] pub struct BasicOutputQueryParameters { - /// Returns outputs that were created after a certain slot index. - created_after: Option, - /// Returns outputs that were created before a certain slot index. - created_before: Option, - /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). - cursor: Option, /// Filters outputs based on the presence of a native token. has_native_token: Option, /// Filters outputs based on the presence of a specific native token. native_token: Option, - /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is - /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. - page_size: Option, /// Returns outputs that are unlockable by the bech32 address. unlockable_by_address: Option, /// Bech32-encoded address that should be searched for. address: Option, + /// Filters outputs based on the presence of storage deposit return unlock condition. + has_storage_deposit_return: Option, + /// Filters outputs based on the presence of a specific return address in the storage deposit return unlock + /// condition. + storage_deposit_return_address: Option, + /// Filters outputs based on the presence of expiration unlock condition. + has_expiration: Option, /// Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock /// condition. expiration_return_address: Option, - /// Returns outputs that expire after a certain slot index. - expires_after: Option, /// Returns outputs that expire before a certain slot index. expires_before: Option, - /// Filters outputs based on the presence of expiration unlock condition. - has_expiration: Option, - /// Filters outputs based on the presence of storage deposit return unlock condition. - has_storage_deposit_return: Option, + /// Returns outputs that expire after a certain slot index. + expires_after: Option, /// Filters outputs based on the presence of timelock unlock condition. has_timelock: Option, + /// Returns outputs that are timelocked before a certain slot index. + timelocked_before: Option, + /// Returns outputs that are timelocked after a certain slot index. + timelocked_after: Option, /// Filters outputs based on the presence of validated Sender (bech32 encoded). sender: Option, - /// Filters outputs based on the presence of a specific return address in the storage deposit return unlock - /// condition. - storage_deposit_return_address: Option, /// Filters outputs based on matching Tag Block. tag: Option, - /// Returns outputs that are timelocked after a certain slot index. - timelocked_after: Option, - /// Returns outputs that are timelocked before a certain slot index. - timelocked_before: Option, + /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is + /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. + page_size: Option, + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty String is provided, only + /// the first page is returned. + cursor: Option, + /// Returns outputs that were created before a certain slot index. + created_before: Option, + /// Returns outputs that were created after a certain slot index. + created_after: Option, } impl_query_parameters_methods!(BasicOutputQueryParameters); @@ -150,71 +150,148 @@ impl BasicOutputQueryParameters { #[setters(strip_option)] #[serde(rename_all = "camelCase")] pub struct AccountOutputQueryParameters { - /// Returns outputs that were created after a certain slot index. - created_after: Option, - /// Returns outputs that were created before a certain slot index. - created_before: Option, - /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). - cursor: Option, + /// Bech32-encoded address that should be searched for. + address: Option, + /// Filters outputs based on bech32-encoded issuer address. + issuer: Option, + /// Filters outputs based on the presence of validated Sender (bech32 encoded). + sender: Option, /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. page_size: Option, + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty String is provided, only + /// the first page is returned. + cursor: Option, + /// Returns outputs that were created before a certain slot index. + created_before: Option, + /// Returns outputs that were created after a certain slot index. + created_after: Option, +} + +impl_query_parameters_methods!(AccountOutputQueryParameters); + +/// Query parameters for anchor output requests. +#[derive(Setters, Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[setters(strip_option)] +#[serde(rename_all = "camelCase")] +pub struct AnchorOutputQueryParameters { /// Returns outputs that are unlockable by the bech32 address. unlockable_by_address: Option, - /// Filters outputs based on the presence of validated Sender (bech32 encoded). - sender: Option, + /// Filters outputs based on bech32-encoded state controller address. + state_controller: Option, + /// Filters outputs based on bech32-encoded governor (governance controller) address. + governor: Option, /// Filters outputs based on bech32-encoded issuer address. issuer: Option, - /// Bech32-encoded address that should be searched for. - address: Option, + /// Filters outputs based on the presence of validated Sender (bech32 encoded). + sender: Option, + /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is + /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. + page_size: Option, + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty String is provided, only + /// the first page is returned. + cursor: Option, + /// Returns outputs that were created before a certain slot index. + created_before: Option, + /// Returns outputs that were created after a certain slot index. + created_after: Option, } -impl_query_parameters_methods!(AccountOutputQueryParameters); +impl_query_parameters_methods!(AnchorOutputQueryParameters); -/// Query parameters for nft output requests. +/// Query parameters for delegation output requests. #[derive(Setters, Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] #[setters(strip_option)] #[serde(rename_all = "camelCase")] -pub struct NftOutputQueryParameters { - /// Returns outputs that were created after a certain slot index. - created_after: Option, +pub struct DelegationOutputQueryParameters { + /// Bech32-encoded address that should be searched for. + address: Option, + /// Filter foundry outputs based on bech32-encoded address of the validator. + validator: Option, + /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is + /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. + page_size: Option, + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty String is provided, only + /// the first page is returned. + cursor: Option, /// Returns outputs that were created before a certain slot index. created_before: Option, - /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). - cursor: Option, + /// Returns outputs that were created after a certain slot index. + created_after: Option, +} + +impl_query_parameters_methods!(DelegationOutputQueryParameters); + +/// Query parameters for foundry output requests. +#[derive(Setters, Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[setters(strip_option)] +#[serde(rename_all = "camelCase")] +pub struct FoundryOutputQueryParameters { + /// Filters outputs based on the presence of a native token. + has_native_token: Option, + /// Filters outputs based on the presence of a specific native token. + native_token: Option, + /// Filter foundry outputs based on bech32-encoded address of the controlling account. + account: Option, /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. page_size: Option, + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty String is provided, only + /// the first page is returned. + cursor: Option, + /// Returns outputs that were created before a certain slot index. + created_before: Option, + /// Returns outputs that were created after a certain slot index. + created_after: Option, +} + +impl_query_parameters_methods!(FoundryOutputQueryParameters); + +/// Query parameters for nft output requests. +#[derive(Setters, Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[setters(strip_option)] +#[serde(rename_all = "camelCase")] +pub struct NftOutputQueryParameters { /// Returns outputs that are unlockable by the bech32 address. unlockable_by_address: Option, /// Bech32-encoded address that should be searched for. address: Option, + /// Filters outputs based on the presence of storage deposit return unlock condition. + has_storage_deposit_return: Option, + /// Filters outputs based on the presence of a specific return address in the storage deposit return unlock + /// condition. + storage_deposit_return_address: Option, + /// Filters outputs based on the presence of expiration unlock condition. + has_expiration: Option, /// Filters outputs based on the presence of a specific Bech32-encoded return address in the expiration unlock /// condition. expiration_return_address: Option, - /// Returns outputs that expire after a certain slot index. - expires_after: Option, /// Returns outputs that expire before a certain slot index. expires_before: Option, - /// Filters outputs based on the presence of expiration unlock condition. - has_expiration: Option, - /// Filters outputs based on the presence of storage deposit return unlock condition. - has_storage_deposit_return: Option, + /// Returns outputs that expire after a certain slot index. + expires_after: Option, /// Filters outputs based on the presence of timelock unlock condition. has_timelock: Option, - /// Filters outputs based on the presence of validated Sender (bech32 encoded). - sender: Option, + /// Returns outputs that are timelocked before a certain slot index. + timelocked_before: Option, + /// Returns outputs that are timelocked after a certain slot index. + timelocked_after: Option, /// Filters outputs based on bech32-encoded issuer address. issuer: Option, - /// Filters outputs based on the presence of a specific return address in the storage deposit return unlock - /// condition. - storage_deposit_return_address: Option, + /// Filters outputs based on the presence of validated Sender (bech32 encoded). + sender: Option, /// Filters outputs based on matching Tag Block. tag: Option, - /// Returns outputs that are timelocked after a certain slot index. - timelocked_after: Option, - /// Returns outputs that are timelocked before a certain slot index. - timelocked_before: Option, + /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is + /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. + page_size: Option, + /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). If an empty String is provided, only + /// the first page is returned. + cursor: Option, + /// Returns outputs that were created before a certain slot index. + created_before: Option, + /// Returns outputs that were created after a certain slot index. + created_after: Option, } impl_query_parameters_methods!(NftOutputQueryParameters); @@ -231,52 +308,6 @@ impl NftOutputQueryParameters { } } -/// Query parameters for foundry output requests. -#[derive(Setters, Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] -#[setters(strip_option)] -#[serde(rename_all = "camelCase")] -pub struct FoundryOutputQueryParameters { - /// Returns outputs that were created after a certain slot index. - created_after: Option, - /// Returns outputs that were created before a certain slot index. - created_before: Option, - /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). - cursor: Option, - /// Filters outputs based on the presence of a native token. - has_native_token: Option, - /// Filters outputs based on the presence of a specific native token. - native_token: Option, - /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is - /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. - page_size: Option, - /// Filter foundry outputs based on bech32-encoded address of the controlling account. - account_address: Option, -} - -impl_query_parameters_methods!(FoundryOutputQueryParameters); - -/// Query parameters for delegation output requests. -#[derive(Setters, Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] -#[setters(strip_option)] -#[serde(rename_all = "camelCase")] -pub struct DelegationOutputQueryParameters { - /// Returns outputs that were created after a certain slot index. - created_after: Option, - /// Returns outputs that were created before a certain slot index. - created_before: Option, - /// Starts the search from the cursor (createdSlotIndex+outputId.pageSize). - cursor: Option, - /// The maximum amount of items returned in one call. If there are more items, a cursor to the next page is - /// returned too. The parameter is ignored when pageSize is defined via the cursor parameter. - page_size: Option, - /// Bech32-encoded address that should be searched for. - address: Option, - /// Filter foundry outputs based on bech32-encoded address of the validator. - validator: Option, -} - -impl_query_parameters_methods!(DelegationOutputQueryParameters); - #[cfg(test)] mod tests { use super::*; diff --git a/sdk/src/client/node_api/indexer/routes.rs b/sdk/src/client/node_api/indexer/routes.rs index 3ec73248f6..10df56dabd 100644 --- a/sdk/src/client/node_api/indexer/routes.rs +++ b/sdk/src/client/node_api/indexer/routes.rs @@ -1,29 +1,26 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! IOTA node indexer routes +//! IOTA node indexer routes. use crate::{ client::{ node_api::indexer::query_parameters::{ - AccountOutputQueryParameters, BasicOutputQueryParameters, FoundryOutputQueryParameters, - NftOutputQueryParameters, OutputQueryParameters, + AccountOutputQueryParameters, AnchorOutputQueryParameters, BasicOutputQueryParameters, + DelegationOutputQueryParameters, FoundryOutputQueryParameters, NftOutputQueryParameters, + OutputQueryParameters, }, ClientInner, Error, Result, }, types::{ api::plugins::indexer::OutputIdsResponse, - block::output::{AccountId, FoundryId, NftId, OutputId}, + block::output::{AccountId, AnchorId, DelegationId, FoundryId, NftId, OutputId}, }, }; -// hornet: https://github.com/gohornet/hornet/blob/develop/plugins/indexer/routes.go - impl ClientInner { /// Get basic, alias, nft and foundry outputs filtered by the given parameters. /// GET with query parameter returns all outputIDs that fit these filter criteria. - /// Query parameters: "hasNativeTokens", "minNativeTokenCount", "maxNativeTokenCount", "unlockableByAddress", - /// "createdBefore", "createdAfter", "cursor", "pageSize". /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs pub async fn output_ids(&self, query_parameters: OutputQueryParameters) -> Result { @@ -34,9 +31,6 @@ impl ClientInner { /// Get basic outputs filtered by the given parameters. /// GET with query parameter returns all outputIDs that fit these filter criteria. - /// Query parameters: "address", "hasStorageDepositReturn", "storageDepositReturnAddress", - /// "hasExpiration", "expiresBefore", "expiresAfter", "hasTimelock", "timelockedBefore", - /// "timelockedAfter", "sender", "tag", "createdBefore" and "createdAfter". /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs/basic pub async fn basic_output_ids(&self, query_parameters: BasicOutputQueryParameters) -> Result { @@ -47,7 +41,6 @@ impl ClientInner { /// Get account outputs filtered by the given parameters. /// GET with query parameter returns all outputIDs that fit these filter criteria. - /// Query parameters: "address", "issuer", "sender", "createdBefore", "createdAfter" /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs/account pub async fn account_output_ids( @@ -71,9 +64,55 @@ impl ClientInner { .ok_or_else(|| Error::NoOutput(format!("{account_id:?}")))?)) } + /// Get anchor outputs filtered by the given parameters. + /// GET with query parameter returns all outputIDs that fit these filter criteria. + /// Returns Err(Node(NotFound) if no results are found. + /// api/indexer/v2/outputs/anchor + pub async fn anchor_output_ids(&self, query_parameters: AnchorOutputQueryParameters) -> Result { + let route = "api/indexer/v2/outputs/anchor"; + + self.get_output_ids(route, query_parameters, true, false).await + } + + /// Get anchor output by its anchorID. + /// api/indexer/v2/outputs/anchor/:{AnchorId} + pub async fn anchor_output_id(&self, anchor_id: AnchorId) -> Result { + let route = format!("api/indexer/v2/outputs/anchor/{anchor_id}"); + + Ok(*(self + .get_output_ids(&route, AnchorOutputQueryParameters::new(), true, false) + .await? + .first() + .ok_or_else(|| Error::NoOutput(format!("{anchor_id:?}")))?)) + } + + /// Get delegation outputs filtered by the given parameters. + /// GET with query parameter returns all outputIDs that fit these filter criteria. + /// Returns Err(Node(NotFound) if no results are found. + /// api/indexer/v2/outputs/delegation + pub async fn delegation_output_ids( + &self, + query_parameters: DelegationOutputQueryParameters, + ) -> Result { + let route = "api/indexer/v2/outputs/delegation"; + + self.get_output_ids(route, query_parameters, true, false).await + } + + /// Get delegation output by its delegationID. + /// api/indexer/v2/outputs/delegation/:{DelegationId} + pub async fn delegation_output_id(&self, delegation_id: DelegationId) -> Result { + let route = format!("api/indexer/v2/outputs/delegation/{delegation_id}"); + + Ok(*(self + .get_output_ids(&route, DelegationOutputQueryParameters::new(), true, false) + .await? + .first() + .ok_or_else(|| Error::NoOutput(format!("{delegation_id:?}")))?)) + } + /// Get foundry outputs filtered by the given parameters. /// GET with query parameter returns all outputIDs that fit these filter criteria. - /// Query parameters: "address", "createdBefore", "createdAfter" /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs/foundry pub async fn foundry_output_ids( @@ -98,9 +137,6 @@ impl ClientInner { } /// Get NFT outputs filtered by the given parameters. - /// Query parameters: "address", "hasStorageDepositReturn", "storageDepositReturnAddress", - /// "hasExpiration", "expiresBefore", "expiresAfter", "hasTimelock", "timelockedBefore", - /// "timelockedAfter", "issuer", "sender", "tag", "createdBefore", "createdAfter" /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs/nft pub async fn nft_output_ids(&self, query_parameters: NftOutputQueryParameters) -> Result { diff --git a/sdk/src/client/node_api/mqtt/error.rs b/sdk/src/client/node_api/mqtt/error.rs index c00d75a8cc..5a872ac47b 100644 --- a/sdk/src/client/node_api/mqtt/error.rs +++ b/sdk/src/client/node_api/mqtt/error.rs @@ -3,6 +3,7 @@ /// MQTT related errors. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// Client error. #[error("client error {0}")] diff --git a/sdk/src/client/node_api/participation.rs b/sdk/src/client/node_api/participation.rs index 68c721fa2f..5f9dddc4a4 100644 --- a/sdk/src/client/node_api/participation.rs +++ b/sdk/src/client/node_api/participation.rs @@ -15,8 +15,9 @@ use crate::{ ParticipationEventType, }, }, - block::{address::Bech32Address, output::OutputId, ConvertTo}, + block::{address::Bech32Address, output::OutputId}, }, + utils::ConvertTo, }; impl ClientInner { diff --git a/sdk/src/client/secret/ledger_nano.rs b/sdk/src/client/secret/ledger_nano.rs index 80fff366f7..e627293adf 100644 --- a/sdk/src/client/secret/ledger_nano.rs +++ b/sdk/src/client/secret/ledger_nano.rs @@ -37,6 +37,7 @@ use crate::{ /// Ledger nano errors. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// Denied by User #[error("denied by user")] diff --git a/sdk/src/client/secret/types.rs b/sdk/src/client/secret/types.rs index 62cc0e5bac..352869b3ab 100644 --- a/sdk/src/client/secret/types.rs +++ b/sdk/src/client/secret/types.rs @@ -7,10 +7,7 @@ use crypto::keys::bip44::Bip44; use serde::{Deserialize, Serialize}; use crate::{ - types::{ - block::output::{dto::OutputDto, Output, OutputId, OutputMetadata}, - TryFromDto, ValidationParams, - }, + types::block::output::{dto::OutputDto, Output, OutputId, OutputMetadata}, utils::serde::bip44::option_bip44, }; @@ -167,13 +164,12 @@ pub struct InputSigningDataDto { pub chain: Option, } -impl TryFromDto for InputSigningData { - type Dto = InputSigningDataDto; +impl TryFrom for InputSigningData { type Error = crate::client::Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { + fn try_from(dto: InputSigningDataDto) -> Result { Ok(Self { - output: Output::try_from_dto_with_params_inner(dto.output, params)?, + output: Output::try_from(dto.output)?, output_metadata: dto.output_metadata, chain: dto.chain, }) diff --git a/sdk/src/client/stronghold/error.rs b/sdk/src/client/stronghold/error.rs index 65db54d5fd..da4abbb44d 100644 --- a/sdk/src/client/stronghold/error.rs +++ b/sdk/src/client/stronghold/error.rs @@ -3,6 +3,7 @@ /// Stronghold errors. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { /// Crypto.rs error #[error("{0}")] diff --git a/sdk/src/client/utils.rs b/sdk/src/client/utils.rs index ce7f79de12..ba2f0056aa 100644 --- a/sdk/src/client/utils.rs +++ b/sdk/src/client/utils.rs @@ -21,8 +21,9 @@ use crate::{ address::{Address, Bech32Address, Ed25519Address, Hrp, ToBech32Ext}, output::{AccountId, NftId}, payload::TaggedDataPayload, - BlockId, ConvertTo, SignedBlock, + BlockId, SignedBlock, }, + utils::ConvertTo, }; /// Transforms bech32 to hex @@ -33,6 +34,7 @@ pub fn bech32_to_hex(bech32: impl ConvertTo) -> Result { Address::Nft(nft) => nft.to_string(), Address::Anchor(anchor) => anchor.to_string(), Address::ImplicitAccountCreation(implicit) => implicit.to_string(), + Address::Multi(multi) => multi.to_string(), Address::Restricted(restricted) => restricted.to_string(), }) } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 9c05c833f1..ce004f33d2 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] // TODO missing_docs -#![deny(clippy::nursery, rust_2018_idioms, /* warnings, */ unreachable_pub)] +#![deny(clippy::nursery, rust_2018_idioms, /* warnings, unreachable_pub */)] #![allow( clippy::redundant_pub_crate, clippy::missing_const_for_fn, diff --git a/sdk/src/types/api/plugins/participation/error.rs b/sdk/src/types/api/plugins/participation/error.rs index 5171fb97c1..c585d73345 100644 --- a/sdk/src/types/api/plugins/participation/error.rs +++ b/sdk/src/types/api/plugins/participation/error.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #[derive(Debug)] +#[non_exhaustive] pub enum Error { /// Invalid participations error InvalidParticipations, diff --git a/sdk/src/types/api/plugins/participation/types.rs b/sdk/src/types/api/plugins/participation/types.rs index 88c9f35a84..cfab17c7c3 100644 --- a/sdk/src/types/api/plugins/participation/types.rs +++ b/sdk/src/types/api/plugins/participation/types.rs @@ -12,7 +12,7 @@ use getset::Getters; use hashbrown::HashMap; use packable::PackableExt; -use crate::types::{api::plugins::participation::error::Error, block::impl_id}; +use crate::types::api::plugins::participation::error::Error; /// Participation tag. pub const PARTICIPATION_TAG: &str = "PARTICIPATE"; @@ -51,7 +51,7 @@ pub struct ParticipationEvent { pub data: ParticipationEventData, } -impl_id!( +crate::impl_id!( /// A participation event id. pub ParticipationEventId { pub const LENGTH: usize = 32; diff --git a/sdk/src/types/block/address/account.rs b/sdk/src/types/block/address/account.rs index 915c83eb29..4c9816798f 100644 --- a/sdk/src/types/block/address/account.rs +++ b/sdk/src/types/block/address/account.rs @@ -5,7 +5,10 @@ use core::str::FromStr; use derive_more::{AsRef, Deref, Display, From}; -use crate::types::block::{output::AccountId, Error}; +use crate::types::block::{ + output::{AccountId, StorageScore}, + Error, +}; /// An account address. #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Display, packable::Packable)] @@ -37,6 +40,8 @@ impl AccountAddress { } } +impl StorageScore for AccountAddress {} + impl FromStr for AccountAddress { type Err = Error; diff --git a/sdk/src/types/block/address/anchor.rs b/sdk/src/types/block/address/anchor.rs index d15af00f0e..613bd83248 100644 --- a/sdk/src/types/block/address/anchor.rs +++ b/sdk/src/types/block/address/anchor.rs @@ -6,7 +6,7 @@ use core::str::FromStr; use derive_more::{AsRef, Deref, Display, From}; use crate::types::block::{ - output::{AnchorId, OutputId}, + output::{AnchorId, OutputId, StorageScore}, Error, }; @@ -40,6 +40,8 @@ impl AnchorAddress { } } +impl StorageScore for AnchorAddress {} + impl FromStr for AnchorAddress { type Err = Error; diff --git a/sdk/src/types/block/address/bech32.rs b/sdk/src/types/block/address/bech32.rs index dd0caa2e1e..36cbe7bbea 100644 --- a/sdk/src/types/block/address/bech32.rs +++ b/sdk/src/types/block/address/bech32.rs @@ -7,6 +7,7 @@ use alloc::{ }; use core::str::FromStr; +use crypto::hashes::{blake2b::Blake2b256, Digest}; use derive_more::{AsRef, Deref, Display}; use packable::{ error::{UnpackError, UnpackErrorExt}, @@ -15,7 +16,13 @@ use packable::{ Packable, PackableExt, }; -use crate::types::block::{address::Address, ConvertTo, Error}; +use crate::{ + types::block::{ + address::{Address, MultiAddress}, + Error, + }, + utils::ConvertTo, +}; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Display)] #[repr(transparent)] @@ -172,11 +179,16 @@ impl Bech32Address { impl core::fmt::Display for Bech32Address { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{}", - bech32::encode::(self.hrp.0, &self.inner.pack_to_vec(),).unwrap() - ) + let bytes = if self.inner.is_multi() { + core::iter::once(MultiAddress::KIND) + .chain(Blake2b256::digest(self.inner.pack_to_vec())) + .collect() + } else { + self.inner.pack_to_vec() + }; + + // PANIC: unwrap is fine as the Bech32Address has been validated at construction. + write!(f, "{}", bech32::encode::(self.hrp.0, &bytes).unwrap()) } } diff --git a/sdk/src/types/block/address/ed25519.rs b/sdk/src/types/block/address/ed25519.rs index 2aa4880b19..4d44b1e5e9 100644 --- a/sdk/src/types/block/address/ed25519.rs +++ b/sdk/src/types/block/address/ed25519.rs @@ -10,7 +10,7 @@ use crypto::{ use derive_more::{AsRef, Deref, From}; use packable::Packable; -use crate::types::block::Error; +use crate::types::block::{output::StorageScore, Error}; /// An Ed25519 address. #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Packable)] @@ -25,8 +25,12 @@ impl Ed25519Address { /// Creates a new [`Ed25519Address`]. #[inline(always)] - pub fn new(address: [u8; Self::LENGTH]) -> Self { - Self::from(address) + pub const fn new(address: [u8; Self::LENGTH]) -> Self { + Self(address) + } + + pub(crate) const fn null() -> Self { + Self([0; Self::LENGTH]) } /// Creates a new [`Ed25519Address`] from the bytes of a [`PublicKey`]. @@ -35,6 +39,8 @@ impl Ed25519Address { } } +impl StorageScore for Ed25519Address {} + impl FromStr for Ed25519Address { type Err = Error; diff --git a/sdk/src/types/block/address/implicit_account_creation.rs b/sdk/src/types/block/address/implicit_account_creation.rs index ecb900e7ab..21e3f3e877 100644 --- a/sdk/src/types/block/address/implicit_account_creation.rs +++ b/sdk/src/types/block/address/implicit_account_creation.rs @@ -4,7 +4,10 @@ use derive_more::{AsRef, Deref, Display, From, FromStr}; use packable::Packable; -use crate::types::block::address::Ed25519Address; +use crate::types::block::{ + address::Ed25519Address, + output::{StorageScore, StorageScoreParameters}, +}; /// An implicit account creation address that can be used to convert a /// [`BasicOutput`](crate::types::block::output::BasicOutput) to an @@ -24,6 +27,17 @@ impl ImplicitAccountCreationAddress { pub fn new(address: [u8; Self::LENGTH]) -> Self { Self(Ed25519Address::new(address)) } + + /// Returns the inner [`Ed25519Address`] of the [`ImplicitAccountCreationAddress`]. + pub fn ed25519_address(&self) -> &Ed25519Address { + &self.0 + } +} + +impl StorageScore for ImplicitAccountCreationAddress { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.implicit_account_creation_address_offset() + } } impl core::fmt::Debug for ImplicitAccountCreationAddress { diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index fa6dda1a84..58fd1587aa 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -6,6 +6,7 @@ mod anchor; mod bech32; mod ed25519; mod implicit_account_creation; +mod multi; mod nft; mod restricted; @@ -14,21 +15,26 @@ use alloc::boxed::Box; use derive_more::{Display, From}; use packable::Packable; +pub(crate) use self::multi::WeightedAddressCount; pub use self::{ account::AccountAddress, anchor::AnchorAddress, bech32::{Bech32Address, Hrp}, ed25519::Ed25519Address, implicit_account_creation::ImplicitAccountCreationAddress, + multi::{MultiAddress, WeightedAddress}, nft::NftAddress, restricted::{AddressCapabilities, AddressCapabilityFlag, RestrictedAddress}, }; -use crate::types::block::{ - output::Output, - semantic::{SemanticValidationContext, TransactionFailureReason}, - signature::Signature, - unlock::Unlock, - ConvertTo, Error, +use crate::{ + types::block::{ + output::{Output, StorageScore, StorageScoreParameters}, + semantic::{SemanticValidationContext, TransactionFailureReason}, + signature::Signature, + unlock::Unlock, + Error, + }, + utils::ConvertTo, }; /// A generic address supporting different address kinds. @@ -52,6 +58,9 @@ pub enum Address { /// An implicit account creation address. #[packable(tag = ImplicitAccountCreationAddress::KIND)] ImplicitAccountCreation(ImplicitAccountCreationAddress), + /// A multi address. + #[packable(tag = MultiAddress::KIND)] + Multi(MultiAddress), /// An address with restricted capabilities. #[packable(tag = RestrictedAddress::KIND)] #[from(ignore)] @@ -72,6 +81,7 @@ impl core::fmt::Debug for Address { Self::Nft(address) => address.fmt(f), Self::Anchor(address) => address.fmt(f), Self::ImplicitAccountCreation(address) => address.fmt(f), + Self::Multi(address) => address.fmt(f), Self::Restricted(address) => address.fmt(f), } } @@ -86,11 +96,12 @@ impl Address { Self::Nft(_) => NftAddress::KIND, Self::Anchor(_) => AnchorAddress::KIND, Self::ImplicitAccountCreation(_) => ImplicitAccountCreationAddress::KIND, + Self::Multi(_) => MultiAddress::KIND, Self::Restricted(_) => RestrictedAddress::KIND, } } - crate::def_is_as_opt!(Address: Ed25519, Account, Nft, Anchor, ImplicitAccountCreation, Restricted); + crate::def_is_as_opt!(Address: Ed25519, Account, Nft, Anchor, ImplicitAccountCreation, Multi, Restricted); /// Tries to create an [`Address`] from a bech32 encoded string. pub fn try_from_bech32(address: impl AsRef) -> Result { @@ -126,7 +137,7 @@ impl Address { context.unlocked_addresses.insert(self.clone()); } - (Self::Ed25519(_ed25519_address), Unlock::Reference(_unlock)) => { + (Self::Ed25519(_), Unlock::Reference(_)) => { // TODO actually check that it was unlocked by the same signature. if !context.unlocked_addresses.contains(self) { return Err(TransactionFailureReason::InvalidInputUnlock); @@ -160,6 +171,27 @@ impl Address { } // TODO maybe shouldn't be a semantic error but this function currently returns a TransactionFailureReason. (Self::Anchor(_), _) => return Err(TransactionFailureReason::SemanticValidationFailed), + (Self::ImplicitAccountCreation(implicit_account_creation_address), _) => { + return Self::from(*implicit_account_creation_address.ed25519_address()).unlock(unlock, context); + } + (Self::Multi(multi_address), Unlock::Multi(unlock)) => { + if multi_address.len() != unlock.len() { + return Err(TransactionFailureReason::InvalidInputUnlock); + } + + let mut cumulative_unlocked_weight = 0u16; + + for (address, unlock) in multi_address.addresses().iter().zip(unlock.unlocks()) { + if !unlock.is_empty() { + address.unlock(unlock, context)?; + cumulative_unlocked_weight += address.weight() as u16; + } + } + + if cumulative_unlocked_weight < multi_address.threshold() { + return Err(TransactionFailureReason::InvalidInputUnlock); + } + } _ => return Err(TransactionFailureReason::InvalidInputUnlock), } @@ -167,6 +199,20 @@ impl Address { } } +impl StorageScore for Address { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + match self { + Address::Ed25519(address) => address.storage_score(params), + Address::Account(address) => address.storage_score(params), + Address::Nft(address) => address.storage_score(params), + Address::Anchor(address) => address.storage_score(params), + Address::ImplicitAccountCreation(address) => address.storage_score(params), + Address::Multi(address) => address.storage_score(params), + Address::Restricted(address) => address.storage_score(params), + } + } +} + pub trait ToBech32Ext: Sized { /// Try to encode this address to a bech32 string with the given Human Readable Part as prefix. fn try_to_bech32(self, hrp: impl ConvertTo) -> Result; diff --git a/sdk/src/types/block/address/multi.rs b/sdk/src/types/block/address/multi.rs new file mode 100644 index 0000000000..b4f59e2284 --- /dev/null +++ b/sdk/src/types/block/address/multi.rs @@ -0,0 +1,204 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::{boxed::Box, string::ToString, vec::Vec}; +use core::{fmt, ops::RangeInclusive}; + +use derive_more::{AsRef, Deref, Display, From}; +use iterator_sorted::is_unique_sorted; +use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; + +use crate::types::block::{address::Address, output::StorageScore, Error}; + +pub(crate) type WeightedAddressCount = + BoundedU8<{ *MultiAddress::ADDRESSES_COUNT.start() }, { *MultiAddress::ADDRESSES_COUNT.end() }>; + +/// An address with an assigned weight. +#[derive(Clone, Debug, Display, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Packable)] +#[display(fmt = "{address}")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WeightedAddress { + /// The unlocked address. + #[deref] + #[packable(verify_with = verify_address)] + address: Address, + /// The weight of the unlocked address. + #[packable(verify_with = verify_weight)] + weight: u8, +} + +impl WeightedAddress { + /// Creates a new [`WeightedAddress`]. + pub fn new(address: Address, weight: u8) -> Result { + verify_address::(&address, &())?; + verify_weight::(&weight, &())?; + + Ok(Self { address, weight }) + } + + /// Returns the address of the [`WeightedAddress`]. + pub fn address(&self) -> &Address { + &self.address + } + + /// Returns the weight of the [`WeightedAddress`]. + pub fn weight(&self) -> u8 { + self.weight + } +} + +fn verify_address(address: &Address, _visitor: &()) -> Result<(), Error> { + if VERIFY { + if !matches!( + address, + Address::Ed25519(_) | Address::Account(_) | Address::Nft(_) | Address::Anchor(_) + ) { + return Err(Error::InvalidAddressKind(address.kind())); + } + } + Ok(()) +} + +fn verify_weight(weight: &u8, _visitor: &()) -> Result<(), Error> { + if VERIFY && *weight == 0 { + return Err(Error::InvalidAddressWeight(*weight)); + } else { + Ok(()) + } +} + +/// An address that consists of addresses with weights and a threshold value. +/// The Multi Address can be unlocked if the cumulative weight of all unlocked addresses is equal to or exceeds the +/// threshold. +#[derive(Clone, Debug, Deref, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] +#[packable(unpack_error = Error)] +#[packable(verify_with = verify_multi_address)] +pub struct MultiAddress { + /// The weighted unlocked addresses. + #[deref] + #[packable(verify_with = verify_addresses)] + #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidWeightedAddressCount(p.into())))] + addresses: BoxedSlicePrefix, + /// The threshold that needs to be reached by the unlocked addresses in order to unlock the multi address. + #[packable(verify_with = verify_threshold)] + threshold: u16, +} + +impl MultiAddress { + /// The [`Address`](crate::types::block::address::Address) kind of a [`MultiAddress`]. + pub const KIND: u8 = 40; + /// The allowed range of inner [`Address`]es. + pub const ADDRESSES_COUNT: RangeInclusive = 1..=10; + + /// Creates a new [`MultiAddress`]. + #[inline(always)] + pub fn new(addresses: impl IntoIterator, threshold: u16) -> Result { + let addresses = addresses.into_iter().collect::>(); + + verify_addresses::(&addresses, &())?; + verify_threshold::(&threshold, &())?; + + let addresses = BoxedSlicePrefix::::try_from(addresses) + .map_err(Error::InvalidWeightedAddressCount)?; + + let multi_address = Self { addresses, threshold }; + + verify_multi_address::(&multi_address, &())?; + + Ok(multi_address) + } + + /// Returns the addresses of a [`MultiAddress`]. + #[inline(always)] + pub fn addresses(&self) -> &[WeightedAddress] { + &self.addresses + } + + /// Returns the threshold of a [`MultiAddress`]. + #[inline(always)] + pub fn threshold(&self) -> u16 { + self.threshold + } +} + +fn verify_addresses(addresses: &[WeightedAddress], _visitor: &()) -> Result<(), Error> { + if VERIFY && !is_unique_sorted(addresses.iter().map(WeightedAddress::address)) { + return Err(Error::WeightedAddressesNotUniqueSorted); + } else { + Ok(()) + } +} + +fn verify_threshold(threshold: &u16, _visitor: &()) -> Result<(), Error> { + if VERIFY && *threshold == 0 { + return Err(Error::InvalidMultiAddressThreshold(*threshold)); + } else { + Ok(()) + } +} + +fn verify_multi_address(address: &MultiAddress, _visitor: &()) -> Result<(), Error> { + if VERIFY { + let cumulative_weight = address.iter().map(|address| address.weight as u16).sum::(); + + if cumulative_weight < address.threshold { + return Err(Error::InvalidMultiAddressCumulativeWeight { + cumulative_weight, + threshold: address.threshold, + }); + } + } + Ok(()) +} + +impl StorageScore for MultiAddress {} + +impl fmt::Display for MultiAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "[{}]", + self.addresses() + .iter() + .map(|address| address.to_string()) + .collect::>() + .join(", ") + ) + } +} + +#[cfg(feature = "serde")] +mod dto { + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + struct MultiAddressDto { + #[serde(rename = "type")] + kind: u8, + addresses: Vec, + threshold: u16, + } + + impl From<&MultiAddress> for MultiAddressDto { + fn from(value: &MultiAddress) -> Self { + Self { + kind: MultiAddress::KIND, + addresses: value.addresses.to_vec(), + threshold: value.threshold, + } + } + } + + impl TryFrom for MultiAddress { + type Error = Error; + + fn try_from(value: MultiAddressDto) -> Result { + Self::new(value.addresses, value.threshold) + } + } + + crate::impl_serde_typed_dto!(MultiAddress, MultiAddressDto, "multi address"); +} diff --git a/sdk/src/types/block/address/nft.rs b/sdk/src/types/block/address/nft.rs index 5bb31750be..995256960e 100644 --- a/sdk/src/types/block/address/nft.rs +++ b/sdk/src/types/block/address/nft.rs @@ -5,7 +5,10 @@ use core::str::FromStr; use derive_more::{AsRef, Deref, Display, From}; -use crate::types::block::{output::NftId, Error}; +use crate::types::block::{ + output::{NftId, StorageScore}, + Error, +}; /// An NFT address. #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Display, packable::Packable)] @@ -37,6 +40,8 @@ impl NftAddress { } } +impl StorageScore for NftAddress {} + impl FromStr for NftAddress { type Err = Error; diff --git a/sdk/src/types/block/address/restricted.rs b/sdk/src/types/block/address/restricted.rs index 0ef8024ca0..0d6a2c7da4 100644 --- a/sdk/src/types/block/address/restricted.rs +++ b/sdk/src/types/block/address/restricted.rs @@ -7,6 +7,7 @@ use packable::{Packable, PackableExt}; use super::Address; use crate::types::block::{ capabilities::{Capabilities, CapabilityFlag}, + output::{StorageScore, StorageScoreParameters}, Error, }; @@ -54,6 +55,12 @@ impl RestrictedAddress { } } +impl StorageScore for RestrictedAddress { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.address.storage_score(params) + } +} + impl TryFrom
for RestrictedAddress { type Error = Error; @@ -157,12 +164,9 @@ pub type AddressCapabilities = Capabilities; #[cfg(feature = "serde")] pub(crate) mod dto { - use alloc::boxed::Box; - use serde::{Deserialize, Serialize}; use super::*; - use crate::utils::serde::prefix_hex_bytes; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -170,8 +174,8 @@ pub(crate) mod dto { #[serde(rename = "type")] kind: u8, pub address: Address, - #[serde(with = "prefix_hex_bytes")] - pub allowed_capabilities: Box<[u8]>, + #[serde(default, skip_serializing_if = "AddressCapabilities::is_none")] + pub allowed_capabilities: AddressCapabilities, } impl core::ops::Deref for RestrictedAddressDto { @@ -187,7 +191,7 @@ pub(crate) mod dto { Self { kind: RestrictedAddress::KIND, address: value.address.clone(), - allowed_capabilities: value.allowed_capabilities.iter().copied().collect(), + allowed_capabilities: value.allowed_capabilities.clone(), } } } @@ -196,14 +200,7 @@ pub(crate) mod dto { type Error = Error; fn try_from(value: RestrictedAddressDto) -> Result { - Ok( - Self::new(value.address)?.with_allowed_capabilities(AddressCapabilities::from_bytes( - value - .allowed_capabilities - .try_into() - .map_err(Error::InvalidCapabilitiesCount)?, - )), - ) + Ok(Self::new(value.address)?.with_allowed_capabilities(value.allowed_capabilities)) } } diff --git a/sdk/src/types/block/capabilities.rs b/sdk/src/types/block/capabilities.rs index 1985e43aa6..d647be374c 100644 --- a/sdk/src/types/block/capabilities.rs +++ b/sdk/src/types/block/capabilities.rs @@ -6,11 +6,13 @@ use core::marker::PhantomData; use derive_more::Deref; use packable::{ - error::UnpackErrorExt, + error::{UnpackError, UnpackErrorExt}, prefix::{BoxedSlicePrefix, UnpackPrefixError}, Packable, }; +use crate::types::block::Error; + /// A list of bitflags that represent capabilities. #[derive(Debug, Deref)] #[repr(transparent)] @@ -21,13 +23,6 @@ pub struct Capabilities { } impl Capabilities { - pub(crate) fn from_bytes(bytes: BoxedSlicePrefix) -> Self { - Self { - bytes, - _flag: PhantomData, - } - } - /// Returns a [`Capabilities`] with every possible flag disabled. pub fn none() -> Self { Self::default() @@ -37,9 +32,44 @@ impl Capabilities { pub fn is_none(&self) -> bool { self.iter().all(|b| 0.eq(b)) } + + /// Disables every possible flag. + pub fn set_none(&mut self) -> &mut Self { + *self = Default::default(); + self + } } impl Capabilities { + /// Try to create capabilities from serialized bytes. Bytes with trailing zeroes are invalid. + pub fn from_bytes(bytes: impl Into>) -> Result { + Self::from_prefix_box_slice(bytes.into().try_into().map_err(Error::InvalidCapabilitiesCount)?) + } + + /// Try to create capabilities from serialized bytes. Bytes with trailing zeroes are invalid. + pub(crate) fn from_prefix_box_slice(bytes: BoxedSlicePrefix) -> Result { + // Check if there is a trailing zero. + if bytes.last().map(|b| *b == 0).unwrap_or_default() { + return Err(Error::TrailingCapabilityBytes); + } + // Check if the bytes are valid instances of the flag type. + for (index, &byte) in bytes.iter().enumerate() { + // Get the max value of the flags at this index + let mut b = 0; + for flag in Flag::all().filter(|f| f.index() == index) { + b |= flag.as_byte(); + } + // Check whether the byte contains erroneous bits by using the max value as a mask + if b | byte != b { + return Err(Error::InvalidCapabilityByte { index, byte }); + } + } + Ok(Self { + bytes, + _flag: PhantomData, + }) + } + /// Returns a [`Capabilities`] with every possible flag enabled. pub fn all() -> Self { let mut res = Self::default(); @@ -60,12 +90,6 @@ impl Capabilities { self } - /// Disables every possible flag. - pub fn set_none(&mut self) -> &mut Self { - *self = Default::default(); - self - } - /// Enables a given flag. pub fn add_capability(&mut self, flag: Flag) -> &mut Self { if self.bytes.len() <= flag.index() { @@ -168,30 +192,27 @@ impl, Flag: CapabilityFlag> From for Capabilitie } } -impl Packable for Capabilities { +impl Packable for Capabilities { type UnpackError = crate::types::block::Error; type UnpackVisitor = (); fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - if !self.is_none() { - self.bytes.pack(packer)?; - } else { - 0_u8.pack(packer)?; - } + self.bytes.pack(packer)?; Ok(()) } fn unpack( unpacker: &mut U, visitor: &Self::UnpackVisitor, - ) -> Result> { - Ok(Self::from_bytes( + ) -> Result> { + Self::from_prefix_box_slice( BoxedSlicePrefix::unpack::<_, VERIFY>(unpacker, visitor) .map_packable_err(|e| match e { UnpackPrefixError::Item(i) | UnpackPrefixError::Prefix(i) => i, }) .coerce()?, - )) + ) + .map_err(UnpackError::Packable) } } @@ -207,3 +228,163 @@ pub trait CapabilityFlag { /// Returns an iterator over all flags. fn all() -> Self::Iterator; } + +#[cfg(feature = "serde")] +mod serde { + use ::serde::{Deserialize, Serialize}; + + use super::*; + + impl Serialize for Capabilities { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + crate::utils::serde::boxed_slice_prefix_hex_bytes::serialize(&self.bytes, serializer) + } + } + + impl<'de, Flag: CapabilityFlag> Deserialize<'de> for Capabilities { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + Self::from_prefix_box_slice(crate::utils::serde::boxed_slice_prefix_hex_bytes::deserialize( + deserializer, + )?) + .map_err(::serde::de::Error::custom) + } + } +} + +#[cfg(test)] +mod test { + use pretty_assertions::assert_eq; + + use super::*; + + #[derive(Debug)] + #[allow(unused)] + enum TestFlag { + Val1, + Val2, + Val3, + Val4, + Val5, + Val6, + Val7, + Val8, + Val9, + } + + impl TestFlag { + const VAL_1: u8 = 0b00000001; + const VAL_2: u8 = 0b00000010; + const VAL_3: u8 = 0b00000100; + const VAL_4: u8 = 0b00001000; + const VAL_5: u8 = 0b00010000; + const VAL_6: u8 = 0b00100000; + const VAL_7: u8 = 0b01000000; + const VAL_8: u8 = 0b10000000; + const VAL_9: u8 = 0b00000001; + } + + impl CapabilityFlag for TestFlag { + type Iterator = core::array::IntoIter; + + fn as_byte(&self) -> u8 { + match self { + TestFlag::Val1 => Self::VAL_1, + TestFlag::Val2 => Self::VAL_2, + TestFlag::Val3 => Self::VAL_3, + TestFlag::Val4 => Self::VAL_4, + TestFlag::Val5 => Self::VAL_5, + TestFlag::Val6 => Self::VAL_6, + TestFlag::Val7 => Self::VAL_7, + TestFlag::Val8 => Self::VAL_8, + TestFlag::Val9 => Self::VAL_9, + } + } + + fn index(&self) -> usize { + match self { + TestFlag::Val1 + | TestFlag::Val2 + | TestFlag::Val3 + | TestFlag::Val4 + | TestFlag::Val5 + | TestFlag::Val6 + | TestFlag::Val7 + | TestFlag::Val8 => 0, + TestFlag::Val9 => 1, + } + } + + fn all() -> Self::Iterator { + [ + Self::Val1, + Self::Val2, + Self::Val3, + Self::Val4, + Self::Val5, + Self::Val6, + Self::Val7, + Self::Val8, + Self::Val9, + ] + .into_iter() + } + } + + #[test] + fn test_valid() { + let capability_bytes = [TestFlag::VAL_1 | TestFlag::VAL_3 | TestFlag::VAL_4]; + let deser = Capabilities::::from_bytes(capability_bytes).unwrap(); + let built = Capabilities::default().with_capabilities([TestFlag::Val1, TestFlag::Val3, TestFlag::Val4]); + assert_eq!(deser, built); + + let capability_bytes = [0, TestFlag::VAL_9]; + let deser = Capabilities::::from_bytes(capability_bytes).unwrap(); + let built = Capabilities::default().with_capabilities([TestFlag::Val9]); + assert_eq!(deser, built); + } + + #[test] + fn test_out_of_range() { + let capability_bytes = [TestFlag::VAL_1 | TestFlag::VAL_4, TestFlag::VAL_9, TestFlag::VAL_3]; + assert_eq!( + Capabilities::::from_bytes(capability_bytes), + Err(Error::InvalidCapabilityByte { + index: 2, + byte: TestFlag::VAL_3 + }) + ); + } + + #[test] + fn test_trailing() { + let capability_bytes = [0, 0]; + assert_eq!( + Capabilities::::from_bytes(capability_bytes), + Err(Error::TrailingCapabilityBytes) + ); + + let capability_bytes = [TestFlag::VAL_1 | TestFlag::VAL_4, 0]; + assert_eq!( + Capabilities::::from_bytes(capability_bytes), + Err(Error::TrailingCapabilityBytes) + ); + } + + #[test] + fn test_invalid_byte() { + let capability_bytes = [TestFlag::VAL_1 | TestFlag::VAL_3, TestFlag::VAL_9 | TestFlag::VAL_2]; + assert_eq!( + Capabilities::::from_bytes(capability_bytes), + Err(Error::InvalidCapabilityByte { + index: 1, + byte: TestFlag::VAL_9 | TestFlag::VAL_2 + }) + ); + } +} diff --git a/sdk/src/types/block/core/basic.rs b/sdk/src/types/block/core/basic.rs index 5535c70075..1ce750986e 100644 --- a/sdk/src/types/block/core/basic.rs +++ b/sdk/src/types/block/core/basic.rs @@ -1,12 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use packable::{ - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - unpacker::Unpacker, - Packable, -}; +use packable::Packable; use crate::types::block::{ core::{parent::verify_parents_sets, Block, Parents}, @@ -107,7 +102,10 @@ impl From for BasicBlockBuilder { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] +#[packable(verify_with = verify_basic_block)] pub struct BasicBlock { /// Blocks that are strongly directly approved. strong_parents: StrongParents, @@ -156,45 +154,16 @@ impl BasicBlock { } } -impl Packable for BasicBlock { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - self.strong_parents.pack(packer)?; - self.weak_parents.pack(packer)?; - self.shallow_like_parents.pack(packer)?; - self.payload.pack(packer)?; - self.max_burned_mana.pack(packer)?; - - Ok(()) +fn verify_basic_block(basic_block: &BasicBlock, _: &ProtocolParameters) -> Result<(), Error> { + if VERIFY { + verify_parents_sets( + &basic_block.strong_parents, + &basic_block.weak_parents, + &basic_block.shallow_like_parents, + )?; } - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - let strong_parents = StrongParents::unpack::<_, VERIFY>(unpacker, &())?; - let weak_parents = WeakParents::unpack::<_, VERIFY>(unpacker, &())?; - let shallow_like_parents = ShallowLikeParents::unpack::<_, VERIFY>(unpacker, &())?; - - if VERIFY { - verify_parents_sets(&strong_parents, &weak_parents, &shallow_like_parents) - .map_err(UnpackError::Packable)?; - } - - let payload = OptionalPayload::unpack::<_, VERIFY>(unpacker, visitor)?; - - let max_burned_mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - Ok(Self { - strong_parents, - weak_parents, - shallow_like_parents, - payload, - max_burned_mana, - }) - } + Ok(()) } #[cfg(feature = "serde")] diff --git a/sdk/src/types/block/core/mod.rs b/sdk/src/types/block/core/mod.rs index a6f8b6cd9d..c873d35dff 100644 --- a/sdk/src/types/block/core/mod.rs +++ b/sdk/src/types/block/core/mod.rs @@ -10,12 +10,7 @@ use alloc::boxed::Box; use crypto::hashes::{blake2b::Blake2b256, Digest}; use derive_more::From; -use packable::{ - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - unpacker::Unpacker, - Packable, PackableExt, -}; +use packable::{Packable, PackableExt}; pub use self::{ basic::{BasicBlock, BasicBlockBuilder}, @@ -28,9 +23,14 @@ use crate::types::block::{ Error, }; -#[derive(Clone, Debug, Eq, PartialEq, From)] +#[derive(Clone, Debug, Eq, PartialEq, From, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] +#[packable(tag_type = u8, with_error = Error::InvalidBlockKind)] pub enum Block { + #[packable(tag = BasicBlock::KIND)] Basic(Box), + #[packable(tag = ValidationBlock::KIND)] Validation(Box), } @@ -102,37 +102,6 @@ impl Block { } } -impl Packable for Block { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - match self { - Self::Basic(block) => { - BasicBlock::KIND.pack(packer)?; - block.pack(packer) - } - Self::Validation(block) => { - ValidationBlock::KIND.pack(packer)?; - block.pack(packer) - } - }?; - - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? { - BasicBlock::KIND => Self::from(BasicBlock::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - ValidationBlock::KIND => Self::from(ValidationBlock::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - k => return Err(UnpackError::Packable(Error::InvalidBlockKind(k))), - }) - } -} - #[cfg(feature = "serde")] pub(crate) mod dto { use alloc::format; diff --git a/sdk/src/types/block/core/validation.rs b/sdk/src/types/block/core/validation.rs index f93970c41b..3e37275bc7 100644 --- a/sdk/src/types/block/core/validation.rs +++ b/sdk/src/types/block/core/validation.rs @@ -1,12 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use packable::{ - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - unpacker::Unpacker, - Packable, -}; +use packable::Packable; use crate::types::block::{ core::{parent::verify_parents_sets, Block, Parents}, @@ -113,7 +108,10 @@ impl From for ValidationBlockBuilder { /// A Validation Block is a special type of block used by validators to secure the network. It is recognized by the /// Congestion Control of the IOTA 2.0 protocol and can be issued without burning Mana within the constraints of the /// allowed validator throughput. It is allowed to reference more parent blocks than a normal Basic Block. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] +#[packable(verify_with = verify_validation_block)] pub struct ValidationBlock { /// Blocks that are strongly directly approved. strong_parents: StrongParents, @@ -124,6 +122,7 @@ pub struct ValidationBlock { /// The highest supported protocol version the issuer of this block supports. highest_supported_version: u8, /// The hash of the protocol parameters for the Highest Supported Version. + #[packable(verify_with = verify_protocol_parameters_hash)] protocol_parameters_hash: ProtocolParametersHash, } @@ -161,59 +160,34 @@ impl ValidationBlock { } } -impl Packable for ValidationBlock { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - self.strong_parents.pack(packer)?; - self.weak_parents.pack(packer)?; - self.shallow_like_parents.pack(packer)?; - self.highest_supported_version.pack(packer)?; - self.protocol_parameters_hash.pack(packer)?; - - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - let strong_parents = StrongParents::unpack::<_, VERIFY>(unpacker, &())?; - let weak_parents = WeakParents::unpack::<_, VERIFY>(unpacker, &())?; - let shallow_like_parents = ShallowLikeParents::unpack::<_, VERIFY>(unpacker, &())?; - - if VERIFY { - verify_parents_sets(&strong_parents, &weak_parents, &shallow_like_parents) - .map_err(UnpackError::Packable)?; - } - - let highest_supported_version = u8::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - let protocol_parameters_hash = ProtocolParametersHash::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - if VERIFY { - validate_protocol_params_hash(&protocol_parameters_hash, visitor).map_err(UnpackError::Packable)?; +fn verify_protocol_parameters_hash( + hash: &ProtocolParametersHash, + params: &ProtocolParameters, +) -> Result<(), Error> { + if VERIFY { + let params_hash = params.hash(); + + if hash != ¶ms_hash { + return Err(Error::InvalidProtocolParametersHash { + expected: params_hash, + actual: *hash, + }); } - - Ok(Self { - strong_parents, - weak_parents, - shallow_like_parents, - highest_supported_version, - protocol_parameters_hash, - }) } -} -fn validate_protocol_params_hash(hash: &ProtocolParametersHash, params: &ProtocolParameters) -> Result<(), Error> { - let params_hash = params.hash(); + Ok(()) +} - if hash != ¶ms_hash { - return Err(Error::InvalidProtocolParametersHash { - expected: params_hash, - actual: *hash, - }); +fn verify_validation_block( + validation_block: &ValidationBlock, + _: &ProtocolParameters, +) -> Result<(), Error> { + if VERIFY { + verify_parents_sets( + &validation_block.strong_parents, + &validation_block.weak_parents, + &validation_block.shallow_like_parents, + )?; } Ok(()) @@ -262,7 +236,7 @@ pub(crate) mod dto { fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { if let Some(protocol_params) = params.protocol_parameters() { - validate_protocol_params_hash(&dto.protocol_parameters_hash, protocol_params)?; + verify_protocol_parameters_hash::(&dto.protocol_parameters_hash, protocol_params)?; } ValidationBlockBuilder::new( diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index e333cb795c..e6639f3eda 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -11,6 +11,7 @@ use primitive_types::U256; use super::slot::EpochIndex; use crate::types::block::{ + address::WeightedAddressCount, context_input::RewardContextInputIndex, input::UtxoInput, mana::ManaAllotmentCount, @@ -22,12 +23,13 @@ use crate::types::block::{ }, payload::{ContextInputCount, InputCount, OutputCount, TagLength, TaggedDataLength}, protocol::ProtocolParametersHash, - unlock::{UnlockCount, UnlockIndex}, + unlock::{UnlockCount, UnlockIndex, UnlocksCount}, }; /// Error occurring when creating/parsing/validating blocks. #[derive(Debug, PartialEq, Eq)] #[allow(missing_docs)] +#[non_exhaustive] pub enum Error { ManaAllotmentsNotUniqueSorted, ConsumedAmountOverflow, @@ -69,6 +71,16 @@ pub enum Error { deposit: u64, required: u64, }, + InvalidAddressWeight(u8), + InvalidMultiAddressThreshold(u16), + InvalidMultiAddressCumulativeWeight { + cumulative_weight: u16, + threshold: u16, + }, + InvalidWeightedAddressCount(>::Error), + InvalidMultiUnlockCount(>::Error), + MultiUnlockRecursion, + WeightedAddressesNotUniqueSorted, InvalidContextInputKind(u8), InvalidContextInputCount(>::Error), InvalidFeatureCount(>::Error), @@ -84,6 +96,10 @@ pub enum Error { InvalidInputOutputIndex(>::Error), InvalidBech32Hrp(Bech32HrpError), InvalidCapabilitiesCount(>::Error), + InvalidCapabilityByte { + index: usize, + byte: u8, + }, InvalidSignedBlockLength(usize), InvalidStateMetadataLength(>::Error), InvalidManaValue(u64), @@ -182,6 +198,7 @@ pub enum Error { created: EpochIndex, target: EpochIndex, }, + TrailingCapabilityBytes, } #[cfg(feature = "std")] @@ -226,6 +243,9 @@ impl fmt::Display for Error { Self::InvalidAnchorIndex(index) => write!(f, "invalid anchor index: {index}"), Self::InvalidBech32Hrp(e) => write!(f, "invalid bech32 hrp: {e}"), Self::InvalidCapabilitiesCount(e) => write!(f, "invalid capabilities count: {e}"), + Self::InvalidCapabilityByte { index, byte } => { + write!(f, "invalid capability byte at index {index}: {byte:x}") + } Self::InvalidBlockKind(k) => write!(f, "invalid block kind: {k}"), Self::InvalidRewardInputIndex(idx) => write!(f, "invalid reward input index: {idx}"), Self::InvalidStorageDepositAmount(amount) => { @@ -243,7 +263,7 @@ impl fmt::Display for Error { Self::InsufficientStorageDepositReturnAmount { deposit, required } => { write!( f, - "the return deposit ({deposit}) must be greater than the minimum storage deposit ({required})" + "the return deposit ({deposit}) must be greater than the minimum output amount ({required})" ) } Self::StorageDepositReturnExceedsOutputAmount { deposit, amount } => write!( @@ -251,6 +271,23 @@ impl fmt::Display for Error { "storage deposit return of {deposit} exceeds the original output amount of {amount}" ), Self::InvalidContextInputCount(count) => write!(f, "invalid context input count: {count}"), + Self::InvalidAddressWeight(w) => write!(f, "invalid address weight: {w}"), + Self::InvalidMultiAddressThreshold(t) => write!(f, "invalid multi address threshold: {t}"), + Self::InvalidMultiAddressCumulativeWeight { + cumulative_weight, + threshold, + } => { + write!( + f, + "invalid multi address cumulative weight {cumulative_weight} < threshold {threshold}" + ) + } + Self::InvalidWeightedAddressCount(count) => write!(f, "invalid weighted address count: {count}"), + Self::InvalidMultiUnlockCount(count) => write!(f, "invalid multi unlock count: {count}"), + Self::MultiUnlockRecursion => write!(f, "multi unlock recursion"), + Self::WeightedAddressesNotUniqueSorted => { + write!(f, "weighted addresses are not unique and/or sorted") + } Self::InvalidContextInputKind(k) => write!(f, "invalid context input kind: {k}"), Self::InvalidFeatureCount(count) => write!(f, "invalid feature count: {count}"), Self::InvalidFeatureKind(k) => write!(f, "invalid feature kind: {k}"), @@ -392,6 +429,7 @@ impl fmt::Display for Error { Self::InvalidEpochDelta { created, target } => { write!(f, "invalid epoch delta: created {created}, target {target}") } + Self::TrailingCapabilityBytes => write!(f, "capability bytes have trailing zeroes"), } } } diff --git a/sdk/src/types/block/macro.rs b/sdk/src/types/block/macro.rs index d4783a93cd..74b74a6ec8 100644 --- a/sdk/src/types/block/macro.rs +++ b/sdk/src/types/block/macro.rs @@ -75,13 +75,13 @@ macro_rules! impl_id { } } - impl $crate::types::block::ConvertTo<$hash_name> for &alloc::string::String { + impl $crate::utils::ConvertTo<$hash_name> for &alloc::string::String { fn convert(self) -> Result<$hash_name, $crate::types::block::Error> { self.try_into() } } - impl $crate::types::block::ConvertTo<$hash_name> for &str { + impl $crate::utils::ConvertTo<$hash_name> for &str { fn convert(self) -> Result<$hash_name, $crate::types::block::Error> { self.try_into() } @@ -205,13 +205,13 @@ macro_rules! impl_id { } } - impl $crate::types::block::ConvertTo<$id_name> for &alloc::string::String { + impl $crate::utils::ConvertTo<$id_name> for &alloc::string::String { fn convert(self) -> Result<$id_name, $crate::types::block::Error> { self.try_into() } } - impl $crate::types::block::ConvertTo<$id_name> for &str { + impl $crate::utils::ConvertTo<$id_name> for &str { fn convert(self) -> Result<$id_name, $crate::types::block::Error> { self.try_into() } @@ -229,7 +229,6 @@ macro_rules! impl_id { )? }; } -pub(crate) use impl_id; /// Convenience macro to serialize types to string via serde. #[cfg(feature = "serde")] @@ -297,7 +296,6 @@ macro_rules! create_bitflags { } }; } -pub(crate) use create_bitflags; #[macro_export] macro_rules! impl_serde_typed_dto { diff --git a/sdk/src/types/block/mana/allotment.rs b/sdk/src/types/block/mana/allotment.rs index 16ad2ff509..fbb319f4d2 100644 --- a/sdk/src/types/block/mana/allotment.rs +++ b/sdk/src/types/block/mana/allotment.rs @@ -1,20 +1,18 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use packable::{ - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - unpacker::Unpacker, - Packable, -}; +use packable::Packable; use crate::types::block::{output::AccountId, protocol::ProtocolParameters, Error}; /// An allotment of Mana which will be added upon commitment of the slot in which the containing transaction was issued, /// in the form of Block Issuance Credits to the account. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] pub struct ManaAllotment { pub(crate) account_id: AccountId, + #[packable(verify_with = verify_mana)] pub(crate) mana: u64, } @@ -32,9 +30,8 @@ impl Ord for ManaAllotment { impl ManaAllotment { pub fn new(account_id: AccountId, mana: u64, protocol_params: &ProtocolParameters) -> Result { - if mana > protocol_params.mana_parameters().max_mana() { - return Err(Error::InvalidManaValue(mana)); - } + verify_mana::(&mana, protocol_params)?; + Ok(Self { account_id, mana }) } @@ -47,26 +44,12 @@ impl ManaAllotment { } } -impl Packable for ManaAllotment { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - self.account_id.pack(packer)?; - self.mana.pack(packer)?; - - Ok(()) +fn verify_mana(mana: &u64, params: &ProtocolParameters) -> Result<(), Error> { + if VERIFY && *mana > params.mana_parameters().max_mana() { + return Err(Error::InvalidManaValue(*mana)); } - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - let account_id = AccountId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - Self::new(account_id, mana, visitor).map_err(UnpackError::Packable) - } + Ok(()) } #[cfg(feature = "serde")] diff --git a/sdk/src/types/block/mod.rs b/sdk/src/types/block/mod.rs index d010281bab..df210e8224 100644 --- a/sdk/src/types/block/mod.rs +++ b/sdk/src/types/block/mod.rs @@ -4,7 +4,6 @@ //! Core data types for blocks in the tangle. mod block_id; -mod convert; mod error; mod issuer_id; mod r#macro; @@ -46,7 +45,6 @@ pub use self::core::dto::{BlockDto, SignedBlockDto, UnsignedBlockDto}; pub(crate) use self::r#macro::*; pub use self::{ block_id::{BlockHash, BlockId}, - convert::ConvertTo, core::{Block, SignedBlock, UnsignedBlock}, error::Error, issuer_id::IssuerId, diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 1948ffa95d..20f1316e45 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -1,35 +1,29 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{collections::BTreeSet, vec::Vec}; +use alloc::collections::BTreeSet; use hashbrown::HashMap; use packable::{ - bounded::BoundedU16, error::{UnpackError, UnpackErrorExt}, packer::Packer, unpacker::Unpacker, - Packable, + Packable, PackableExt, }; -use crate::types::{ - block::{ - address::{AccountAddress, Address}, - output::{ - feature::{verify_allowed_features, Feature, FeatureFlags, Features}, - unlock_condition::{ - verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions, - }, - verify_output_amount_min, verify_output_amount_packable, verify_output_amount_supply, ChainId, Output, - OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, - }, - payload::signed_transaction::TransactionCapabilityFlag, - protocol::ProtocolParameters, - semantic::{SemanticValidationContext, TransactionFailureReason}, - unlock::Unlock, - Error, +use crate::types::block::{ + address::{AccountAddress, Address}, + output::{ + feature::{verify_allowed_features, Feature, FeatureFlags, Features}, + unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StateTransitionError, + StateTransitionVerifier, StorageScore, StorageScoreParameters, }, - ValidationParams, + payload::signed_transaction::TransactionCapabilityFlag, + protocol::ProtocolParameters, + semantic::{SemanticValidationContext, TransactionFailureReason}, + unlock::Unlock, + Error, }; crate::impl_id!( @@ -48,7 +42,11 @@ impl From<&OutputId> for AccountId { impl AccountId { /// pub fn or_from_output_id(self, output_id: &OutputId) -> Self { - if self.is_null() { Self::from(output_id) } else { self } + if self.is_null() { + Self::from(output_id) + } else { + self + } } } @@ -77,10 +75,10 @@ impl AccountOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), account_id) } - /// Creates an [`AccountOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. - pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure, account_id: AccountId) -> Self { - Self::new(OutputBuilderAmount::MinimumStorageDeposit(rent_structure), account_id) + /// Creates an [`AccountOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. + pub fn new_with_minimum_amount(params: StorageScoreParameters, account_id: AccountId) -> Self { + Self::new(OutputBuilderAmount::MinimumAmount(params), account_id) } fn new(amount: OutputBuilderAmount, account_id: AccountId) -> Self { @@ -102,10 +100,10 @@ impl AccountOutputBuilder { self } - /// Sets the amount to the minimum storage deposit. + /// Sets the amount to the minimum required amount. #[inline(always)] - pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { - self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure); + pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::MinimumAmount(params); self } @@ -233,7 +231,7 @@ impl AccountOutputBuilder { verify_allowed_features(&immutable_features, AccountOutput::ALLOWED_IMMUTABLE_FEATURES)?; let mut output = AccountOutput { - amount: 1, + amount: 0, mana: self.mana, account_id: self.account_id, foundry_counter, @@ -244,33 +242,15 @@ impl AccountOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Account(output.clone()).rent_cost(rent_structure) - } + OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; - verify_output_amount_min(output.amount)?; - - Ok(output) - } - - /// - pub fn finish_with_params<'a>( - self, - params: impl Into> + Send, - ) -> Result { - let output = self.finish()?; - - if let Some(token_supply) = params.into().token_supply() { - verify_output_amount_supply(output.amount, token_supply)?; - } - Ok(output) } /// Finishes the [`AccountOutputBuilder`] into an [`Output`]. - pub fn finish_output<'a>(self, params: impl Into> + Send) -> Result { - Ok(Output::Account(self.finish_with_params(params)?)) + pub fn finish_output(self) -> Result { + Ok(Output::Account(self.finish()?)) } } @@ -288,8 +268,6 @@ impl From<&AccountOutput> for AccountOutputBuilder { } } -pub(crate) type StateMetadataLength = BoundedU16<0, { AccountOutput::STATE_METADATA_LENGTH_MAX }>; - /// Describes an account in the ledger that can be controlled by the state and governance controllers. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct AccountOutput { @@ -310,8 +288,6 @@ pub struct AccountOutput { impl AccountOutput { /// The [`Output`](crate::types::block::output::Output) kind of an [`AccountOutput`]. pub const KIND: u8 = 1; - /// Maximum possible length in bytes of the state metadata. - pub const STATE_METADATA_LENGTH_MAX: u16 = 8192; /// The set of allowed [`UnlockCondition`]s for an [`AccountOutput`]. pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS; /// The set of allowed [`Feature`]s for an [`AccountOutput`]. @@ -325,14 +301,11 @@ impl AccountOutput { AccountOutputBuilder::new_with_amount(amount, account_id) } - /// Creates a new [`AccountOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. + /// Creates a new [`AccountOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount. #[inline(always)] - pub fn build_with_minimum_storage_deposit( - rent_structure: RentStructure, - account_id: AccountId, - ) -> AccountOutputBuilder { - AccountOutputBuilder::new_with_minimum_storage_deposit(rent_structure, account_id) + pub fn build_with_minimum_amount(params: StorageScoreParameters, account_id: AccountId) -> AccountOutputBuilder { + AccountOutputBuilder::new_with_minimum_amount(params, account_id) } /// @@ -523,6 +496,19 @@ impl StateTransitionVerifier for AccountOutput { } } +impl StorageScore for AccountOutput { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.output_offset() + // Type byte + + (1 + self.packed_len() as u64) * params.data_factor() as u64 + + self.unlock_conditions.storage_score(params) + + self.features.storage_score(params) + + self.immutable_features.storage_score(params) + } +} + +impl MinimumOutputAmount for AccountOutput {} + impl Packable for AccountOutput { type UnpackError = Error; type UnpackVisitor = ProtocolParameters; @@ -545,8 +531,6 @@ impl Packable for AccountOutput { ) -> Result> { let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - verify_output_amount_packable::(&amount, visitor).map_err(UnpackError::Packable)?; - let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let account_id = AccountId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; @@ -613,14 +597,13 @@ fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, account_id: &A #[cfg(feature = "serde")] pub(crate) mod dto { + use alloc::vec::Vec; + use serde::{Deserialize, Serialize}; use super::*; use crate::{ - types::{ - block::{output::unlock_condition::dto::UnlockConditionDto, Error}, - TryFromDto, - }, + types::block::{output::unlock_condition::dto::UnlockConditionDto, Error}, utils::serde::string, }; @@ -658,11 +641,10 @@ pub(crate) mod dto { } } - impl TryFromDto for AccountOutput { - type Dto = AccountOutputDto; + impl TryFrom for AccountOutput { type Error = Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { + fn try_from(dto: AccountOutputDto) -> Result { let mut builder = AccountOutputBuilder::new_with_amount(dto.amount, dto.account_id) .with_mana(dto.mana) .with_foundry_counter(dto.foundry_counter) @@ -670,10 +652,10 @@ pub(crate) mod dto { .with_immutable_features(dto.immutable_features); for u in dto.unlock_conditions { - builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_with_params(u, ¶ms)?); + builder = builder.add_unlock_condition(UnlockCondition::from(u)); } - builder.finish_with_params(params) + builder.finish() } } @@ -687,13 +669,11 @@ pub(crate) mod dto { unlock_conditions: Vec, features: Option>, immutable_features: Option>, - params: impl Into> + Send, ) -> Result { - let params = params.into(); let mut builder = match amount { OutputBuilderAmount::Amount(amount) => AccountOutputBuilder::new_with_amount(amount, *account_id), - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - AccountOutputBuilder::new_with_minimum_storage_deposit(rent_structure, *account_id) + OutputBuilderAmount::MinimumAmount(params) => { + AccountOutputBuilder::new_with_minimum_amount(params, *account_id) } } .with_mana(mana); @@ -704,8 +684,8 @@ pub(crate) mod dto { let unlock_conditions = unlock_conditions .into_iter() - .map(|u| UnlockCondition::try_from_dto_with_params(u, ¶ms)) - .collect::, Error>>()?; + .map(UnlockCondition::from) + .collect::>(); builder = builder.with_unlock_conditions(unlock_conditions); if let Some(features) = features { @@ -716,7 +696,7 @@ pub(crate) mod dto { builder = builder.with_immutable_features(immutable_features); } - builder.finish_with_params(params) + builder.finish() } } } @@ -726,19 +706,16 @@ mod tests { use pretty_assertions::assert_eq; use super::*; - use crate::types::{ - block::{ - output::dto::OutputDto, - protocol::protocol_parameters, - rand::{ - address::rand_account_address, - output::{ - feature::rand_allowed_features, rand_account_id, rand_account_output, - unlock_condition::rand_address_unlock_condition_different_from_account_id, - }, + use crate::types::block::{ + output::{dto::OutputDto, FoundryId, SimpleTokenScheme, TokenId}, + protocol::protocol_parameters, + rand::{ + address::rand_account_address, + output::{ + feature::rand_allowed_features, rand_account_id, rand_account_output, + unlock_condition::rand_address_unlock_condition_different_from_account_id, }, }, - TryFromDto, }; #[test] @@ -746,9 +723,9 @@ mod tests { let protocol_parameters = protocol_parameters(); let output = rand_account_output(protocol_parameters.token_supply()); let dto = OutputDto::Account((&output).into()); - let output_unver = Output::try_from_dto(dto.clone()).unwrap(); + let output_unver = Output::try_from(dto.clone()).unwrap(); assert_eq!(&output, output_unver.as_account()); - let output_ver = Output::try_from_dto_with_params(dto, &protocol_parameters).unwrap(); + let output_ver = Output::try_from(dto).unwrap(); assert_eq!(&output, output_ver.as_account()); let output_split = AccountOutput::try_from_dtos( @@ -759,7 +736,6 @@ mod tests { output.unlock_conditions().iter().map(Into::into).collect(), Some(output.features().to_vec()), Some(output.immutable_features().to_vec()), - &protocol_parameters, ) .unwrap(); assert_eq!(output, output_split); @@ -776,10 +752,9 @@ mod tests { builder.unlock_conditions.iter().map(Into::into).collect(), Some(builder.features.iter().cloned().collect()), Some(builder.immutable_features.iter().cloned().collect()), - &protocol_parameters, ) .unwrap(); - assert_eq!(builder.finish_with_params(&protocol_parameters).unwrap(), output_split); + assert_eq!(builder.finish().unwrap(), output_split); }; let builder = AccountOutput::build_with_amount(100, account_id) @@ -789,7 +764,7 @@ mod tests { test_split_dto(builder); let builder = - AccountOutput::build_with_minimum_storage_deposit(protocol_parameters.rent_structure(), account_id) + AccountOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters(), account_id) .add_unlock_condition(address) .with_features(rand_allowed_features(AccountOutput::ALLOWED_FEATURES)) .with_immutable_features(rand_allowed_features(AccountOutput::ALLOWED_IMMUTABLE_FEATURES)); diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index 21f363386f..c21c77b149 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -10,28 +10,22 @@ use packable::{ packer::Packer, prefix::BoxedSlicePrefix, unpacker::Unpacker, - Packable, + Packable, PackableExt, }; -use crate::types::{ - block::{ - address::{Address, AnchorAddress}, - output::{ - feature::{verify_allowed_features, Feature, FeatureFlags, Features}, - unlock_condition::{ - verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions, - }, - verify_output_amount_min, verify_output_amount_packable, verify_output_amount_supply, ChainId, NativeToken, - NativeTokens, Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, - StateTransitionVerifier, - }, - payload::signed_transaction::TransactionCapabilityFlag, - protocol::ProtocolParameters, - semantic::{SemanticValidationContext, TransactionFailureReason}, - unlock::Unlock, - Error, +use crate::types::block::{ + address::{Address, AnchorAddress}, + output::{ + feature::{verify_allowed_features, Feature, FeatureFlags, Features}, + unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + ChainId, MinimumOutputAmount, NativeToken, NativeTokens, Output, OutputBuilderAmount, OutputId, + StateTransitionError, StateTransitionVerifier, StorageScore, StorageScoreParameters, }, - ValidationParams, + payload::signed_transaction::TransactionCapabilityFlag, + protocol::ProtocolParameters, + semantic::{SemanticValidationContext, TransactionFailureReason}, + unlock::Unlock, + Error, }; crate::impl_id!( @@ -99,7 +93,7 @@ pub struct AnchorOutputBuilder { mana: u64, native_tokens: BTreeSet, anchor_id: AnchorId, - state_index: Option, + state_index: u32, state_metadata: Vec, unlock_conditions: BTreeSet, features: BTreeSet, @@ -112,10 +106,11 @@ impl AnchorOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), anchor_id) } - /// Creates an [`AnchorOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. - pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure, anchor_id: AnchorId) -> Self { - Self::new(OutputBuilderAmount::MinimumStorageDeposit(rent_structure), anchor_id) + /// Creates an [`AnchorOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. + #[inline(always)] + pub fn new_with_minimum_amount(params: StorageScoreParameters, anchor_id: AnchorId) -> Self { + Self::new(OutputBuilderAmount::MinimumAmount(params), anchor_id) } fn new(amount: OutputBuilderAmount, anchor_id: AnchorId) -> Self { @@ -124,7 +119,7 @@ impl AnchorOutputBuilder { mana: Default::default(), native_tokens: BTreeSet::new(), anchor_id, - state_index: None, + state_index: 0, state_metadata: Vec::new(), unlock_conditions: BTreeSet::new(), features: BTreeSet::new(), @@ -139,10 +134,10 @@ impl AnchorOutputBuilder { self } - /// Sets the amount to the minimum storage deposit. + /// Sets the amount to the minimum required amount. #[inline(always)] - pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { - self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure); + pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::MinimumAmount(params); self } @@ -176,8 +171,8 @@ impl AnchorOutputBuilder { /// #[inline(always)] - pub fn with_state_index(mut self, state_index: impl Into>) -> Self { - self.state_index = state_index.into(); + pub fn with_state_index(mut self, state_index: u32) -> Self { + self.state_index = state_index; self } @@ -274,15 +269,13 @@ impl AnchorOutputBuilder { /// pub fn finish(self) -> Result { - let state_index = self.state_index.unwrap_or(0); - let state_metadata = self .state_metadata .into_boxed_slice() .try_into() .map_err(Error::InvalidStateMetadataLength)?; - verify_index_counter(&self.anchor_id, state_index)?; + verify_index_counter(&self.anchor_id, self.state_index)?; let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; @@ -297,11 +290,11 @@ impl AnchorOutputBuilder { verify_allowed_features(&immutable_features, AnchorOutput::ALLOWED_IMMUTABLE_FEATURES)?; let mut output = AnchorOutput { - amount: 1, + amount: 0, mana: self.mana, native_tokens: NativeTokens::from_set(self.native_tokens)?, anchor_id: self.anchor_id, - state_index, + state_index: self.state_index, state_metadata, unlock_conditions, features, @@ -310,30 +303,15 @@ impl AnchorOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Anchor(output.clone()).rent_cost(rent_structure) - } + OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; - verify_output_amount_min(output.amount)?; - - Ok(output) - } - - /// - pub fn finish_with_params<'a>(self, params: impl Into> + Send) -> Result { - let output = self.finish()?; - - if let Some(token_supply) = params.into().token_supply() { - verify_output_amount_supply(output.amount, token_supply)?; - } - Ok(output) } /// Finishes the [`AnchorOutputBuilder`] into an [`Output`]. - pub fn finish_output<'a>(self, params: impl Into> + Send) -> Result { - Ok(Output::Anchor(self.finish_with_params(params)?)) + pub fn finish_output(self) -> Result { + Ok(Output::Anchor(self.finish()?)) } } @@ -344,7 +322,7 @@ impl From<&AnchorOutput> for AnchorOutputBuilder { mana: output.mana, native_tokens: output.native_tokens.iter().copied().collect(), anchor_id: output.anchor_id, - state_index: Some(output.state_index), + state_index: output.state_index, state_metadata: output.state_metadata.to_vec(), unlock_conditions: output.unlock_conditions.iter().cloned().collect(), features: output.features.iter().cloned().collect(), @@ -379,15 +357,14 @@ pub struct AnchorOutput { impl AnchorOutput { /// The [`Output`](crate::types::block::output::Output) kind of an [`AnchorOutput`]. - /// TODO - pub const KIND: u8 = 255; + pub const KIND: u8 = 2; /// Maximum possible length in bytes of the state metadata. pub const STATE_METADATA_LENGTH_MAX: u16 = 8192; /// The set of allowed [`UnlockCondition`]s for an [`AnchorOutput`]. pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::STATE_CONTROLLER_ADDRESS.union(UnlockConditionFlags::GOVERNOR_ADDRESS); /// The set of allowed [`Feature`]s for an [`AnchorOutput`]. - pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::SENDER.union(FeatureFlags::METADATA); + pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::METADATA; /// The set of allowed immutable [`Feature`]s for an [`AnchorOutput`]. pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags = FeatureFlags::ISSUER.union(FeatureFlags::METADATA); @@ -397,14 +374,11 @@ impl AnchorOutput { AnchorOutputBuilder::new_with_amount(amount, anchor_id) } - /// Creates a new [`AnchorOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. + /// Creates a new [`AnchorOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount. #[inline(always)] - pub fn build_with_minimum_storage_deposit( - rent_structure: RentStructure, - anchor_id: AnchorId, - ) -> AnchorOutputBuilder { - AnchorOutputBuilder::new_with_minimum_storage_deposit(rent_structure, anchor_id) + pub fn build_with_minimum_amount(params: StorageScoreParameters, anchor_id: AnchorId) -> AnchorOutputBuilder { + AnchorOutputBuilder::new_with_minimum_amount(params, anchor_id) } /// @@ -570,6 +544,19 @@ impl AnchorOutput { } } +impl StorageScore for AnchorOutput { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.output_offset() + // Type byte + + (1 + self.packed_len() as u64) * params.data_factor() as u64 + + self.unlock_conditions.storage_score(params) + + self.features.storage_score(params) + + self.immutable_features.storage_score(params) + } +} + +impl MinimumOutputAmount for AnchorOutput {} + impl StateTransitionVerifier for AnchorOutput { fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !next_state.anchor_id.is_null() { @@ -634,8 +621,6 @@ impl Packable for AnchorOutput { ) -> Result> { let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - verify_output_amount_packable::(&amount, visitor).map_err(UnpackError::Packable)?; - let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let native_tokens = NativeTokens::unpack::<_, VERIFY>(unpacker, &())?; @@ -722,10 +707,7 @@ pub(crate) mod dto { use super::*; use crate::{ - types::{ - block::{output::unlock_condition::dto::UnlockConditionDto, Error}, - TryFromDto, - }, + types::block::{output::unlock_condition::dto::UnlockConditionDto, Error}, utils::serde::{prefix_hex_bytes, string}, }; @@ -769,11 +751,10 @@ pub(crate) mod dto { } } - impl TryFromDto for AnchorOutput { - type Dto = AnchorOutputDto; + impl TryFrom for AnchorOutput { type Error = Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { + fn try_from(dto: AnchorOutputDto) -> Result { let mut builder = AnchorOutputBuilder::new_with_amount(dto.amount, dto.anchor_id) .with_mana(dto.mana) .with_state_index(dto.state_index) @@ -783,10 +764,10 @@ pub(crate) mod dto { .with_state_metadata(dto.state_metadata); for u in dto.unlock_conditions { - builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_with_params(u, ¶ms)?); + builder = builder.add_unlock_condition(UnlockCondition::from(u)); } - builder.finish_with_params(params) + builder.finish() } } @@ -797,38 +778,33 @@ pub(crate) mod dto { mana: u64, native_tokens: Option>, anchor_id: &AnchorId, - state_index: Option, + state_index: u32, state_metadata: Option>, unlock_conditions: Vec, features: Option>, immutable_features: Option>, - params: impl Into> + Send, ) -> Result { - let params = params.into(); let mut builder = match amount { OutputBuilderAmount::Amount(amount) => AnchorOutputBuilder::new_with_amount(amount, *anchor_id), - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - AnchorOutputBuilder::new_with_minimum_storage_deposit(rent_structure, *anchor_id) + OutputBuilderAmount::MinimumAmount(params) => { + AnchorOutputBuilder::new_with_minimum_amount(params, *anchor_id) } } - .with_mana(mana); + .with_mana(mana) + .with_state_index(state_index); if let Some(native_tokens) = native_tokens { builder = builder.with_native_tokens(native_tokens); } - if let Some(state_index) = state_index { - builder = builder.with_state_index(state_index); - } - if let Some(state_metadata) = state_metadata { builder = builder.with_state_metadata(state_metadata); } let unlock_conditions = unlock_conditions .into_iter() - .map(|u| UnlockCondition::try_from_dto_with_params(u, ¶ms)) - .collect::, Error>>()?; + .map(UnlockCondition::from) + .collect::>(); builder = builder.with_unlock_conditions(unlock_conditions); if let Some(features) = features { @@ -839,7 +815,7 @@ pub(crate) mod dto { builder = builder.with_immutable_features(immutable_features); } - builder.finish_with_params(params) + builder.finish() } } } @@ -847,20 +823,17 @@ pub(crate) mod dto { #[cfg(test)] mod tests { use super::*; - use crate::types::{ - block::{ - output::dto::OutputDto, - protocol::protocol_parameters, - rand::output::{ - feature::rand_allowed_features, - rand_anchor_id, rand_anchor_output, - unlock_condition::{ - rand_governor_address_unlock_condition_different_from, - rand_state_controller_address_unlock_condition_different_from, - }, + use crate::types::block::{ + output::dto::OutputDto, + protocol::protocol_parameters, + rand::output::{ + feature::rand_allowed_features, + rand_anchor_id, rand_anchor_output, + unlock_condition::{ + rand_governor_address_unlock_condition_different_from, + rand_state_controller_address_unlock_condition_different_from, }, }, - TryFromDto, }; #[test] @@ -868,9 +841,9 @@ mod tests { let protocol_parameters = protocol_parameters(); let output = rand_anchor_output(protocol_parameters.token_supply()); let dto = OutputDto::Anchor((&output).into()); - let output_unver = Output::try_from_dto(dto.clone()).unwrap(); + let output_unver = Output::try_from(dto.clone()).unwrap(); assert_eq!(&output, output_unver.as_anchor()); - let output_ver = Output::try_from_dto_with_params(dto, &protocol_parameters).unwrap(); + let output_ver = Output::try_from(dto).unwrap(); assert_eq!(&output, output_ver.as_anchor()); let output_split = AnchorOutput::try_from_dtos( @@ -883,7 +856,6 @@ mod tests { output.unlock_conditions().iter().map(Into::into).collect(), Some(output.features().to_vec()), Some(output.immutable_features().to_vec()), - &protocol_parameters, ) .unwrap(); assert_eq!(output, output_split); @@ -903,10 +875,9 @@ mod tests { builder.unlock_conditions.iter().map(Into::into).collect(), Some(builder.features.iter().cloned().collect()), Some(builder.immutable_features.iter().cloned().collect()), - &protocol_parameters, ) .unwrap(); - assert_eq!(builder.finish_with_params(&protocol_parameters).unwrap(), output_split); + assert_eq!(builder.finish().unwrap(), output_split); }; let builder = AnchorOutput::build_with_amount(100, anchor_id) @@ -916,11 +887,12 @@ mod tests { .with_immutable_features(rand_allowed_features(AnchorOutput::ALLOWED_IMMUTABLE_FEATURES)); test_split_dto(builder); - let builder = AnchorOutput::build_with_minimum_storage_deposit(protocol_parameters.rent_structure(), anchor_id) - .add_unlock_condition(gov_address) - .add_unlock_condition(state_address) - .with_features(rand_allowed_features(AnchorOutput::ALLOWED_FEATURES)) - .with_immutable_features(rand_allowed_features(AnchorOutput::ALLOWED_IMMUTABLE_FEATURES)); + let builder = + AnchorOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters(), anchor_id) + .add_unlock_condition(gov_address) + .add_unlock_condition(state_address) + .with_features(rand_allowed_features(AnchorOutput::ALLOWED_FEATURES)) + .with_immutable_features(rand_allowed_features(AnchorOutput::ALLOWED_IMMUTABLE_FEATURES)); test_split_dto(builder); } } diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 2db7134db1..221f68d1fa 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -3,25 +3,22 @@ use alloc::collections::BTreeSet; -use packable::Packable; - -use crate::types::{ - block::{ - address::Address, - output::{ - feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, - unlock_condition::{ - verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions, - }, - verify_output_amount_min, verify_output_amount_packable, verify_output_amount_supply, NativeToken, Output, - OutputBuilderAmount, OutputId, Rent, RentStructure, +use packable::{Packable, PackableExt}; + +use crate::types::block::{ + address::Address, + output::{ + feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, + unlock_condition::{ + verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, + UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - protocol::ProtocolParameters, - semantic::{SemanticValidationContext, TransactionFailureReason}, - unlock::Unlock, - Error, + MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, - ValidationParams, + protocol::ProtocolParameters, + semantic::{SemanticValidationContext, TransactionFailureReason}, + unlock::Unlock, + Error, }; /// Builder for a [`BasicOutput`]. @@ -41,11 +38,11 @@ impl BasicOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount)) } - /// Creates an [`BasicOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. + /// Creates an [`BasicOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. #[inline(always)] - pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure) -> Self { - Self::new(OutputBuilderAmount::MinimumStorageDeposit(rent_structure)) + pub fn new_with_minimum_amount(params: StorageScoreParameters) -> Self { + Self::new(OutputBuilderAmount::MinimumAmount(params)) } fn new(amount: OutputBuilderAmount) -> Self { @@ -64,10 +61,10 @@ impl BasicOutputBuilder { self } - /// Sets the amount to the minimum storage deposit. + /// Sets the amount to the minimum required amount. #[inline(always)] - pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { - self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure); + pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::MinimumAmount(params); self } @@ -141,6 +138,54 @@ impl BasicOutputBuilder { self.add_feature(NativeTokenFeature::from(native_token.into())) } + /// Adds a storage deposit return unlock condition if one is needed to cover the current amount + /// (i.e. `amount < minimum_amount`). This will increase the total amount to satisfy the `minimum_amount` with + /// the additional unlock condition that will return the remainder to the provided `return_address`. + pub fn with_sufficient_storage_deposit( + mut self, + return_address: impl Into
, + params: StorageScoreParameters, + ) -> Result { + Ok(match self.amount { + OutputBuilderAmount::Amount(amount) => { + let return_address = return_address.into(); + // Get the current storage requirement + let minimum_amount = self.clone().finish()?.minimum_amount(params); + // Check whether we already have enough funds to cover it + if amount < minimum_amount { + // Get the projected minimum amount of the return output + let return_min_amount = Self::new_with_minimum_amount(params) + .add_unlock_condition(AddressUnlockCondition::new(return_address.clone())) + .finish()? + .amount(); + // Add a temporary storage deposit unlock condition so the new storage requirement can be calculated + self = + self.add_unlock_condition(StorageDepositReturnUnlockCondition::new(return_address.clone(), 1)); + // Get the min amount of the output with the added storage deposit return unlock condition + let min_amount_with_sdruc = self.clone().finish()?.minimum_amount(params); + // If the return storage cost and amount are less than the required min + let (amount, sdruc_amount) = if min_amount_with_sdruc >= return_min_amount + amount { + // Then sending storage_cost_with_sdruc covers both minimum requirements + (min_amount_with_sdruc, min_amount_with_sdruc - amount) + } else { + // Otherwise we must use the total of the return minimum and the original amount + // which is unfortunately more than the storage_cost_with_sdruc + (return_min_amount + amount, return_min_amount) + }; + // Add the required storage deposit unlock condition and the additional storage amount + self.with_amount(amount) + .replace_unlock_condition(StorageDepositReturnUnlockCondition::new( + return_address, + sdruc_amount, + )) + } else { + self + } + } + OutputBuilderAmount::MinimumAmount(_) => self, + }) + } + /// pub fn finish(self) -> Result { let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; @@ -152,7 +197,7 @@ impl BasicOutputBuilder { verify_features::(&features)?; let mut output = BasicOutput { - amount: 1u64, + amount: 0, mana: self.mana, unlock_conditions, features, @@ -160,30 +205,15 @@ impl BasicOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Basic(output.clone()).rent_cost(rent_structure) - } + OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; - verify_output_amount_min(output.amount)?; - - Ok(output) - } - - /// - pub fn finish_with_params<'a>(self, params: impl Into> + Send) -> Result { - let output = self.finish()?; - - if let Some(token_supply) = params.into().token_supply() { - verify_output_amount_supply(output.amount, token_supply)?; - } - Ok(output) } /// Finishes the [`BasicOutputBuilder`] into an [`Output`]. - pub fn finish_output<'a>(self, params: impl Into> + Send) -> Result { - Ok(Output::Basic(self.finish_with_params(params)?)) + pub fn finish_output(self) -> Result { + Ok(Output::Basic(self.finish()?)) } } @@ -204,15 +234,14 @@ impl From<&BasicOutput> for BasicOutputBuilder { #[packable(unpack_visitor = ProtocolParameters)] pub struct BasicOutput { /// Amount of IOTA coins to deposit with this output. - #[packable(verify_with = verify_output_amount_packable)] amount: u64, /// Amount of stored Mana held by this output. mana: u64, /// Define how the output can be unlocked in a transaction. #[packable(verify_with = verify_unlock_conditions_packable)] unlock_conditions: UnlockConditions, - #[packable(verify_with = verify_features_packable)] /// Features of the output. + #[packable(verify_with = verify_features_packable)] features: Features, } @@ -237,11 +266,11 @@ impl BasicOutput { BasicOutputBuilder::new_with_amount(amount) } - /// Creates a new [`BasicOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. + /// Creates a new [`BasicOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount. #[inline(always)] - pub fn build_with_minimum_storage_deposit(rent_structure: RentStructure) -> BasicOutputBuilder { - BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) + pub fn build_with_minimum_amount(params: StorageScoreParameters) -> BasicOutputBuilder { + BasicOutputBuilder::new_with_minimum_amount(params) } /// @@ -307,8 +336,40 @@ impl BasicOutput { None } + + /// Checks whether the basic output is an implicit account. + pub fn is_implicit_account(&self) -> bool { + if let [UnlockCondition::Address(uc)] = self.unlock_conditions().as_ref() { + uc.address().is_implicit_account_creation() + } else { + false + } + } + + /// Computes the minimum amount of the most Basic Output. + pub fn minimum_amount(address: &Address, params: StorageScoreParameters) -> u64 { + // PANIC: This can never fail because the amount will always be within the valid range. Also, the actual value + // is not important, we are only interested in the storage requirements of the type. + BasicOutputBuilder::new_with_minimum_amount(params) + .add_unlock_condition(AddressUnlockCondition::new(address.clone())) + .finish() + .unwrap() + .amount() + } } +impl StorageScore for BasicOutput { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.output_offset() + // Type byte + + (1 + self.packed_len() as u64) * params.data_factor() as u64 + + self.unlock_conditions.storage_score(params) + + self.features.storage_score(params) + } +} + +impl MinimumOutputAmount for BasicOutput {} + fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> { if VERIFY { if unlock_conditions.address().is_none() { @@ -348,10 +409,7 @@ pub(crate) mod dto { use super::*; use crate::{ - types::{ - block::{output::unlock_condition::dto::UnlockConditionDto, Error}, - TryFromDto, - }, + types::block::{output::unlock_condition::dto::UnlockConditionDto, Error}, utils::serde::string, }; @@ -381,20 +439,19 @@ pub(crate) mod dto { } } - impl TryFromDto for BasicOutput { - type Dto = BasicOutputDto; + impl TryFrom for BasicOutput { type Error = Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { + fn try_from(dto: BasicOutputDto) -> Result { let mut builder = BasicOutputBuilder::new_with_amount(dto.amount) .with_mana(dto.mana) .with_features(dto.features); for u in dto.unlock_conditions { - builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_with_params(u, ¶ms)?); + builder = builder.add_unlock_condition(UnlockCondition::from(u)); } - builder.finish_with_params(params) + builder.finish() } } @@ -404,28 +461,24 @@ pub(crate) mod dto { mana: u64, unlock_conditions: Vec, features: Option>, - params: impl Into> + Send, ) -> Result { - let params = params.into(); let mut builder = match amount { OutputBuilderAmount::Amount(amount) => BasicOutputBuilder::new_with_amount(amount), - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - } + OutputBuilderAmount::MinimumAmount(params) => BasicOutputBuilder::new_with_minimum_amount(params), } .with_mana(mana); let unlock_conditions = unlock_conditions .into_iter() - .map(|u| UnlockCondition::try_from_dto_with_params(u, ¶ms)) - .collect::, Error>>()?; + .map(UnlockCondition::from) + .collect::>(); builder = builder.with_unlock_conditions(unlock_conditions); if let Some(features) = features { builder = builder.with_features(features); } - builder.finish_with_params(params) + builder.finish() } } } @@ -435,18 +488,15 @@ mod tests { use pretty_assertions::assert_eq; use super::*; - use crate::types::{ - block::{ - output::{dto::OutputDto, FoundryId, SimpleTokenScheme, TokenId}, - protocol::protocol_parameters, - rand::{ - address::rand_account_address, - output::{ - feature::rand_allowed_features, rand_basic_output, unlock_condition::rand_address_unlock_condition, - }, + use crate::types::block::{ + output::{dto::OutputDto, FoundryId, SimpleTokenScheme, TokenId}, + protocol::protocol_parameters, + rand::{ + address::rand_account_address, + output::{ + feature::rand_allowed_features, rand_basic_output, unlock_condition::rand_address_unlock_condition, }, }, - TryFromDto, }; #[test] @@ -454,9 +504,9 @@ mod tests { let protocol_parameters = protocol_parameters(); let output = rand_basic_output(protocol_parameters.token_supply()); let dto = OutputDto::Basic((&output).into()); - let output_unver = Output::try_from_dto(dto.clone()).unwrap(); + let output_unver = Output::try_from(dto.clone()).unwrap(); assert_eq!(&output, output_unver.as_basic()); - let output_ver = Output::try_from_dto_with_params(dto, &protocol_parameters).unwrap(); + let output_ver = Output::try_from(dto).unwrap(); assert_eq!(&output, output_ver.as_basic()); let output_split = BasicOutput::try_from_dtos( @@ -464,7 +514,6 @@ mod tests { output.mana(), output.unlock_conditions().iter().map(Into::into).collect(), Some(output.features().to_vec()), - protocol_parameters.token_supply(), ) .unwrap(); assert_eq!(output, output_split); @@ -478,13 +527,9 @@ mod tests { builder.mana, builder.unlock_conditions.iter().map(Into::into).collect(), Some(builder.features.iter().cloned().collect()), - protocol_parameters.token_supply(), ) .unwrap(); - assert_eq!( - builder.finish_with_params(protocol_parameters.token_supply()).unwrap(), - output_split - ); + assert_eq!(builder.finish().unwrap(), output_split); }; let builder = BasicOutput::build_with_amount(100) @@ -493,10 +538,50 @@ mod tests { .with_features(rand_allowed_features(BasicOutput::ALLOWED_FEATURES)); test_split_dto(builder); - let builder = BasicOutput::build_with_minimum_storage_deposit(protocol_parameters.rent_structure()) + let builder = BasicOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters()) .add_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) .add_unlock_condition(address) .with_features(rand_allowed_features(BasicOutput::ALLOWED_FEATURES)); test_split_dto(builder); } + + // TODO: re-enable when rent is figured out + // #[test] + // fn storage_deposit() { + // let protocol_parameters = protocol_parameters(); + // let address_unlock = rand_address_unlock_condition(); + // let return_address = rand_address(); + + // let builder_1 = BasicOutput::build_with_amount(1).add_unlock_condition(address_unlock.clone()); + + // let builder_2 = BasicOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters()) + // .add_unlock_condition(address_unlock); + + // assert_eq!( + // builder_1.storage_cost(protocol_parameters.storage_score_parameters()), + // builder_2.amount() + // ); + // assert_eq!( + // builder_1.clone().finish_output(&protocol_parameters), + // Err(Error::InsufficientStorageDepositAmount { + // amount: 1, + // required: builder_1.storage_cost(protocol_parameters.storage_score_parameters()) + // }) + // ); + + // let builder_1 = builder_1 + // .with_sufficient_storage_deposit( + // return_address.clone(), + // protocol_parameters.storage_score_parameters(), + // protocol_parameters.token_supply(), + // ) + // .unwrap(); + + // let sdruc_cost = + // StorageDepositReturnUnlockCondition::new(return_address, 1, protocol_parameters.token_supply()) + // .unwrap() + // .storage_cost(protocol_parameters.storage_score_parameters()); + + // assert_eq!(builder_1.amount(), builder_2.amount() + sdruc_cost); + // } } diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 0e40245870..b1b3049cb3 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -3,31 +3,21 @@ use alloc::collections::BTreeSet; -use packable::{ - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - unpacker::Unpacker, - Packable, -}; - -use crate::types::{ - block::{ - address::{AccountAddress, Address}, - output::{ - chain_id::ChainId, - unlock_condition::{ - verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions, - }, - verify_output_amount_min, verify_output_amount_packable, verify_output_amount_supply, Output, - OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, - }, - protocol::ProtocolParameters, - semantic::{SemanticValidationContext, TransactionFailureReason}, - slot::EpochIndex, - unlock::Unlock, - Error, +use packable::{Packable, PackableExt}; + +use crate::types::block::{ + address::{AccountAddress, Address}, + output::{ + chain_id::ChainId, + unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StateTransitionError, StateTransitionVerifier, + StorageScore, StorageScoreParameters, }, - ValidationParams, + protocol::ProtocolParameters, + semantic::{SemanticValidationContext, TransactionFailureReason}, + slot::EpochIndex, + unlock::Unlock, + Error, }; crate::impl_id!( @@ -79,16 +69,16 @@ impl DelegationOutputBuilder { ) } - /// Creates a [`DelegationOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. - pub fn new_with_minimum_storage_deposit( - rent_structure: RentStructure, + /// Creates a [`DelegationOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. + pub fn new_with_minimum_amount( + params: StorageScoreParameters, delegated_amount: u64, delegation_id: DelegationId, validator_address: AccountAddress, ) -> Self { Self::new( - OutputBuilderAmount::MinimumStorageDeposit(rent_structure), + OutputBuilderAmount::MinimumAmount(params), delegated_amount, delegation_id, validator_address, @@ -118,9 +108,9 @@ impl DelegationOutputBuilder { self } - /// Sets the amount to the minimum storage deposit. - pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { - self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure); + /// Sets the amount to the minimum required amount. + pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::MinimumAmount(params); self } @@ -177,16 +167,14 @@ impl DelegationOutputBuilder { /// Finishes the builder into a [`DelegationOutput`] without parameters verification. pub fn finish(self) -> Result { - if self.validator_address.is_null() { - return Err(Error::NullDelegationValidatorId); - } + verify_validator_address::(&self.validator_address)?; let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; verify_unlock_conditions::(&unlock_conditions)?; let mut output = DelegationOutput { - amount: 1u64, + amount: 0, delegated_amount: self.delegated_amount, delegation_id: self.delegation_id, validator_address: self.validator_address, @@ -197,33 +185,15 @@ impl DelegationOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Delegation(output.clone()).rent_cost(rent_structure) - } + OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; - verify_output_amount_min(output.amount)?; - - Ok(output) - } - - /// Finishes the builder into a [`DelegationOutput`] with parameters verification. - pub fn finish_with_params<'a>( - self, - params: impl Into> + Send, - ) -> Result { - let output = self.finish()?; - - if let Some(token_supply) = params.into().token_supply() { - verify_output_amount_supply(output.amount, token_supply)?; - } - Ok(output) } /// Finishes the [`DelegationOutputBuilder`] into an [`Output`]. - pub fn finish_output(self, token_supply: u64) -> Result { - Ok(Output::Delegation(self.finish_with_params(token_supply)?)) + pub fn finish_output(self) -> Result { + Ok(Output::Delegation(self.finish()?)) } } @@ -242,7 +212,9 @@ impl From<&DelegationOutput> for DelegationOutputBuilder { } /// An output which delegates its contained IOTA coins as voting power to a validator. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] pub struct DelegationOutput { /// Amount of IOTA coins to deposit with this output. amount: u64, @@ -251,18 +223,20 @@ pub struct DelegationOutput { /// Unique identifier of the delegation output. delegation_id: DelegationId, /// Account address of the validator to which this output is delegating. + #[packable(verify_with = verify_validator_address_packable)] validator_address: AccountAddress, /// Index of the first epoch for which this output delegates. start_epoch: EpochIndex, /// Index of the last epoch for which this output delegates. end_epoch: EpochIndex, /// Define how the output can be unlocked in a transaction. + #[packable(verify_with = verify_unlock_conditions_packable)] unlock_conditions: UnlockConditions, } impl DelegationOutput { /// The [`Output`](crate::types::block::output::Output) kind of a [`DelegationOutput`]. - pub const KIND: u8 = 4; + pub const KIND: u8 = 5; /// The set of allowed [`UnlockCondition`]s for a [`DelegationOutput`]. pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS; @@ -276,20 +250,15 @@ impl DelegationOutput { DelegationOutputBuilder::new_with_amount(amount, delegated_amount, delegation_id, validator_address) } - /// Creates a new [`DelegationOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. - pub fn build_with_minimum_storage_deposit( - rent_structure: RentStructure, + /// Creates a new [`DelegationOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount. + pub fn build_with_minimum_amount( + params: StorageScoreParameters, delegated_amount: u64, delegation_id: DelegationId, validator_address: AccountAddress, ) -> DelegationOutputBuilder { - DelegationOutputBuilder::new_with_minimum_storage_deposit( - rent_structure, - delegated_amount, - delegation_id, - validator_address, - ) + DelegationOutputBuilder::new_with_minimum_amount(params, delegated_amount, delegation_id, validator_address) } /// Returns the amount of the [`DelegationOutput`]. @@ -411,54 +380,31 @@ impl StateTransitionVerifier for DelegationOutput { } } -impl Packable for DelegationOutput { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; +impl StorageScore for DelegationOutput { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.output_offset() + // Type byte + + (1 + self.packed_len() as u64) * params.data_factor() as u64 + + params.delegation_offset() + + self.unlock_conditions.storage_score(params) + } +} - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - self.amount.pack(packer)?; - self.delegated_amount.pack(packer)?; - self.delegation_id.pack(packer)?; - self.validator_address.pack(packer)?; - self.start_epoch.pack(packer)?; - self.end_epoch.pack(packer)?; - self.unlock_conditions.pack(packer)?; +impl MinimumOutputAmount for DelegationOutput {} +fn verify_validator_address(validator_address: &AccountAddress) -> Result<(), Error> { + if VERIFY && validator_address.is_null() { + Err(Error::NullDelegationValidatorId) + } else { Ok(()) } +} - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - verify_output_amount_packable::(&amount, visitor).map_err(UnpackError::Packable)?; - - let delegated_amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let delegation_id = DelegationId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let validator_address = AccountAddress::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - - if validator_address.is_null() { - return Err(UnpackError::Packable(Error::NullDelegationValidatorId)); - } - - let start_epoch = EpochIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let end_epoch = EpochIndex::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; - - verify_unlock_conditions::(&unlock_conditions).map_err(UnpackError::Packable)?; - - Ok(Self { - amount, - delegated_amount, - delegation_id, - validator_address, - start_epoch, - end_epoch, - unlock_conditions, - }) - } +fn verify_validator_address_packable( + validator_address: &AccountAddress, + _: &ProtocolParameters, +) -> Result<(), Error> { + verify_validator_address::(validator_address) } fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> { @@ -473,6 +419,13 @@ fn verify_unlock_conditions(unlock_conditions: &UnlockCondit } } +fn verify_unlock_conditions_packable( + unlock_conditions: &UnlockConditions, + _: &ProtocolParameters, +) -> Result<(), Error> { + verify_unlock_conditions::(unlock_conditions) +} + #[cfg(feature = "serde")] pub(crate) mod dto { use alloc::vec::Vec; @@ -481,12 +434,9 @@ pub(crate) mod dto { use super::*; use crate::{ - types::{ - block::{ - output::{unlock_condition::dto::UnlockConditionDto, OutputBuilderAmount}, - Error, - }, - TryFromDto, + types::block::{ + output::{unlock_condition::dto::UnlockConditionDto, OutputBuilderAmount}, + Error, }, utils::serde::string, }; @@ -522,14 +472,10 @@ pub(crate) mod dto { } } - impl TryFromDto for DelegationOutput { - type Dto = DelegationOutputDto; + impl TryFrom for DelegationOutput { type Error = Error; - fn try_from_dto_with_params_inner( - dto: Self::Dto, - params: crate::types::ValidationParams<'_>, - ) -> Result { + fn try_from(dto: DelegationOutputDto) -> Result { let mut builder = DelegationOutputBuilder::new_with_amount( dto.amount, dto.delegated_amount, @@ -540,10 +486,10 @@ pub(crate) mod dto { .with_end_epoch(dto.end_epoch); for u in dto.unlock_conditions { - builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_with_params(u, ¶ms)?); + builder = builder.add_unlock_condition(UnlockCondition::from(u)); } - builder.finish_with_params(params) + builder.finish() } } @@ -557,9 +503,7 @@ pub(crate) mod dto { start_epoch: impl Into, end_epoch: impl Into, unlock_conditions: Vec, - params: impl Into> + Send, ) -> Result { - let params = params.into(); let mut builder = match amount { OutputBuilderAmount::Amount(amount) => DelegationOutputBuilder::new_with_amount( amount, @@ -567,25 +511,23 @@ pub(crate) mod dto { *delegation_id, *validator_address, ), - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - DelegationOutputBuilder::new_with_minimum_storage_deposit( - rent_structure, - delegated_amount, - *delegation_id, - *validator_address, - ) - } + OutputBuilderAmount::MinimumAmount(params) => DelegationOutputBuilder::new_with_minimum_amount( + params, + delegated_amount, + *delegation_id, + *validator_address, + ), } .with_start_epoch(start_epoch) .with_end_epoch(end_epoch); let unlock_conditions = unlock_conditions .into_iter() - .map(|u| UnlockCondition::try_from_dto_with_params(u, ¶ms)) - .collect::, Error>>()?; + .map(UnlockCondition::from) + .collect::>(); builder = builder.with_unlock_conditions(unlock_conditions); - builder.finish_with_params(params) + builder.finish() } } } diff --git a/sdk/src/types/block/output/feature/block_issuer.rs b/sdk/src/types/block/output/feature/block_issuer.rs index 900085ed5e..bf83bd1796 100644 --- a/sdk/src/types/block/output/feature/block_issuer.rs +++ b/sdk/src/types/block/output/feature/block_issuer.rs @@ -16,7 +16,11 @@ use packable::{ Packable, }; -use crate::types::block::{slot::SlotIndex, Error}; +use crate::types::block::{ + output::{StorageScore, StorageScoreParameters}, + slot::SlotIndex, + Error, +}; #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(untagged))] @@ -47,6 +51,14 @@ impl BlockIssuerKey { crate::def_is_as_opt!(BlockIssuerKey: Ed25519); } +impl StorageScore for BlockIssuerKey { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + match self { + BlockIssuerKey::Ed25519(e) => e.storage_score(params), + } + } +} + /// An Ed25519 block issuer key. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, AsRef, From)] #[as_ref(forward)] @@ -63,6 +75,17 @@ impl Ed25519BlockIssuerKey { pub fn try_from_bytes(bytes: [u8; Self::LENGTH]) -> Result { Ok(Self(ed25519::PublicKey::try_from_bytes(bytes)?)) } + + pub(crate) fn null() -> Self { + // Unwrap: we provide a valid byte array + Self::try_from_bytes([0; Self::LENGTH]).unwrap() + } +} + +impl StorageScore for Ed25519BlockIssuerKey { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.ed25519_block_issuer_key_offset() + } } impl core::fmt::Debug for Ed25519BlockIssuerKey { @@ -171,6 +194,12 @@ impl BlockIssuerKeys { } } +impl StorageScore for BlockIssuerKeys { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.iter().map(|b| b.storage_score(params)).sum::() + } +} + /// This feature defines the block issuer keys with which a signature from the containing /// account's Block Issuance Credit can be verified in order to burn Mana. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] @@ -212,6 +241,12 @@ impl BlockIssuerFeature { } } +impl StorageScore for BlockIssuerFeature { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.block_issuer_keys.storage_score(params) + } +} + #[cfg(feature = "serde")] mod dto { use alloc::{string::String, vec::Vec}; diff --git a/sdk/src/types/block/output/feature/issuer.rs b/sdk/src/types/block/output/feature/issuer.rs index 087470538b..fe96f195f0 100644 --- a/sdk/src/types/block/output/feature/issuer.rs +++ b/sdk/src/types/block/output/feature/issuer.rs @@ -3,7 +3,10 @@ use derive_more::From; -use crate::types::block::address::Address; +use crate::types::block::{ + address::Address, + output::{StorageScore, StorageScoreParameters}, +}; /// Identifies the validated issuer of the UTXO state machine. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] @@ -26,8 +29,14 @@ impl IssuerFeature { } } +impl StorageScore for IssuerFeature { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.address().storage_score(params) + } +} + #[cfg(feature = "serde")] -pub(crate) mod dto { +mod dto { use serde::{Deserialize, Serialize}; use super::*; diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index c9dbfb3ee1..b198e8c9f2 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -6,7 +6,7 @@ use core::{ops::RangeInclusive, str::FromStr}; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix}; -use crate::types::block::Error; +use crate::types::block::{output::StorageScore, Error}; pub(crate) type MetadataFeatureLength = BoundedU16<{ *MetadataFeature::LENGTH_RANGE.start() }, { *MetadataFeature::LENGTH_RANGE.end() }>; @@ -19,6 +19,8 @@ pub struct MetadataFeature( pub(crate) BoxedSlicePrefix, ); +impl StorageScore for MetadataFeature {} + macro_rules! impl_from_vec { ($type:ty) => { impl TryFrom<$type> for MetadataFeature { diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index 77de19e8d6..654f2f4815 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -30,7 +30,10 @@ pub use self::{ staking::StakingFeature, tag::TagFeature, }; -use crate::types::block::{create_bitflags, Error}; +use crate::types::block::{ + output::{StorageScore, StorageScoreParameters}, + Error, +}; /// #[derive(Clone, Eq, PartialEq, Hash, From, Packable)] @@ -73,6 +76,19 @@ impl Ord for Feature { } } +impl StorageScore for Feature { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + match self { + Self::Sender(feature) => feature.storage_score(params), + Self::Issuer(feature) => feature.storage_score(params), + Self::Metadata(feature) => feature.storage_score(params), + Self::Tag(feature) => feature.storage_score(params), + Self::BlockIssuer(feature) => feature.storage_score(params), + Self::Staking(feature) => feature.storage_score(params), + } + } +} + impl core::fmt::Debug for Feature { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { @@ -117,7 +133,7 @@ impl Feature { crate::def_is_as_opt!(Feature: Sender, Issuer, Metadata, Tag, NativeToken, BlockIssuer, Staking); } -create_bitflags!( +crate::create_bitflags!( /// A bitflags-based representation of the set of active [`Feature`]s. pub FeatureFlags, u16, @@ -239,6 +255,12 @@ impl Features { } } +impl StorageScore for Features { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.iter().map(|f| f.storage_score(params)).sum::() + } +} + #[inline] fn verify_unique_sorted(features: &[Feature], _: &()) -> Result<(), Error> { if VERIFY && !is_unique_sorted(features.iter().map(Feature::kind)) { diff --git a/sdk/src/types/block/output/feature/sender.rs b/sdk/src/types/block/output/feature/sender.rs index a481fa4a03..f25ab1d97c 100644 --- a/sdk/src/types/block/output/feature/sender.rs +++ b/sdk/src/types/block/output/feature/sender.rs @@ -3,7 +3,10 @@ use derive_more::From; -use crate::types::block::address::Address; +use crate::types::block::{ + address::Address, + output::{storage_score::StorageScoreParameters, StorageScore}, +}; /// Identifies the validated sender of an output. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] @@ -26,8 +29,14 @@ impl SenderFeature { } } +impl StorageScore for SenderFeature { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.address().storage_score(params) + } +} + #[cfg(feature = "serde")] -pub(crate) mod dto { +mod dto { use serde::{Deserialize, Serialize}; use super::*; diff --git a/sdk/src/types/block/output/feature/staking.rs b/sdk/src/types/block/output/feature/staking.rs index a19aed69b6..9ab86dfd3d 100644 --- a/sdk/src/types/block/output/feature/staking.rs +++ b/sdk/src/types/block/output/feature/staking.rs @@ -1,7 +1,10 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::slot::EpochIndex; +use crate::types::block::{ + output::{StorageScore, StorageScoreParameters}, + slot::EpochIndex, +}; /// Stakes coins to become eligible for committee selection, validate the network and receive Mana rewards. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] @@ -56,8 +59,14 @@ impl StakingFeature { } } +impl StorageScore for StakingFeature { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.staking_feature_offset() + } +} + #[cfg(feature = "serde")] -pub(crate) mod dto { +mod dto { use serde::{Deserialize, Serialize}; use super::*; diff --git a/sdk/src/types/block/output/feature/tag.rs b/sdk/src/types/block/output/feature/tag.rs index 1a53dcb375..d062da26bf 100644 --- a/sdk/src/types/block/output/feature/tag.rs +++ b/sdk/src/types/block/output/feature/tag.rs @@ -6,7 +6,7 @@ use core::ops::RangeInclusive; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix}; -use crate::types::block::Error; +use crate::types::block::{output::StorageScore, Error}; pub(crate) type TagFeatureLength = BoundedU8<{ *TagFeature::LENGTH_RANGE.start() }, { *TagFeature::LENGTH_RANGE.end() }>; @@ -19,6 +19,8 @@ pub struct TagFeature( pub(crate) BoxedSlicePrefix, ); +impl StorageScore for TagFeature {} + impl TryFrom> for TagFeature { type Error = Error; diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index bdef6c5375..4e0197f968 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -8,30 +8,24 @@ use packable::{ error::{UnpackError, UnpackErrorExt}, packer::{Packer, SlicePacker}, unpacker::Unpacker, - Packable, + Packable, PackableExt, }; use primitive_types::U256; -use crate::types::{ - block::{ - address::{AccountAddress, Address}, - output::{ - account::AccountId, - feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, - unlock_condition::{ - verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions, - }, - verify_output_amount_min, verify_output_amount_packable, verify_output_amount_supply, ChainId, NativeToken, - Output, OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, - TokenId, TokenScheme, - }, - payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, - protocol::ProtocolParameters, - semantic::{SemanticValidationContext, TransactionFailureReason}, - unlock::Unlock, - Error, +use crate::types::block::{ + address::{AccountAddress, Address}, + output::{ + account::AccountId, + feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, + unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + ChainId, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputId, StateTransitionError, + StateTransitionVerifier, StorageScore, StorageScoreParameters, TokenId, TokenScheme, }, - ValidationParams, + payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + protocol::ProtocolParameters, + semantic::{SemanticValidationContext, TransactionFailureReason}, + unlock::Unlock, + Error, }; crate::impl_id!( @@ -103,18 +97,14 @@ impl FoundryOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), serial_number, token_scheme) } - /// Creates a [`FoundryOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. - pub fn new_with_minimum_storage_deposit( - rent_structure: RentStructure, + /// Creates a [`FoundryOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. + pub fn new_with_minimum_amount( + params: StorageScoreParameters, serial_number: u32, token_scheme: TokenScheme, ) -> Self { - Self::new( - OutputBuilderAmount::MinimumStorageDeposit(rent_structure), - serial_number, - token_scheme, - ) + Self::new(OutputBuilderAmount::MinimumAmount(params), serial_number, token_scheme) } fn new(amount: OutputBuilderAmount, serial_number: u32, token_scheme: TokenScheme) -> Self { @@ -135,10 +125,10 @@ impl FoundryOutputBuilder { self } - /// Sets the amount to the minimum storage deposit. + /// Sets the amount to the minimum required amount. #[inline(always)] - pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { - self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure); + pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::MinimumAmount(params); self } @@ -275,33 +265,15 @@ impl FoundryOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Foundry(output.clone()).rent_cost(rent_structure) - } + OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; - verify_output_amount_min(output.amount)?; - - Ok(output) - } - - /// - pub fn finish_with_params<'a>( - self, - params: impl Into> + Send, - ) -> Result { - let output = self.finish()?; - - if let Some(token_supply) = params.into().token_supply() { - verify_output_amount_supply(output.amount, token_supply)?; - } - Ok(output) } /// Finishes the [`FoundryOutputBuilder`] into an [`Output`]. - pub fn finish_output<'a>(self, params: impl Into> + Send) -> Result { - Ok(Output::Foundry(self.finish_with_params(params)?)) + pub fn finish_output(self) -> Result { + Ok(Output::Foundry(self.finish()?)) } } @@ -337,7 +309,7 @@ pub struct FoundryOutput { impl FoundryOutput { /// The [`Output`](crate::types::block::output::Output) kind of a [`FoundryOutput`]. - pub const KIND: u8 = 2; + pub const KIND: u8 = 3; /// The set of allowed [`UnlockCondition`]s for a [`FoundryOutput`]. pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::IMMUTABLE_ACCOUNT_ADDRESS; /// The set of allowed [`Feature`]s for a [`FoundryOutput`]. @@ -351,15 +323,15 @@ impl FoundryOutput { FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme) } - /// Creates a new [`FoundryOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. + /// Creates a new [`FoundryOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. #[inline(always)] - pub fn build_with_minimum_storage_deposit( - rent_structure: RentStructure, + pub fn build_with_minimum_amount( + params: StorageScoreParameters, serial_number: u32, token_scheme: TokenScheme, ) -> FoundryOutputBuilder { - FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme) + FoundryOutputBuilder::new_with_minimum_amount(params, serial_number, token_scheme) } /// @@ -527,6 +499,19 @@ impl FoundryOutput { } } +impl StorageScore for FoundryOutput { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.output_offset() + // Type byte + + (1 + self.packed_len() as u64) * params.data_factor() as u64 + + self.unlock_conditions.storage_score(params) + + self.features.storage_score(params) + + self.immutable_features.storage_score(params) + } +} + +impl MinimumOutputAmount for FoundryOutput {} + impl StateTransitionVerifier for FoundryOutput { fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { let account_chain_id = ChainId::from(*next_state.account_address().account_id()); @@ -623,8 +608,6 @@ impl Packable for FoundryOutput { ) -> Result> { let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - verify_output_amount_packable::(&amount, visitor).map_err(UnpackError::Packable)?; - let serial_number = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let token_scheme = TokenScheme::unpack::<_, VERIFY>(unpacker, &())?; @@ -674,10 +657,7 @@ pub(crate) mod dto { use super::*; use crate::{ - types::{ - block::{output::unlock_condition::dto::UnlockConditionDto, Error}, - TryFromDto, - }, + types::block::{output::unlock_condition::dto::UnlockConditionDto, Error}, utils::serde::string, }; @@ -711,12 +691,12 @@ pub(crate) mod dto { } } - impl TryFromDto for FoundryOutput { - type Dto = FoundryOutputDto; + impl TryFrom for FoundryOutput { type Error = Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { - let mut builder = FoundryOutputBuilder::new_with_amount(dto.amount, dto.serial_number, dto.token_scheme); + fn try_from(dto: FoundryOutputDto) -> Result { + let mut builder: FoundryOutputBuilder = + FoundryOutputBuilder::new_with_amount(dto.amount, dto.serial_number, dto.token_scheme); for b in dto.features { builder = builder.add_feature(b); @@ -727,10 +707,10 @@ pub(crate) mod dto { } for u in dto.unlock_conditions { - builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_with_params(u, ¶ms)?); + builder = builder.add_unlock_condition(UnlockCondition::from(u)); } - builder.finish_with_params(params) + builder.finish() } } @@ -743,23 +723,20 @@ pub(crate) mod dto { unlock_conditions: Vec, features: Option>, immutable_features: Option>, - params: impl Into> + Send, ) -> Result { - let params = params.into(); - let mut builder = match amount { OutputBuilderAmount::Amount(amount) => { FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme) } - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme) + OutputBuilderAmount::MinimumAmount(params) => { + FoundryOutputBuilder::new_with_minimum_amount(params, serial_number, token_scheme) } }; let unlock_conditions = unlock_conditions .into_iter() - .map(|u| UnlockCondition::try_from_dto_with_params(u, ¶ms)) - .collect::, Error>>()?; + .map(UnlockCondition::from) + .collect::>(); builder = builder.with_unlock_conditions(unlock_conditions); if let Some(features) = features { @@ -770,7 +747,7 @@ pub(crate) mod dto { builder = builder.with_immutable_features(immutable_features); } - builder.finish_with_params(params) + builder.finish() } } } @@ -780,22 +757,19 @@ mod tests { use pretty_assertions::assert_eq; use super::*; - use crate::types::{ - block::{ + use crate::types::block::{ + output::{ + dto::OutputDto, unlock_condition::ImmutableAccountAddressUnlockCondition, FoundryId, SimpleTokenScheme, + TokenId, + }, + protocol::protocol_parameters, + rand::{ + address::rand_account_address, output::{ - dto::OutputDto, unlock_condition::ImmutableAccountAddressUnlockCondition, FoundryId, SimpleTokenScheme, - TokenId, - }, - protocol::protocol_parameters, - rand::{ - address::rand_account_address, - output::{ - feature::{rand_allowed_features, rand_metadata_feature}, - rand_foundry_output, rand_token_scheme, - }, + feature::{rand_allowed_features, rand_metadata_feature}, + rand_foundry_output, rand_token_scheme, }, }, - TryFromDto, }; #[test] @@ -803,9 +777,9 @@ mod tests { let protocol_parameters = protocol_parameters(); let output = rand_foundry_output(protocol_parameters.token_supply()); let dto = OutputDto::Foundry((&output).into()); - let output_unver = Output::try_from_dto(dto.clone()).unwrap(); + let output_unver = Output::try_from(dto.clone()).unwrap(); assert_eq!(&output, output_unver.as_foundry()); - let output_ver = Output::try_from_dto_with_params(dto, &protocol_parameters).unwrap(); + let output_ver = Output::try_from(dto).unwrap(); assert_eq!(&output, output_ver.as_foundry()); let foundry_id = FoundryId::build(&rand_account_address(), 0, SimpleTokenScheme::KIND); @@ -818,13 +792,9 @@ mod tests { builder.unlock_conditions.iter().map(Into::into).collect(), Some(builder.features.iter().cloned().collect()), Some(builder.immutable_features.iter().cloned().collect()), - protocol_parameters.clone(), ) .unwrap(); - assert_eq!( - builder.finish_with_params(protocol_parameters.clone()).unwrap(), - output_split - ); + assert_eq!(builder.finish().unwrap(), output_split); }; let builder = FoundryOutput::build_with_amount(100, 123, rand_token_scheme()) @@ -834,8 +804,8 @@ mod tests { .with_features(rand_allowed_features(FoundryOutput::ALLOWED_FEATURES)); test_split_dto(builder); - let builder = FoundryOutput::build_with_minimum_storage_deposit( - protocol_parameters.rent_structure(), + let builder = FoundryOutput::build_with_minimum_amount( + protocol_parameters.storage_score_parameters(), 123, rand_token_scheme(), ) diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index c0e3beff63..2da8e9ad19 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -7,8 +7,8 @@ mod delegation; mod metadata; mod native_token; mod output_id; -mod rent; mod state_transition; +mod storage_score; mod token_scheme; /// @@ -27,20 +27,8 @@ pub mod unlock_condition; use core::ops::RangeInclusive; use derive_more::From; -use packable::{ - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - unpacker::Unpacker, - Packable, PackableExt, -}; +use packable::Packable; -pub(crate) use self::{ - account::StateMetadataLength, - feature::{MetadataFeatureLength, TagFeatureLength}, - native_token::NativeTokenCount, - output_id::OutputIndex, - unlock_condition::AddressUnlockCondition, -}; pub use self::{ account::{AccountId, AccountOutput, AccountOutputBuilder}, anchor::{AnchorId, AnchorOutput, AnchorTransition}, @@ -53,11 +41,18 @@ pub use self::{ native_token::{NativeToken, NativeTokens, NativeTokensBuilder, TokenId}, nft::{NftId, NftOutput, NftOutputBuilder}, output_id::OutputId, - rent::{MinimumStorageDepositBasicOutput, Rent, RentStructure}, state_transition::{StateTransitionError, StateTransitionVerifier}, + storage_score::{StorageScore, StorageScoreParameters}, token_scheme::{SimpleTokenScheme, TokenScheme}, unlock_condition::{UnlockCondition, UnlockConditions}, }; +pub(crate) use self::{ + anchor::StateMetadataLength, + feature::{MetadataFeatureLength, TagFeatureLength}, + native_token::NativeTokenCount, + output_id::OutputIndex, + unlock_condition::AddressUnlockCondition, +}; use super::protocol::ProtocolParameters; use crate::types::block::{address::Address, semantic::SemanticValidationContext, slot::SlotIndex, Error}; @@ -73,7 +68,7 @@ pub const OUTPUT_INDEX_RANGE: RangeInclusive = 0..=OUTPUT_INDEX_MAX; // [0. #[derive(Copy, Clone)] pub enum OutputBuilderAmount { Amount(u64), - MinimumStorageDeposit(RentStructure), + MinimumAmount(StorageScoreParameters), } /// Contains the generic [`Output`] with associated [`OutputMetadata`]. @@ -111,20 +106,29 @@ impl OutputWithMetadata { } /// A generic output that can represent different types defining the deposit of funds. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] +#[packable(tag_type = u8, with_error = Error::InvalidOutputKind)] pub enum Output { /// A basic output. + #[packable(tag = BasicOutput::KIND)] Basic(BasicOutput), /// An account output. + #[packable(tag = AccountOutput::KIND)] Account(AccountOutput), + /// An anchor output. + #[packable(tag = AnchorOutput::KIND)] + Anchor(AnchorOutput), /// A foundry output. + #[packable(tag = FoundryOutput::KIND)] Foundry(FoundryOutput), /// An NFT output. + #[packable(tag = NftOutput::KIND)] Nft(NftOutput), /// A delegation output. + #[packable(tag = DelegationOutput::KIND)] Delegation(DelegationOutput), - /// An anchor output. - Anchor(AnchorOutput), } impl core::fmt::Debug for Output { @@ -132,27 +136,24 @@ impl core::fmt::Debug for Output { match self { Self::Basic(output) => output.fmt(f), Self::Account(output) => output.fmt(f), + Self::Anchor(output) => output.fmt(f), Self::Foundry(output) => output.fmt(f), Self::Nft(output) => output.fmt(f), Self::Delegation(output) => output.fmt(f), - Self::Anchor(output) => output.fmt(f), } } } impl Output { - /// Minimum amount for an output. - pub const AMOUNT_MIN: u64 = 1; - /// Return the output kind of an [`Output`]. pub fn kind(&self) -> u8 { match self { Self::Basic(_) => BasicOutput::KIND, Self::Account(_) => AccountOutput::KIND, + Self::Anchor(_) => AnchorOutput::KIND, Self::Foundry(_) => FoundryOutput::KIND, Self::Nft(_) => NftOutput::KIND, Self::Delegation(_) => DelegationOutput::KIND, - Self::Anchor(_) => AnchorOutput::KIND, } } @@ -161,10 +162,10 @@ impl Output { match self { Self::Basic(_) => "Basic", Self::Account(_) => "Account", + Self::Anchor(_) => "Anchor", Self::Foundry(_) => "Foundry", Self::Nft(_) => "Nft", Self::Delegation(_) => "Delegation", - Self::Anchor(_) => "Anchor", } } @@ -173,10 +174,10 @@ impl Output { match self { Self::Basic(output) => output.amount(), Self::Account(output) => output.amount(), + Self::Anchor(output) => output.amount(), Self::Foundry(output) => output.amount(), Self::Nft(output) => output.amount(), Self::Delegation(output) => output.amount(), - Self::Anchor(output) => output.amount(), } } @@ -185,10 +186,10 @@ impl Output { match self { Self::Basic(output) => output.mana(), Self::Account(output) => output.mana(), + Self::Anchor(output) => output.mana(), Self::Foundry(_) => 0, Self::Nft(output) => output.mana(), Self::Delegation(_) => 0, - Self::Anchor(output) => output.mana(), } } @@ -197,10 +198,10 @@ impl Output { match self { Self::Basic(output) => Some(output.unlock_conditions()), Self::Account(output) => Some(output.unlock_conditions()), + Self::Anchor(output) => Some(output.unlock_conditions()), Self::Foundry(output) => Some(output.unlock_conditions()), Self::Nft(output) => Some(output.unlock_conditions()), Self::Delegation(output) => Some(output.unlock_conditions()), - Self::Anchor(output) => Some(output.unlock_conditions()), } } @@ -209,10 +210,10 @@ impl Output { match self { Self::Basic(output) => Some(output.features()), Self::Account(output) => Some(output.features()), + Self::Anchor(output) => Some(output.features()), Self::Foundry(output) => Some(output.features()), Self::Nft(output) => Some(output.features()), Self::Delegation(_) => None, - Self::Anchor(output) => Some(output.features()), } } @@ -233,10 +234,10 @@ impl Output { match self { Self::Basic(_) => None, Self::Account(output) => Some(output.immutable_features()), + Self::Anchor(output) => Some(output.immutable_features()), Self::Foundry(output) => Some(output.immutable_features()), Self::Nft(output) => Some(output.immutable_features()), Self::Delegation(_) => None, - Self::Anchor(output) => Some(output.immutable_features()), } } @@ -245,10 +246,19 @@ impl Output { match self { Self::Basic(_) => None, Self::Account(output) => Some(output.chain_id()), + Self::Anchor(output) => Some(output.chain_id()), Self::Foundry(output) => Some(output.chain_id()), Self::Nft(output) => Some(output.chain_id()), Self::Delegation(_) => None, - Self::Anchor(output) => Some(output.chain_id()), + } + } + + /// Checks whether the output is an implicit account. + pub fn is_implicit_account(&self) -> bool { + if let Output::Basic(output) = self { + output.is_implicit_account() + } else { + false } } @@ -277,6 +287,7 @@ impl Output { .clone(), Some(Address::Account(output.account_address(output_id))), )), + Self::Anchor(_) => Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), Self::Foundry(output) => Ok((Address::Account(*output.account_address()), None)), Self::Nft(output) => Ok(( output @@ -292,7 +303,6 @@ impl Output { .clone(), None, )), - Self::Anchor(_) => Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), } } @@ -335,11 +345,11 @@ impl Output { } /// Verifies if a valid storage deposit was made. Each [`Output`] has to have an amount that covers its associated - /// byte cost, given by [`RentStructure`]. + /// byte cost, given by [`StorageScoreParameters`]. /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition), /// its amount is also checked. - pub fn verify_storage_deposit(&self, rent_structure: RentStructure, token_supply: u64) -> Result<(), Error> { - let required_output_amount = self.rent_cost(rent_structure); + pub fn verify_storage_deposit(&self, params: StorageScoreParameters) -> Result<(), Error> { + let required_output_amount = self.minimum_amount(params); if self.amount() < required_output_amount { return Err(Error::InsufficientStorageDepositAmount { @@ -361,8 +371,7 @@ impl Output { }); } - let minimum_deposit = - minimum_storage_deposit(return_condition.return_address(), rent_structure, token_supply); + let minimum_deposit = BasicOutput::minimum_amount(return_condition.return_address(), params); // `Minimum Storage Deposit` ≤ `Return Amount` if return_condition.amount() < minimum_deposit { @@ -377,104 +386,28 @@ impl Output { } } -impl Packable for Output { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { +impl StorageScore for Output { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { match self { - Self::Basic(output) => { - BasicOutput::KIND.pack(packer)?; - output.pack(packer) - } - Self::Account(output) => { - AccountOutput::KIND.pack(packer)?; - output.pack(packer) - } - Self::Foundry(output) => { - FoundryOutput::KIND.pack(packer)?; - output.pack(packer) - } - Self::Nft(output) => { - NftOutput::KIND.pack(packer)?; - output.pack(packer) - } - Self::Delegation(output) => { - DelegationOutput::KIND.pack(packer)?; - output.pack(packer) - } - Self::Anchor(output) => { - AnchorOutput::KIND.pack(packer)?; - output.pack(packer) - } - }?; - - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? { - BasicOutput::KIND => Self::from(BasicOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - AccountOutput::KIND => Self::from(AccountOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - FoundryOutput::KIND => Self::from(FoundryOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - NftOutput::KIND => Self::from(NftOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - DelegationOutput::KIND => Self::from(DelegationOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - AnchorOutput::KIND => Self::from(AnchorOutput::unpack::<_, VERIFY>(unpacker, visitor).coerce()?), - k => return Err(UnpackError::Packable(Error::InvalidOutputKind(k))), - }) - } -} - -impl Rent for Output { - fn weighted_bytes(&self, rent_structure: RentStructure) -> u64 { - self.packed_len() as u64 * rent_structure.byte_factor_data() as u64 - } -} - -pub(crate) fn verify_output_amount_min(amount: u64) -> Result<(), Error> { - if amount < Output::AMOUNT_MIN { - Err(Error::InvalidOutputAmount(amount)) - } else { - Ok(()) - } -} - -pub(crate) fn verify_output_amount_supply(amount: u64, token_supply: u64) -> Result<(), Error> { - if amount > token_supply { - Err(Error::InvalidOutputAmount(amount)) - } else { - Ok(()) + Self::Basic(o) => o.storage_score(params), + Self::Account(o) => o.storage_score(params), + Self::Anchor(o) => o.storage_score(params), + Self::Foundry(o) => o.storage_score(params), + Self::Nft(o) => o.storage_score(params), + Self::Delegation(o) => o.storage_score(params), + } } } -pub(crate) fn verify_output_amount(amount: u64, token_supply: u64) -> Result<(), Error> { - verify_output_amount_min(amount)?; - verify_output_amount_supply(amount, token_supply) -} +impl MinimumOutputAmount for Output {} -pub(crate) fn verify_output_amount_packable( - amount: &u64, - protocol_parameters: &ProtocolParameters, -) -> Result<(), Error> { - if VERIFY { - verify_output_amount(*amount, protocol_parameters.token_supply())?; +/// A trait that is shared by all output types, which is used to calculate the minimum amount the output +/// must contain to satisfy its storage cost. +pub trait MinimumOutputAmount: StorageScore { + /// Computes the minimum amount of this output given [`StorageScoreParameters`]. + fn minimum_amount(&self, params: StorageScoreParameters) -> u64 { + params.storage_cost() * self.storage_score(params) } - Ok(()) -} - -/// Computes the minimum amount that a storage deposit has to match to allow creating a return [`Output`] back to the -/// sender [`Address`]. -fn minimum_storage_deposit(address: &Address, rent_structure: RentStructure, token_supply: u64) -> u64 { - // PANIC: This can never fail because the amount will always be within the valid range. Also, the actual value is - // not important, we are only interested in the storage requirements of the type. - BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - .add_unlock_condition(AddressUnlockCondition::new(address.clone())) - .finish_with_params(token_supply) - .unwrap() - .amount() } #[cfg(feature = "serde")] @@ -489,17 +422,16 @@ pub mod dto { account::dto::AccountOutputDto, anchor::dto::AnchorOutputDto, basic::dto::BasicOutputDto, delegation::dto::DelegationOutputDto, foundry::dto::FoundryOutputDto, nft::dto::NftOutputDto, }; - use crate::types::{block::Error, TryFromDto, ValidationParams}; /// Describes all the different output types. #[derive(Clone, Debug, Eq, PartialEq, From)] pub enum OutputDto { Basic(BasicOutputDto), Account(AccountOutputDto), + Anchor(AnchorOutputDto), Foundry(FoundryOutputDto), Nft(NftOutputDto), Delegation(DelegationOutputDto), - Anchor(AnchorOutputDto), } impl From<&Output> for OutputDto { @@ -507,28 +439,25 @@ pub mod dto { match value { Output::Basic(o) => Self::Basic(o.into()), Output::Account(o) => Self::Account(o.into()), + Output::Anchor(o) => Self::Anchor(o.into()), Output::Foundry(o) => Self::Foundry(o.into()), Output::Nft(o) => Self::Nft(o.into()), Output::Delegation(o) => Self::Delegation(o.into()), - Output::Anchor(o) => Self::Anchor(o.into()), } } } - impl TryFromDto for Output { - type Dto = OutputDto; + impl TryFrom for Output { type Error = Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { + fn try_from(dto: OutputDto) -> Result { Ok(match dto { - OutputDto::Basic(o) => Self::Basic(BasicOutput::try_from_dto_with_params_inner(o, params)?), - OutputDto::Account(o) => Self::Account(AccountOutput::try_from_dto_with_params_inner(o, params)?), - OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from_dto_with_params_inner(o, params)?), - OutputDto::Nft(o) => Self::Nft(NftOutput::try_from_dto_with_params_inner(o, params)?), - OutputDto::Delegation(o) => { - Self::Delegation(DelegationOutput::try_from_dto_with_params_inner(o, params)?) - } - OutputDto::Anchor(o) => Self::Anchor(AnchorOutput::try_from_dto_with_params_inner(o, params)?), + OutputDto::Basic(o) => Self::Basic(BasicOutput::try_from(o)?), + OutputDto::Account(o) => Self::Account(AccountOutput::try_from(o)?), + OutputDto::Anchor(o) => Self::Anchor(AnchorOutput::try_from(o)?), + OutputDto::Foundry(o) => Self::Foundry(FoundryOutput::try_from(o)?), + OutputDto::Nft(o) => Self::Nft(NftOutput::try_from(o)?), + OutputDto::Delegation(o) => Self::Delegation(DelegationOutput::try_from(o)?), }) } } @@ -550,6 +479,10 @@ pub mod dto { AccountOutputDto::deserialize(value) .map_err(|e| serde::de::Error::custom(format!("cannot deserialize account output: {e}")))?, ), + AnchorOutput::KIND => Self::Anchor( + AnchorOutputDto::deserialize(value) + .map_err(|e| serde::de::Error::custom(format!("cannot deserialize anchor output: {e}")))?, + ), FoundryOutput::KIND => Self::Foundry( FoundryOutputDto::deserialize(value) .map_err(|e| serde::de::Error::custom(format!("cannot deserialize foundry output: {e}")))?, @@ -563,10 +496,6 @@ pub mod dto { serde::de::Error::custom(format!("cannot deserialize delegation output: {e}")) })?) } - AnchorOutput::KIND => Self::Anchor( - AnchorOutputDto::deserialize(value) - .map_err(|e| serde::de::Error::custom(format!("cannot deserialize anchor output: {e}")))?, - ), _ => return Err(serde::de::Error::custom("invalid output type")), }, ) @@ -583,10 +512,10 @@ pub mod dto { enum OutputDto_<'a> { T0(&'a BasicOutputDto), T1(&'a AccountOutputDto), - T2(&'a FoundryOutputDto), - T3(&'a NftOutputDto), - T4(&'a DelegationOutputDto), - T5(&'a AnchorOutputDto), + T2(&'a AnchorOutputDto), + T3(&'a FoundryOutputDto), + T4(&'a NftOutputDto), + T5(&'a DelegationOutputDto), } #[derive(Serialize)] struct TypedOutput<'a> { @@ -600,16 +529,16 @@ pub mod dto { Self::Account(o) => TypedOutput { output: OutputDto_::T1(o), }, - Self::Foundry(o) => TypedOutput { + Self::Anchor(o) => TypedOutput { output: OutputDto_::T2(o), }, - Self::Nft(o) => TypedOutput { + Self::Foundry(o) => TypedOutput { output: OutputDto_::T3(o), }, - Self::Delegation(o) => TypedOutput { + Self::Nft(o) => TypedOutput { output: OutputDto_::T4(o), }, - Self::Anchor(o) => TypedOutput { + Self::Delegation(o) => TypedOutput { output: OutputDto_::T5(o), }, }; diff --git a/sdk/src/types/block/output/native_token.rs b/sdk/src/types/block/output/native_token.rs index c0af04976d..767f4dce39 100644 --- a/sdk/src/types/block/output/native_token.rs +++ b/sdk/src/types/block/output/native_token.rs @@ -12,7 +12,10 @@ use iterator_sorted::is_unique_sorted; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; use primitive_types::U256; -use crate::types::block::{output::foundry::FoundryId, Error}; +use crate::types::block::{ + output::{FoundryId, StorageScore}, + Error, +}; crate::impl_id!( /// Unique identifier of a [`NativeToken`](crate::types::block::output::NativeToken). @@ -77,6 +80,9 @@ impl Ord for NativeToken { } } +// TODO remove when NT are a feature +impl StorageScore for NativeToken {} + #[inline] fn verify_amount(amount: &U256, _: &()) -> Result<(), Error> { if VERIFY && amount.is_zero() { diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index fbafd1fdc5..7da6b1abcf 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -7,27 +7,25 @@ use packable::{ error::{UnpackError, UnpackErrorExt}, packer::Packer, unpacker::Unpacker, - Packable, + Packable, PackableExt, }; -use crate::types::{ - block::{ - address::{Address, NftAddress}, - output::{ - feature::{verify_allowed_features, Feature, FeatureFlags, Features}, - unlock_condition::{ - verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions, - }, - verify_output_amount_min, verify_output_amount_packable, verify_output_amount_supply, ChainId, Output, - OutputBuilderAmount, OutputId, Rent, RentStructure, StateTransitionError, StateTransitionVerifier, +use crate::types::block::{ + address::{Address, NftAddress}, + output::{ + feature::{verify_allowed_features, Feature, FeatureFlags, Features}, + unlock_condition::{ + verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, + UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - payload::signed_transaction::TransactionCapabilityFlag, - protocol::ProtocolParameters, - semantic::{SemanticValidationContext, TransactionFailureReason}, - unlock::Unlock, - Error, + BasicOutputBuilder, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StateTransitionError, + StateTransitionVerifier, StorageScore, StorageScoreParameters, }, - ValidationParams, + payload::signed_transaction::TransactionCapabilityFlag, + protocol::ProtocolParameters, + semantic::{SemanticValidationContext, TransactionFailureReason}, + unlock::Unlock, + Error, }; crate::impl_id!( @@ -47,7 +45,11 @@ impl From<&OutputId> for NftId { impl NftId { /// pub fn or_from_output_id(self, output_id: &OutputId) -> Self { - if self.is_null() { Self::from(output_id) } else { self } + if self.is_null() { + Self::from(output_id) + } else { + self + } } } @@ -75,10 +77,10 @@ impl NftOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), nft_id) } - /// Creates an [`NftOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. - pub fn new_with_minimum_storage_deposit(rent_structure: RentStructure, nft_id: NftId) -> Self { - Self::new(OutputBuilderAmount::MinimumStorageDeposit(rent_structure), nft_id) + /// Creates an [`NftOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. + pub fn new_with_minimum_amount(params: StorageScoreParameters, nft_id: NftId) -> Self { + Self::new(OutputBuilderAmount::MinimumAmount(params), nft_id) } fn new(amount: OutputBuilderAmount, nft_id: NftId) -> Self { @@ -99,10 +101,10 @@ impl NftOutputBuilder { self } - /// Sets the amount to the minimum storage deposit. + /// Sets the amount to the minimum required amount. #[inline(always)] - pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { - self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure); + pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::MinimumAmount(params); self } @@ -204,6 +206,54 @@ impl NftOutputBuilder { self } + /// Adds a storage deposit return unlock condition if one is needed to cover the current amount + /// (i.e. `amount < minimum_amount`). This will increase the total amount to satisfy the `minimum_amount` with + /// the additional unlock condition that will return the remainder to the provided `return_address`. + pub fn with_sufficient_storage_deposit( + mut self, + return_address: impl Into
, + params: StorageScoreParameters, + ) -> Result { + Ok(match self.amount { + OutputBuilderAmount::Amount(amount) => { + let return_address = return_address.into(); + // Get the current storage requirement + let minimum_amount = self.clone().finish()?.minimum_amount(params); + // Check whether we already have enough funds to cover it + if amount < minimum_amount { + // Get the projected minimum amount of the return output + let return_min_amount = BasicOutputBuilder::new_with_minimum_amount(params) + .add_unlock_condition(AddressUnlockCondition::new(return_address.clone())) + .finish()? + .amount(); + // Add a temporary storage deposit unlock condition so the new storage requirement can be calculated + self = + self.add_unlock_condition(StorageDepositReturnUnlockCondition::new(return_address.clone(), 1)); + // Get the min amount of the output with the added storage deposit return unlock condition + let min_amount_with_sdruc = self.clone().finish()?.minimum_amount(params); + // If the return storage cost and amount are less than the required min + let (amount, sdruc_amount) = if min_amount_with_sdruc >= return_min_amount + amount { + // Then sending storage_cost_with_sdruc covers both minimum requirements + (min_amount_with_sdruc, min_amount_with_sdruc - amount) + } else { + // Otherwise we must use the total of the return minimum and the original amount + // which is unfortunately more than the storage_cost_with_sdruc + (return_min_amount + amount, return_min_amount) + }; + // Add the required storage deposit unlock condition and the additional storage amount + self.with_amount(amount) + .replace_unlock_condition(StorageDepositReturnUnlockCondition::new( + return_address, + sdruc_amount, + )) + } else { + self + } + } + OutputBuilderAmount::MinimumAmount(_) => self, + }) + } + /// pub fn finish(self) -> Result { let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; @@ -219,7 +269,7 @@ impl NftOutputBuilder { verify_allowed_features(&immutable_features, NftOutput::ALLOWED_IMMUTABLE_FEATURES)?; let mut output = NftOutput { - amount: 1u64, + amount: 0, mana: self.mana, nft_id: self.nft_id, unlock_conditions, @@ -229,30 +279,15 @@ impl NftOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - Output::Nft(output.clone()).rent_cost(rent_structure) - } + OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; - verify_output_amount_min(output.amount)?; - - Ok(output) - } - - /// - pub fn finish_with_params<'a>(self, params: impl Into> + Send) -> Result { - let output = self.finish()?; - - if let Some(token_supply) = params.into().token_supply() { - verify_output_amount_supply(output.amount, token_supply)?; - } - Ok(output) } /// Finishes the [`NftOutputBuilder`] into an [`Output`]. - pub fn finish_output<'a>(self, params: impl Into> + Send) -> Result { - Ok(Output::Nft(self.finish_with_params(params)?)) + pub fn finish_output(self) -> Result { + Ok(Output::Nft(self.finish()?)) } } @@ -288,7 +323,7 @@ pub struct NftOutput { impl NftOutput { /// The [`Output`](crate::types::block::output::Output) kind of an [`NftOutput`]. - pub const KIND: u8 = 3; + pub const KIND: u8 = 4; /// The set of allowed [`UnlockCondition`]s for an [`NftOutput`]. pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS .union(UnlockConditionFlags::STORAGE_DEPOSIT_RETURN) @@ -307,11 +342,11 @@ impl NftOutput { NftOutputBuilder::new_with_amount(amount, nft_id) } - /// Creates a new [`NftOutputBuilder`] with a provided rent structure. - /// The amount will be set to the minimum storage deposit. + /// Creates a new [`NftOutputBuilder`] with provided storage score parameters. + /// The amount will be set to the minimum required amount of the resulting output. #[inline(always)] - pub fn build_with_minimum_storage_deposit(rent_structure: RentStructure, nft_id: NftId) -> NftOutputBuilder { - NftOutputBuilder::new_with_minimum_storage_deposit(rent_structure, nft_id) + pub fn build_with_minimum_amount(params: StorageScoreParameters, nft_id: NftId) -> NftOutputBuilder { + NftOutputBuilder::new_with_minimum_amount(params, nft_id) } /// @@ -409,6 +444,19 @@ impl NftOutput { } } +impl StorageScore for NftOutput { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + params.output_offset() + // Type byte + + (1 + self.packed_len() as u64) * params.data_factor() as u64 + + self.unlock_conditions.storage_score(params) + + self.features.storage_score(params) + + self.immutable_features.storage_score(params) + } +} + +impl MinimumOutputAmount for NftOutput {} + impl StateTransitionVerifier for NftOutput { fn creation(next_state: &Self, context: &SemanticValidationContext<'_>) -> Result<(), StateTransitionError> { if !next_state.nft_id.is_null() { @@ -464,8 +512,6 @@ impl Packable for NftOutput { ) -> Result> { let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - verify_output_amount_packable::(&amount, visitor).map_err(UnpackError::Packable)?; - let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; let nft_id = NftId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; @@ -521,10 +567,7 @@ pub(crate) mod dto { use super::*; use crate::{ - types::{ - block::{output::unlock_condition::dto::UnlockConditionDto, Error}, - TryFromDto, - }, + types::block::{output::unlock_condition::dto::UnlockConditionDto, Error}, utils::serde::string, }; @@ -559,21 +602,20 @@ pub(crate) mod dto { } } - impl TryFromDto for NftOutput { - type Dto = NftOutputDto; + impl TryFrom for NftOutput { type Error = Error; - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { + fn try_from(dto: NftOutputDto) -> Result { let mut builder = NftOutputBuilder::new_with_amount(dto.amount, dto.nft_id) .with_mana(dto.mana) .with_features(dto.features) .with_immutable_features(dto.immutable_features); for u in dto.unlock_conditions { - builder = builder.add_unlock_condition(UnlockCondition::try_from_dto_with_params(u, ¶ms)?); + builder = builder.add_unlock_condition(UnlockCondition::from(u)); } - builder.finish_with_params(params) + builder.finish() } } @@ -586,21 +628,19 @@ pub(crate) mod dto { unlock_conditions: Vec, features: Option>, immutable_features: Option>, - params: impl Into> + Send, ) -> Result { - let params = params.into(); let mut builder = match amount { OutputBuilderAmount::Amount(amount) => NftOutputBuilder::new_with_amount(amount, *nft_id), - OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => { - NftOutputBuilder::new_with_minimum_storage_deposit(rent_structure, *nft_id) + OutputBuilderAmount::MinimumAmount(params) => { + NftOutputBuilder::new_with_minimum_amount(params, *nft_id) } } .with_mana(mana); let unlock_conditions = unlock_conditions .into_iter() - .map(|u| UnlockCondition::try_from_dto_with_params(u, ¶ms)) - .collect::, Error>>()?; + .map(UnlockCondition::from) + .collect::>(); builder = builder.with_unlock_conditions(unlock_conditions); if let Some(features) = features { @@ -611,7 +651,7 @@ pub(crate) mod dto { builder = builder.with_immutable_features(immutable_features); } - builder.finish_with_params(params) + builder.finish() } } } @@ -621,15 +661,15 @@ mod tests { use pretty_assertions::assert_eq; use super::*; - use crate::types::{ - block::{ - output::dto::OutputDto, - protocol::protocol_parameters, - rand::output::{ + use crate::types::block::{ + output::{dto::OutputDto, FoundryId, SimpleTokenScheme, TokenId}, + protocol::protocol_parameters, + rand::{ + address::rand_account_address, + output::{ feature::rand_allowed_features, rand_nft_output, unlock_condition::rand_address_unlock_condition, }, }, - TryFromDto, }; #[test] @@ -637,9 +677,9 @@ mod tests { let protocol_parameters = protocol_parameters(); let output = rand_nft_output(protocol_parameters.token_supply()); let dto = OutputDto::Nft((&output).into()); - let output_unver = Output::try_from_dto(dto.clone()).unwrap(); + let output_unver = Output::try_from(dto.clone()).unwrap(); assert_eq!(&output, output_unver.as_nft()); - let output_ver = Output::try_from_dto_with_params(dto, &protocol_parameters).unwrap(); + let output_ver = Output::try_from(dto).unwrap(); assert_eq!(&output, output_ver.as_nft()); let output_split = NftOutput::try_from_dtos( @@ -649,7 +689,6 @@ mod tests { output.unlock_conditions().iter().map(Into::into).collect(), Some(output.features().to_vec()), Some(output.immutable_features().to_vec()), - &protocol_parameters, ) .unwrap(); assert_eq!(output, output_split); @@ -662,10 +701,9 @@ mod tests { builder.unlock_conditions.iter().map(Into::into).collect(), Some(builder.features.iter().cloned().collect()), Some(builder.immutable_features.iter().cloned().collect()), - &protocol_parameters, ) .unwrap(); - assert_eq!(builder.finish_with_params(&protocol_parameters).unwrap(), output_split); + assert_eq!(builder.finish().unwrap(), output_split); }; let builder = NftOutput::build_with_amount(100, NftId::null()) @@ -675,7 +713,7 @@ mod tests { test_split_dto(builder); let builder = - NftOutput::build_with_minimum_storage_deposit(protocol_parameters.rent_structure(), NftId::null()) + NftOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters(), NftId::null()) .add_unlock_condition(rand_address_unlock_condition()) .with_features(rand_allowed_features(NftOutput::ALLOWED_FEATURES)) .with_immutable_features(rand_allowed_features(NftOutput::ALLOWED_IMMUTABLE_FEATURES)); diff --git a/sdk/src/types/block/output/rent.rs b/sdk/src/types/block/output/rent.rs deleted file mode 100644 index a62d223892..0000000000 --- a/sdk/src/types/block/output/rent.rs +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use core::mem::size_of; - -use packable::Packable; - -use crate::types::block::{ - address::{Address, Ed25519Address}, - output::{ - feature::NativeTokenFeature, - unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition}, - BasicOutputBuilder, NativeToken, Output, OutputId, - }, - slot::SlotIndex, - BlockId, Error, -}; - -const DEFAULT_BYTE_COST: u32 = 100; -const DEFAULT_BYTE_COST_FACTOR_KEY: u8 = 10; -const DEFAULT_BYTE_COST_FACTOR_DATA: u8 = 1; -// TODO: fill in the real values -const DEFAULT_BYTE_COST_FACTOR_DELEGATION: u8 = 1; -const DEFAULT_BYTE_COST_FACTOR_STAKING_FEATURE: u8 = 1; -const DEFAULT_BYTE_COST_FACTOR_BLOCK_ISSUER_KEY: u8 = 1; - -/// Specifies the current parameters for the byte cost computation. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize, serde::Deserialize), - serde(rename_all = "camelCase") -)] -pub struct RentStructure { - /// Cost in tokens per virtual byte. - v_byte_cost: u32, - /// The weight factor used for data fields in the outputs. - v_byte_factor_data: u8, - /// The weight factor used for key fields in the outputs. - v_byte_factor_key: u8, - /// The weight factor used for block issuer key fields in the outputs. - v_byte_factor_block_issuer_key: u8, - /// The weight factor used for staking fields in the outputs. - v_byte_factor_staking_feature: u8, - /// The weight factor used for delegation fields in the outputs. - v_byte_factor_delegation: u8, -} - -impl Default for RentStructure { - fn default() -> Self { - Self { - v_byte_cost: DEFAULT_BYTE_COST, - v_byte_factor_data: DEFAULT_BYTE_COST_FACTOR_DATA, - v_byte_factor_key: DEFAULT_BYTE_COST_FACTOR_KEY, - v_byte_factor_block_issuer_key: DEFAULT_BYTE_COST_FACTOR_BLOCK_ISSUER_KEY, - v_byte_factor_staking_feature: DEFAULT_BYTE_COST_FACTOR_STAKING_FEATURE, - v_byte_factor_delegation: DEFAULT_BYTE_COST_FACTOR_DELEGATION, - } - } -} - -impl RentStructure { - /// Creates a new [`RentStructure`]. - pub fn new( - byte_cost: u32, - byte_factor_data: u8, - byte_factor_key: u8, - byte_factor_block_issuer_key: u8, - byte_factor_staking_feature: u8, - byte_factor_delegation: u8, - ) -> Self { - Self { - v_byte_cost: byte_cost, - v_byte_factor_data: byte_factor_data, - v_byte_factor_key: byte_factor_key, - v_byte_factor_block_issuer_key: byte_factor_block_issuer_key, - v_byte_factor_staking_feature: byte_factor_staking_feature, - v_byte_factor_delegation: byte_factor_delegation, - } - } - - /// Sets the byte cost for the storage deposit. - pub fn with_byte_cost(mut self, byte_cost: u32) -> Self { - self.v_byte_cost = byte_cost; - self - } - - /// Sets the virtual byte weight for the data fields. - pub fn with_byte_factor_data(mut self, byte_factor_data: u8) -> Self { - self.v_byte_factor_data = byte_factor_data; - self - } - - /// Sets the virtual byte weight for the key fields. - pub fn with_byte_factor_key(mut self, byte_factor_key: u8) -> Self { - self.v_byte_factor_key = byte_factor_key; - self - } - - /// Sets the virtual byte weight for the block issuer key fields. - pub fn with_byte_factor_block_issuer_key(mut self, byte_factor_block_issuer_key: u8) -> Self { - self.v_byte_factor_block_issuer_key = byte_factor_block_issuer_key; - self - } - - /// Sets the virtual byte weight for the staking fields. - pub fn with_byte_factor_staking_feature(mut self, byte_factor_staking_feature: u8) -> Self { - self.v_byte_factor_staking_feature = byte_factor_staking_feature; - self - } - - /// Sets the virtual byte weight for the delegation fields. - pub fn with_byte_factor_delegation(mut self, byte_factor_delegation: u8) -> Self { - self.v_byte_factor_delegation = byte_factor_delegation; - self - } - - /// Returns the byte cost of the [`RentStructure`]. - pub const fn byte_cost(&self) -> u32 { - self.v_byte_cost - } - - /// Returns the byte factor data of the [`RentStructure`]. - pub const fn byte_factor_data(&self) -> u8 { - self.v_byte_factor_data - } - - /// Returns the byte factor key of the [`RentStructure`]. - pub const fn byte_factor_key(&self) -> u8 { - self.v_byte_factor_key - } - - /// Returns the block issuer key byte factor of the [`RentStructure`]. - pub const fn byte_factor_block_issuer_key(&self) -> u8 { - self.v_byte_factor_block_issuer_key - } - - /// Returns the staking byte factor of the [`RentStructure`]. - pub const fn byte_factor_staking_feature(&self) -> u8 { - self.v_byte_factor_staking_feature - } - - /// Returns the delegation byte factor of the [`RentStructure`]. - pub const fn byte_factor_delegation(&self) -> u8 { - self.v_byte_factor_delegation - } -} - -/// A trait to facilitate the computation of the byte cost of block outputs, which is central to dust protection. -pub trait Rent { - /// Computes the byte offset given a [`RentStructure`]. - fn byte_offset(&self, rent_structure: RentStructure) -> u32 { - // The ID of the output. - size_of::() as u32 * rent_structure.v_byte_factor_key as u32 - // The ID of the block in which the transaction payload that created this output was included. - + size_of::() as u32 * rent_structure.v_byte_factor_data as u32 - // The index of the slot in which the transaction that created it was booked. - + size_of::() as u32 * rent_structure.v_byte_factor_data as u32 - // The index of the slot in which the transaction was created. - + size_of::() as u32 * rent_structure.v_byte_factor_data as u32 - } - - /// Different fields in a type lead to different storage requirements for the ledger state. - fn weighted_bytes(&self, config: RentStructure) -> u64; - - /// Computes the rent cost given a [`RentStructure`]. - fn rent_cost(&self, rent_structure: RentStructure) -> u64 { - rent_structure.v_byte_cost as u64 - * (self.weighted_bytes(rent_structure) + self.byte_offset(rent_structure) as u64) - } -} - -impl Rent for [T; N] { - fn weighted_bytes(&self, config: RentStructure) -> u64 { - self.iter().map(|elem| elem.weighted_bytes(config)).sum() - } -} - -pub struct MinimumStorageDepositBasicOutput { - config: RentStructure, - token_supply: u64, - builder: BasicOutputBuilder, -} - -impl MinimumStorageDepositBasicOutput { - pub fn new(config: RentStructure, token_supply: u64) -> Self { - Self { - config, - token_supply, - builder: BasicOutputBuilder::new_with_amount(Output::AMOUNT_MIN).add_unlock_condition( - AddressUnlockCondition::new(Address::from(Ed25519Address::from([0; Ed25519Address::LENGTH]))), - ), - } - } - - pub fn with_native_token(mut self, native_token: impl Into>) -> Self { - if let Some(native_token) = native_token.into() { - self.builder = self.builder.add_feature(NativeTokenFeature::from(native_token)); - } - self - } - - pub fn with_storage_deposit_return(mut self) -> Result { - self.builder = self - .builder - .add_unlock_condition(StorageDepositReturnUnlockCondition::new( - Address::from(Ed25519Address::from([0; Ed25519Address::LENGTH])), - Output::AMOUNT_MIN, - self.token_supply, - )?); - Ok(self) - } - - pub fn with_expiration(mut self) -> Result { - self.builder = self.builder.add_unlock_condition(ExpirationUnlockCondition::new( - Address::from(Ed25519Address::from([0; Ed25519Address::LENGTH])), - 1, - )?); - Ok(self) - } - - pub fn finish(self) -> Result { - Ok(self.builder.finish_output(self.token_supply)?.rent_cost(self.config)) - } -} diff --git a/sdk/src/types/block/output/storage_score.rs b/sdk/src/types/block/output/storage_score.rs new file mode 100644 index 0000000000..02d6b09be4 --- /dev/null +++ b/sdk/src/types/block/output/storage_score.rs @@ -0,0 +1,181 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use packable::Packable; + +use crate::types::block::{ + address::Ed25519Address, + output::{ + feature::{BlockIssuerFeature, BlockIssuerKey, Ed25519BlockIssuerKey}, + AccountId, AccountOutputBuilder, AddressUnlockCondition, BasicOutputBuilder, OutputId, + }, + slot::SlotIndex, + BlockId, +}; + +const DEFAULT_STORAGE_COST: u64 = 500; +const DEFAULT_FACTOR_DATA: u8 = 1; +const DEFAULT_OFFSET_OUTPUT_OVERHEAD: u64 = 10; +const DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY: u64 = 50; +const DEFAULT_OFFSET_STAKING_FEATURE: u64 = 100; +const DEFAULT_OFFSET_DELEGATION: u64 = 100; + +// Defines the parameters of storage score calculations on objects which take node resources. +// This structure defines the minimum base token deposit required on an object. This deposit does not +// generate Mana, which serves as a payment in Mana for storing the object. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct StorageScoreParameters { + /// Defines the number of IOTA tokens required per unit of storage score. + #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + storage_cost: u64, + /// Defines the factor to be used for data only fields. + factor_data: u8, + /// Defines the offset to be applied to all outputs for the overhead of handling them in storage. + #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + offset_output_overhead: u64, + /// Defines the offset to be used for block issuer feature public keys. + #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + offset_ed25519_block_issuer_key: u64, + /// Defines the offset to be used for staking feature. + #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + offset_staking_feature: u64, + /// Defines the offset to be used for delegation output. + #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] + offset_delegation: u64, +} + +impl Default for StorageScoreParameters { + fn default() -> Self { + Self { + storage_cost: DEFAULT_STORAGE_COST, + factor_data: DEFAULT_FACTOR_DATA, + offset_output_overhead: DEFAULT_OFFSET_OUTPUT_OVERHEAD, + offset_ed25519_block_issuer_key: DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY, + offset_staking_feature: DEFAULT_OFFSET_STAKING_FEATURE, + offset_delegation: DEFAULT_OFFSET_DELEGATION, + } + } +} + +impl StorageScoreParameters { + /// Creates a new [`StorageScoreParameters`]. + pub fn new( + storage_cost: u64, + data_factor: u8, + output_overhead_offset: u64, + ed25519_block_issuer_key_offset: u64, + staking_feature_offset: u64, + delegation_offset: u64, + ) -> Self { + Self { + storage_cost, + factor_data: data_factor, + offset_output_overhead: output_overhead_offset, + offset_ed25519_block_issuer_key: ed25519_block_issuer_key_offset, + offset_staking_feature: staking_feature_offset, + offset_delegation: delegation_offset, + } + } + + /// Sets the storage cost per unit of storage score. + pub fn with_storage_cost(mut self, storage_cost: u64) -> Self { + self.storage_cost = storage_cost; + self + } + + /// Sets the storage score factor for data fields. + pub fn with_data_factor(mut self, factor: u8) -> Self { + self.factor_data = factor; + self + } + + /// Sets the storage score offset overhead per output. + pub fn with_output_overhead_offset(mut self, offset: u64) -> Self { + self.offset_output_overhead = offset; + self + } + + /// Sets the storage score offset for Ed25519 block issuer key fields. + pub fn with_ed25519_block_issuer_key_offset(mut self, offset: u64) -> Self { + self.offset_ed25519_block_issuer_key = offset; + self + } + + /// Sets the storage score offset for staking fields. + pub fn with_staking_feature_offset(mut self, offset: u64) -> Self { + self.offset_staking_feature = offset; + self + } + + /// Sets the storage score offset for delegation fields. + pub fn with_delegation_offset(mut self, offset: u64) -> Self { + self.offset_delegation = offset; + self + } + + /// Returns the storage cost per unit of storage score. + pub fn storage_cost(&self) -> u64 { + self.storage_cost + } + + /// Returns the storage score factor for data fields. + pub fn data_factor(&self) -> u8 { + self.factor_data + } + + /// Returns the storage score offset overhead per output. + pub fn output_overhead_offset(&self) -> u64 { + self.offset_output_overhead + } + + /// Returns the storage score offset for Ed25519 block issuer key fields. + pub fn ed25519_block_issuer_key_offset(&self) -> u64 { + self.offset_ed25519_block_issuer_key + } + + /// Returns the storage score offset for staking fields. + pub fn staking_feature_offset(&self) -> u64 { + self.offset_staking_feature + } + + /// Returns the storage score offset for delegation fields. + pub fn delegation_offset(&self) -> u64 { + self.offset_delegation + } + + /// Returns the storage score offset per output. + pub fn output_offset(&self) -> u64 { + self.output_overhead_offset() + + (self.data_factor() as usize * (OutputId::LENGTH + BlockId::LENGTH + core::mem::size_of::())) + as u64 + } + + /// Returns the storage score offset for implicit account creation address fields. + pub fn implicit_account_creation_address_offset(&self) -> u64 { + let null_address = Ed25519Address::null(); + let basic_output_score = BasicOutputBuilder::new_with_amount(0) + .add_unlock_condition(AddressUnlockCondition::new(null_address)) + .finish() + .unwrap() + .storage_score(*self); + let account_output_score = AccountOutputBuilder::new_with_amount(0, AccountId::null()) + .add_unlock_condition(AddressUnlockCondition::new(null_address)) + .add_feature(BlockIssuerFeature::new(0, [BlockIssuerKey::Ed25519(Ed25519BlockIssuerKey::null())]).unwrap()) + .finish() + .unwrap() + .storage_score(*self); + account_output_score - basic_output_score + null_address.storage_score(*self) + } +} + +/// A trait to facilitate the computation of the byte cost of block outputs, which is central to dust protection. +pub trait StorageScore { + fn storage_score(&self, _params: StorageScoreParameters) -> u64 { + 0 + } +} diff --git a/sdk/src/types/block/output/token_scheme/simple.rs b/sdk/src/types/block/output/token_scheme/simple.rs index 229ba6b905..b675bcbc69 100644 --- a/sdk/src/types/block/output/token_scheme/simple.rs +++ b/sdk/src/types/block/output/token_scheme/simple.rs @@ -1,18 +1,15 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use packable::{ - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - unpacker::Unpacker, - Packable, -}; +use packable::Packable; use primitive_types::U256; use crate::types::block::Error; /// -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] +#[packable(unpack_error = Error)] +#[packable(verify_with = verify_simple_token_scheme)] pub struct SimpleTokenScheme { // Amount of tokens minted by a foundry. minted_tokens: U256, @@ -37,13 +34,15 @@ impl SimpleTokenScheme { let melted_tokens = melted_tokens.into(); let maximum_supply = maximum_supply.into(); - verify_supply(&minted_tokens, &melted_tokens, &maximum_supply)?; - - Ok(Self { + let token_scheme = Self { minted_tokens, melted_tokens, maximum_supply, - }) + }; + + verify_simple_token_scheme::(&token_scheme, &())?; + + Ok(token_scheme) } /// Returns the number of minted tokens of the [`SimpleTokenScheme`]. @@ -71,45 +70,20 @@ impl SimpleTokenScheme { } } -impl Packable for SimpleTokenScheme { - type UnpackError = Error; - type UnpackVisitor = (); - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - self.minted_tokens.pack(packer)?; - self.melted_tokens.pack(packer)?; - self.maximum_supply.pack(packer)?; - - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - let minted_tokens = U256::unpack::<_, VERIFY>(unpacker, visitor).coerce()?; - let melted_tokens = U256::unpack::<_, VERIFY>(unpacker, visitor).coerce()?; - let maximum_supply = U256::unpack::<_, VERIFY>(unpacker, visitor).coerce()?; - - if VERIFY { - verify_supply(&minted_tokens, &melted_tokens, &maximum_supply).map_err(UnpackError::Packable)?; - } - - Ok(Self { - minted_tokens, - melted_tokens, - maximum_supply, - }) - } -} - #[inline] -fn verify_supply(minted_tokens: &U256, melted_tokens: &U256, maximum_supply: &U256) -> Result<(), Error> { - if maximum_supply.is_zero() || melted_tokens > minted_tokens || minted_tokens - melted_tokens > *maximum_supply { +fn verify_simple_token_scheme( + token_scheme: &SimpleTokenScheme, + _visitor: &(), +) -> Result<(), Error> { + if VERIFY + && (token_scheme.maximum_supply.is_zero() + || token_scheme.melted_tokens > token_scheme.minted_tokens + || token_scheme.minted_tokens - token_scheme.melted_tokens > token_scheme.maximum_supply) + { return Err(Error::InvalidFoundryOutputSupply { - minted: *minted_tokens, - melted: *melted_tokens, - max: *maximum_supply, + minted: token_scheme.minted_tokens, + melted: token_scheme.melted_tokens, + max: token_scheme.maximum_supply, }); } diff --git a/sdk/src/types/block/output/unlock_condition/address.rs b/sdk/src/types/block/output/unlock_condition/address.rs index f4ab9bfcd7..9a89bfc2bb 100644 --- a/sdk/src/types/block/output/unlock_condition/address.rs +++ b/sdk/src/types/block/output/unlock_condition/address.rs @@ -3,7 +3,10 @@ use derive_more::From; -use crate::types::block::address::Address; +use crate::types::block::{ + address::Address, + output::{storage_score::StorageScoreParameters, StorageScore}, +}; /// Defines the Address that owns this output, that is, it can unlock it with the proper Unlock in a transaction. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] @@ -26,6 +29,12 @@ impl AddressUnlockCondition { } } +impl StorageScore for AddressUnlockCondition { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.address().storage_score(params) + } +} + #[cfg(feature = "serde")] pub(crate) mod dto { use serde::{Deserialize, Serialize}; diff --git a/sdk/src/types/block/output/unlock_condition/expiration.rs b/sdk/src/types/block/output/unlock_condition/expiration.rs index 73860f81cc..41563f41c4 100644 --- a/sdk/src/types/block/output/unlock_condition/expiration.rs +++ b/sdk/src/types/block/output/unlock_condition/expiration.rs @@ -3,7 +3,12 @@ use derive_more::From; -use crate::types::block::{address::Address, slot::SlotIndex, Error}; +use crate::types::block::{ + address::Address, + output::{StorageScore, StorageScoreParameters}, + slot::SlotIndex, + Error, +}; /// Defines an expiration slot index. Before the slot index is reached, only the Address defined in the Address /// Unlock Condition is allowed to unlock the output. Afterward, only the Return Address can unlock it. @@ -55,6 +60,12 @@ impl ExpirationUnlockCondition { } } +impl StorageScore for ExpirationUnlockCondition { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.return_address().storage_score(params) + } +} + #[inline] fn verify_slot_index(slot_index: &SlotIndex, _: &()) -> Result<(), Error> { if VERIFY && *slot_index == 0 { diff --git a/sdk/src/types/block/output/unlock_condition/governor_address.rs b/sdk/src/types/block/output/unlock_condition/governor_address.rs index e2c351e5cc..eb71ef1c7b 100644 --- a/sdk/src/types/block/output/unlock_condition/governor_address.rs +++ b/sdk/src/types/block/output/unlock_condition/governor_address.rs @@ -3,7 +3,10 @@ use derive_more::From; -use crate::types::block::address::Address; +use crate::types::block::{ + address::Address, + output::{StorageScore, StorageScoreParameters}, +}; /// Defines the Governor Address that owns this output, that is, it can unlock it with the proper Unlock in a /// transaction that governance transitions the anchor output. @@ -28,6 +31,12 @@ impl GovernorAddressUnlockCondition { } } +impl StorageScore for GovernorAddressUnlockCondition { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.address().storage_score(params) + } +} + #[cfg(feature = "serde")] pub(crate) mod dto { use serde::{Deserialize, Serialize}; diff --git a/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs b/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs index e6ce94c437..936aeac41d 100644 --- a/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs +++ b/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs @@ -3,7 +3,10 @@ use derive_more::From; -use crate::types::block::address::AccountAddress; +use crate::types::block::{ + address::AccountAddress, + output::{StorageScore, StorageScoreParameters}, +}; /// Defines the permanent [`AccountAddress`] that owns this output. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] @@ -26,6 +29,12 @@ impl ImmutableAccountAddressUnlockCondition { } } +impl StorageScore for ImmutableAccountAddressUnlockCondition { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.address().storage_score(params) + } +} + #[cfg(feature = "serde")] mod dto { use serde::{Deserialize, Serialize}; diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index e11f6d36a0..0c3e2d3d70 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -14,14 +14,7 @@ use alloc::{boxed::Box, collections::BTreeSet, vec::Vec}; use bitflags::bitflags; use derive_more::{Deref, From}; use iterator_sorted::is_unique_sorted; -use packable::{ - bounded::BoundedU8, - error::{UnpackError, UnpackErrorExt}, - packer::Packer, - prefix::BoxedSlicePrefix, - unpacker::Unpacker, - Packable, -}; +use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; pub use self::{ address::AddressUnlockCondition, expiration::ExpirationUnlockCondition, @@ -30,24 +23,40 @@ pub use self::{ state_controller_address::StateControllerAddressUnlockCondition, storage_deposit_return::StorageDepositReturnUnlockCondition, timelock::TimelockUnlockCondition, }; -use crate::types::block::{address::Address, create_bitflags, protocol::ProtocolParameters, slot::SlotIndex, Error}; +use crate::types::block::{ + address::Address, + output::{StorageScore, StorageScoreParameters}, + protocol::ProtocolParameters, + slot::SlotIndex, + Error, +}; /// -#[derive(Clone, Eq, PartialEq, Hash, From)] +#[derive(Clone, Eq, PartialEq, Hash, From, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] +#[packable(tag_type = u8, with_error = Error::InvalidUnlockConditionKind)] pub enum UnlockCondition { /// An address unlock condition. + #[packable(tag = AddressUnlockCondition::KIND)] Address(AddressUnlockCondition), /// A storage deposit return unlock condition. + #[packable(tag = StorageDepositReturnUnlockCondition::KIND)] StorageDepositReturn(StorageDepositReturnUnlockCondition), /// A timelock unlock condition. + #[packable(tag = TimelockUnlockCondition::KIND)] Timelock(TimelockUnlockCondition), /// An expiration unlock condition. + #[packable(tag = ExpirationUnlockCondition::KIND)] Expiration(ExpirationUnlockCondition), /// A state controller address unlock condition. + #[packable(tag = StateControllerAddressUnlockCondition::KIND)] StateControllerAddress(StateControllerAddressUnlockCondition), /// A governor address unlock condition. + #[packable(tag = GovernorAddressUnlockCondition::KIND)] GovernorAddress(GovernorAddressUnlockCondition), /// An immutable account address unlock condition. + #[packable(tag = ImmutableAccountAddressUnlockCondition::KIND)] ImmutableAccountAddress(ImmutableAccountAddressUnlockCondition), } @@ -62,6 +71,20 @@ impl Ord for UnlockCondition { } } +impl StorageScore for UnlockCondition { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + match self { + Self::Address(uc) => uc.storage_score(params), + Self::StorageDepositReturn(uc) => uc.storage_score(params), + Self::Timelock(uc) => uc.storage_score(params), + Self::Expiration(uc) => uc.storage_score(params), + Self::StateControllerAddress(uc) => uc.storage_score(params), + Self::GovernorAddress(uc) => uc.storage_score(params), + Self::ImmutableAccountAddress(uc) => uc.storage_score(params), + } + } +} + impl core::fmt::Debug for UnlockCondition { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { @@ -114,7 +137,7 @@ impl UnlockCondition { ); } -create_bitflags!( +crate::create_bitflags!( /// A bitflags-based representation of the set of active [`UnlockCondition`]s. pub UnlockConditionFlags, u16, @@ -129,76 +152,6 @@ create_bitflags!( ] ); -impl Packable for UnlockCondition { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - match self { - Self::Address(unlock_condition) => { - AddressUnlockCondition::KIND.pack(packer)?; - unlock_condition.pack(packer) - } - Self::StorageDepositReturn(unlock_condition) => { - StorageDepositReturnUnlockCondition::KIND.pack(packer)?; - unlock_condition.pack(packer) - } - Self::Timelock(unlock_condition) => { - TimelockUnlockCondition::KIND.pack(packer)?; - unlock_condition.pack(packer) - } - Self::Expiration(unlock_condition) => { - ExpirationUnlockCondition::KIND.pack(packer)?; - unlock_condition.pack(packer) - } - Self::StateControllerAddress(unlock_condition) => { - StateControllerAddressUnlockCondition::KIND.pack(packer)?; - unlock_condition.pack(packer) - } - Self::GovernorAddress(unlock_condition) => { - GovernorAddressUnlockCondition::KIND.pack(packer)?; - unlock_condition.pack(packer) - } - Self::ImmutableAccountAddress(unlock_condition) => { - ImmutableAccountAddressUnlockCondition::KIND.pack(packer)?; - unlock_condition.pack(packer) - } - }?; - - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? { - AddressUnlockCondition::KIND => { - Self::from(AddressUnlockCondition::unpack::<_, VERIFY>(unpacker, &()).coerce()?) - } - StorageDepositReturnUnlockCondition::KIND => { - Self::from(StorageDepositReturnUnlockCondition::unpack::<_, VERIFY>(unpacker, visitor).coerce()?) - } - TimelockUnlockCondition::KIND => { - Self::from(TimelockUnlockCondition::unpack::<_, VERIFY>(unpacker, &()).coerce()?) - } - ExpirationUnlockCondition::KIND => { - Self::from(ExpirationUnlockCondition::unpack::<_, VERIFY>(unpacker, &()).coerce()?) - } - StateControllerAddressUnlockCondition::KIND => { - Self::from(StateControllerAddressUnlockCondition::unpack::<_, VERIFY>(unpacker, &()).coerce()?) - } - GovernorAddressUnlockCondition::KIND => { - Self::from(GovernorAddressUnlockCondition::unpack::<_, VERIFY>(unpacker, &()).coerce()?) - } - ImmutableAccountAddressUnlockCondition::KIND => { - Self::from(ImmutableAccountAddressUnlockCondition::unpack::<_, VERIFY>(unpacker, &()).coerce()?) - } - k => return Err(UnpackError::Packable(Error::InvalidOutputKind(k))), - }) - } -} - pub(crate) type UnlockConditionCount = BoundedU8<0, { UnlockConditions::COUNT_MAX }>; /// @@ -349,6 +302,12 @@ impl UnlockConditions { } } +impl StorageScore for UnlockConditions { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.iter().map(|uc| uc.storage_score(params)).sum::() + } +} + #[inline] fn verify_unique_sorted(unlock_conditions: &[UnlockCondition]) -> Result<(), Error> { if VERIFY && !is_unique_sorted(unlock_conditions.iter().map(UnlockCondition::kind)) { @@ -409,9 +368,7 @@ mod test { pub mod dto { use serde::{Deserialize, Serialize}; - pub use self::storage_deposit_return::dto::StorageDepositReturnUnlockConditionDto; use super::*; - use crate::types::{block::Error, TryFromDto, ValidationParams}; #[derive(Clone, Debug, Eq, PartialEq, From, Serialize, Deserialize)] #[serde(untagged)] @@ -419,7 +376,7 @@ pub mod dto { /// An address unlock condition. Address(AddressUnlockCondition), /// A storage deposit return unlock condition. - StorageDepositReturn(StorageDepositReturnUnlockConditionDto), + StorageDepositReturn(StorageDepositReturnUnlockCondition), /// A timelock unlock condition. Timelock(TimelockUnlockCondition), /// An expiration unlock condition. @@ -436,7 +393,7 @@ pub mod dto { fn from(value: &UnlockCondition) -> Self { match value { UnlockCondition::Address(v) => Self::Address(v.clone()), - UnlockCondition::StorageDepositReturn(v) => Self::StorageDepositReturn(v.into()), + UnlockCondition::StorageDepositReturn(v) => Self::StorageDepositReturn(v.clone()), UnlockCondition::Timelock(v) => Self::Timelock(*v), UnlockCondition::Expiration(v) => Self::Expiration(v.clone()), UnlockCondition::StateControllerAddress(v) => Self::StateControllerAddress(v.clone()), @@ -446,22 +403,17 @@ pub mod dto { } } - impl TryFromDto for UnlockCondition { - type Dto = UnlockConditionDto; - type Error = Error; - - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { - Ok(match dto { + impl From for UnlockCondition { + fn from(dto: UnlockConditionDto) -> Self { + match dto { UnlockConditionDto::Address(v) => Self::Address(v), - UnlockConditionDto::StorageDepositReturn(v) => Self::StorageDepositReturn( - StorageDepositReturnUnlockCondition::try_from_dto_with_params_inner(v, params)?, - ), + UnlockConditionDto::StorageDepositReturn(v) => Self::StorageDepositReturn(v), UnlockConditionDto::Timelock(v) => Self::Timelock(v), UnlockConditionDto::Expiration(v) => Self::Expiration(v), UnlockConditionDto::StateControllerAddress(v) => Self::StateControllerAddress(v), UnlockConditionDto::GovernorAddress(v) => Self::GovernorAddress(v), UnlockConditionDto::ImmutableAccountAddress(v) => Self::ImmutableAccountAddress(v), - }) + } } } diff --git a/sdk/src/types/block/output/unlock_condition/state_controller_address.rs b/sdk/src/types/block/output/unlock_condition/state_controller_address.rs index 759a55f3f6..41c2015536 100644 --- a/sdk/src/types/block/output/unlock_condition/state_controller_address.rs +++ b/sdk/src/types/block/output/unlock_condition/state_controller_address.rs @@ -3,7 +3,10 @@ use derive_more::From; -use crate::types::block::address::Address; +use crate::types::block::{ + address::Address, + output::{storage_score::StorageScore, StorageScoreParameters}, +}; /// Defines the State Controller Address that owns this output, that is, it can unlock it with the proper Unlock in a /// transaction that state transitions the anchor output. @@ -28,6 +31,12 @@ impl StateControllerAddressUnlockCondition { } } +impl StorageScore for StateControllerAddressUnlockCondition { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.address().storage_score(params) + } +} + #[cfg(feature = "serde")] pub(crate) mod dto { use serde::{Deserialize, Serialize}; diff --git a/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs b/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs index f10022849f..6824e728ec 100644 --- a/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs +++ b/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs @@ -1,7 +1,11 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::{address::Address, output::verify_output_amount, protocol::ProtocolParameters, Error}; +use crate::types::block::{ + address::Address, + output::storage_score::{StorageScore, StorageScoreParameters}, + protocol::ProtocolParameters, +}; /// Defines the amount of IOTAs used as storage deposit that have to be returned to the return [`Address`]. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] @@ -10,7 +14,6 @@ pub struct StorageDepositReturnUnlockCondition { // The [`Address`] to return the amount to. return_address: Address, // Amount of IOTA coins the consuming transaction should deposit to `return_address`. - #[packable(verify_with = verify_amount_packable)] amount: u64, } @@ -21,13 +24,11 @@ impl StorageDepositReturnUnlockCondition { /// Creates a new [`StorageDepositReturnUnlockCondition`]. #[inline(always)] - pub fn new(return_address: impl Into
, amount: u64, token_supply: u64) -> Result { - verify_amount::(amount, token_supply)?; - - Ok(Self { + pub fn new(return_address: impl Into
, amount: u64) -> Self { + Self { return_address: return_address.into(), amount, - }) + } } /// Returns the return address. @@ -43,32 +44,20 @@ impl StorageDepositReturnUnlockCondition { } } -fn verify_amount(amount: u64, token_supply: u64) -> Result<(), Error> { - if VERIFY { - verify_output_amount(amount, token_supply).map_err(|_| Error::InvalidStorageDepositAmount(amount))?; +impl StorageScore for StorageDepositReturnUnlockCondition { + fn storage_score(&self, params: StorageScoreParameters) -> u64 { + self.return_address().storage_score(params) } - - Ok(()) -} - -fn verify_amount_packable( - amount: &u64, - protocol_parameters: &ProtocolParameters, -) -> Result<(), Error> { - verify_amount::(*amount, protocol_parameters.token_supply()) } #[cfg(feature = "serde")] -pub(crate) mod dto { +mod dto { use alloc::format; use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::{block::Error, TryFromDto, ValidationParams}, - utils::serde::string, - }; + use crate::utils::serde::string; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -105,28 +94,18 @@ pub(crate) mod dto { } } - impl TryFromDto for StorageDepositReturnUnlockCondition { - type Dto = StorageDepositReturnUnlockConditionDto; - type Error = Error; - - fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { - Ok(if let Some(token_supply) = params.token_supply() { - Self::new(dto.return_address, dto.amount, token_supply)? - } else { - Self { - return_address: dto.return_address, - amount: dto.amount, - } - }) + impl From for StorageDepositReturnUnlockCondition { + fn from(dto: StorageDepositReturnUnlockConditionDto) -> Self { + Self { + return_address: dto.return_address, + amount: dto.amount, + } } } - impl Serialize for StorageDepositReturnUnlockCondition { - fn serialize(&self, s: S) -> Result - where - S: serde::Serializer, - { - StorageDepositReturnUnlockConditionDto::from(self).serialize(s) - } - } + crate::impl_serde_typed_dto!( + StorageDepositReturnUnlockCondition, + StorageDepositReturnUnlockConditionDto, + "storage deposit return unlock condition" + ); } diff --git a/sdk/src/types/block/output/unlock_condition/timelock.rs b/sdk/src/types/block/output/unlock_condition/timelock.rs index aeffb7ab5d..a01faa6faf 100644 --- a/sdk/src/types/block/output/unlock_condition/timelock.rs +++ b/sdk/src/types/block/output/unlock_condition/timelock.rs @@ -3,7 +3,7 @@ use derive_more::From; -use crate::types::block::{slot::SlotIndex, Error}; +use crate::types::block::{output::StorageScore, slot::SlotIndex, Error}; /// Defines a slot index until which the output can not be unlocked. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] @@ -31,6 +31,8 @@ impl TimelockUnlockCondition { } } +impl StorageScore for TimelockUnlockCondition {} + #[inline] fn verify_slot_index(slot_index: &SlotIndex, _: &()) -> Result<(), Error> { if VERIFY && *slot_index == 0 { diff --git a/sdk/src/types/block/payload/candidacy_announcement.rs b/sdk/src/types/block/payload/candidacy_announcement.rs index 0934e05cdd..b21916515b 100644 --- a/sdk/src/types/block/payload/candidacy_announcement.rs +++ b/sdk/src/types/block/payload/candidacy_announcement.rs @@ -1,8 +1,10 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use packable::Packable; + /// A payload which is used to indicate candidacy for committee selection for the next epoch. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Packable)] pub struct CandidacyAnnouncementPayload; impl CandidacyAnnouncementPayload { diff --git a/sdk/src/types/block/payload/mod.rs b/sdk/src/types/block/payload/mod.rs index 639f0c4a83..c88fcad25f 100644 --- a/sdk/src/types/block/payload/mod.rs +++ b/sdk/src/types/block/payload/mod.rs @@ -29,44 +29,50 @@ pub(crate) use self::{ use crate::types::block::{protocol::ProtocolParameters, Error}; /// A generic payload that can represent different types defining block payloads. -#[derive(Clone, Eq, PartialEq, From)] +#[derive(Clone, Eq, PartialEq, From, Packable)] +#[packable(unpack_error = Error)] +#[packable(unpack_visitor = ProtocolParameters)] +#[packable(tag_type = u8, with_error = Error::InvalidPayloadKind)] pub enum Payload { - /// A signed transaction payload. - SignedTransaction(Box), /// A tagged data payload. + #[packable(tag = TaggedDataPayload::KIND)] TaggedData(Box), + /// A signed transaction payload. + #[packable(tag = SignedTransactionPayload::KIND)] + SignedTransaction(Box), /// A candidacy announcement payload. + #[packable(tag = CandidacyAnnouncementPayload::KIND)] CandidacyAnnouncement(CandidacyAnnouncementPayload), } impl core::fmt::Debug for Payload { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::SignedTransaction(payload) => payload.fmt(f), Self::TaggedData(payload) => payload.fmt(f), + Self::SignedTransaction(payload) => payload.fmt(f), Self::CandidacyAnnouncement(payload) => payload.fmt(f), } } } -impl From for Payload { - fn from(payload: SignedTransactionPayload) -> Self { - Self::SignedTransaction(Box::new(payload)) - } -} - impl From for Payload { fn from(payload: TaggedDataPayload) -> Self { Self::TaggedData(Box::new(payload)) } } +impl From for Payload { + fn from(payload: SignedTransactionPayload) -> Self { + Self::SignedTransaction(Box::new(payload)) + } +} + impl Payload { /// Returns the payload kind of a `Payload`. pub fn kind(&self) -> u8 { match self { - Self::SignedTransaction(_) => SignedTransactionPayload::KIND, Self::TaggedData(_) => TaggedDataPayload::KIND, + Self::SignedTransaction(_) => SignedTransactionPayload::KIND, Self::CandidacyAnnouncement(_) => CandidacyAnnouncementPayload::KIND, } } @@ -74,41 +80,6 @@ impl Payload { crate::def_is_as_opt!(Payload: SignedTransaction, TaggedData); } -impl Packable for Payload { - type UnpackError = Error; - type UnpackVisitor = ProtocolParameters; - - fn pack(&self, packer: &mut P) -> Result<(), P::Error> { - match self { - Self::SignedTransaction(transaction) => { - SignedTransactionPayload::KIND.pack(packer)?; - transaction.pack(packer) - } - Self::TaggedData(tagged_data) => { - TaggedDataPayload::KIND.pack(packer)?; - tagged_data.pack(packer) - } - Self::CandidacyAnnouncement(_) => CandidacyAnnouncementPayload::KIND.pack(packer), - }?; - - Ok(()) - } - - fn unpack( - unpacker: &mut U, - visitor: &Self::UnpackVisitor, - ) -> Result> { - Ok(match u8::unpack::<_, VERIFY>(unpacker, &()).coerce()? { - SignedTransactionPayload::KIND => { - Self::from(SignedTransactionPayload::unpack::<_, VERIFY>(unpacker, visitor).coerce()?) - } - TaggedDataPayload::KIND => Self::from(TaggedDataPayload::unpack::<_, VERIFY>(unpacker, &()).coerce()?), - CandidacyAnnouncementPayload::KIND => Self::from(CandidacyAnnouncementPayload), - k => return Err(UnpackError::Packable(Error::InvalidPayloadKind(k))), - }) - } -} - /// Representation of an optional [`Payload`]. /// Essentially an `Option` with a different [`Packable`] implementation, to conform to specs. #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -197,30 +168,30 @@ pub mod dto { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum PayloadDto { - SignedTransaction(Box), TaggedData(Box), + SignedTransaction(Box), CandidacyAnnouncement, } - impl From for PayloadDto { - fn from(payload: SignedTransactionPayloadDto) -> Self { - Self::SignedTransaction(Box::new(payload)) - } - } - impl From for PayloadDto { fn from(payload: TaggedDataPayload) -> Self { Self::TaggedData(Box::new(payload)) } } + impl From for PayloadDto { + fn from(payload: SignedTransactionPayloadDto) -> Self { + Self::SignedTransaction(Box::new(payload)) + } + } + impl From<&Payload> for PayloadDto { fn from(value: &Payload) -> Self { match value { + Payload::TaggedData(p) => Self::TaggedData(p.clone()), Payload::SignedTransaction(p) => { Self::SignedTransaction(Box::new(SignedTransactionPayloadDto::from(p.as_ref()))) } - Payload::TaggedData(p) => Self::TaggedData(p.clone()), Payload::CandidacyAnnouncement(_) => Self::CandidacyAnnouncement, } } @@ -232,10 +203,10 @@ pub mod dto { fn try_from_dto_with_params_inner(dto: Self::Dto, params: ValidationParams<'_>) -> Result { Ok(match dto { + PayloadDto::TaggedData(p) => Self::from(*p), PayloadDto::SignedTransaction(p) => { Self::from(SignedTransactionPayload::try_from_dto_with_params_inner(*p, params)?) } - PayloadDto::TaggedData(p) => Self::from(*p), PayloadDto::CandidacyAnnouncement => Self::from(CandidacyAnnouncementPayload), }) } diff --git a/sdk/src/types/block/payload/signed_transaction/transaction.rs b/sdk/src/types/block/payload/signed_transaction/transaction.rs index 7d04e11fcf..af792f9798 100644 --- a/sdk/src/types/block/payload/signed_transaction/transaction.rs +++ b/sdk/src/types/block/payload/signed_transaction/transaction.rs @@ -431,10 +431,10 @@ fn verify_outputs(outputs: &[Output], visitor: &ProtocolPara let (amount, chain_id) = match output { Output::Basic(output) => (output.amount(), None), Output::Account(output) => (output.amount(), Some(output.chain_id())), + Output::Anchor(output) => (output.amount(), Some(output.chain_id())), Output::Foundry(output) => (output.amount(), Some(output.chain_id())), Output::Nft(output) => (output.amount(), Some(output.chain_id())), Output::Delegation(output) => (output.amount(), Some(output.chain_id())), - Output::Anchor(output) => (output.amount(), Some(output.chain_id())), }; amount_sum = amount_sum @@ -452,7 +452,7 @@ fn verify_outputs(outputs: &[Output], visitor: &ProtocolPara } } - output.verify_storage_deposit(visitor.rent_structure(), visitor.token_supply())?; + output.verify_storage_deposit(visitor.storage_score_parameters())?; } } @@ -521,20 +521,14 @@ pub type TransactionCapabilities = Capabilities; #[cfg(feature = "serde")] pub(crate) mod dto { - use alloc::{ - boxed::Box, - string::{String, ToString}, - }; + use alloc::string::{String, ToString}; use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::{ - block::{mana::ManaAllotmentDto, output::dto::OutputDto, payload::dto::PayloadDto, Error}, - TryFromDto, - }, - utils::serde::prefix_hex_bytes, + use crate::types::{ + block::{mana::ManaAllotmentDto, output::dto::OutputDto, payload::dto::PayloadDto, Error}, + TryFromDto, }; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -545,8 +539,8 @@ pub(crate) mod dto { pub context_inputs: Vec, pub inputs: Vec, pub allotments: Vec, - #[serde(with = "prefix_hex_bytes")] - pub capabilities: Box<[u8]>, + #[serde(default, skip_serializing_if = "TransactionCapabilities::is_none")] + pub capabilities: TransactionCapabilities, #[serde(default, skip_serializing_if = "Option::is_none")] pub payload: Option, pub outputs: Vec, @@ -560,7 +554,7 @@ pub(crate) mod dto { context_inputs: value.context_inputs().to_vec(), inputs: value.inputs().to_vec(), allotments: value.mana_allotments().iter().map(Into::into).collect(), - capabilities: value.capabilities().iter().copied().collect(), + capabilities: value.capabilities().clone(), payload: match value.payload() { Some(p @ Payload::TaggedData(_)) => Some(p.into()), Some(_) => unimplemented!(), @@ -588,7 +582,7 @@ pub(crate) mod dto { let outputs = dto .outputs .into_iter() - .map(|o| Output::try_from_dto_with_params(o, ¶ms)) + .map(Output::try_from) .collect::, Error>>()?; let mut builder = Self::builder(network_id) @@ -596,9 +590,7 @@ pub(crate) mod dto { .with_context_inputs(dto.context_inputs) .with_inputs(dto.inputs) .with_mana_allotments(mana_allotments) - .with_capabilities(Capabilities::from_bytes( - dto.capabilities.try_into().map_err(Error::InvalidCapabilitiesCount)?, - )) + .with_capabilities(dto.capabilities) .with_outputs(outputs); builder = if let Some(p) = dto.payload { diff --git a/sdk/src/types/block/protocol.rs b/sdk/src/types/block/protocol.rs index 56c73ac7bc..4577a31b94 100644 --- a/sdk/src/types/block/protocol.rs +++ b/sdk/src/types/block/protocol.rs @@ -8,12 +8,17 @@ use crypto::hashes::{blake2b::Blake2b256, Digest}; use getset::{CopyGetters, Getters}; use packable::{prefix::StringPrefix, Packable, PackableExt}; -use super::{ - address::Hrp, - mana::{ManaParameters, RewardsParameters}, - slot::{EpochIndex, SlotIndex}, +use crate::{ + types::block::{ + address::Hrp, + helper::network_name_to_id, + mana::{ManaParameters, RewardsParameters}, + output::StorageScoreParameters, + slot::{EpochIndex, SlotIndex}, + Error, PROTOCOL_VERSION, + }, + utils::ConvertTo, }; -use crate::types::block::{helper::network_name_to_id, output::RentStructure, ConvertTo, Error, PROTOCOL_VERSION}; /// Defines the parameters of the protocol at a particular version. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, Getters, CopyGetters)] @@ -37,13 +42,18 @@ pub struct ProtocolParameters { pub(crate) network_name: StringPrefix, /// The HRP prefix used for Bech32 addresses in the network. pub(crate) bech32_hrp: Hrp, - /// The rent structure used by given node/network. - pub(crate) rent_structure: RentStructure, + /// The storage score parameters used by given node/network. + pub(crate) storage_score_parameters: StorageScoreParameters, /// The work score parameters used by the node/network. pub(crate) work_score_parameters: WorkScoreParameters, + /// The parameters used for mana calculations. + #[getset(skip)] + pub(crate) mana_parameters: ManaParameters, /// TokenSupply defines the current token supply on the network. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] pub(crate) token_supply: u64, + /// Defines the slot of the genesis. + pub(crate) genesis_slot: u32, /// Genesis timestamp at which the slots start to count. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] pub(crate) genesis_unix_timestamp: u64, @@ -51,18 +61,16 @@ pub struct ProtocolParameters { pub(crate) slot_duration_in_seconds: u8, /// The number of slots in an epoch expressed as an exponent of 2. pub(crate) slots_per_epoch_exponent: u8, - /// The parameters used for mana calculations. - #[getset(skip)] - pub(crate) mana_parameters: ManaParameters, /// The unbonding period in epochs before an account can stop staking. pub(crate) staking_unbonding_period: u32, /// The number of validation blocks that each validator should issue each slot. - pub(crate) validation_blocks_per_slot: u16, + pub(crate) validation_blocks_per_slot: u8, /// The number of epochs worth of Mana that a node is punished with for each additional validation block it issues. pub(crate) punishment_epochs: u32, - /// Liveness Threshold is used by tip-selection to determine if a block is eligible by evaluating issuingTimes and - /// commitments in its past-cone to Accepted Tangle Time and lastCommittedSlot respectively. - pub(crate) liveness_threshold: u32, + /// Used by tip-selection to determine if a block is eligible by evaluating issuing times. + pub(crate) liveness_threshold_lower_bound: u16, + /// Used by tip-selection to determine if a block is eligible by evaluating issuing times + pub(crate) liveness_threshold_upper_bound: u16, /// Minimum age relative to the accepted tangle time slot index that a slot can be committed. pub(crate) min_committable_age: u32, /// Maximum age for a slot commitment to be included in a block relative to the slot index of the block issuing @@ -77,6 +85,9 @@ pub struct ProtocolParameters { pub(crate) version_signaling: VersionSignalingParameters, /// Defines the parameters used for reward calculation. pub(crate) rewards_parameters: RewardsParameters, + /// Defines the target size of the committee. If there's fewer candidates the actual committee size could be + /// smaller in a given epoch. + pub(crate) target_committee_size: u8, } // This implementation is required to make [`ProtocolParameters`] a [`Packable`] visitor. @@ -94,9 +105,10 @@ impl Default for ProtocolParameters { // Unwrap: Known to be valid network_name: String::from("iota-core-testnet").try_into().unwrap(), bech32_hrp: Hrp::from_str_unchecked("smr"), - rent_structure: Default::default(), + storage_score_parameters: Default::default(), work_score_parameters: Default::default(), token_supply: 1_813_620_509_061_365, + genesis_slot: 0, genesis_unix_timestamp: 1582328545, slot_duration_in_seconds: 10, epoch_nearing_threshold: 20, @@ -105,12 +117,14 @@ impl Default for ProtocolParameters { staking_unbonding_period: 10, validation_blocks_per_slot: 10, punishment_epochs: 9, - liveness_threshold: 5, + liveness_threshold_lower_bound: 15, + liveness_threshold_upper_bound: 30, min_committable_age: 10, max_committable_age: 20, congestion_control_parameters: Default::default(), version_signaling: Default::default(), rewards_parameters: Default::default(), + target_committee_size: 32, } } } @@ -122,7 +136,7 @@ impl ProtocolParameters { version: u8, network_name: impl Into, bech32_hrp: impl ConvertTo, - rent_structure: RentStructure, + storage_score_parameters: StorageScoreParameters, token_supply: u64, genesis_unix_timestamp: u64, slot_duration_in_seconds: u8, @@ -132,7 +146,7 @@ impl ProtocolParameters { version, network_name: >::try_from(network_name.into()).map_err(Error::InvalidStringPrefix)?, bech32_hrp: bech32_hrp.convert()?, - rent_structure, + storage_score_parameters, token_supply, genesis_unix_timestamp, slot_duration_in_seconds, @@ -321,7 +335,7 @@ pub fn protocol_parameters() -> ProtocolParameters { 2, "testnet", "rms", - crate::types::block::output::RentStructure::new(500, 1, 10, 1, 1, 1), + crate::types::block::output::StorageScoreParameters::new(500, 1, 10, 1, 1, 1), 1_813_620_509_061_365, 1582328545, 10, diff --git a/sdk/src/types/block/rand/output/mod.rs b/sdk/src/types/block/rand/output/mod.rs index 7b2cc58a20..f96ada2d25 100644 --- a/sdk/src/types/block/rand/output/mod.rs +++ b/sdk/src/types/block/rand/output/mod.rs @@ -44,10 +44,10 @@ pub fn rand_output_id() -> OutputId { /// Generates a random [`BasicOutput`](BasicOutput). pub fn rand_basic_output(token_supply: u64) -> BasicOutput { // TODO: Add `NativeTokens` - BasicOutput::build_with_amount(rand_number_range(Output::AMOUNT_MIN..token_supply)) + BasicOutput::build_with_amount(rand_number_range(0..token_supply)) .with_features(rand_allowed_features(BasicOutput::ALLOWED_FEATURES)) .add_unlock_condition(rand_address_unlock_condition()) - .finish_with_params(token_supply) + .finish() .unwrap() } @@ -66,10 +66,10 @@ pub fn rand_account_output(token_supply: u64) -> AccountOutput { // We need to make sure that `AccountId` and `Address` don't match. let account_id = rand_account_id(); - AccountOutput::build_with_amount(rand_number_range(Output::AMOUNT_MIN..token_supply), account_id) + AccountOutput::build_with_amount(rand_number_range(0..token_supply), account_id) .with_features(rand_allowed_features(AccountOutput::ALLOWED_FEATURES)) .add_unlock_condition(rand_address_unlock_condition_different_from_account_id(&account_id)) - .finish_with_params(token_supply) + .finish() .unwrap() } @@ -78,7 +78,7 @@ pub fn rand_anchor_output(token_supply: u64) -> AnchorOutput { // We need to make sure that `AnchorId` and `Address` don't match. let anchor_id = rand_anchor_id(); - AnchorOutput::build_with_amount(rand_number_range(Output::AMOUNT_MIN..token_supply), anchor_id) + AnchorOutput::build_with_amount(rand_number_range(0..token_supply), anchor_id) .with_features(rand_allowed_features(AnchorOutput::ALLOWED_FEATURES)) .add_unlock_condition(rand_state_controller_address_unlock_condition_different_from( &anchor_id, @@ -87,7 +87,7 @@ pub fn rand_anchor_output(token_supply: u64) -> AnchorOutput { .add_unlock_condition(rand_state_controller_address_unlock_condition_different_from( &anchor_id, )) - .finish_with_params(token_supply) + .finish() .unwrap() } @@ -102,15 +102,11 @@ pub fn rand_token_scheme() -> TokenScheme { /// Generates a random [`FoundryOutput`](FoundryOutput). pub fn rand_foundry_output(token_supply: u64) -> FoundryOutput { - FoundryOutput::build_with_amount( - rand_number_range(Output::AMOUNT_MIN..token_supply), - rand_number(), - rand_token_scheme(), - ) - .with_features(rand_allowed_features(FoundryOutput::ALLOWED_FEATURES)) - .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(rand_account_address())) - .finish_with_params(token_supply) - .unwrap() + FoundryOutput::build_with_amount(rand_number_range(0..token_supply), rand_number(), rand_token_scheme()) + .with_features(rand_allowed_features(FoundryOutput::ALLOWED_FEATURES)) + .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(rand_account_address())) + .finish() + .unwrap() } /// Generates a random [`NftOutput`](NftOutput). @@ -118,10 +114,10 @@ pub fn rand_nft_output(token_supply: u64) -> NftOutput { // We need to make sure that `NftId` and `Address` don't match. let nft_id = NftId::from(rand_bytes_array()); - NftOutput::build_with_amount(rand_number_range(Output::AMOUNT_MIN..token_supply), nft_id) + NftOutput::build_with_amount(rand_number_range(0..token_supply), nft_id) .with_features(rand_allowed_features(NftOutput::ALLOWED_FEATURES)) .add_unlock_condition(rand_address_unlock_condition_different_from(&nft_id)) - .finish_with_params(token_supply) + .finish() .unwrap() } diff --git a/sdk/src/types/block/semantic.rs b/sdk/src/types/block/semantic.rs index 18381736a8..d5d4d169b8 100644 --- a/sdk/src/types/block/semantic.rs +++ b/sdk/src/types/block/semantic.rs @@ -273,6 +273,7 @@ impl<'a> SemanticValidationContext<'a> { None, output.unlock_conditions(), ), + Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), Output::Foundry(output) => ( output.unlock(output_id, unlock, &mut self), output.amount(), @@ -294,7 +295,6 @@ impl<'a> SemanticValidationContext<'a> { None, output.unlock_conditions(), ), - Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), }; if let Err(conflict) = conflict { @@ -357,10 +357,10 @@ impl<'a> SemanticValidationContext<'a> { ) } Output::Account(output) => (output.amount(), output.mana(), None, Some(output.features())), + Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), Output::Foundry(output) => (output.amount(), 0, output.native_token(), Some(output.features())), Output::Nft(output) => (output.amount(), output.mana(), None, Some(output.features())), Output::Delegation(output) => (output.amount(), 0, None, None), - Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), }; if let Some(unlock_conditions) = created_output.unlock_conditions() { @@ -411,9 +411,9 @@ impl<'a> SemanticValidationContext<'a> { if match &created_output { Output::Account(_) => !address.has_capability(AddressCapabilityFlag::AccountOutputs), + Output::Anchor(_) => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), Output::Nft(_) => !address.has_capability(AddressCapabilityFlag::NftOutputs), Output::Delegation(_) => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), - Output::Anchor(_) => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), _ => false, } { // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 diff --git a/sdk/src/types/block/unlock/account.rs b/sdk/src/types/block/unlock/account.rs index 25ade32fcd..f8f7872ac3 100644 --- a/sdk/src/types/block/unlock/account.rs +++ b/sdk/src/types/block/unlock/account.rs @@ -36,6 +36,7 @@ impl AccountUnlock { } } +#[cfg(feature = "serde")] mod dto { use serde::{Deserialize, Serialize}; diff --git a/sdk/src/types/block/unlock/anchor.rs b/sdk/src/types/block/unlock/anchor.rs index 21073c48d4..aad3c49b78 100644 --- a/sdk/src/types/block/unlock/anchor.rs +++ b/sdk/src/types/block/unlock/anchor.rs @@ -21,7 +21,7 @@ impl TryFrom for AnchorUnlock { impl AnchorUnlock { /// The [`Unlock`](crate::types::block::unlock::Unlock) kind of an [`AnchorUnlock`]. - pub const KIND: u8 = 4; + pub const KIND: u8 = 3; /// Creates a new [`AnchorUnlock`]. #[inline(always)] @@ -36,6 +36,7 @@ impl AnchorUnlock { } } +#[cfg(feature = "serde")] mod dto { use serde::{Deserialize, Serialize}; diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs new file mode 100644 index 0000000000..2449e7668f --- /dev/null +++ b/sdk/src/types/block/unlock/empty.rs @@ -0,0 +1,44 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +/// Used to maintain correct index relationship between addresses and signatures when unlocking a +/// [`MultiAddress`](crate::types::block::address::MultiAddress) where not all addresses are unlocked. +#[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] +pub struct EmptyUnlock; + +impl EmptyUnlock { + /// The [`Unlock`](crate::types::block::unlock::Unlock) kind of an [`EmptyUnlock`]. + pub const KIND: u8 = 6; +} + +#[cfg(feature = "serde")] +mod dto { + use serde::{Deserialize, Serialize}; + + use super::*; + use crate::types::block::Error; + + #[derive(Serialize, Deserialize)] + struct EmptyUnlockDto { + #[serde(rename = "type")] + kind: u8, + } + + impl From<&EmptyUnlock> for EmptyUnlockDto { + fn from(_: &EmptyUnlock) -> Self { + Self { + kind: EmptyUnlock::KIND, + } + } + } + + impl TryFrom for EmptyUnlock { + type Error = Error; + + fn try_from(_: EmptyUnlockDto) -> Result { + Ok(Self) + } + } + + crate::impl_serde_typed_dto!(EmptyUnlock, EmptyUnlockDto, "empty unlock"); +} diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index 559c60a341..fce07f8150 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -3,6 +3,8 @@ mod account; mod anchor; +mod empty; +mod multi; mod nft; mod reference; mod signature; @@ -14,9 +16,10 @@ use derive_more::{Deref, From}; use hashbrown::HashSet; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; +pub(crate) use self::multi::UnlocksCount; pub use self::{ - account::AccountUnlock, anchor::AnchorUnlock, nft::NftUnlock, reference::ReferenceUnlock, - signature::SignatureUnlock, + account::AccountUnlock, anchor::AnchorUnlock, empty::EmptyUnlock, multi::MultiUnlock, nft::NftUnlock, + reference::ReferenceUnlock, signature::SignatureUnlock, }; use crate::types::block::{ input::{INPUT_COUNT_MAX, INPUT_COUNT_RANGE, INPUT_INDEX_MAX}, @@ -50,12 +53,18 @@ pub enum Unlock { /// An account unlock. #[packable(tag = AccountUnlock::KIND)] Account(AccountUnlock), + /// An anchor unlock. + #[packable(tag = AnchorUnlock::KIND)] + Anchor(AnchorUnlock), /// An NFT unlock. #[packable(tag = NftUnlock::KIND)] Nft(NftUnlock), - /// An Anchor unlock. - #[packable(tag = AnchorUnlock::KIND)] - Anchor(AnchorUnlock), + /// A multi unlock. + #[packable(tag = MultiUnlock::KIND)] + Multi(MultiUnlock), + /// An empty unlock. + #[packable(tag = EmptyUnlock::KIND)] + Empty(EmptyUnlock), } impl From for Unlock { @@ -70,8 +79,10 @@ impl core::fmt::Debug for Unlock { Self::Signature(unlock) => unlock.fmt(f), Self::Reference(unlock) => unlock.fmt(f), Self::Account(unlock) => unlock.fmt(f), - Self::Nft(unlock) => unlock.fmt(f), Self::Anchor(unlock) => unlock.fmt(f), + Self::Nft(unlock) => unlock.fmt(f), + Self::Multi(unlock) => unlock.fmt(f), + Self::Empty(unlock) => unlock.fmt(f), } } } @@ -83,12 +94,14 @@ impl Unlock { Self::Signature(_) => SignatureUnlock::KIND, Self::Reference(_) => ReferenceUnlock::KIND, Self::Account(_) => AccountUnlock::KIND, - Self::Nft(_) => NftUnlock::KIND, Self::Anchor(_) => AnchorUnlock::KIND, + Self::Nft(_) => NftUnlock::KIND, + Self::Multi(_) => MultiUnlock::KIND, + Self::Empty(_) => EmptyUnlock::KIND, } } - crate::def_is_as_opt!(Unlock: Signature, Reference, Account, Nft); + crate::def_is_as_opt!(Unlock: Signature, Reference, Account, Anchor, Nft, Multi, Empty); } pub(crate) type UnlockCount = BoundedU16<{ *UNLOCK_COUNT_RANGE.start() }, { *UNLOCK_COUNT_RANGE.end() }>; @@ -120,40 +133,62 @@ impl Unlocks { } } +/// Verifies the consistency of non-multi unlocks. +/// Will error on multi unlocks as they can't be nested. +fn verify_non_multi_unlock<'a>( + unlocks: &'a [Unlock], + unlock: &'a Unlock, + index: u16, + seen_signatures: &mut HashSet<&'a SignatureUnlock>, +) -> Result<(), Error> { + match unlock { + Unlock::Signature(signature) => { + if !seen_signatures.insert(signature.as_ref()) { + return Err(Error::DuplicateSignatureUnlock(index)); + } + } + Unlock::Reference(reference) => { + if index == 0 + || reference.index() >= index + || !matches!(unlocks[reference.index() as usize], Unlock::Signature(_)) + { + return Err(Error::InvalidUnlockReference(index)); + } + } + Unlock::Account(account) => { + if index == 0 || account.index() >= index { + return Err(Error::InvalidUnlockAccount(index)); + } + } + Unlock::Anchor(anchor) => { + if index == 0 || anchor.index() >= index { + return Err(Error::InvalidUnlockAnchor(index)); + } + } + Unlock::Nft(nft) => { + if index == 0 || nft.index() >= index { + return Err(Error::InvalidUnlockNft(index)); + } + } + Unlock::Multi(_) => return Err(Error::MultiUnlockRecursion), + Unlock::Empty(_) => {} + } + + Ok(()) +} + fn verify_unlocks(unlocks: &[Unlock], _: &()) -> Result<(), Error> { if VERIFY { let mut seen_signatures = HashSet::new(); for (index, unlock) in (0u16..).zip(unlocks.iter()) { match unlock { - Unlock::Signature(signature) => { - if !seen_signatures.insert(signature) { - return Err(Error::DuplicateSignatureUnlock(index)); - } - } - Unlock::Reference(reference) => { - if index == 0 - || reference.index() >= index - || !matches!(unlocks[reference.index() as usize], Unlock::Signature(_)) - { - return Err(Error::InvalidUnlockReference(index)); - } - } - Unlock::Account(account) => { - if index == 0 || account.index() >= index { - return Err(Error::InvalidUnlockAccount(index)); - } - } - Unlock::Nft(nft) => { - if index == 0 || nft.index() >= index { - return Err(Error::InvalidUnlockNft(index)); - } - } - Unlock::Anchor(anchor) => { - if index == 0 || anchor.index() >= index { - return Err(Error::InvalidUnlockAnchor(index)); + Unlock::Multi(multi) => { + for unlock in multi.unlocks() { + verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)? } } + _ => verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)?, } } } diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs new file mode 100644 index 0000000000..99a2f2f0fd --- /dev/null +++ b/sdk/src/types/block/unlock/multi.rs @@ -0,0 +1,82 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::boxed::Box; + +use derive_more::Deref; +use packable::{prefix::BoxedSlicePrefix, Packable}; + +use crate::types::block::{address::WeightedAddressCount, unlock::Unlock, Error}; + +pub(crate) type UnlocksCount = WeightedAddressCount; + +/// Unlocks a [`MultiAddress`](crate::types::block::address::MultiAddress) with a list of other unlocks. +#[derive(Clone, Debug, Deref, Eq, PartialEq, Hash, Packable)] +#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidMultiUnlockCount(p.into())))] +pub struct MultiUnlock(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix); + +impl MultiUnlock { + /// The [`Unlock`](crate::types::block::unlock::Unlock) kind of an [`MultiUnlock`]. + pub const KIND: u8 = 5; + + /// Creates a new [`MultiUnlock`]. + #[inline(always)] + pub fn new(unlocks: impl IntoIterator) -> Result { + let unlocks = unlocks.into_iter().collect::>(); + + verify_unlocks::(&unlocks, &())?; + + Ok(Self( + BoxedSlicePrefix::::try_from(unlocks).map_err(Error::InvalidMultiUnlockCount)?, + )) + } + + /// Return the inner unlocks of an [`MultiUnlock`]. + #[inline(always)] + pub fn unlocks(&self) -> &[Unlock] { + &self.0 + } +} + +fn verify_unlocks(unlocks: &[Unlock], _visitor: &()) -> Result<(), Error> { + if VERIFY && unlocks.iter().any(Unlock::is_multi) { + return Err(Error::MultiUnlockRecursion); + } else { + Ok(()) + } +} + +#[cfg(feature = "serde")] +mod dto { + use alloc::vec::Vec; + + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Serialize, Deserialize)] + struct MultiUnlockDto { + #[serde(rename = "type")] + kind: u8, + unlocks: Vec, + } + + impl From<&MultiUnlock> for MultiUnlockDto { + fn from(value: &MultiUnlock) -> Self { + Self { + kind: MultiUnlock::KIND, + unlocks: value.0.to_vec(), + } + } + } + + impl TryFrom for MultiUnlock { + type Error = Error; + + fn try_from(value: MultiUnlockDto) -> Result { + Self::new(value.unlocks) + } + } + + crate::impl_serde_typed_dto!(MultiUnlock, MultiUnlockDto, "multi unlock"); +} diff --git a/sdk/src/types/block/unlock/nft.rs b/sdk/src/types/block/unlock/nft.rs index 85323bdc99..930b647824 100644 --- a/sdk/src/types/block/unlock/nft.rs +++ b/sdk/src/types/block/unlock/nft.rs @@ -21,7 +21,7 @@ impl TryFrom for NftUnlock { impl NftUnlock { /// The [`Unlock`](crate::types::block::unlock::Unlock) kind of a [`NftUnlock`]. - pub const KIND: u8 = 3; + pub const KIND: u8 = 4; /// Creates a new [`NftUnlock`]. #[inline(always)] diff --git a/sdk/src/types/fuzz/Cargo.toml b/sdk/src/types/fuzz/Cargo.toml index 44190bc39e..787a244911 100644 --- a/sdk/src/types/fuzz/Cargo.toml +++ b/sdk/src/types/fuzz/Cargo.toml @@ -12,7 +12,7 @@ cargo-fuzz = true iota-types = { path = "..", default-features = false } libfuzzer-sys = { version = "0.4.7", default-features = false } -packable = { version = "0.8.3", default-features = false } +packable = { version = "0.9.0", default-features = false } # Prevent this from interfering with workspaces [workspace] diff --git a/sdk/src/types/block/convert.rs b/sdk/src/utils/convert.rs similarity index 100% rename from sdk/src/types/block/convert.rs rename to sdk/src/utils/convert.rs diff --git a/sdk/src/utils/mod.rs b/sdk/src/utils/mod.rs index 9671dfe13d..1341ced35a 100644 --- a/sdk/src/utils/mod.rs +++ b/sdk/src/utils/mod.rs @@ -1,6 +1,10 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod convert; + pub mod merkle_hasher; #[cfg(feature = "serde")] pub mod serde; + +pub use convert::ConvertTo; diff --git a/sdk/src/wallet/account/builder.rs b/sdk/src/wallet/account/builder.rs deleted file mode 100644 index 98a50fae3d..0000000000 --- a/sdk/src/wallet/account/builder.rs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::{HashMap, HashSet}; - -use tokio::sync::RwLock; - -use crate::{ - client::secret::{SecretManage, SecretManager}, - types::block::address::{Address, Bech32Address, Ed25519Address, Hrp}, - wallet::{ - account::{types::Bip44Address, Account, AccountDetails}, - Error, Wallet, - }, -}; - -/// The AccountBuilder -pub struct AccountBuilder { - addresses: Option>, - alias: Option, - bech32_hrp: Option, - wallet: Wallet, -} - -impl AccountBuilder -where - crate::wallet::Error: From, -{ - /// Create an IOTA client builder - pub fn new(wallet: Wallet) -> Self { - Self { - addresses: None, - alias: None, - bech32_hrp: None, - wallet, - } - } - - /// Set the addresses, should only be used for accounts with an offline counterpart account from which the addresses - /// were exported - pub fn with_addresses(mut self, addresses: impl Into>>) -> Self { - self.addresses = addresses.into(); - self - } - - /// Set the alias - pub fn with_alias(mut self, alias: impl Into) -> Self { - self.alias = Some(alias.into()); - self - } - - /// Set the bech32 HRP - pub fn with_bech32_hrp(mut self, bech32_hrp: impl Into>) -> Self { - self.bech32_hrp = bech32_hrp.into(); - self - } - - /// Build the Account and add it to the accounts from Wallet - /// Also generates the first address of the account and if it's not the first account, the address for the first - /// account will also be generated and compared, so no accounts get generated with different seeds - pub async fn finish(&mut self) -> crate::wallet::Result> { - let mut accounts = self.wallet.accounts.write().await; - let account_index = accounts.len() as u32; - // If no alias is provided, the account index will be set as alias - let account_alias = self.alias.clone().unwrap_or_else(|| account_index.to_string()); - log::debug!( - "[ACCOUNT BUILDER] creating new account {} with index {}", - account_alias, - account_index - ); - - // Check that the alias isn't already used for another account - for account in accounts.iter() { - let account = account.details().await; - if account.alias().to_lowercase() == account_alias.to_lowercase() { - return Err(Error::AccountAliasAlreadyExists(account_alias)); - } - } - - let coin_type = self.wallet.coin_type.load(core::sync::atomic::Ordering::Relaxed); - - // If addresses are provided we will use them directly without the additional checks, because then we assume - // that it's for offline signing and the secretManager can't be used - let addresses = match &self.addresses { - Some(addresses) => addresses.clone(), - None => { - let mut bech32_hrp = self.bech32_hrp; - if let Some(first_account) = accounts.first() { - let first_account_coin_type = *first_account.details().await.coin_type(); - // Generate the first address of the first account and compare it to the stored address from the - // first account to prevent having multiple accounts created with different - // seeds - let first_account_public_address = - get_first_public_address(&self.wallet.secret_manager, first_account_coin_type, 0).await?; - let first_account_addresses = first_account.public_addresses().await; - - if Address::Ed25519(first_account_public_address) - != first_account_addresses - .first() - .ok_or(Error::FailedToGetRemainder)? - .address - .inner - { - return Err(Error::InvalidMnemonic( - "first account address used another seed".to_string(), - )); - } - - // Get bech32_hrp from address - if let Some(address) = first_account_addresses.first() { - if bech32_hrp.is_none() { - bech32_hrp = Some(address.address.hrp); - } - } - } - - // get bech32_hrp - let bech32_hrp = { - match bech32_hrp { - Some(bech32_hrp) => bech32_hrp, - None => self.wallet.client().get_bech32_hrp().await?, - } - }; - - let first_public_address = - get_first_public_address(&self.wallet.secret_manager, coin_type, account_index).await?; - - let first_public_account_address = Bip44Address { - address: Bech32Address::new(bech32_hrp, first_public_address), - key_index: 0, - internal: false, - }; - - vec![first_public_account_address] - } - }; - - let account = AccountDetails { - index: account_index, - coin_type, - alias: account_alias, - public_addresses: addresses, - internal_addresses: Vec::new(), - addresses_with_unspent_outputs: Vec::new(), - outputs: HashMap::new(), - locked_outputs: HashSet::new(), - unspent_outputs: HashMap::new(), - transactions: HashMap::new(), - pending_transactions: HashSet::new(), - incoming_transactions: HashMap::new(), - inaccessible_incoming_transactions: HashSet::new(), - native_token_foundries: HashMap::new(), - }; - - let account = Account::new(account, self.wallet.inner.clone()).await?; - #[cfg(feature = "storage")] - account.save(None).await?; - accounts.push(account.clone()); - - Ok(account) - } -} - -/// Generate the first public address of an account -pub(crate) async fn get_first_public_address( - secret_manager: &RwLock, - coin_type: u32, - account_index: u32, -) -> crate::wallet::Result -where - crate::wallet::Error: From, -{ - Ok(secret_manager - .read() - .await - .generate_ed25519_addresses(coin_type, account_index, 0..1, None) - .await?[0]) -} diff --git a/sdk/src/wallet/account/mod.rs b/sdk/src/wallet/account/mod.rs deleted file mode 100644 index 8e52ef7f0f..0000000000 --- a/sdk/src/wallet/account/mod.rs +++ /dev/null @@ -1,751 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -/// The module with the AccountBuilder. -pub(crate) mod builder; -/// Constants used for the account and account operations. -pub(crate) mod constants; -/// The account operations like address generation, syncing and creating transactions. -pub(crate) mod operations; -/// Types used in an account and returned from methods. -pub mod types; -/// Methods to update the account state. -pub(crate) mod update; - -use std::{ - collections::{HashMap, HashSet}, - ops::Deref, - sync::Arc, -}; - -use getset::{Getters, Setters}; -use serde::{Deserialize, Serialize}; -use tokio::sync::{Mutex, RwLock}; - -#[cfg(feature = "participation")] -pub use self::operations::participation::{AccountParticipationOverview, ParticipationEventWithNodes}; -use self::types::{ - address::{AddressWithUnspentOutputs, Bip44Address}, - Balance, OutputData, TransactionWithMetadata, TransactionWithMetadataDto, -}; -pub use self::{ - operations::{ - output_claiming::OutputsToClaim, - output_consolidation::ConsolidationParams, - syncing::{ - options::{AccountSyncOptions, AliasSyncOptions, NftSyncOptions}, - SyncOptions, - }, - transaction::{ - high_level::{ - create_account::CreateAccountParams, - minting::{ - create_native_token::{ - CreateNativeTokenParams, CreateNativeTokenTransactionDto, - PreparedCreateNativeTokenTransactionDto, - }, - mint_nfts::MintNftParams, - }, - }, - prepare_output::{Assets, Features, OutputParams, ReturnStrategy, StorageDeposit, Unlocks}, - RemainderValueStrategy, TransactionOptions, - }, - }, - types::OutputDataDto, -}; -use super::core::WalletInner; -use crate::{ - client::{ - secret::{SecretManage, SecretManager}, - Client, - }, - types::{ - api::core::OutputWithMetadataResponse, - block::{ - address::Bech32Address, - output::{dto::FoundryOutputDto, AccountId, FoundryId, FoundryOutput, NftId, Output, OutputId, TokenId}, - payload::{signed_transaction::TransactionId, SignedTransactionPayload}, - }, - TryFromDto, - }, - wallet::{account::types::InclusionState, Result}, -}; - -/// Options to filter outputs -#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct FilterOptions { - /// Filter all outputs where the booked milestone index is below the specified timestamp - pub lower_bound_booked_timestamp: Option, - /// Filter all outputs where the booked milestone index is above the specified timestamp - pub upper_bound_booked_timestamp: Option, - /// Filter all outputs for the provided types (Basic = 3, Account = 4, Foundry = 5, NFT = 6). - pub output_types: Option>, - /// Return all account outputs matching these IDs. - pub account_ids: Option>, - /// Return all foundry outputs matching these IDs. - pub foundry_ids: Option>, - /// Return all nft outputs matching these IDs. - pub nft_ids: Option>, -} - -/// Details of an account. -#[derive(Clone, Debug, Eq, PartialEq, Getters, Setters)] -#[getset(get = "pub")] -pub struct AccountDetails { - /// The account index - index: u32, - /// The coin type - coin_type: u32, - /// The account alias. - alias: String, - /// Public addresses - pub(crate) public_addresses: Vec, - /// Internal addresses - pub(crate) internal_addresses: Vec, - /// Addresses with unspent outputs - // used to improve performance for syncing and get balance because it's in most cases only a subset of all - // addresses - addresses_with_unspent_outputs: Vec, - /// Outputs - // stored separated from the account for performance? - outputs: HashMap, - /// Unspent outputs that are currently used as input for transactions - // outputs used in transactions should be locked here so they don't get used again, which would result in a - // conflicting transaction - pub(crate) locked_outputs: HashSet, - /// Unspent outputs - // have unspent outputs in a separated hashmap so we don't need to iterate over all outputs we have - unspent_outputs: HashMap, - /// Sent transactions - // stored separated from the account for performance and only the transaction id here? where to add the network id? - // transactions: HashSet, - transactions: HashMap, - /// Pending transactions - // Maybe pending transactions even additionally separated? - pending_transactions: HashSet, - /// Transaction payloads for received outputs with inputs when not pruned before syncing, can be used to determine - /// the sender address(es) - incoming_transactions: HashMap, - /// Some incoming transactions can be pruned by the node before we requested them, then this node can never return - /// it. To avoid useless requests, these transaction ids are stored here and cleared when new client options are - /// set, because another node might still have them. - inaccessible_incoming_transactions: HashSet, - /// Foundries for native tokens in outputs - native_token_foundries: HashMap, -} - -/// A thread guard over an account, so we can lock the account during operations. -#[derive(Debug)] -pub struct Account { - inner: Arc, - pub(crate) wallet: Arc>, -} - -impl Clone for Account { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - wallet: self.wallet.clone(), - } - } -} - -impl Account { - pub fn get_secret_manager(&self) -> &Arc> { - self.wallet.get_secret_manager() - } -} - -#[derive(Debug)] -pub struct AccountInner { - details: RwLock, - // mutex to prevent multiple sync calls at the same or almost the same time, the u128 is a timestamp - // if the last synced time was < `MIN_SYNC_INTERVAL` second ago, we don't sync, but only calculate the balance - // again, because sending transactions can change that - pub(crate) last_synced: Mutex, - pub(crate) default_sync_options: Mutex, -} - -// impl Deref so we can use `account.details()` instead of `account.details.read()` -impl Deref for Account { - type Target = AccountInner; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl Account -where - crate::wallet::Error: From, -{ - /// Create a new Account with an AccountDetails - pub(crate) async fn new(details: AccountDetails, wallet: Arc>) -> Result { - #[cfg(feature = "storage")] - let default_sync_options = wallet - .storage_manager - .read() - .await - .get_default_sync_options(*details.index()) - .await? - .unwrap_or_default(); - #[cfg(not(feature = "storage"))] - let default_sync_options = Default::default(); - - Ok(Self { - wallet, - inner: Arc::new(AccountInner { - details: RwLock::new(details), - last_synced: Default::default(), - default_sync_options: Mutex::new(default_sync_options), - }), - }) - } - - // Get the Client - pub fn client(&self) -> &Client { - &self.wallet.client - } - - /// Get the [`Output`] that minted a native token by the token ID. First try to get it - /// from the account, if it isn't in the account try to get it from the node - pub async fn get_foundry_output(&self, native_token_id: TokenId) -> Result { - let foundry_id = FoundryId::from(native_token_id); - - for output_data in self.details().await.outputs().values() { - if let Output::Foundry(foundry_output) = &output_data.output { - if foundry_output.id() == foundry_id { - return Ok(output_data.output.clone()); - } - } - } - - // Foundry was not found in the account, try to get it from the node - let foundry_output_id = self.client().foundry_output_id(foundry_id).await?; - let output = self.client().get_output(&foundry_output_id).await?; - - Ok(output) - } - - /// Save the account to the database, accepts the updated_account as option so we don't need to drop it before - /// saving - #[cfg(feature = "storage")] - pub(crate) async fn save(&self, updated_account: Option<&AccountDetails>) -> Result<()> { - log::debug!("[save] saving account to database"); - match updated_account { - Some(account) => { - let mut storage_manager = self.wallet.storage_manager.write().await; - storage_manager.save_account(account).await?; - drop(storage_manager); - } - None => { - let account_details = self.details().await; - let mut storage_manager = self.wallet.storage_manager.write().await; - storage_manager.save_account(&account_details).await?; - drop(storage_manager); - drop(account_details); - } - } - Ok(()) - } - - #[cfg(feature = "events")] - pub(crate) async fn emit(&self, account_index: u32, wallet_event: super::events::types::WalletEvent) { - self.wallet.emit(account_index, wallet_event).await - } -} - -impl AccountInner { - pub async fn details(&self) -> tokio::sync::RwLockReadGuard<'_, AccountDetails> { - self.details.read().await - } - - pub async fn details_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, AccountDetails> { - self.details.write().await - } - - pub async fn alias(&self) -> String { - self.details().await.alias.clone() - } - - /// Get the [`OutputData`] of an output stored in the account - pub async fn get_output(&self, output_id: &OutputId) -> Option { - self.details().await.outputs().get(output_id).cloned() - } - - /// Get the [`Transaction`] of a transaction stored in the account - pub async fn get_transaction(&self, transaction_id: &TransactionId) -> Option { - self.details().await.transactions().get(transaction_id).cloned() - } - - /// Get the transaction with inputs of an incoming transaction stored in the account - /// List might not be complete, if the node pruned the data already - pub async fn get_incoming_transaction(&self, transaction_id: &TransactionId) -> Option { - self.details() - .await - .incoming_transactions() - .get(transaction_id) - .cloned() - } - - /// Returns all addresses of the account - pub async fn addresses(&self) -> Vec { - let account_details = self.details().await; - let mut all_addresses = account_details.public_addresses().clone(); - all_addresses.extend(account_details.internal_addresses().clone()); - - all_addresses.to_vec() - } - - /// Returns the first address of the account as bech32 - pub async fn first_address_bech32(&self) -> Bech32Address { - // PANIC: unwrap is fine as one address is always generated during account creation. - self.addresses().await.into_iter().next().unwrap().into_bech32() - } - - /// Returns all public addresses of the account - pub(crate) async fn public_addresses(&self) -> Vec { - self.details().await.public_addresses().to_vec() - } - - /// Returns only addresses of the account with balance - pub async fn addresses_with_unspent_outputs(&self) -> Result> { - Ok(self.details().await.addresses_with_unspent_outputs().to_vec()) - } - - fn filter_outputs<'a>( - &self, - outputs: impl Iterator, - filter: impl Into>, - ) -> Result> { - let filter = filter.into(); - - if let Some(filter) = filter { - let mut filtered_outputs = Vec::new(); - - for output in outputs { - match &output.output { - Output::Account(alias) => { - if let Some(account_ids) = &filter.account_ids { - let account_id = alias.account_id_non_null(&output.output_id); - if account_ids.contains(&account_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - Output::Foundry(foundry) => { - if let Some(foundry_ids) = &filter.foundry_ids { - let foundry_id = foundry.id(); - if foundry_ids.contains(&foundry_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - Output::Nft(nft) => { - if let Some(nft_ids) = &filter.nft_ids { - let nft_id = nft.nft_id_non_null(&output.output_id); - if nft_ids.contains(&nft_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - _ => {} - } - - // TODO check if we can still filter since milestone_timestamp_booked is gone - // if let Some(lower_bound_booked_timestamp) = filter.lower_bound_booked_timestamp { - // if output.metadata.milestone_timestamp_booked() < lower_bound_booked_timestamp { - // continue; - // } - // } - // if let Some(upper_bound_booked_timestamp) = filter.upper_bound_booked_timestamp { - // if output.metadata.milestone_timestamp_booked() > upper_bound_booked_timestamp { - // continue; - // } - // } - - if let Some(output_types) = &filter.output_types { - if !output_types.contains(&output.output.kind()) { - continue; - } - } - - // If ids are provided, only return them and no other outputs. - if filter.account_ids.is_none() && filter.foundry_ids.is_none() && filter.nft_ids.is_none() { - filtered_outputs.push(output.clone()); - } - } - - Ok(filtered_outputs) - } else { - Ok(outputs.cloned().collect()) - } - } - - /// Returns outputs of the account - pub async fn outputs(&self, filter: impl Into> + Send) -> Result> { - self.filter_outputs(self.details().await.outputs.values(), filter) - } - - /// Returns unspent outputs of the account - pub async fn unspent_outputs(&self, filter: impl Into> + Send) -> Result> { - self.filter_outputs(self.details().await.unspent_outputs.values(), filter) - } - - /// Gets the unspent account output matching the given ID. - pub async fn unspent_account_output(&self, account_id: &AccountId) -> Result> { - self.unspent_outputs(FilterOptions { - account_ids: Some([*account_id].into()), - ..Default::default() - }) - .await - .map(|res| res.get(0).cloned()) - } - - /// Gets the unspent foundry output matching the given ID. - pub async fn unspent_foundry_output(&self, foundry_id: &FoundryId) -> Result> { - self.unspent_outputs(FilterOptions { - foundry_ids: Some([*foundry_id].into()), - ..Default::default() - }) - .await - .map(|res| res.get(0).cloned()) - } - - /// Gets the unspent nft output matching the given ID. - pub async fn unspent_nft_output(&self, nft_id: &NftId) -> Result> { - self.unspent_outputs(FilterOptions { - nft_ids: Some([*nft_id].into()), - ..Default::default() - }) - .await - .map(|res| res.get(0).cloned()) - } - - /// Returns all incoming transactions of the account - pub async fn incoming_transactions(&self) -> Vec { - self.details().await.incoming_transactions.values().cloned().collect() - } - - /// Returns all transactions of the account - pub async fn transactions(&self) -> Vec { - self.details().await.transactions.values().cloned().collect() - } - - /// Returns all pending transactions of the account - pub async fn pending_transactions(&self) -> Vec { - let mut transactions = Vec::new(); - let account_details = self.details().await; - - for transaction_id in &account_details.pending_transactions { - if let Some(transaction) = account_details.transactions.get(transaction_id) { - transactions.push(transaction.clone()); - } - } - - transactions - } -} - -pub(crate) fn build_transaction_from_payload_and_inputs( - tx_id: TransactionId, - tx_payload: SignedTransactionPayload, - inputs: Vec, -) -> crate::wallet::Result { - Ok(TransactionWithMetadata { - payload: tx_payload.clone(), - block_id: inputs.first().map(|i| *i.metadata.block_id()), - inclusion_state: InclusionState::Confirmed, - timestamp: 0, - // TODO check if we keep a timestamp in Transaction since milestone_timestamp_spent is gone - // inputs - // .first() - // .and_then(|i| i.metadata.milestone_timestamp_spent.map(|t| t as u128 * 1000)) - // .unwrap_or_else(|| crate::utils::unix_timestamp_now().as_millis()), - transaction_id: tx_id, - network_id: tx_payload.transaction().network_id(), - incoming: true, - note: None, - inputs, - }) -} - -/// Dto for an Account. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AccountDetailsDto { - /// The account index - pub index: u32, - /// The coin type - pub coin_type: u32, - /// The account alias. - pub alias: String, - /// Public addresses - pub public_addresses: Vec, - /// Internal addresses - pub internal_addresses: Vec, - /// Addresses with unspent outputs - pub addresses_with_unspent_outputs: Vec, - /// Outputs - pub outputs: HashMap, - /// Unspent outputs that are currently used as input for transactions - pub locked_outputs: HashSet, - /// Unspent outputs - pub unspent_outputs: HashMap, - /// Sent transactions - pub transactions: HashMap, - /// Pending transactions - pub pending_transactions: HashSet, - /// Incoming transactions - pub incoming_transactions: HashMap, - /// Foundries for native tokens in outputs - #[serde(default)] - pub native_token_foundries: HashMap, -} - -impl TryFromDto for AccountDetails { - type Dto = AccountDetailsDto; - type Error = crate::wallet::Error; - - fn try_from_dto_with_params_inner( - dto: Self::Dto, - params: crate::types::ValidationParams<'_>, - ) -> core::result::Result { - Ok(Self { - index: dto.index, - coin_type: dto.coin_type, - alias: dto.alias, - public_addresses: dto.public_addresses, - internal_addresses: dto.internal_addresses, - addresses_with_unspent_outputs: dto.addresses_with_unspent_outputs, - outputs: dto - .outputs - .into_iter() - .map(|(id, o)| Ok((id, OutputData::try_from_dto_with_params(o, ¶ms)?))) - .collect::>()?, - locked_outputs: dto.locked_outputs, - unspent_outputs: dto - .unspent_outputs - .into_iter() - .map(|(id, o)| Ok((id, OutputData::try_from_dto_with_params(o, ¶ms)?))) - .collect::>()?, - transactions: dto - .transactions - .into_iter() - .map(|(id, o)| Ok((id, TransactionWithMetadata::try_from_dto_with_params(o, ¶ms)?))) - .collect::>()?, - pending_transactions: dto.pending_transactions, - incoming_transactions: dto - .incoming_transactions - .into_iter() - .map(|(id, o)| Ok((id, TransactionWithMetadata::try_from_dto_with_params(o, ¶ms)?))) - .collect::>()?, - inaccessible_incoming_transactions: Default::default(), - native_token_foundries: dto - .native_token_foundries - .into_iter() - .map(|(id, o)| Ok((id, FoundryOutput::try_from_dto_with_params(o, ¶ms)?))) - .collect::>()?, - }) - } -} - -impl From<&AccountDetails> for AccountDetailsDto { - fn from(value: &AccountDetails) -> Self { - Self { - index: *value.index(), - coin_type: *value.coin_type(), - alias: value.alias().clone(), - public_addresses: value.public_addresses().clone(), - internal_addresses: value.internal_addresses().clone(), - addresses_with_unspent_outputs: value.addresses_with_unspent_outputs().clone(), - outputs: value - .outputs() - .iter() - .map(|(id, output)| (*id, OutputDataDto::from(output))) - .collect(), - locked_outputs: value.locked_outputs().clone(), - unspent_outputs: value - .unspent_outputs() - .iter() - .map(|(id, output)| (*id, OutputDataDto::from(output))) - .collect(), - transactions: value - .transactions() - .iter() - .map(|(id, transaction)| (*id, TransactionWithMetadataDto::from(transaction))) - .collect(), - pending_transactions: value.pending_transactions().clone(), - incoming_transactions: value - .incoming_transactions() - .iter() - .map(|(id, transaction)| (*id, TransactionWithMetadataDto::from(transaction))) - .collect(), - native_token_foundries: value - .native_token_foundries() - .iter() - .map(|(id, foundry)| (*id, FoundryOutputDto::from(foundry))) - .collect(), - } - } -} - -#[cfg(test)] -mod test { - use core::str::FromStr; - - use pretty_assertions::assert_eq; - - use super::*; - use crate::types::block::{ - address::{Address, Ed25519Address}, - input::{Input, UtxoInput}, - output::{AddressUnlockCondition, BasicOutput, Output}, - payload::signed_transaction::{SignedTransactionPayload, Transaction, TransactionId}, - protocol::ProtocolParameters, - rand::mana::rand_mana_allotment, - signature::{Ed25519Signature, Signature}, - unlock::{ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, - }; - - const TRANSACTION_ID: &str = "0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d588300000000"; - const ED25519_ADDRESS: &str = "0xe594f9a895c0e0a6760dd12cffc2c3d1e1cbf7269b328091f96ce3d0dd550b75"; - const ED25519_PUBLIC_KEY: &str = "0x1da5ddd11ba3f961acab68fafee3177d039875eaa94ac5fdbff8b53f0c50bfb9"; - const ED25519_SIGNATURE: &str = "0xc6a40edf9a089f42c18f4ebccb35fe4b578d93b879e99b87f63573324a710d3456b03fb6d1fcc027e6401cbd9581f790ee3ed7a3f68e9c225fcb9f1cd7b7110d"; - - #[test] - fn serialize() { - let protocol_parameters = ProtocolParameters::new( - 2, - "testnet", - "rms", - crate::types::block::output::RentStructure::new(500, 1, 10, 1, 1, 1), - 1_813_620_509_061_365, - 1582328545, - 10, - 20, - ) - .unwrap(); - - let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); - let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); - let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); - let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS).unwrap(); - let address = Address::from(Ed25519Address::new(bytes)); - let amount = 1_000_000; - let output = Output::Basic( - BasicOutput::build_with_amount(amount) - .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.clone()) - .unwrap(), - ); - let transaction = Transaction::builder(protocol_parameters.network_id()) - .with_inputs([input1, input2]) - .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(protocol_parameters) - .unwrap(); - - let pub_key_bytes = prefix_hex::decode(ED25519_PUBLIC_KEY).unwrap(); - let sig_bytes = prefix_hex::decode(ED25519_SIGNATURE).unwrap(); - let signature = Ed25519Signature::try_from_bytes(pub_key_bytes, sig_bytes).unwrap(); - let sig_unlock = Unlock::from(SignatureUnlock::from(Signature::from(signature))); - let ref_unlock = Unlock::from(ReferenceUnlock::new(0).unwrap()); - let unlocks = Unlocks::new([sig_unlock, ref_unlock]).unwrap(); - - let tx_payload = SignedTransactionPayload::new(transaction, unlocks).unwrap(); - - let incoming_transaction = TransactionWithMetadata { - transaction_id: TransactionId::from_str( - "0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a00000000", - ) - .unwrap(), - payload: tx_payload, - block_id: None, - network_id: 0, - timestamp: 0, - inclusion_state: InclusionState::Pending, - incoming: false, - note: None, - inputs: Vec::new(), - }; - - let mut incoming_transactions = HashMap::new(); - incoming_transactions.insert( - TransactionId::from_str("0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a00000000") - .unwrap(), - incoming_transaction, - ); - - let account = AccountDetails { - index: 0, - coin_type: 4218, - alias: "Alice".to_string(), - public_addresses: vec![Bip44Address { - address: crate::types::block::address::Bech32Address::from_str( - "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", - ) - .unwrap(), - key_index: 0, - internal: false, - }], - internal_addresses: Vec::new(), - addresses_with_unspent_outputs: Vec::new(), - outputs: HashMap::new(), - locked_outputs: HashSet::new(), - unspent_outputs: HashMap::new(), - transactions: HashMap::new(), - pending_transactions: HashSet::new(), - incoming_transactions, - inaccessible_incoming_transactions: HashSet::new(), - native_token_foundries: HashMap::new(), - }; - - let deser_account = AccountDetails::try_from_dto( - serde_json::from_str::( - &serde_json::to_string(&AccountDetailsDto::from(&account)).unwrap(), - ) - .unwrap(), - ) - .unwrap(); - - assert_eq!(account, deser_account); - } - - impl AccountDetails { - /// Returns a mock of this type with the following values: - /// index: 0, coin_type: 4218, alias: "Alice", public_addresses: contains a single public account address - /// (rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy), all other fields are set to their Rust - /// defaults. - #[cfg(feature = "storage")] - pub(crate) fn mock() -> Self { - Self { - index: 0, - coin_type: 4218, - alias: "Alice".to_string(), - public_addresses: vec![Bip44Address { - address: crate::types::block::address::Bech32Address::from_str( - "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", - ) - .unwrap(), - key_index: 0, - internal: false, - }], - internal_addresses: Vec::new(), - addresses_with_unspent_outputs: Vec::new(), - outputs: HashMap::new(), - locked_outputs: HashSet::new(), - unspent_outputs: HashMap::new(), - transactions: HashMap::new(), - pending_transactions: HashSet::new(), - incoming_transactions: HashMap::new(), - inaccessible_incoming_transactions: HashSet::new(), - native_token_foundries: HashMap::new(), - } - } - } -} diff --git a/sdk/src/wallet/account/operations/address_generation.rs b/sdk/src/wallet/account/operations/address_generation.rs deleted file mode 100644 index 5d7d100cfc..0000000000 --- a/sdk/src/wallet/account/operations/address_generation.rs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "ledger_nano")] -use crate::client::secret::{ledger_nano::LedgerSecretManager, DowncastSecretManager}; -use crate::{ - client::secret::{GenerateAddressOptions, SecretManage}, - types::block::address::Bech32Address, - wallet::account::{types::address::Bip44Address, Account}, -}; -#[cfg(all(feature = "events", feature = "ledger_nano"))] -use crate::{ - types::block::address::ToBech32Ext, - wallet::events::types::{AddressData, WalletEvent}, -}; - -impl Account -where - crate::wallet::Error: From, -{ - /// Generate addresses and stores them in the account - /// ```ignore - /// let public_addresses = account.generate_ed25519_addresses(2, None).await?; - /// // internal addresses are used for remainder outputs, if the RemainderValueStrategy for transactions is set to ChangeAddress - /// let internal_addresses = account - /// .generate_ed25519_addresses( - /// 1, - /// Some(GenerateAddressOptions { - /// internal: true, - /// ..Default::default() - /// }), - /// ) - /// .await?; - /// ``` - pub async fn generate_ed25519_addresses( - &self, - amount: u32, - options: impl Into> + Send, - ) -> crate::wallet::Result> { - let options = options.into().unwrap_or_default(); - log::debug!( - "[ADDRESS GENERATION] generating {amount} addresses, internal: {}", - options.internal - ); - if amount == 0 { - return Ok(Vec::new()); - } - - let account_details = self.details().await; - - // get the highest index for the public or internal addresses - let highest_current_index_plus_one = if options.internal { - account_details.internal_addresses.len() as u32 - } else { - account_details.public_addresses.len() as u32 - }; - - // get bech32_hrp - let bech32_hrp = { - match account_details.public_addresses.first() { - Some(address) => address.address.hrp, - None => self.client().get_bech32_hrp().await?, - } - }; - - let address_range = highest_current_index_plus_one..highest_current_index_plus_one + amount; - - // If we don't sync, then we want to display the prompt on the ledger with the address. But the user - // needs to have it visible on the computer first, so we need to generate it without the - // prompt first - #[cfg(feature = "ledger_nano")] - let addresses = { - use crate::wallet::account::SecretManager; - let secret_manager = self.wallet.secret_manager.read().await; - if secret_manager - .downcast::() - .or_else(|| { - secret_manager.downcast::().and_then(|s| { - if let SecretManager::LedgerNano(n) = s { - Some(n) - } else { - None - } - }) - }) - .is_some() - { - #[cfg(feature = "events")] - let changed_options = { - // Change options so ledger will not show the prompt the first time - let mut changed_options = options; - changed_options.ledger_nano_prompt = false; - changed_options - }; - let mut addresses = Vec::new(); - - for address_index in address_range { - #[cfg(feature = "events")] - { - // Generate without prompt to be able to display it - let address = self - .wallet - .secret_manager - .read() - .await - .generate_ed25519_addresses( - account_details.coin_type, - account_details.index, - address_index..address_index + 1, - Some(changed_options), - ) - .await?; - self.emit( - account_details.index, - WalletEvent::LedgerAddressGeneration(AddressData { - address: address[0].to_bech32(bech32_hrp), - }), - ) - .await; - } - // Generate with prompt so the user can verify - let address = self - .wallet - .secret_manager - .read() - .await - .generate_ed25519_addresses( - account_details.coin_type, - account_details.index, - address_index..address_index + 1, - Some(options), - ) - .await?; - addresses.push(address[0]); - } - addresses - } else { - self.wallet - .secret_manager - .read() - .await - .generate_ed25519_addresses( - account_details.coin_type, - account_details.index, - address_range, - Some(options), - ) - .await? - } - }; - - #[cfg(not(feature = "ledger_nano"))] - let addresses = self - .wallet - .secret_manager - .read() - .await - .generate_ed25519_addresses( - account_details.coin_type, - account_details.index, - address_range, - Some(options), - ) - .await?; - - drop(account_details); - - let generate_addresses: Vec = addresses - .into_iter() - .enumerate() - .map(|(index, address)| Bip44Address { - address: Bech32Address::new(bech32_hrp, address), - key_index: highest_current_index_plus_one + index as u32, - internal: options.internal, - }) - .collect(); - - self.update_account_addresses(options.internal, generate_addresses.clone()) - .await?; - - Ok(generate_addresses) - } - - /// Generate an internal address and store in the account, internal addresses are used for remainder outputs - pub(crate) async fn generate_remainder_address(&self) -> crate::wallet::Result { - let result = self - .generate_ed25519_addresses(1, Some(GenerateAddressOptions::internal())) - .await? - .first() - .ok_or(crate::wallet::Error::FailedToGetRemainder)? - .clone(); - - Ok(result) - } -} diff --git a/sdk/src/wallet/account/operations/balance.rs b/sdk/src/wallet/account/operations/balance.rs deleted file mode 100644 index 522ccc505b..0000000000 --- a/sdk/src/wallet/account/operations/balance.rs +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use primitive_types::U256; - -use crate::{ - client::secret::SecretManage, - types::block::{ - address::Bech32Address, - output::{unlock_condition::UnlockCondition, FoundryId, NativeTokensBuilder, Output, Rent}, - ConvertTo, - }, - wallet::{ - account::{ - operations::helpers::time::can_output_be_unlocked_forever_from_now_on, - types::{AddressWithUnspentOutputs, Balance, NativeTokensBalance}, - Account, AccountDetails, OutputsToClaim, - }, - Error, Result, - }, -}; - -impl Account -where - Error: From, - crate::client::Error: From, -{ - /// Get the balance of the account. - pub async fn balance(&self) -> Result { - log::debug!("[BALANCE] balance"); - - let account_details = self.details().await; - - self.balance_inner(account_details.addresses_with_unspent_outputs.iter(), &account_details) - .await - } - - /// Get the balance of the given addresses. - pub async fn addresses_balance(&self, addresses: Vec>) -> Result { - log::debug!("[BALANCE] addresses_balance"); - - let account_details = self.details().await; - - let addresses_with_unspent_outputs = addresses - .into_iter() - .map(|address| { - let address = address.convert()?; - account_details - .addresses_with_unspent_outputs - .iter() - .find(|&a| a.address == address) - .ok_or(Error::AddressNotFoundInAccount(address)) - }) - .collect::>>()?; - - self.balance_inner(addresses_with_unspent_outputs.into_iter(), &account_details) - .await - } - - async fn balance_inner( - &self, - addresses_with_unspent_outputs: impl Iterator + Send, - account_details: &AccountDetails, - ) -> Result { - let network_id = self.client().get_network_id().await?; - let rent_structure = self.client().get_rent_structure().await?; - let mut balance = Balance::default(); - let mut total_rent_amount = 0; - let mut total_native_tokens = NativeTokensBuilder::default(); - - #[cfg(feature = "participation")] - let voting_output = self.get_voting_output().await?; - - for address_with_unspent_outputs in addresses_with_unspent_outputs { - #[cfg(feature = "participation")] - { - if let Some(voting_output) = &voting_output { - if voting_output.output.as_basic().address() == address_with_unspent_outputs.address.inner() { - balance.base_coin.voting_power = voting_output.output.amount(); - } - } - } - - for output_id in &address_with_unspent_outputs.output_ids { - if let Some(data) = account_details.unspent_outputs.get(output_id) { - // Check if output is from the network we're currently connected to - if data.network_id != network_id { - continue; - } - - let output = &data.output; - let rent = output.rent_cost(rent_structure); - - // Add account and foundry outputs here because they can't have a - // [`StorageDepositReturnUnlockCondition`] or time related unlock conditions - match output { - Output::Account(output) => { - // Add amount - balance.base_coin.total += output.amount(); - // Add storage deposit - balance.required_storage_deposit.account += rent; - if !account_details.locked_outputs.contains(output_id) { - total_rent_amount += rent; - } - - let account_id = output.account_id_non_null(output_id); - balance.accounts.push(account_id); - } - Output::Foundry(output) => { - // Add amount - balance.base_coin.total += output.amount(); - // Add storage deposit - balance.required_storage_deposit.foundry += rent; - if !account_details.locked_outputs.contains(output_id) { - total_rent_amount += rent; - } - // Add native tokens - if let Some(native_token) = output.native_token() { - total_native_tokens.add_native_token(native_token.clone())?; - } - - balance.foundries.push(output.id()); - } - _ => { - // If there is only an [AddressUnlockCondition], then we can spend the output at any time - // without restrictions - if let [UnlockCondition::Address(_)] = output - .unlock_conditions() - .expect("output needs to have unlock conditions") - .as_ref() - { - // add nft_id for nft outputs - if let Output::Nft(output) = &output { - let nft_id = output.nft_id_non_null(output_id); - balance.nfts.push(nft_id); - } - - // Add amount - balance.base_coin.total += output.amount(); - - // Add storage deposit - if output.is_basic() { - balance.required_storage_deposit.basic += rent; - if output.native_token().is_some() - && !account_details.locked_outputs.contains(output_id) - { - total_rent_amount += rent; - } - } else if output.is_nft() { - balance.required_storage_deposit.nft += rent; - if !account_details.locked_outputs.contains(output_id) { - total_rent_amount += rent; - } - } - - // Add native tokens - if let Some(native_token) = output.native_token() { - total_native_tokens.add_native_token(native_token.clone())?; - } - } else { - // if we have multiple unlock conditions for basic or nft outputs, then we might can't - // spend the balance at the moment or in the future - - let account_addresses = self.addresses().await; - let slot_index = self.client().get_slot_index().await?; - let is_claimable = - self.claimable_outputs(OutputsToClaim::All).await?.contains(output_id); - - // For outputs that are expired or have a timelock unlock condition, but no expiration - // unlock condition and we then can unlock them, then - // they can never be not available for us anymore - // and should be added to the balance - if is_claimable { - // check if output can be unlocked always from now on, in that case it should be - // added to the total amount - let output_can_be_unlocked_now_and_in_future = - can_output_be_unlocked_forever_from_now_on( - // We use the addresses with unspent outputs, because other addresses of - // the account without unspent - // outputs can't be related to this output - &account_details.addresses_with_unspent_outputs, - output, - slot_index, - ); - - if output_can_be_unlocked_now_and_in_future { - // If output has a StorageDepositReturnUnlockCondition, the amount of it should - // be subtracted, because this part - // needs to be sent back - let amount = output - .unlock_conditions() - .and_then(|u| u.storage_deposit_return()) - .map_or_else( - || output.amount(), - |sdr| { - if account_addresses - .iter() - .any(|a| a.address.inner == *sdr.return_address()) - { - // sending to ourself, we get the full amount - output.amount() - } else { - // Sending to someone else - output.amount() - sdr.amount() - } - }, - ); - - // add nft_id for nft outputs - if let Output::Nft(output) = &output { - let nft_id = output.nft_id_non_null(output_id); - balance.nfts.push(nft_id); - } - - // Add amount - balance.base_coin.total += amount; - - // Add storage deposit - if output.is_basic() { - balance.required_storage_deposit.basic += rent; - // Amount for basic outputs isn't added to total_rent_amount if there aren't - // native tokens, since we can - // spend it without burning. - if output.native_token().is_some() - && !account_details.locked_outputs.contains(output_id) - { - total_rent_amount += rent; - } - } else if output.is_nft() { - balance.required_storage_deposit.nft += rent; - if !account_details.locked_outputs.contains(output_id) { - total_rent_amount += rent; - } - } - - // Add native tokens - if let Some(native_token) = output.native_token() { - total_native_tokens.add_native_token(native_token.clone())?; - } - } else { - // only add outputs that can't be locked now and at any point in the future - balance.potentially_locked_outputs.insert(*output_id, true); - } - } else { - // Don't add expired outputs that can't ever be unlocked by us - if let Some(expiration) = output - .unlock_conditions() - .expect("output needs to have unlock conditions") - .expiration() - { - // Not expired, could get unlockable when it's expired, so we insert it - if slot_index < expiration.slot_index() { - balance.potentially_locked_outputs.insert(*output_id, false); - } - } else { - balance.potentially_locked_outputs.insert(*output_id, false); - } - } - } - } - } - } - } - } - - self.finish( - balance, - account_details, - network_id, - total_rent_amount, - total_native_tokens, - ) - } - - fn finish( - &self, - mut balance: Balance, - account_details: &AccountDetails, - network_id: u64, - total_rent_amount: u64, - total_native_tokens: NativeTokensBuilder, - ) -> Result { - // for `available` get locked_outputs, sum outputs amount and subtract from total_amount - log::debug!("[BALANCE] locked outputs: {:#?}", account_details.locked_outputs); - - let mut locked_amount = 0; - let mut locked_native_tokens = NativeTokensBuilder::default(); - - for locked_output in &account_details.locked_outputs { - // Skip potentially_locked_outputs, as their amounts aren't added to the balance - if balance.potentially_locked_outputs.contains_key(locked_output) { - continue; - } - if let Some(output_data) = account_details.unspent_outputs.get(locked_output) { - // Only check outputs that are in this network - if output_data.network_id == network_id { - locked_amount += output_data.output.amount(); - if let Some(native_token) = output_data.output.native_token() { - locked_native_tokens.add_native_token(native_token.clone())?; - } - } - } - } - - log::debug!( - "[BALANCE] total_amount: {}, locked_amount: {}, total_rent_amount: {}", - balance.base_coin.total, - locked_amount, - total_rent_amount, - ); - - locked_amount += total_rent_amount; - - for native_token in total_native_tokens.finish_set()? { - // Check if some amount is currently locked - let locked_native_token_amount = locked_native_tokens.iter().find_map(|(id, amount)| { - if id == native_token.token_id() { - Some(amount) - } else { - None - } - }); - - let metadata = account_details - .native_token_foundries - .get(&FoundryId::from(*native_token.token_id())) - .and_then(|foundry| foundry.immutable_features().metadata()) - .cloned(); - - balance.native_tokens.push(NativeTokensBalance { - token_id: *native_token.token_id(), - total: native_token.amount(), - available: native_token.amount() - *locked_native_token_amount.unwrap_or(&U256::from(0u8)), - metadata, - }) - } - - #[cfg(not(feature = "participation"))] - { - balance.base_coin.available = balance.base_coin.total.saturating_sub(locked_amount); - } - #[cfg(feature = "participation")] - { - balance.base_coin.available = balance - .base_coin - .total - .saturating_sub(locked_amount) - .saturating_sub(balance.base_coin.voting_power); - } - - Ok(balance) - } -} diff --git a/sdk/src/wallet/account/operations/output_finder.rs b/sdk/src/wallet/account/operations/output_finder.rs deleted file mode 100644 index e15c6cf683..0000000000 --- a/sdk/src/wallet/account/operations/output_finder.rs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::cmp; - -use crate::{ - client::secret::{GenerateAddressOptions, SecretManage}, - wallet::account::{operations::syncing::SyncOptions, types::AddressWithUnspentOutputs, Account}, -}; - -impl Account -where - crate::wallet::Error: From, - crate::client::Error: From, -{ - /// Search addresses with unspent outputs - /// `address_gap_limit`: The number of addresses to search for, after the last address with unspent outputs - /// Addresses that got crated during this operation and have a higher key_index than the latest one with outputs, - /// will be removed again, to keep the account size smaller - pub(crate) async fn search_addresses_with_outputs( - &self, - mut address_gap_limit: u32, - sync_options: Option, - ) -> crate::wallet::Result { - log::debug!("[search_addresses_with_outputs]"); - let mut sync_options = match sync_options { - Some(opt) => opt, - None => self.default_sync_options().await.clone(), - }; - - // store the current index, so we can remove new addresses with higher indexes later again, if they don't have - // outputs - let (highest_public_address_index, highest_internal_address_index) = { - let account_details = self.details().await; - ( - account_details - .public_addresses - .last() - .map(|a| a.key_index) - .expect("account needs to have a public address"), - account_details.internal_addresses.last().map(|a| a.key_index), - ) - }; - - // public addresses - if sync_options.address_start_index != 0 { - let mut address_amount_to_generate = - sync_options.address_start_index.abs_diff(highest_public_address_index); - // -1 if it's larger than 0, to get the correct amount, because the address with the actual start index - // gets generated later - address_amount_to_generate = address_amount_to_generate.saturating_sub(1); - log::debug!( - "[search_addresses_with_outputs] generate {address_amount_to_generate} public addresses below the start index" - ); - self.generate_ed25519_addresses(address_amount_to_generate, None) - .await?; - } - // internal addresses - if sync_options.address_start_index_internal != 0 { - let mut address_amount_to_generate = sync_options - .address_start_index_internal - .abs_diff(highest_internal_address_index.unwrap_or(0)); - // -1 if it's larger than 0, to get the correct amount, because the address with the actual start index - // gets generated later - if address_amount_to_generate > 0 && highest_internal_address_index.is_some() { - address_amount_to_generate -= 1; - } - log::debug!( - "[search_addresses_with_outputs] generate {address_amount_to_generate} internal addresses below the start index" - ); - self.generate_ed25519_addresses(address_amount_to_generate, Some(GenerateAddressOptions::internal())) - .await?; - } - - let mut address_gap_limit_internal = address_gap_limit; - - let mut latest_outputs_count = 0; - loop { - // Also needs to be in the loop so it gets updated every round for internal use without modifying the values - // outside - let (highest_public_address_index, highest_internal_address_index) = { - let account_details = self.details().await; - ( - account_details - .public_addresses - .last() - .map(|a| a.key_index) - .expect("account needs to have a public address"), - account_details.internal_addresses.last().map(|a| a.key_index), - ) - }; - log::debug!( - "[search_addresses_with_outputs] address_gap_limit: {address_gap_limit}, address_gap_limit_internal: {address_gap_limit_internal}" - ); - // generate public and internal addresses - let addresses = self.generate_ed25519_addresses(address_gap_limit, None).await?; - let internal_addresses = self - .generate_ed25519_addresses(address_gap_limit_internal, Some(GenerateAddressOptions::internal())) - .await?; - - let address_start_index = addresses - .first() - .map(|a| { - // If the index is 1, then we only have the single address before we got during account creation - // To also sync that, we set the index to 0 - if a.key_index == 1 { 0 } else { a.key_index } - }) - // +1, because we don't want to sync the latest address again - .unwrap_or(highest_public_address_index + 1); - - let address_start_index_internal = internal_addresses - .first() - .map(|a| a.key_index) - // +1, because we don't want to sync the latest address again - .unwrap_or_else(|| highest_internal_address_index.unwrap_or(0) + 1); - - sync_options.force_syncing = true; - sync_options.address_start_index = address_start_index; - sync_options.address_start_index_internal = address_start_index_internal; - self.sync(Some(sync_options.clone())).await?; - - let output_count = self.details().await.unspent_outputs.len(); - - // break if we didn't find more outputs with the new addresses - if output_count <= latest_outputs_count { - break; - } - - latest_outputs_count = output_count; - - // Update address_gap_limit to only generate the amount of addresses we need to have `address_gap_limit` - // amount of empty addresses after the latest one with outputs - - let account_details = self.details().await; - - let highest_address_index = account_details - .public_addresses - .iter() - .max_by_key(|a| *a.key_index()) - .map(|a| *a.key_index()) - .expect("account needs to have at least one public address"); - - let highest_address_index_internal = account_details - .internal_addresses - .iter() - .max_by_key(|a| *a.key_index()) - .map(|a| *a.key_index()) - .unwrap_or(0); - - drop(account_details); - - let addresses_with_unspent_outputs = self.addresses_with_unspent_outputs().await?; - - let (addresses_with_outputs_internal, address_with_outputs): ( - Vec<&AddressWithUnspentOutputs>, - Vec<&AddressWithUnspentOutputs>, - ) = addresses_with_unspent_outputs.iter().partition(|a| a.internal); - - let latest_address_index_with_outputs = address_with_outputs - .iter() - .max_by_key(|a| *a.key_index()) - .map(|a| *a.key_index() as i64) - // -1 as default, because we will subtract this value and want to have the amount of empty addresses in - // a row and not the address index - .unwrap_or(-1); - - let latest_address_index_with_outputs_internal = addresses_with_outputs_internal - .iter() - .max_by_key(|a| *a.key_index()) - .map(|a| *a.key_index() as i64) - // -1 as default, because we will subtract this value and want to have the amount of empty addresses in - // a row and not the address index - .unwrap_or(-1); - - log::debug!( - "new highest_address_index: {highest_address_index}, internal: {highest_address_index_internal}" - ); - log::debug!( - "new latest_address_index_with_outputs: {latest_address_index_with_outputs:?}, internal: {latest_address_index_with_outputs_internal:?}" - ); - - let empty_addresses_in_row = (highest_address_index as i64 - latest_address_index_with_outputs) as u32; - - let empty_addresses_in_row_internal = - (highest_address_index_internal as i64 - latest_address_index_with_outputs_internal) as u32; - - log::debug!( - "new empty_addresses_in_row: {empty_addresses_in_row}, internal: {empty_addresses_in_row_internal}" - ); - - if empty_addresses_in_row > address_gap_limit { - log::debug!("empty_addresses_in_row: {empty_addresses_in_row}, setting address_gap_limit to 0"); - address_gap_limit = 0; - } else { - address_gap_limit -= empty_addresses_in_row; - } - if empty_addresses_in_row_internal > address_gap_limit_internal { - log::debug!( - "empty_addresses_in_row_internal: {empty_addresses_in_row_internal}, setting address_gap_limit_internal to 0" - ); - address_gap_limit_internal = 0; - } else { - address_gap_limit_internal -= empty_addresses_in_row_internal; - } - - log::debug!("new address_gap_limit: {address_gap_limit}, internal: {address_gap_limit_internal}"); - - if address_gap_limit == 0 && address_gap_limit_internal == 0 { - break; - } - } - - self.clean_account_after_recovery(highest_public_address_index, highest_internal_address_index) - .await; - - #[cfg(feature = "storage")] - { - log::debug!( - "[search_addresses_with_outputs] storing account {} with new synced data", - self.alias().await - ); - self.save(None).await?; - } - - Ok(latest_outputs_count) - } - - /// During search_addresses_with_outputs we created new addresses that don't have funds, so we remove them again. - // `old_highest_public_address_index` is not optional, because we need to have at least one public address in the - // account - async fn clean_account_after_recovery( - &self, - old_highest_public_address_index: u32, - old_highest_internal_address_index: Option, - ) { - let mut account_details = self.details_mut().await; - - let (internal_addresses_with_unspent_outputs, public_addresses_with_spent_outputs): ( - Vec<&AddressWithUnspentOutputs>, - Vec<&AddressWithUnspentOutputs>, - ) = account_details - .addresses_with_unspent_outputs() - .iter() - .partition(|address| address.internal); - - let highest_public_index_with_outputs = public_addresses_with_spent_outputs - .iter() - .map(|a| a.key_index) - .max() - // We want to have at least one public address - .unwrap_or(0); - - let highest_internal_index_with_outputs = internal_addresses_with_unspent_outputs - .iter() - .map(|a| a.key_index) - .max(); - - // The new highest index should be either the old one before we searched for funds or if we found addresses with - // funds the highest index from an address with outputs - let new_latest_public_index = cmp::max(highest_public_index_with_outputs, old_highest_public_address_index); - account_details.public_addresses = account_details - .public_addresses - .clone() - .into_iter() - .filter(|a| a.key_index <= new_latest_public_index) - .collect(); - - account_details.internal_addresses = - if old_highest_internal_address_index.is_none() && highest_internal_index_with_outputs.is_none() { - // For internal addresses we don't leave an empty address, that's only required for the public address - Vec::new() - } else { - let new_latest_internal_index = cmp::max( - highest_internal_index_with_outputs.unwrap_or(0), - old_highest_internal_address_index.unwrap_or(0), - ); - account_details - .internal_addresses - .clone() - .into_iter() - .filter(|a| a.key_index <= new_latest_internal_index) - .collect() - }; - } -} diff --git a/sdk/src/wallet/account/operations/syncing/addresses/mod.rs b/sdk/src/wallet/account/operations/syncing/addresses/mod.rs deleted file mode 100644 index a4e75d13c5..0000000000 --- a/sdk/src/wallet/account/operations/syncing/addresses/mod.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -mod output_ids; -mod outputs; - -use std::collections::HashSet; - -use crate::{ - client::secret::SecretManage, - wallet::account::{operations::syncing::SyncOptions, types::address::AddressWithUnspentOutputs, Account}, -}; - -impl Account -where - crate::wallet::Error: From, -{ - /// Get the addresses that should be synced with the current known unspent output ids - /// Also adds account and nft addresses from unspent account or nft outputs that have no Timelock, Expiration or - /// StorageDepositReturn [`UnlockCondition`] - pub(crate) async fn get_addresses_to_sync( - &self, - options: &SyncOptions, - ) -> crate::wallet::Result> { - log::debug!("[SYNC] get_addresses_to_sync"); - - let mut addresses_before_syncing = self.addresses().await; - - // If custom addresses are provided check if they are in the account and only use them - if !options.addresses.is_empty() { - let mut specific_addresses_to_sync = HashSet::new(); - for bech32_address in &options.addresses { - match addresses_before_syncing.iter().find(|a| &a.address == bech32_address) { - Some(address) => { - specific_addresses_to_sync.insert(address.clone()); - } - None => { - return Err(crate::wallet::Error::AddressNotFoundInAccount(bech32_address.clone())); - } - } - } - addresses_before_syncing = specific_addresses_to_sync.into_iter().collect(); - } else if options.address_start_index != 0 || options.address_start_index_internal != 0 { - // Filter addresses when address_start_index(_internal) is not 0, so we skip these addresses - addresses_before_syncing.retain(|a| { - if a.internal { - a.key_index >= options.address_start_index_internal - } else { - a.key_index >= options.address_start_index - } - }); - } - - // Check if selected addresses contains addresses with balance so we can correctly update them - let addresses_with_unspent_outputs = self.addresses_with_unspent_outputs().await?; - let mut addresses_with_old_output_ids = Vec::new(); - for address in addresses_before_syncing { - let mut output_ids = Vec::new(); - // Add currently known unspent output ids, so we can later compare them with the new output ids and see if - // one got spent (is missing in the new returned output ids) - if let Some(address_with_unspent_outputs) = addresses_with_unspent_outputs - .iter() - .find(|a| a.address == address.address) - { - output_ids = address_with_unspent_outputs.output_ids.to_vec(); - } - addresses_with_old_output_ids.push(AddressWithUnspentOutputs { - address: address.address, - key_index: address.key_index, - internal: address.internal, - output_ids, - }) - } - - Ok(addresses_with_old_output_ids) - } -} diff --git a/sdk/src/wallet/account/update.rs b/sdk/src/wallet/account/update.rs deleted file mode 100644 index 930efd41c0..0000000000 --- a/sdk/src/wallet/account/update.rs +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; - -use crate::{ - client::secret::SecretManage, - types::block::output::{OutputId, OutputMetadata}, - wallet::account::{ - operations::syncing::options::SyncOptions, - types::{address::AddressWithUnspentOutputs, InclusionState, OutputData, TransactionWithMetadata}, - Account, Bip44Address, - }, -}; -#[cfg(feature = "events")] -use crate::{ - types::{ - api::core::OutputWithMetadataResponse, block::payload::signed_transaction::dto::SignedTransactionPayloadDto, - }, - wallet::{ - account::types::OutputDataDto, - events::types::{NewOutputEvent, SpentOutputEvent, TransactionInclusionEvent, WalletEvent}, - }, -}; - -impl Account -where - crate::wallet::Error: From, -{ - /// Set the alias for the account - pub async fn set_alias(&self, alias: &str) -> crate::wallet::Result<()> { - let mut account_details = self.details_mut().await; - account_details.alias = alias.to_string(); - #[cfg(feature = "storage")] - self.save(Some(&account_details)).await?; - Ok(()) - } - - /// Update account with newly synced data and emit events for outputs - pub(crate) async fn update_account( - &self, - addresses_with_unspent_outputs: Vec, - unspent_outputs: Vec, - spent_or_unsynced_output_metadata_map: HashMap>, - options: &SyncOptions, - ) -> crate::wallet::Result<()> { - log::debug!("[SYNC] Update account with new synced transactions"); - - let network_id = self.client().get_network_id().await?; - let mut account_details = self.details_mut().await; - #[cfg(feature = "events")] - let account_index = account_details.index; - - // Update addresses_with_unspent_outputs - // only keep addresses below the address start index, because we synced the addresses above and will update them - account_details.addresses_with_unspent_outputs.retain(|a| { - if a.internal { - a.key_index < options.address_start_index_internal - } else { - a.key_index < options.address_start_index - } - }); - - // then add all synced addresses with balance, all other addresses that had balance before will then be removed - // from this list - account_details - .addresses_with_unspent_outputs - .extend(addresses_with_unspent_outputs); - - // Update spent outputs - for (output_id, output_metadata_response_opt) in spent_or_unsynced_output_metadata_map { - // If we got the output response and it's still unspent, skip it - if let Some(output_metadata_response) = output_metadata_response_opt { - if output_metadata_response.is_spent() { - account_details.unspent_outputs.remove(&output_id); - if let Some(output_data) = account_details.outputs.get_mut(&output_id) { - output_data.metadata = output_metadata_response; - } - } else { - // not spent, just not synced, skip - continue; - } - } - - if let Some(output) = account_details.outputs.get(&output_id) { - // Could also be outputs from other networks after we switched the node, so we check that first - if output.network_id == network_id { - log::debug!("[SYNC] Spent output {}", output_id); - account_details.locked_outputs.remove(&output_id); - account_details.unspent_outputs.remove(&output_id); - // Update spent data fields - if let Some(output_data) = account_details.outputs.get_mut(&output_id) { - output_data.metadata.set_spent(true); - output_data.is_spent = true; - #[cfg(feature = "events")] - { - self.emit( - account_index, - WalletEvent::SpentOutput(Box::new(SpentOutputEvent { - output: OutputDataDto::from(&*output_data), - })), - ) - .await; - } - } - } - } - } - - // Add new synced outputs - for output_data in unspent_outputs { - // Insert output, if it's unknown emit the NewOutputEvent - if account_details - .outputs - .insert(output_data.output_id, output_data.clone()) - .is_none() - { - #[cfg(feature = "events")] - { - let transaction = account_details - .incoming_transactions - .get(output_data.output_id.transaction_id()); - self.emit( - account_index, - WalletEvent::NewOutput(Box::new(NewOutputEvent { - output: OutputDataDto::from(&output_data), - transaction: transaction - .as_ref() - .map(|tx| SignedTransactionPayloadDto::from(&tx.payload)), - transaction_inputs: transaction.as_ref().map(|tx| { - tx.inputs - .clone() - .into_iter() - .map(OutputWithMetadataResponse::from) - .collect() - }), - })), - ) - .await; - } - }; - if !output_data.is_spent { - account_details - .unspent_outputs - .insert(output_data.output_id, output_data); - } - } - - #[cfg(feature = "storage")] - { - log::debug!( - "[SYNC] storing account {} with new synced data", - account_details.alias() - ); - self.save(Some(&account_details)).await?; - } - Ok(()) - } - - /// Update account with newly synced transactions - pub(crate) async fn update_account_with_transactions( - &self, - updated_transactions: Vec, - spent_output_ids: Vec, - output_ids_to_unlock: Vec, - ) -> crate::wallet::Result<()> { - log::debug!("[SYNC] Update account with new synced transactions"); - - let mut account_details = self.details_mut().await; - - for transaction in updated_transactions { - match transaction.inclusion_state { - InclusionState::Confirmed | InclusionState::Conflicting | InclusionState::UnknownPruned => { - let transaction_id = transaction.payload.transaction().id(); - account_details.pending_transactions.remove(&transaction_id); - log::debug!( - "[SYNC] inclusion_state of {transaction_id} changed to {:?}", - transaction.inclusion_state - ); - #[cfg(feature = "events")] - { - self.emit( - account_details.index, - WalletEvent::TransactionInclusion(TransactionInclusionEvent { - transaction_id, - inclusion_state: transaction.inclusion_state, - }), - ) - .await; - } - } - _ => {} - } - account_details - .transactions - .insert(transaction.payload.transaction().id(), transaction.clone()); - } - - for output_to_unlock in &spent_output_ids { - if let Some(output) = account_details.outputs.get_mut(output_to_unlock) { - output.is_spent = true; - } - account_details.locked_outputs.remove(output_to_unlock); - account_details.unspent_outputs.remove(output_to_unlock); - log::debug!("[SYNC] Unlocked spent output {}", output_to_unlock); - } - - for output_to_unlock in &output_ids_to_unlock { - account_details.locked_outputs.remove(output_to_unlock); - log::debug!( - "[SYNC] Unlocked unspent output {} because of a conflicting transaction", - output_to_unlock - ); - } - - #[cfg(feature = "storage")] - { - log::debug!( - "[SYNC] storing account {} with new synced transactions", - account_details.alias() - ); - self.save(Some(&account_details)).await?; - } - Ok(()) - } - - /// Update account with newly generated addresses - pub(crate) async fn update_account_addresses( - &self, - internal: bool, - new_addresses: Vec, - ) -> crate::wallet::Result<()> { - log::debug!("[update_account_addresses]"); - - let mut account_details = self.details_mut().await; - - // add addresses to the account - if internal { - account_details.internal_addresses.extend(new_addresses); - } else { - account_details.public_addresses.extend(new_addresses); - }; - - #[cfg(feature = "storage")] - { - log::debug!("[update_account_addresses] storing account {}", account_details.index()); - self.save(Some(&account_details)).await?; - } - Ok(()) - } - - // Should only be called from the Wallet so all accounts are on the same state - // Will update the addresses with a possible new Bech32 HRP and clear the inaccessible_incoming_transactions. - pub(crate) async fn update_account_bech32_hrp(&mut self) -> crate::wallet::Result<()> { - let bech32_hrp = self.client().get_bech32_hrp().await?; - log::debug!("[UPDATE ACCOUNT WITH BECH32 HRP] new bech32_hrp: {}", bech32_hrp); - let mut account_details = self.details_mut().await; - for address in &mut account_details.addresses_with_unspent_outputs { - address.address.hrp = bech32_hrp; - } - for address in &mut account_details.public_addresses { - address.address.hrp = bech32_hrp; - } - for address in &mut account_details.internal_addresses { - address.address.hrp = bech32_hrp; - } - - account_details.inaccessible_incoming_transactions.clear(); - - #[cfg(feature = "storage")] - { - log::debug!( - "[SYNC] storing account {} after updating it with new bech32 hrp", - account_details.alias() - ); - self.save(Some(&account_details)).await?; - } - - Ok(()) - } -} diff --git a/sdk/src/wallet/account/constants.rs b/sdk/src/wallet/constants.rs similarity index 94% rename from sdk/src/wallet/account/constants.rs rename to sdk/src/wallet/constants.rs index bdd3b81fa1..59cf49b8f2 100644 --- a/sdk/src/wallet/account/constants.rs +++ b/sdk/src/wallet/constants.rs @@ -11,7 +11,7 @@ pub(crate) const DEFAULT_LEDGER_OUTPUT_CONSOLIDATION_THRESHOLD: usize = 15; /// Amount of API request that can be sent in parallel during syncing pub(crate) const PARALLEL_REQUESTS_AMOUNT: usize = 500; -/// ms before an account actually syncs with the network, before it just returns the previous syncing result +/// ms before the wallet actually syncs with the network, before it just returns the previous syncing result /// this is done to prevent unnecessary simultaneous synchronizations pub(crate) const MIN_SYNC_INTERVAL: u128 = 5; diff --git a/sdk/src/wallet/core/builder.rs b/sdk/src/wallet/core/builder.rs index 31e2bba876..06a24ebb16 100644 --- a/sdk/src/wallet/core/builder.rs +++ b/sdk/src/wallet/core/builder.rs @@ -1,16 +1,12 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::{ - atomic::{AtomicU32, AtomicUsize}, - Arc, -}; #[cfg(feature = "storage")] -use std::{collections::HashSet, sync::atomic::Ordering}; +use std::collections::HashSet; +use std::sync::{atomic::AtomicUsize, Arc}; -use futures::{future::try_join_all, FutureExt}; use serde::Serialize; -use tokio::sync::RwLock; +use tokio::sync::{Mutex, RwLock}; use super::operations::storage::SaveLoadWallet; #[cfg(feature = "events")] @@ -18,21 +14,25 @@ use crate::wallet::events::EventEmitter; #[cfg(all(feature = "storage", not(feature = "rocksdb")))] use crate::wallet::storage::adapter::memory::Memory; #[cfg(feature = "storage")] -use crate::wallet::{ - account::AccountDetails, - storage::{StorageManager, StorageOptions}, -}; +use crate::wallet::storage::{StorageManager, StorageOptions}; use crate::{ - client::secret::{SecretManage, SecretManager}, - wallet::{core::WalletInner, Account, ClientOptions, Wallet}, + client::secret::{GenerateAddressOptions, SecretManage, SecretManager}, + types::block::address::{Address, Bech32Address}, + wallet::{ + core::{Bip44, WalletData, WalletInner}, + operations::syncing::SyncOptions, + ClientOptions, Wallet, + }, }; /// Builder for the wallet. #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct WalletBuilder { + pub(crate) bip_path: Option, + pub(crate) address: Option, + pub(crate) alias: Option, pub(crate) client_options: Option, - pub(crate) coin_type: Option, #[cfg(feature = "storage")] pub(crate) storage_options: Option, #[serde(skip)] @@ -42,8 +42,10 @@ pub struct WalletBuilder { impl Default for WalletBuilder { fn default() -> Self { Self { + bip_path: Default::default(), + address: Default::default(), + alias: Default::default(), client_options: Default::default(), - coin_type: Default::default(), #[cfg(feature = "storage")] storage_options: Default::default(), secret_manager: Default::default(), @@ -63,15 +65,27 @@ where } } - /// Set the client options for the core nodes. - pub fn with_client_options(mut self, client_options: impl Into>) -> Self { - self.client_options = client_options.into(); + /// Set the BIP44 path of the wallet. + pub fn with_bip_path(mut self, bip_path: impl Into>) -> Self { + self.bip_path = bip_path.into(); self } - /// Set the coin type for the wallet. Registered coin types can be found at . - pub fn with_coin_type(mut self, coin_type: impl Into>) -> Self { - self.coin_type = coin_type.into(); + /// Set the wallet address. + pub fn with_address(mut self, address: impl Into>) -> Self { + self.address = address.into(); + self + } + + /// Set the alias of the wallet. + pub fn with_alias(mut self, alias: impl Into>) -> Self { + self.alias = alias.into(); + self + } + + /// Set the client options for the core nodes. + pub fn with_client_options(mut self, client_options: impl Into>) -> Self { + self.client_options = client_options.into(); self } @@ -111,9 +125,10 @@ where impl WalletBuilder where crate::wallet::Error: From, + crate::client::Error: From, Self: SaveLoadWallet, { - /// Builds the wallet + /// Builds the wallet. pub async fn finish(mut self) -> crate::wallet::Result> { log::debug!("[WalletBuilder]"); @@ -126,12 +141,6 @@ where if self.client_options.is_none() { return Err(crate::wallet::Error::MissingParameter("client_options")); } - if self.coin_type.is_none() { - return Err(crate::wallet::Error::MissingParameter("coin_type")); - } - if self.secret_manager.is_none() { - return Err(crate::wallet::Error::MissingParameter("secret_manager")); - } } #[cfg(all(feature = "rocksdb", feature = "storage"))] @@ -144,56 +153,79 @@ where let mut storage_manager = StorageManager::new(storage, storage_options.encryption_key.clone()).await?; #[cfg(feature = "storage")] - let read_manager_builder = Self::load(&storage_manager).await?; + let loaded_wallet_builder = Self::load(&storage_manager).await?; #[cfg(not(feature = "storage"))] - let read_manager_builder: Option = None; + let loaded_wallet_builder: Option = None; - // Prioritize provided client_options and secret_manager over stored ones - let new_provided_client_options = if self.client_options.is_none() { - let loaded_client_options = read_manager_builder + // May use a previously stored client options if those weren't provided + let provided_client_options = if self.client_options.is_none() { + let loaded_client_options = loaded_wallet_builder .as_ref() .and_then(|data| data.client_options.clone()) .ok_or(crate::wallet::Error::MissingParameter("client_options"))?; // Update self so it gets used and stored again - self.client_options.replace(loaded_client_options); + self.client_options = Some(loaded_client_options); false } else { true }; + // May use a previously stored secret manager if it wasn't provided if self.secret_manager.is_none() { - let secret_manager = read_manager_builder + let secret_manager = loaded_wallet_builder .as_ref() - .and_then(|data| data.secret_manager.clone()) - .ok_or(crate::wallet::Error::MissingParameter("secret_manager"))?; + .and_then(|builder| builder.secret_manager.clone()); - // Update self so it gets used and stored again - self.secret_manager.replace(secret_manager); + self.secret_manager = secret_manager; + } + + // May use a previously stored BIP path if it wasn't provided + if self.bip_path.is_none() { + self.bip_path = loaded_wallet_builder.as_ref().and_then(|builder| builder.bip_path); } - if self.coin_type.is_none() { - self.coin_type = read_manager_builder.and_then(|builder| builder.coin_type); + // May use a previously stored wallet alias if it wasn't provided + if self.alias.is_none() { + self.alias = loaded_wallet_builder.as_ref().and_then(|builder| builder.alias.clone()); } - let coin_type = self.coin_type.ok_or(crate::wallet::Error::MissingParameter( - "coin_type (IOTA: 4218, Shimmer: 4219)", - ))?; + + // May use a previously stored wallet address if it wasn't provided + if self.address.is_none() { + self.address = loaded_wallet_builder + .as_ref() + .and_then(|builder| builder.address.clone()); + } + + // May create a default Ed25519 wallet address if there's a secret manager. + if self.address.is_none() { + if self.secret_manager.is_some() { + let address = self.create_default_wallet_address().await?; + self.address = Some(address); + } else { + return Err(crate::wallet::Error::MissingParameter("address")); + } + } + // Panic: can be safely unwrapped now + let address = self.address.as_ref().unwrap().clone(); #[cfg(feature = "storage")] - let mut accounts = storage_manager.get_accounts().await?; + let mut wallet_data = storage_manager.load_wallet_data().await?; - // Check against potential account coin type before saving the wallet data + // The bip path must not change. #[cfg(feature = "storage")] - if let Some(account) = accounts.first() { - if *account.coin_type() != coin_type { - return Err(crate::wallet::Error::InvalidCoinType { - new_coin_type: coin_type, - existing_coin_type: *account.coin_type(), + if let Some(wallet_data) = &wallet_data { + let new_bip_path = self.bip_path; + let old_bip_path = wallet_data.bip_path; + if new_bip_path != old_bip_path { + return Err(crate::wallet::Error::BipPathMismatch { + new_bip_path, + old_bip_path, }); } } - // Store wallet data in storage + // Store the wallet builder (for convenience reasons) #[cfg(feature = "storage")] self.save(&storage_manager).await?; @@ -203,56 +235,87 @@ where // It happened that inputs got locked, the transaction failed, but they weren't unlocked again, so we do this // here #[cfg(feature = "storage")] - unlock_unused_inputs(&mut accounts)?; - #[cfg(not(feature = "storage"))] - let accounts = Vec::new(); - let wallet_inner = Arc::new(WalletInner { + if let Some(wallet_data) = &mut wallet_data { + unlock_unused_inputs(wallet_data)?; + } + + // Create the node client. + let client = self + .client_options + .clone() + .ok_or(crate::wallet::Error::MissingParameter("client_options"))? + .finish() + .await?; + + // Build the wallet. + let wallet_inner = WalletInner { + default_sync_options: Mutex::new(SyncOptions::default()), + last_synced: Mutex::new(0), background_syncing_status: AtomicUsize::new(0), - client: self - .client_options - .clone() - .ok_or(crate::wallet::Error::MissingParameter("client_options"))? - .finish() - .await?, - coin_type: AtomicU32::new(coin_type), - secret_manager: self - .secret_manager - .ok_or(crate::wallet::Error::MissingParameter("secret_manager"))?, + client, + secret_manager: self.secret_manager.expect("make WalletInner::secret_manager optional?"), #[cfg(feature = "events")] event_emitter, #[cfg(feature = "storage")] storage_options, #[cfg(feature = "storage")] storage_manager: tokio::sync::RwLock::new(storage_manager), - }); - - let mut accounts: Vec> = try_join_all( - accounts - .into_iter() - .map(|a| Account::new(a, wallet_inner.clone()).boxed()), - ) - .await?; + }; + let wallet_data = WalletData::new(self.bip_path, address, self.alias.clone()); + let wallet = Wallet { + inner: Arc::new(wallet_inner), + data: Arc::new(RwLock::new(wallet_data)), + }; // If the wallet builder is not set, it means the user provided it and we need to update the addresses. // In the other case it was loaded from the database and addresses are up to date. - if new_provided_client_options { - for account in accounts.iter_mut() { - // Safe to unwrap because we create the client if accounts aren't empty - account.update_account_bech32_hrp().await?; - } + if provided_client_options { + wallet.update_bech32_hrp().await?; } - Ok(Wallet { - inner: wallet_inner, - accounts: Arc::new(RwLock::new(accounts)), - }) + Ok(wallet) + } + + /// Generate the wallet address. + pub(crate) async fn create_default_wallet_address(&self) -> crate::wallet::Result { + let bech32_hrp = self + .client_options + .as_ref() + .unwrap() + .network_info + .protocol_parameters + .bech32_hrp; + let bip_path = self.bip_path.as_ref().unwrap(); + + Ok(Bech32Address::new( + bech32_hrp, + Address::Ed25519( + self.secret_manager + .as_ref() + .unwrap() + .read() + .await + .generate_ed25519_addresses( + bip_path.coin_type, + bip_path.account, + bip_path.address_index..bip_path.address_index + 1, + GenerateAddressOptions { + internal: bip_path.change != 0, + ledger_nano_prompt: false, + }, + ) + .await?[0], + ), + )) } #[cfg(feature = "storage")] pub(crate) async fn from_wallet(wallet: &Wallet) -> Self { Self { + bip_path: wallet.bip_path().await, + address: Some(wallet.address().await), + alias: wallet.alias().await, client_options: Some(wallet.client_options().await), - coin_type: Some(wallet.coin_type.load(Ordering::Relaxed)), storage_options: Some(wallet.storage_options.clone()), secret_manager: Some(wallet.secret_manager.clone()), } @@ -262,25 +325,23 @@ where // Check if any of the locked inputs is not used in a transaction and unlock them, so they get available for new // transactions #[cfg(feature = "storage")] -fn unlock_unused_inputs(accounts: &mut [AccountDetails]) -> crate::wallet::Result<()> { +fn unlock_unused_inputs(wallet_data: &mut WalletData) -> crate::wallet::Result<()> { log::debug!("[unlock_unused_inputs]"); - for account in accounts.iter_mut() { - let mut used_inputs = HashSet::new(); - for transaction_id in account.pending_transactions() { - if let Some(tx) = account.transactions().get(transaction_id) { - for input in &tx.inputs { - used_inputs.insert(*input.metadata.output_id()); - } + let mut used_inputs = HashSet::new(); + for transaction_id in &wallet_data.pending_transactions { + if let Some(tx) = wallet_data.transactions.get(transaction_id) { + for input in &tx.inputs { + used_inputs.insert(*input.metadata.output_id()); } } - account.locked_outputs.retain(|input| { - let used = used_inputs.contains(input); - if !used { - log::debug!("unlocking unused input {input}"); - } - used - }) } + wallet_data.locked_outputs.retain(|input| { + let used = used_inputs.contains(input); + if !used { + log::debug!("unlocking unused input {input}"); + } + used + }); Ok(()) } @@ -296,9 +357,13 @@ pub(crate) mod dto { #[serde(rename_all = "camelCase")] pub struct WalletBuilderDto { #[serde(default, skip_serializing_if = "Option::is_none")] - pub(crate) client_options: Option, + pub(crate) bip_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) address: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub(crate) coin_type: Option, + pub(crate) alias: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) client_options: Option, #[cfg(feature = "storage")] #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) storage_options: Option, @@ -307,8 +372,10 @@ pub(crate) mod dto { impl From for WalletBuilder { fn from(value: WalletBuilderDto) -> Self { Self { + bip_path: value.bip_path, + address: value.address, + alias: value.alias, client_options: value.client_options, - coin_type: value.coin_type, #[cfg(feature = "storage")] storage_options: value.storage_options, secret_manager: None, diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index 0eb401fdf9..f9ee311fab 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -4,19 +4,23 @@ pub(crate) mod builder; pub(crate) mod operations; -use core::sync::atomic::Ordering; -use std::sync::{ - atomic::{AtomicU32, AtomicUsize}, - Arc, +use std::{ + collections::{HashMap, HashSet}, + sync::{atomic::AtomicUsize, Arc}, }; -use crypto::keys::bip39::{Mnemonic, MnemonicRef}; -use tokio::sync::RwLock; +use crypto::keys::{ + bip39::{Mnemonic, MnemonicRef}, + bip44::Bip44, +}; +use serde::{Deserialize, Serialize}; +use tokio::sync::{Mutex, RwLock}; pub use self::builder::WalletBuilder; +use super::types::{TransactionWithMetadata, TransactionWithMetadataDto}; #[cfg(feature = "events")] use crate::wallet::events::{ - types::{Event, WalletEventType}, + types::{WalletEvent, WalletEventType}, EventEmitter, }; #[cfg(feature = "storage")] @@ -26,23 +30,36 @@ use crate::{ secret::{SecretManage, SecretManager}, verify_mnemonic, Client, }, - wallet::account::{builder::AccountBuilder, operations::syncing::SyncOptions, types::Balance, Account}, + types::{ + block::{ + address::{Address, Bech32Address, Hrp, ImplicitAccountCreationAddress}, + output::{ + dto::FoundryOutputDto, AccountId, AnchorId, DelegationId, FoundryId, FoundryOutput, NftId, Output, + OutputId, TokenId, + }, + payload::signed_transaction::TransactionId, + }, + TryFromDto, + }, + wallet::{ + operations::syncing::SyncOptions, + types::{OutputData, OutputDataDto}, + Error, FilterOptions, Result, + }, }; -/// The wallet, used to create and get accounts. One wallet can hold many accounts, but they should -/// all share the same secret_manager type with the same seed/mnemonic. +/// The stateful wallet used to interact with an IOTA network. #[derive(Debug)] pub struct Wallet { pub(crate) inner: Arc>, - // TODO should we use a hashmap instead of a vec? - pub(crate) accounts: Arc>>>, + pub(crate) data: Arc>, } impl Clone for Wallet { fn clone(&self) -> Self { Self { inner: self.inner.clone(), - accounts: self.accounts.clone(), + data: self.data.clone(), } } } @@ -63,20 +80,20 @@ where pub fn builder() -> WalletBuilder { WalletBuilder::::new() } - - /// Create a new account - pub fn create_account(&self) -> AccountBuilder { - log::debug!("creating account"); - AccountBuilder::::new(self.clone()) - } } +/// Wallet inner. #[derive(Debug)] pub struct WalletInner { + // mutex to prevent multiple sync calls at the same or almost the same time, the u128 is a timestamp + // if the last synced time was < `MIN_SYNC_INTERVAL` second ago, we don't sync, but only calculate the balance + // again, because sending transactions can change that + pub(crate) last_synced: Mutex, + pub(crate) default_sync_options: Mutex, // 0 = not running, 1 = running, 2 = stopping pub(crate) background_syncing_status: AtomicUsize, pub(crate) client: Client, - pub(crate) coin_type: AtomicU32, + // TODO: make this optional? pub(crate) secret_manager: Arc>, #[cfg(feature = "events")] pub(crate) event_emitter: tokio::sync::RwLock, @@ -86,93 +103,406 @@ pub struct WalletInner { pub(crate) storage_manager: tokio::sync::RwLock, } +/// Wallet data. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WalletData { + /// The wallet BIP44 path. + pub(crate) bip_path: Option, + /// The wallet address. + pub(crate) address: Bech32Address, + /// The wallet alias. + pub(crate) alias: Option, + /// Outputs + // stored separated from the wallet for performance? + pub(crate) outputs: HashMap, + /// Unspent outputs that are currently used as input for transactions + // outputs used in transactions should be locked here so they don't get used again, which would result in a + // conflicting transaction + pub(crate) locked_outputs: HashSet, + /// Unspent outputs + // have unspent outputs in a separated hashmap so we don't need to iterate over all outputs we have + pub(crate) unspent_outputs: HashMap, + /// Sent transactions + // stored separated from the wallet for performance and only the transaction id here? where to add the network id? + // transactions: HashSet, + pub(crate) transactions: HashMap, + /// Pending transactions + // Maybe pending transactions even additionally separated? + pub(crate) pending_transactions: HashSet, + /// Transaction payloads for received outputs with inputs when not pruned before syncing, can be used to determine + /// the sender address(es) + pub(crate) incoming_transactions: HashMap, + /// Some incoming transactions can be pruned by the node before we requested them, then this node can never return + /// it. To avoid useless requests, these transaction ids are stored here and cleared when new client options are + /// set, because another node might still have them. + pub(crate) inaccessible_incoming_transactions: HashSet, + /// Foundries for native tokens in outputs + pub(crate) native_token_foundries: HashMap, +} + +impl WalletData { + pub(crate) fn new(bip_path: Option, address: Bech32Address, alias: Option) -> Self { + Self { + bip_path, + address, + alias, + outputs: HashMap::new(), + locked_outputs: HashSet::new(), + unspent_outputs: HashMap::new(), + transactions: HashMap::new(), + pending_transactions: HashSet::new(), + incoming_transactions: HashMap::new(), + inaccessible_incoming_transactions: HashSet::new(), + native_token_foundries: HashMap::new(), + } + } +} + impl Wallet where crate::wallet::Error: From, crate::client::Error: From, { - /// Get all accounts - pub async fn get_accounts(&self) -> crate::wallet::Result>> { - Ok(self.accounts.read().await.clone()) - } - - /// Get all account aliases - pub async fn get_account_aliases(&self) -> crate::wallet::Result> { - let accounts = self.accounts.read().await; - let mut account_aliases = Vec::with_capacity(accounts.len()); - for handle in accounts.iter() { - account_aliases.push(handle.details().await.alias().clone()); + /// Create a new wallet. + pub(crate) async fn new(inner: Arc>, data: WalletData) -> Result { + #[cfg(feature = "storage")] + let default_sync_options = inner + .storage_manager + .read() + .await + .get_default_sync_options() + .await? + .unwrap_or_default(); + #[cfg(not(feature = "storage"))] + let default_sync_options = Default::default(); + + // TODO: maybe move this into a `reset` fn or smth to avoid this kinda-weird block. + { + let mut last_synced = inner.last_synced.lock().await; + *last_synced = Default::default(); + let mut sync_options = inner.default_sync_options.lock().await; + *sync_options = default_sync_options; } - Ok(account_aliases) + + Ok(Self { + inner, + data: Arc::new(RwLock::new(data)), + }) } - /// Removes the latest account (account with the largest account index). - pub async fn remove_latest_account(&self) -> crate::wallet::Result<()> { - let mut largest_account_index_opt = None; - let mut accounts = self.accounts.write().await; + /// Get the [`Output`] that minted a native token by the token ID. First try to get it + /// from the wallet, if it isn't in the wallet try to get it from the node + pub async fn get_foundry_output(&self, native_token_id: TokenId) -> Result { + let foundry_id = FoundryId::from(native_token_id); - for account in accounts.iter() { - let account_index = *account.details().await.index(); - if let Some(largest_account_index) = largest_account_index_opt { - if account_index > largest_account_index { - largest_account_index_opt = Some(account_index); + for output_data in self.data.read().await.outputs.values() { + if let Output::Foundry(foundry_output) = &output_data.output { + if foundry_output.id() == foundry_id { + return Ok(output_data.output.clone()); } - } else { - largest_account_index_opt = Some(account_index) } } - if let Some(largest_account_index) = largest_account_index_opt { - for i in 0..accounts.len() { - if let Some(account) = accounts.get(i) { - if *account.details().await.index() == largest_account_index { - let _ = accounts.remove(i); + // Foundry was not found in the wallet, try to get it from the node + let foundry_output_id = self.client().foundry_output_id(foundry_id).await?; + let output = self.client().get_output(&foundry_output_id).await?; + + Ok(output) + } + + /// Save the wallet to the database, accepts the updated wallet data as option so we don't need to drop it before + /// saving + #[cfg(feature = "storage")] + pub(crate) async fn save(&self, updated_wallet: Option<&WalletData>) -> Result<()> { + log::debug!("[save] wallet data"); + match updated_wallet { + Some(wallet) => { + let mut storage_manager = self.storage_manager.write().await; + storage_manager.save_wallet_data(wallet).await?; + drop(storage_manager); + } + None => { + let wallet_data = self.data.read().await; + let mut storage_manager = self.storage_manager.write().await; + storage_manager.save_wallet_data(&wallet_data).await?; + drop(storage_manager); + drop(wallet_data); + } + } + Ok(()) + } + + #[cfg(feature = "events")] + pub(crate) async fn emit(&self, wallet_event: super::events::types::WalletEvent) { + self.inner.emit(wallet_event).await + } + + pub(crate) async fn data(&self) -> tokio::sync::RwLockReadGuard<'_, WalletData> { + self.data.read().await + } + + pub(crate) async fn data_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, WalletData> { + self.data.write().await + } + + /// Get the alias of the wallet if one was set. + pub async fn alias(&self) -> Option { + self.data().await.alias.clone() + } + + /// Get the wallet address. + pub async fn address(&self) -> Bech32Address { + self.data().await.address.clone() + } + + /// Returns the implicit account creation address of the wallet if it is Ed25519 based. + pub async fn implicit_account_creation_address(&self) -> Result { + let bech32_address = &self.data().await.address; + + if let Address::Ed25519(address) = bech32_address.inner() { + Ok(Bech32Address::new( + *bech32_address.hrp(), + ImplicitAccountCreationAddress::from(address.clone()), + )) + } else { + Err(Error::NonEd25519Address) + } + } + + /// Get the wallet's configured Bech32 HRP. + pub async fn bech32_hrp(&self) -> Hrp { + self.data().await.address.hrp + } + + /// Get the wallet's configured bip path. + pub async fn bip_path(&self) -> Option { + self.data().await.bip_path + } + + /// Get the [`OutputData`] of an output stored in the wallet. + pub async fn get_output(&self, output_id: &OutputId) -> Option { + self.data().await.outputs.get(output_id).cloned() + } + + /// Get the [`TransactionWithMetadata`] of a transaction stored in the wallet. + pub async fn get_transaction(&self, transaction_id: &TransactionId) -> Option { + self.data().await.transactions.get(transaction_id).cloned() + } + + /// Get the transaction with inputs of an incoming transaction stored in the wallet. + /// List might not be complete, if the node pruned the data already + pub async fn get_incoming_transaction(&self, transaction_id: &TransactionId) -> Option { + self.data().await.incoming_transactions.get(transaction_id).cloned() + } - #[cfg(feature = "storage")] - self.storage_manager - .write() - .await - .remove_account(largest_account_index) - .await?; + fn filter_outputs<'a>( + &self, + outputs: impl Iterator, + filter: impl Into>, + ) -> Vec { + let filter = filter.into(); + + if let Some(filter) = filter { + let mut filtered_outputs = Vec::new(); + + for output in outputs { + match &output.output { + Output::Account(account) => { + if let Some(account_ids) = &filter.account_ids { + let account_id = account.account_id_non_null(&output.output_id); + if account_ids.contains(&account_id) { + filtered_outputs.push(output.clone()); + continue; + } + } + } + Output::Anchor(anchor) => { + if let Some(anchor_ids) = &filter.anchor_ids { + let anchor_id = anchor.anchor_id_non_null(&output.output_id); + if anchor_ids.contains(&anchor_id) { + filtered_outputs.push(output.clone()); + continue; + } + } + } + Output::Foundry(foundry) => { + if let Some(foundry_ids) = &filter.foundry_ids { + let foundry_id = foundry.id(); + if foundry_ids.contains(&foundry_id) { + filtered_outputs.push(output.clone()); + continue; + } + } + } + Output::Nft(nft) => { + if let Some(nft_ids) = &filter.nft_ids { + let nft_id = nft.nft_id_non_null(&output.output_id); + if nft_ids.contains(&nft_id) { + filtered_outputs.push(output.clone()); + continue; + } + } + } + Output::Delegation(delegation) => { + if let Some(delegation_ids) = &filter.delegation_ids { + let delegation_id = delegation.delegation_id_non_null(&output.output_id); + if delegation_ids.contains(&delegation_id) { + filtered_outputs.push(output.clone()); + continue; + } + } + } + _ => {} + } - return Ok(()); + // TODO filter based on slot index + // if let Some(lower_bound_booked_timestamp) = filter.lower_bound_booked_timestamp { + // if output.metadata.milestone_timestamp_booked() < lower_bound_booked_timestamp { + // continue; + // } + // } + // if let Some(upper_bound_booked_timestamp) = filter.upper_bound_booked_timestamp { + // if output.metadata.milestone_timestamp_booked() > upper_bound_booked_timestamp { + // continue; + // } + // } + + if let Some(output_types) = &filter.output_types { + if !output_types.contains(&output.output.kind()) { + continue; } } + + // Include the output if we're not filtering by IDs. + if filter.account_ids.is_none() + && filter.anchor_ids.is_none() + && filter.foundry_ids.is_none() + && filter.nft_ids.is_none() + && filter.delegation_ids.is_none() + { + filtered_outputs.push(output.clone()); + } } + + filtered_outputs + } else { + outputs.cloned().collect() } + } - Ok(()) + /// Returns outputs of the wallet. + pub async fn outputs(&self, filter: impl Into> + Send) -> Vec { + self.filter_outputs(self.data().await.outputs.values(), filter) + } + + /// Returns unspent outputs of the wallet. + pub async fn unspent_outputs(&self, filter: impl Into> + Send) -> Vec { + self.filter_outputs(self.data().await.unspent_outputs.values(), filter) } - /// Get the balance of all accounts added together - pub async fn balance(&self) -> crate::wallet::Result { - let mut balance = Balance::default(); - let accounts = self.accounts.read().await; + /// Gets the unspent account output matching the given ID. + pub async fn unspent_account_output(&self, account_id: &AccountId) -> Option { + self.unspent_outputs(FilterOptions { + account_ids: Some([*account_id].into()), + ..Default::default() + }) + .await + .first() + .cloned() + } - for account in accounts.iter() { - balance += account.balance().await?; - } + /// Gets the unspent anchor output matching the given ID. + pub async fn unspent_anchor_output(&self, anchor_id: &AnchorId) -> Option { + self.unspent_outputs(FilterOptions { + anchor_ids: Some([*anchor_id].into()), + ..Default::default() + }) + .await + .first() + .cloned() + } + + /// Gets the unspent foundry output matching the given ID. + pub async fn unspent_foundry_output(&self, foundry_id: &FoundryId) -> Option { + self.unspent_outputs(FilterOptions { + foundry_ids: Some([*foundry_id].into()), + ..Default::default() + }) + .await + .first() + .cloned() + } + + /// Gets the unspent nft output matching the given ID. + pub async fn unspent_nft_output(&self, nft_id: &NftId) -> Option { + self.unspent_outputs(FilterOptions { + nft_ids: Some([*nft_id].into()), + ..Default::default() + }) + .await + .first() + .cloned() + } + + /// Gets the unspent delegation output matching the given ID. + pub async fn unspent_delegation_output(&self, delegation_id: &DelegationId) -> Option { + self.unspent_outputs(FilterOptions { + delegation_ids: Some([*delegation_id].into()), + ..Default::default() + }) + .await + .first() + .cloned() + } + + /// Returns implicit accounts of the wallet. + pub async fn implicit_accounts(&self) -> Vec { + self.data() + .await + .unspent_outputs + .values() + .filter(|output_data| output_data.output.is_implicit_account()) + .cloned() + .collect() + } - Ok(balance) + /// Returns accounts of the wallet. + pub async fn accounts(&self) -> Vec { + self.data() + .await + .unspent_outputs + .values() + .filter(|output_data| output_data.output.is_account()) + .cloned() + .collect() } - /// Sync all accounts - pub async fn sync(&self, options: Option) -> crate::wallet::Result { - let mut balance = Balance::default(); + /// Returns all incoming transactions of the wallet + pub async fn incoming_transactions(&self) -> Vec { + self.data().await.incoming_transactions.values().cloned().collect() + } + + /// Returns all transactions of the wallet + pub async fn transactions(&self) -> Vec { + self.data().await.transactions.values().cloned().collect() + } - for account in self.accounts.read().await.iter() { - balance += account.sync(options.clone()).await?; + /// Returns all pending transactions of the wallet + pub async fn pending_transactions(&self) -> Vec { + let mut transactions = Vec::new(); + let wallet_data = self.data().await; + + for transaction_id in &wallet_data.pending_transactions { + if let Some(transaction) = wallet_data.transactions.get(transaction_id) { + transactions.push(transaction.clone()); + } } - Ok(balance) + transactions } } impl WalletInner { - pub fn coin_type(&self) -> u32 { - self.coin_type.load(Ordering::Relaxed) - } - /// Get the [SecretManager] pub fn get_secret_manager(&self) -> &Arc> { &self.secret_manager @@ -184,7 +514,7 @@ impl WalletInner { pub async fn listen + Send>(&self, events: I, handler: F) where I::IntoIter: Send, - F: Fn(&Event) + 'static + Send + Sync, + F: Fn(&WalletEvent) + 'static + Send + Sync, { let mut emitter = self.event_emitter.write().await; emitter.on(events, handler); @@ -213,15 +543,15 @@ impl WalletInner { } #[cfg(feature = "events")] - pub(crate) async fn emit(&self, account_index: u32, event: crate::wallet::events::types::WalletEvent) { - self.event_emitter.read().await.emit(account_index, event); + pub(crate) async fn emit(&self, event: crate::wallet::events::types::WalletEvent) { + self.event_emitter.read().await.emit(event); } - /// Helper function to test events. Emits a provided event with account index 0. + /// Helper function to test events. #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] pub async fn emit_test_event(&self, event: crate::wallet::events::types::WalletEvent) { - self.emit(0, event).await + self.emit(event).await } } @@ -230,3 +560,244 @@ impl Drop for Wallet { log::debug!("drop Wallet"); } } + +/// Dto for the wallet data. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WalletDataDto { + pub bip_path: Option, + pub address: Bech32Address, + pub alias: Option, + pub outputs: HashMap, + pub locked_outputs: HashSet, + pub unspent_outputs: HashMap, + pub transactions: HashMap, + pub pending_transactions: HashSet, + pub incoming_transactions: HashMap, + #[serde(default)] + pub native_token_foundries: HashMap, +} + +impl TryFromDto for WalletData { + type Dto = WalletDataDto; + type Error = crate::wallet::Error; + + fn try_from_dto_with_params_inner( + dto: Self::Dto, + params: crate::types::ValidationParams<'_>, + ) -> core::result::Result { + Ok(Self { + bip_path: dto.bip_path, + address: dto.address, + alias: dto.alias, + outputs: dto + .outputs + .into_iter() + .map(|(id, o)| Ok((id, OutputData::try_from(o)?))) + .collect::>()?, + locked_outputs: dto.locked_outputs, + unspent_outputs: dto + .unspent_outputs + .into_iter() + .map(|(id, o)| Ok((id, OutputData::try_from(o)?))) + .collect::>()?, + transactions: dto + .transactions + .into_iter() + .map(|(id, o)| Ok((id, TransactionWithMetadata::try_from_dto_with_params(o, ¶ms)?))) + .collect::>()?, + pending_transactions: dto.pending_transactions, + incoming_transactions: dto + .incoming_transactions + .into_iter() + .map(|(id, o)| Ok((id, TransactionWithMetadata::try_from_dto_with_params(o, ¶ms)?))) + .collect::>()?, + inaccessible_incoming_transactions: Default::default(), + native_token_foundries: dto + .native_token_foundries + .into_iter() + .map(|(id, o)| Ok((id, FoundryOutput::try_from(o)?))) + .collect::>()?, + }) + } +} + +impl From<&WalletData> for WalletDataDto { + fn from(value: &WalletData) -> Self { + Self { + bip_path: value.bip_path, + address: value.address.clone(), + alias: value.alias.clone(), + outputs: value + .outputs + .iter() + .map(|(id, output)| (*id, OutputDataDto::from(output))) + .collect(), + locked_outputs: value.locked_outputs.clone(), + unspent_outputs: value + .unspent_outputs + .iter() + .map(|(id, output)| (*id, OutputDataDto::from(output))) + .collect(), + transactions: value + .transactions + .iter() + .map(|(id, transaction)| (*id, TransactionWithMetadataDto::from(transaction))) + .collect(), + pending_transactions: value.pending_transactions.clone(), + incoming_transactions: value + .incoming_transactions + .iter() + .map(|(id, transaction)| (*id, TransactionWithMetadataDto::from(transaction))) + .collect(), + native_token_foundries: value + .native_token_foundries + .iter() + .map(|(id, foundry)| (*id, FoundryOutputDto::from(foundry))) + .collect(), + } + } +} + +#[cfg(test)] +mod test { + use core::str::FromStr; + + use pretty_assertions::assert_eq; + + use super::*; + use crate::{ + types::block::{ + address::{Address, Ed25519Address}, + input::{Input, UtxoInput}, + output::{AddressUnlockCondition, BasicOutput, Output, StorageScoreParameters}, + payload::signed_transaction::{SignedTransactionPayload, Transaction, TransactionId}, + protocol::ProtocolParameters, + rand::mana::rand_mana_allotment, + signature::{Ed25519Signature, Signature}, + unlock::{ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, + }, + wallet::types::InclusionState, + }; + + const TRANSACTION_ID: &str = "0x24a1f46bdb6b2bf38f1c59f73cdd4ae5b418804bb231d76d06fbf246498d588300000000"; + const ED25519_ADDRESS: &str = "0xe594f9a895c0e0a6760dd12cffc2c3d1e1cbf7269b328091f96ce3d0dd550b75"; + const ED25519_PUBLIC_KEY: &str = "0x1da5ddd11ba3f961acab68fafee3177d039875eaa94ac5fdbff8b53f0c50bfb9"; + const ED25519_SIGNATURE: &str = "0xc6a40edf9a089f42c18f4ebccb35fe4b578d93b879e99b87f63573324a710d3456b03fb6d1fcc027e6401cbd9581f790ee3ed7a3f68e9c225fcb9f1cd7b7110d"; + + #[test] + fn serialize() { + let protocol_parameters = ProtocolParameters::new( + 2, + "testnet", + "rms", + StorageScoreParameters::new(500, 1, 10, 1, 1, 1), + 1_813_620_509_061_365, + 1582328545, + 10, + 20, + ) + .unwrap(); + + let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); + let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0).unwrap()); + let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1).unwrap()); + let bytes: [u8; 32] = prefix_hex::decode(ED25519_ADDRESS).unwrap(); + let address = Address::from(Ed25519Address::new(bytes)); + let amount = 1_000_000; + let output = Output::Basic( + BasicOutput::build_with_amount(amount) + .add_unlock_condition(AddressUnlockCondition::new(address)) + .finish() + .unwrap(), + ); + let transaction = Transaction::builder(protocol_parameters.network_id()) + .with_inputs([input1, input2]) + .add_output(output) + .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) + .finish_with_params(protocol_parameters) + .unwrap(); + + let pub_key_bytes = prefix_hex::decode(ED25519_PUBLIC_KEY).unwrap(); + let sig_bytes = prefix_hex::decode(ED25519_SIGNATURE).unwrap(); + let signature = Ed25519Signature::try_from_bytes(pub_key_bytes, sig_bytes).unwrap(); + let sig_unlock = Unlock::from(SignatureUnlock::from(Signature::from(signature))); + let ref_unlock = Unlock::from(ReferenceUnlock::new(0).unwrap()); + let unlocks = Unlocks::new([sig_unlock, ref_unlock]).unwrap(); + + let tx_payload = SignedTransactionPayload::new(transaction, unlocks).unwrap(); + + let incoming_transaction = TransactionWithMetadata { + transaction_id: TransactionId::from_str( + "0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a00000000", + ) + .unwrap(), + payload: tx_payload, + block_id: None, + network_id: 0, + timestamp: 0, + inclusion_state: InclusionState::Pending, + incoming: false, + note: None, + inputs: Vec::new(), + }; + + let mut incoming_transactions = HashMap::new(); + incoming_transactions.insert( + TransactionId::from_str("0x131fc4cb8f315ae36ae3bf6a4e4b3486d5f17581288f1217410da3e0700d195a00000000") + .unwrap(), + incoming_transaction, + ); + + let wallet_data = WalletData { + bip_path: Some(Bip44::new(4218)), + address: crate::types::block::address::Bech32Address::from_str( + "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", + ) + .unwrap(), + alias: Some("Alice".to_string()), + outputs: HashMap::new(), + locked_outputs: HashSet::new(), + unspent_outputs: HashMap::new(), + transactions: HashMap::new(), + pending_transactions: HashSet::new(), + incoming_transactions, + inaccessible_incoming_transactions: HashSet::new(), + native_token_foundries: HashMap::new(), + }; + + let deser_wallet_data = WalletData::try_from_dto( + serde_json::from_str::(&serde_json::to_string(&WalletDataDto::from(&wallet_data)).unwrap()) + .unwrap(), + ) + .unwrap(); + + assert_eq!(wallet_data, deser_wallet_data); + } + + impl WalletData { + /// Returns a mock of this type with the following values: + /// index: 0, coin_type: 4218, alias: "Alice", address: + /// rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy, all other fields are set to their Rust + /// defaults. + #[cfg(feature = "storage")] + pub(crate) fn mock() -> Self { + Self { + bip_path: Some(Bip44::new(4218)), + address: crate::types::block::address::Bech32Address::from_str( + "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", + ) + .unwrap(), + alias: Some("Alice".to_string()), + outputs: HashMap::new(), + locked_outputs: HashSet::new(), + unspent_outputs: HashMap::new(), + transactions: HashMap::new(), + pending_transactions: HashSet::new(), + incoming_transactions: HashMap::new(), + inaccessible_incoming_transactions: HashSet::new(), + native_token_foundries: HashMap::new(), + } + } + } +} diff --git a/sdk/src/wallet/core/operations/account_recovery.rs b/sdk/src/wallet/core/operations/account_recovery.rs deleted file mode 100644 index 743639080d..0000000000 --- a/sdk/src/wallet/core/operations/account_recovery.rs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use instant::Instant; - -use crate::{ - client::secret::SecretManage, - wallet::{account::SyncOptions, task, Account, Wallet}, -}; - -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ - /// Find accounts with unspent outputs. - /// - /// Arguments: - /// - /// * `account_start_index`: The index of the first account to search for. - /// * `account_gap_limit`: The number of accounts to search for, after the last account with unspent outputs. - /// * `address_gap_limit`: The number of addresses to search for, after the last address with unspent outputs, in - /// each account. - /// * `sync_options`: Optional parameter to specify the sync options. The `address_start_index` and `force_syncing` - /// fields will be overwritten to skip existing addresses. - /// - /// Returns: - /// - /// A vector of Account - pub async fn recover_accounts( - &self, - account_start_index: u32, - account_gap_limit: u32, - address_gap_limit: u32, - sync_options: Option, - ) -> crate::wallet::Result>> { - log::debug!("[recover_accounts]"); - let start_time = Instant::now(); - let mut max_account_index_to_keep = None; - - // Search for addresses in current accounts - for account in self.accounts.read().await.iter() { - // If the gap limit is 0, there is no need to search for funds - if address_gap_limit > 0 { - account - .search_addresses_with_outputs(address_gap_limit, sync_options.clone()) - .await?; - } - let account_index = *account.details().await.index(); - match max_account_index_to_keep { - Some(max_account_index) => { - if account_index > max_account_index { - max_account_index_to_keep = Some(account_index); - } - } - None => max_account_index_to_keep = Some(account_index), - } - } - - // Create accounts below account_start_index, because we don't want to have gaps in the accounts, but we also - // don't want to sync them - for _ in max_account_index_to_keep.unwrap_or(0)..account_start_index { - // Don't return possible errors here, because we could then still have empty accounts - let _ = self.create_account().finish().await; - } - - // Don't return possible errors here already, because we would then still have empty accounts - let new_accounts_discovery_result = self - .search_new_accounts( - account_gap_limit, - address_gap_limit, - &mut max_account_index_to_keep, - sync_options.clone(), - ) - .await; - - // remove accounts without outputs - let mut new_accounts = Vec::new(); - let mut accounts = self.accounts.write().await; - - for account in accounts.iter() { - let account_index = *account.details().await.index(); - let mut keep_account = false; - - if let Some(max_account_index_to_keep) = max_account_index_to_keep { - if account_index <= max_account_index_to_keep { - new_accounts.push((account_index, account.clone())); - keep_account = true; - } - } - - if !keep_account { - // accounts are stored during syncing, delete the empty accounts again - #[cfg(feature = "storage")] - { - log::debug!("[recover_accounts] delete empty account {}", account_index); - self.storage_manager.write().await.remove_account(account_index).await?; - } - } - } - new_accounts.sort_by_key(|(index, _acc)| *index); - *accounts = new_accounts.into_iter().map(|(_, acc)| acc).collect(); - drop(accounts); - - // Handle result after cleaning up the empty accounts - new_accounts_discovery_result?; - - log::debug!("[recover_accounts] finished in {:?}", start_time.elapsed()); - Ok(self.accounts.read().await.clone()) - } - - /// Generate new accounts and search for unspent outputs - async fn search_new_accounts( - &self, - account_gap_limit: u32, - address_gap_limit: u32, - max_account_index_to_keep: &mut Option, - sync_options: Option, - ) -> crate::wallet::Result<()> { - let mut updated_account_gap_limit = account_gap_limit; - loop { - log::debug!("[recover_accounts] generating {updated_account_gap_limit} new accounts"); - - // Generate account with addresses and get their outputs in parallel - let results = futures::future::try_join_all((0..updated_account_gap_limit).map(|_| { - let mut new_account = self.create_account(); - let sync_options_ = sync_options.clone(); - async move { - task::spawn(async move { - let new_account = new_account.finish().await?; - let account_outputs_count = new_account - .search_addresses_with_outputs(address_gap_limit, sync_options_) - .await?; - let account_index = *new_account.details().await.index(); - crate::wallet::Result::Ok((account_index, account_outputs_count)) - }) - .await? - } - })) - .await?; - - let mut new_accounts_with_outputs = 0; - let mut highest_account_index = 0; - for (account_index, outputs_count) in results { - if outputs_count != 0 { - new_accounts_with_outputs += 1; - - match *max_account_index_to_keep { - Some(max_account_index) => { - if account_index > max_account_index { - *max_account_index_to_keep = Some(account_index); - } - } - None => *max_account_index_to_keep = Some(account_index), - } - } - - if account_index > highest_account_index { - highest_account_index = account_index; - } - } - - // Break if there is no new account with outputs - if new_accounts_with_outputs == 0 { - break; - } - - // Update account_gap_limit to only create so many new accounts, that we would check the initial provided - // account_gap_limit amount of empty accounts - if let Some(max_account_index_to_keep) = &max_account_index_to_keep { - let empty_accounts_in_row = highest_account_index - max_account_index_to_keep; - log::debug!("[recover_accounts] empty_accounts_in_row {empty_accounts_in_row}"); - updated_account_gap_limit = account_gap_limit - empty_accounts_in_row; - } - } - - Ok(()) - } -} diff --git a/sdk/src/wallet/core/operations/address_generation.rs b/sdk/src/wallet/core/operations/address_generation.rs index 6fb477fa87..fe902ee8ec 100644 --- a/sdk/src/wallet/core/operations/address_generation.rs +++ b/sdk/src/wallet/core/operations/address_generation.rs @@ -1,12 +1,10 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::atomic::Ordering; - use crate::{ client::secret::{GenerateAddressOptions, SecretManage, SecretManager}, - types::block::address::{Ed25519Address, Hrp}, - wallet::Wallet, + types::block::address::Ed25519Address, + wallet::{Error, Wallet}, }; #[cfg(all(feature = "events", feature = "ledger_nano"))] use crate::{ @@ -18,12 +16,7 @@ impl Wallet { /// Generate an address without storing it /// ```ignore /// let public_addresses = wallet - /// .generate_ed25519_addresses( - /// 0, - /// false, - /// 0, - /// None, - /// ) + /// .generate_ed25519_address(None) /// .await?; /// ``` pub async fn generate_ed25519_address( @@ -32,6 +25,10 @@ impl Wallet { address_index: u32, options: impl Into> + Send, ) -> crate::wallet::Result { + // TODO #1279: not sure yet whether we also should allow this method to generate addresses for different bip + // paths. + let coin_type = self.bip_path().await.ok_or(Error::MissingBipPath)?.coin_type; + let address = match &*self.secret_manager.read().await { #[cfg(feature = "ledger_nano")] SecretManager::LedgerNano(ledger_nano) => { @@ -50,74 +47,46 @@ impl Wallet { // Generate without prompt to be able to display it let address = ledger_nano .generate_ed25519_addresses( - self.coin_type.load(Ordering::Relaxed), + coin_type, account_index, address_index..address_index + 1, changed_options, ) .await?; - let bech32_hrp = self.get_bech32_hrp().await?; + let bech32_hrp = self.bech32_hrp().await; - self.emit( - account_index, - WalletEvent::LedgerAddressGeneration(AddressData { - address: address[0].to_bech32(bech32_hrp), - }), - ) + self.emit(WalletEvent::LedgerAddressGeneration(AddressData { + address: address[0].to_bech32(bech32_hrp), + })) .await; } // Generate with prompt so the user can verify ledger_nano - .generate_ed25519_addresses( - self.coin_type.load(Ordering::Relaxed), - account_index, - address_index..address_index + 1, - options, - ) + .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) .await? } else { ledger_nano - .generate_ed25519_addresses( - self.coin_type.load(Ordering::Relaxed), - account_index, - address_index..address_index + 1, - options, - ) + .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) .await? } } #[cfg(feature = "stronghold")] SecretManager::Stronghold(stronghold) => { stronghold - .generate_ed25519_addresses( - self.coin_type.load(Ordering::Relaxed), - account_index, - address_index..address_index + 1, - options, - ) + .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) .await? } SecretManager::Mnemonic(mnemonic) => { mnemonic - .generate_ed25519_addresses( - self.coin_type.load(Ordering::Relaxed), - account_index, - address_index..address_index + 1, - options, - ) + .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) .await? } #[cfg(feature = "private_key_secret_manager")] SecretManager::PrivateKey(private_key) => { private_key - .generate_ed25519_addresses( - self.coin_type.load(Ordering::Relaxed), - account_index, - address_index..address_index + 1, - options, - ) + .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) .await? } SecretManager::Placeholder => return Err(crate::client::Error::PlaceholderSecretManager.into()), @@ -128,25 +97,3 @@ impl Wallet { .ok_or(crate::wallet::Error::MissingParameter("address"))?) } } - -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ - /// Get the bech32 hrp from the first account address or if not existent, from the client - pub async fn get_bech32_hrp(&self) -> crate::wallet::Result { - Ok(match self.get_accounts().await?.first() { - Some(account) => { - account - .public_addresses() - .await - .first() - .expect("missing first public address") - .address - .hrp - } - None => self.client().get_bech32_hrp().await?, - }) - } -} diff --git a/sdk/src/wallet/core/operations/background_syncing.rs b/sdk/src/wallet/core/operations/background_syncing.rs index 174317c9c2..9840b967b6 100644 --- a/sdk/src/wallet/core/operations/background_syncing.rs +++ b/sdk/src/wallet/core/operations/background_syncing.rs @@ -7,7 +7,7 @@ use tokio::time::sleep; use crate::{ client::secret::SecretManage, - wallet::{account::operations::syncing::SyncOptions, Wallet}, + wallet::{operations::syncing::SyncOptions, Wallet}, }; /// The default interval for background syncing @@ -18,7 +18,7 @@ where crate::wallet::Error: From, crate::client::Error: From, { - /// Start the background syncing process for all accounts, default interval is 7 seconds + /// Start the background syncing process for the wallet, default interval is 7 seconds pub async fn start_background_syncing( &self, options: Option, @@ -49,18 +49,12 @@ where .unwrap(); runtime.block_on(async { 'outer: loop { - log::debug!("[background_syncing]: syncing accounts"); - for account in wallet.accounts.read().await.iter() { - // Check if the process should stop before syncing each account so it stops faster - if wallet.background_syncing_status.load(Ordering::Relaxed) == 2 { - log::debug!("[background_syncing]: stopping"); - break 'outer; - } - match account.sync(options.clone()).await { - Ok(_) => {} - Err(err) => log::debug!("[background_syncing] error: {}", err), - }; + log::debug!("[background_syncing]: syncing wallet"); + + if let Err(err) = wallet.sync(options.clone()).await { + log::debug!("[background_syncing] error: {}", err) } + // split interval syncing to seconds so stopping the process doesn't have to wait long let seconds = interval.unwrap_or(DEFAULT_BACKGROUNDSYNCING_INTERVAL).as_secs(); for _ in 0..seconds { @@ -78,7 +72,7 @@ where Ok(()) } - /// Stop the background syncing of the accounts + /// Stop the background syncing of the wallet pub async fn stop_background_syncing(&self) -> crate::wallet::Result<()> { log::debug!("[stop_background_syncing]"); // immediately return if not running diff --git a/sdk/src/wallet/core/operations/client.rs b/sdk/src/wallet/core/operations/client.rs index 2750e4a9f6..fadc9309ed 100644 --- a/sdk/src/wallet/core/operations/client.rs +++ b/sdk/src/wallet/core/operations/client.rs @@ -30,6 +30,7 @@ impl Wallet { impl Wallet where + crate::client::Error: From, crate::wallet::Error: From, WalletBuilder: SaveLoadWallet, { @@ -67,9 +68,7 @@ where } *self.client.network_info.write().await = network_info; - for account in self.accounts.write().await.iter_mut() { - account.update_account_bech32_hrp().await?; - } + self.update_bech32_hrp().await?; } #[cfg(feature = "storage")] @@ -154,9 +153,7 @@ where .update_node_manager(node_manager_builder.build(HashMap::new())) .await?; - for account in self.accounts.write().await.iter_mut() { - account.update_account_bech32_hrp().await?; - } + self.update_bech32_hrp().await?; Ok(()) } diff --git a/sdk/src/wallet/core/operations/get_account.rs b/sdk/src/wallet/core/operations/get_account.rs deleted file mode 100644 index 463207333e..0000000000 --- a/sdk/src/wallet/core/operations/get_account.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - client::secret::SecretManage, - wallet::{ - account::{types::AccountIdentifier, Account}, - Wallet, - }, -}; - -impl Wallet { - /// Get an account with an AccountIdentifier - pub async fn get_account + Send>( - &self, - identifier: I, - ) -> crate::wallet::Result> { - let account_id = identifier.into(); - let accounts = self.accounts.read().await; - - match &account_id { - AccountIdentifier::Index(index) => { - for account in accounts.iter() { - let account_details = account.details().await; - - if account_details.index() == index { - return Ok(account.clone()); - } - } - } - AccountIdentifier::Alias(alias) => { - for account in accounts.iter() { - let account_details = account.details().await; - - if account_details.alias() == alias { - return Ok(account.clone()); - } - } - } - }; - - Err(crate::wallet::Error::AccountNotFound(serde_json::to_string( - &account_id, - )?)) - } -} - -impl Wallet -where - crate::wallet::Error: From, -{ - pub async fn get_or_create_account(&self, alias: impl Into + Send) -> crate::wallet::Result> { - let alias = alias.into(); - match self.get_account(&alias).await { - Err(crate::wallet::Error::AccountNotFound(_)) => self.create_account().with_alias(alias).finish().await, - res => res, - } - } -} diff --git a/sdk/src/wallet/core/operations/mod.rs b/sdk/src/wallet/core/operations/mod.rs index 8948801765..e01ca173a6 100644 --- a/sdk/src/wallet/core/operations/mod.rs +++ b/sdk/src/wallet/core/operations/mod.rs @@ -1,11 +1,9 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -pub(crate) mod account_recovery; pub(crate) mod address_generation; pub(crate) mod background_syncing; pub(crate) mod client; -pub(crate) mod get_account; #[cfg(feature = "ledger_nano")] pub(crate) mod ledger_nano; pub(crate) mod storage; @@ -13,5 +11,3 @@ pub(crate) mod storage; pub(crate) mod stronghold; #[cfg(feature = "stronghold")] pub(crate) mod stronghold_backup; -#[cfg(debug_assertions)] -pub(crate) mod verify_integrity; diff --git a/sdk/src/wallet/core/operations/storage.rs b/sdk/src/wallet/core/operations/storage.rs index 2109618030..7ccbc935de 100644 --- a/sdk/src/wallet/core/operations/storage.rs +++ b/sdk/src/wallet/core/operations/storage.rs @@ -12,7 +12,7 @@ mod storage_stub { }, wallet::{ core::builder::dto::WalletBuilderDto, - storage::constants::{SECRET_MANAGER_KEY, WALLET_INDEXATION_KEY}, + storage::constants::{SECRET_MANAGER_KEY, WALLET_BUILDER_KEY}, WalletBuilder, }, }; @@ -34,13 +34,13 @@ mod storage_stub { crate::wallet::Error: From, { async fn save(&self, storage: &impl StorageAdapter) -> crate::wallet::Result<()> { - log::debug!("save_wallet_data"); - storage.set(WALLET_INDEXATION_KEY, self).await?; + log::debug!("[save] wallet builder"); + storage.set(WALLET_BUILDER_KEY, self).await?; if let Some(secret_manager) = &self.secret_manager { let secret_manager = secret_manager.read().await; if let Some(config) = secret_manager.to_config() { - log::debug!("save_secret_manager: {config:?}"); + log::debug!("[save] secret manager: {config:?}"); storage.set(SECRET_MANAGER_KEY, &config).await?; } } @@ -50,14 +50,14 @@ mod storage_stub { async fn load( storage: &impl StorageAdapter, ) -> crate::wallet::Result> { - log::debug!("get_wallet_data"); - if let Some(data) = storage.get::(WALLET_INDEXATION_KEY).await? { - log::debug!("get_wallet_data {data:?}"); + log::debug!("[load] wallet builder"); + if let Some(wallet_builder_dto) = storage.get::(WALLET_BUILDER_KEY).await? { + log::debug!("[load] wallet builder dto: {wallet_builder_dto:?}"); let secret_manager_dto = storage.get(SECRET_MANAGER_KEY).await?; - log::debug!("get_secret_manager {secret_manager_dto:?}"); + log::debug!("[load] secret manager dto: {secret_manager_dto:?}"); - Ok(Some(Self::from(data).with_secret_manager( + Ok(Some(Self::from(wallet_builder_dto).with_secret_manager( secret_manager_dto.map(|dto| S::from_config(&dto)).transpose()?, ))) } else { @@ -69,17 +69,17 @@ mod storage_stub { #[async_trait] impl SaveLoadWallet for WalletBuilder { async fn save(&self, storage: &impl StorageAdapter) -> crate::wallet::Result<()> { - log::debug!("save_wallet_data"); - storage.set(WALLET_INDEXATION_KEY, self).await?; + log::debug!("[save] wallet builder"); + storage.set(WALLET_BUILDER_KEY, self).await?; Ok(()) } async fn load( storage: &impl StorageAdapter, ) -> crate::wallet::Result> { - log::debug!("get_wallet_data"); - let res = storage.get::(WALLET_INDEXATION_KEY).await?; - log::debug!("get_wallet_data {res:?}"); + log::debug!("[load] wallet builder"); + let res = storage.get::(WALLET_BUILDER_KEY).await?; + log::debug!("[load] wallet builder: {res:?}"); Ok(res.map(Into::into)) } } diff --git a/sdk/src/wallet/core/operations/stronghold_backup/mod.rs b/sdk/src/wallet/core/operations/stronghold_backup/mod.rs index 4c667bd77a..ee8bb3fca9 100644 --- a/sdk/src/wallet/core/operations/stronghold_backup/mod.rs +++ b/sdk/src/wallet/core/operations/stronghold_backup/mod.rs @@ -3,11 +3,9 @@ pub(crate) mod stronghold_snapshot; -use std::{fs, path::PathBuf, sync::atomic::Ordering}; +use std::{fs, path::PathBuf}; -use futures::{future::try_join_all, FutureExt}; - -use self::stronghold_snapshot::read_data_from_stronghold_snapshot; +use self::stronghold_snapshot::read_wallet_data_from_stronghold_snapshot; #[cfg(feature = "storage")] use crate::wallet::WalletBuilder; use crate::{ @@ -16,7 +14,7 @@ use crate::{ utils::Password, }, types::block::address::Hrp, - wallet::{Account, Wallet}, + wallet::Wallet, }; impl Wallet { @@ -58,19 +56,19 @@ impl Wallet { } /// Restore a backup from a Stronghold file - /// Replaces client_options, coin_type, secret_manager and accounts. Returns an error if accounts were already + /// Replaces client_options, bip_path, secret_manager and wallet. Returns an error if the wallet was already /// created If Stronghold is used as secret_manager, the existing Stronghold file will be overwritten. If a /// mnemonic was stored, it will be gone. - /// if ignore_if_coin_type_mismatch.is_some(), client options will not be restored - /// if ignore_if_coin_type_mismatch == Some(true), client options coin type and accounts will not be restored if the + /// if ignore_if_bip_path_mismatch.is_some(), client options will not be restored + /// if ignore_if_bip_path_mismatch == Some(true), client options coin type and wallet will not be restored if the /// coin type doesn't match - /// if ignore_if_bech32_hrp_mismatch == Some("rms"), but addresses have something different like "smr", no accounts - /// will be restored. + /// If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current address, + /// the wallet will not be restored. pub async fn restore_backup( &self, backup_path: PathBuf, stronghold_password: impl Into + Send, - ignore_if_coin_type_mismatch: Option, + ignore_if_bip_path_mismatch: Option, ignore_if_bech32_hrp_mismatch: Option, ) -> crate::wallet::Result<()> { let stronghold_password = stronghold_password.into(); @@ -81,12 +79,7 @@ impl Wallet { return Err(crate::wallet::Error::Backup("backup path doesn't exist")); } - // We don't want to overwrite possible existing accounts - if !self.accounts.read().await.is_empty() { - return Err(crate::wallet::Error::Backup( - "can't restore backup when there are already accounts", - )); - } + let mut wallet_data = self.data.write().await; let mut secret_manager = self.secret_manager.as_ref().write().await; // Get the current snapshot path if set @@ -101,25 +94,23 @@ impl Wallet { .password(stronghold_password.clone()) .build(backup_path.clone())?; - let (read_client_options, read_coin_type, read_secret_manager, read_accounts) = - read_data_from_stronghold_snapshot::(&new_stronghold).await?; + let (read_client_options, read_secret_manager, read_wallet_data) = + read_wallet_data_from_stronghold_snapshot::(&new_stronghold).await?; + + let read_bip_path = read_wallet_data.as_ref().and_then(|data| data.bip_path); - // If the coin type is not matching the current one, then the addresses in the accounts will also not be - // correct, so we will not restore them - let ignore_backup_values = ignore_if_coin_type_mismatch.map_or(false, |ignore| { + // If the bip path is not matching the current one, we may ignore the backup + let ignore_backup_values = ignore_if_bip_path_mismatch.map_or(false, |ignore| { if ignore { - read_coin_type.map_or(true, |read_coin_type| { - self.coin_type.load(Ordering::Relaxed) != read_coin_type - }) + // TODO: #1279 okay that if both are none we always load the backup values? + wallet_data.bip_path != read_bip_path } else { false } }); if !ignore_backup_values { - if let Some(read_coin_type) = read_coin_type { - self.coin_type.store(read_coin_type, Ordering::Relaxed); - } + (*wallet_data).bip_path = read_bip_path; } if let Some(mut read_secret_manager) = read_secret_manager { @@ -148,37 +139,21 @@ impl Wallet { // drop secret manager, otherwise we get a deadlock in set_client_options() (there inside of save_wallet_data()) drop(secret_manager); - if ignore_if_coin_type_mismatch.is_none() { + if ignore_if_bip_path_mismatch.is_none() { if let Some(read_client_options) = read_client_options { self.set_client_options(read_client_options).await?; } } - let mut accounts = self.accounts.write().await; - if !ignore_backup_values { - if let Some(read_accounts) = read_accounts { - let restore_accounts = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { + if let Some(read_wallet_data) = read_wallet_data { + let restore_wallet = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { // Only restore if bech32 hrps match - read_accounts.first().map_or(true, |account| { - account - .public_addresses - .first() - .expect("account needs to have a public address") - .address() - .hrp() - == &expected_bech32_hrp - }) + read_wallet_data.address.hrp() == &expected_bech32_hrp }); - if restore_accounts { - let restored_account = try_join_all( - read_accounts - .into_iter() - .map(|a| Account::new(a, self.inner.clone()).boxed()), - ) - .await?; - *accounts = restored_account; + if restore_wallet { + *wallet_data = read_wallet_data; } } } @@ -199,12 +174,12 @@ impl Wallet { .expect("can't convert os string"), ) .with_client_options(self.client_options().await) - .with_coin_type(self.coin_type.load(Ordering::Relaxed)); + .with_bip_path(self.data().await.bip_path); + wallet_builder.save(&*self.storage_manager.read().await).await?; - // also save account to db - for account in accounts.iter() { - account.save(None).await?; - } + + // also save wallet data to db + self.save(Some(&wallet_data)).await?; } Ok(()) @@ -233,20 +208,20 @@ impl Wallet { } /// Restore a backup from a Stronghold file - /// Replaces client_options, coin_type, secret_manager and accounts. Returns an error if accounts were already + /// Replaces client_options, bip path, secret_manager and wallet. Returns an error if the wallet was already /// created If Stronghold is used as secret_manager, the existing Stronghold file will be overwritten. If a /// mnemonic was stored, it will be gone. - /// if ignore_if_coin_type_mismatch.is_some(), client options will not be restored - /// if ignore_if_coin_type_mismatch == Some(true), client options coin type and accounts will not be restored if the - /// coin type doesn't match - /// if ignore_if_bech32_hrp_mismatch == Some("rms"), but addresses have something different like "smr", no accounts - /// will be restored. + /// if ignore_if_bip_path_mismatch.is_some(), client options will not be restored + /// if ignore_if_bip_path_mismatch == Some(true), client options bip path and wallet will not be restored if the + /// bip path doesn't match + /// If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current address, + /// the wallet will not be restored. pub async fn restore_backup( &self, backup_path: PathBuf, stronghold_password: impl Into + Send, - ignore_if_coin_type_mismatch: Option, - ignore_if_bech32_hrp_mismatch: Option<&str>, + ignore_if_bip_path_mismatch: Option, + ignore_if_bech32_hrp_mismatch: Option, ) -> crate::wallet::Result<()> { let stronghold_password = stronghold_password.into(); @@ -256,11 +231,14 @@ impl Wallet { return Err(crate::wallet::Error::Backup("backup path doesn't exist")); } - let mut accounts = self.accounts.write().await; - // We don't want to overwrite possible existing accounts - if !accounts.is_empty() { + // Will be replaced by the restored wallet data + let mut wallet_data = self.data_mut().await; + + // We don't want to overwrite a possible existing wallet + // TODO not too sure about this, it used to check the presence of accounts, this is not 100% equivalent + if !wallet_data.outputs.is_empty() { return Err(crate::wallet::Error::Backup( - "can't restore backup when there are already accounts", + "can't restore backup when there is already a wallet", )); } @@ -273,25 +251,23 @@ impl Wallet { .password(stronghold_password.clone()) .build(backup_path.clone())?; - let (read_client_options, read_coin_type, read_secret_manager, read_accounts) = - read_data_from_stronghold_snapshot::(&new_stronghold).await?; + let (read_client_options, read_secret_manager, read_wallet_data) = + read_wallet_data_from_stronghold_snapshot::(&new_stronghold).await?; + + let read_bip_path = read_wallet_data.as_ref().and_then(|data| data.bip_path); - // If the coin type is not matching the current one, then the addresses in the accounts will also not be - // correct, so we will not restore them - let ignore_backup_values = ignore_if_coin_type_mismatch.map_or(false, |ignore| { + // If the bip path is not matching the current one, we may ignore the backup + let ignore_backup_values = ignore_if_bip_path_mismatch.map_or(false, |ignore| { if ignore { - read_coin_type.map_or(true, |read_coin_type| { - self.coin_type.load(Ordering::Relaxed) != read_coin_type - }) + // TODO: #1279 okay that if both are none we always load the backup values? + wallet_data.bip_path != read_bip_path } else { false } }); if !ignore_backup_values { - if let Some(read_coin_type) = read_coin_type { - self.coin_type.store(read_coin_type, Ordering::Relaxed); - } + (*wallet_data).bip_path = read_bip_path; } if let Some(mut read_secret_manager) = read_secret_manager { @@ -312,7 +288,7 @@ impl Wallet { drop(secret_manager); // Update Wallet with read data - if ignore_if_coin_type_mismatch.is_none() { + if ignore_if_bip_path_mismatch.is_none() { if let Some(read_client_options) = read_client_options { // If the nodes are from the same network as the current client options, then extend it self.set_client_options(read_client_options).await?; @@ -320,28 +296,14 @@ impl Wallet { } if !ignore_backup_values { - if let Some(read_accounts) = read_accounts { - let restore_accounts = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { + if let Some(read_wallet_data) = read_wallet_data { + let restore_wallet = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { // Only restore if bech32 hrps match - read_accounts.first().map_or(true, |account| { - account - .public_addresses - .first() - .expect("account needs to have a public address") - .address() - .hrp() - == expected_bech32_hrp - }) + read_wallet_data.address.hrp() == &expected_bech32_hrp }); - if restore_accounts { - let restored_account = try_join_all( - read_accounts - .into_iter() - .map(|a| Account::new(a, self.inner.clone()).boxed()), - ) - .await?; - *accounts = restored_account; + if restore_wallet { + *wallet_data = read_wallet_data; } } } @@ -362,12 +324,12 @@ impl Wallet { .expect("can't convert os string"), ) .with_client_options(self.client_options().await) - .with_coin_type(self.coin_type.load(Ordering::Relaxed)); + .with_bip_path(self.data().await.bip_path); + wallet_builder.save(&*self.storage_manager.read().await).await?; - // also save account to db - for account in accounts.iter() { - account.save(None).await?; - } + + // also save wallet data to db + self.save(Some(&wallet_data)).await?; } Ok(()) diff --git a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs index 5744e8f6c9..68c3eac448 100644 --- a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs +++ b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs @@ -1,22 +1,19 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::atomic::Ordering; - use crate::{ client::{secret::SecretManagerConfig, storage::StorageAdapter, stronghold::StrongholdAdapter}, types::TryFromDto, wallet::{ - account::{AccountDetails, AccountDetailsDto}, + core::{WalletData, WalletDataDto}, migration::{latest_backup_migration_version, migrate, MIGRATION_VERSION_KEY}, ClientOptions, Error as WalletError, Wallet, }, }; pub(crate) const CLIENT_OPTIONS_KEY: &str = "client_options"; -pub(crate) const COIN_TYPE_KEY: &str = "coin_type"; pub(crate) const SECRET_MANAGER_KEY: &str = "secret_manager"; -pub(crate) const ACCOUNTS_KEY: &str = "accounts"; +pub(crate) const WALLET_DATA_KEY: &str = "wallet_data"; impl Wallet { pub(crate) async fn store_data_to_stronghold(&self, stronghold: &StrongholdAdapter) -> crate::wallet::Result<()> { @@ -28,66 +25,49 @@ impl Wallet { let client_options = self.client_options().await; stronghold.set(CLIENT_OPTIONS_KEY, &client_options).await?; - let coin_type = self.coin_type.load(Ordering::Relaxed); - stronghold.set_bytes(COIN_TYPE_KEY, &coin_type.to_le_bytes()).await?; - if let Some(secret_manager_dto) = self.secret_manager.read().await.to_config() { stronghold.set(SECRET_MANAGER_KEY, &secret_manager_dto).await?; } - let mut serialized_accounts = Vec::new(); - for account in self.accounts.read().await.iter() { - serialized_accounts.push(serde_json::to_value(&AccountDetailsDto::from( - &*account.details().await, - ))?); - } - - stronghold.set(ACCOUNTS_KEY, &serialized_accounts).await?; + let serialized_wallet_data = serde_json::to_value(&WalletDataDto::from(&*self.data.read().await))?; + stronghold.set(WALLET_DATA_KEY, &serialized_wallet_data).await?; Ok(()) } } -pub(crate) async fn read_data_from_stronghold_snapshot( +pub(crate) async fn read_wallet_data_from_stronghold_snapshot( stronghold: &StrongholdAdapter, -) -> crate::wallet::Result<( - Option, - Option, - Option, - Option>, -)> { +) -> crate::wallet::Result<(Option, Option, Option)> { migrate(stronghold).await?; // Get client_options let client_options = stronghold.get(CLIENT_OPTIONS_KEY).await?; - // Get coin_type - let coin_type_bytes = stronghold.get_bytes(COIN_TYPE_KEY).await?; - let coin_type = if let Some(coin_type_bytes) = coin_type_bytes { - let coin_type = u32::from_le_bytes( - coin_type_bytes - .try_into() - .map_err(|_| WalletError::Backup("invalid coin_type"))?, - ); - log::debug!("[restore_backup] restored coin_type: {coin_type}"); - Some(coin_type) - } else { - None - }; + // TODO #1279: remove + // // Get coin_type + // let coin_type_bytes = stronghold.get_bytes(COIN_TYPE_KEY).await?; + // let coin_type = if let Some(coin_type_bytes) = coin_type_bytes { + // let coin_type = u32::from_le_bytes( + // coin_type_bytes + // .try_into() + // .map_err(|_| WalletError::Backup("invalid coin_type"))?, + // ); + // log::debug!("[restore_backup] restored coin_type: {coin_type}"); + // Some(coin_type) + // } else { + // None + // }; // Get secret_manager let restored_secret_manager = stronghold.get(SECRET_MANAGER_KEY).await?; - // Get accounts - let restored_accounts = stronghold - .get::>(ACCOUNTS_KEY) + // Get wallet data + let restored_wallet_data = stronghold + .get::(WALLET_DATA_KEY) .await? - .map(|v| { - v.into_iter() - .map(AccountDetails::try_from_dto) - .collect::, _>>() - }) + .map(|dto| WalletData::try_from_dto(dto)) .transpose()?; - Ok((client_options, coin_type, restored_secret_manager, restored_accounts)) + Ok((client_options, restored_secret_manager, restored_wallet_data)) } diff --git a/sdk/src/wallet/core/operations/verify_integrity.rs b/sdk/src/wallet/core/operations/verify_integrity.rs deleted file mode 100644 index c80a7ba889..0000000000 --- a/sdk/src/wallet/core/operations/verify_integrity.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::{client::secret::SecretManage, wallet::Wallet}; - -impl Wallet { - /// Checks if there is no missing account for example indexes [0, 1, 3] should panic (for now, later return error, - /// automatically fix?) Also checks for each account if there is a gap in an address list and no address is - /// duplicated - pub async fn verify_integrity(&self) -> crate::wallet::Result<()> { - log::debug!("[verify_integrity]"); - - let accounts = self.accounts.read().await; - - // check that no account is missing and they're ordered - // check that no address is missing and they're ordered - for (account_index, account) in accounts.iter().enumerate() { - assert_eq!(accounts[account_index].details().await.index(), &(account_index as u32)); - - let account = account.details().await; - let public_addresses = account.public_addresses(); - for (index, public_address) in public_addresses.iter().enumerate() { - assert_eq!(public_address.key_index, index as u32); - } - let internal_addresses = account.internal_addresses(); - for (index, internal_address) in internal_addresses.iter().enumerate() { - assert_eq!(internal_address.key_index, index as u32); - } - } - - Ok(()) - } -} diff --git a/sdk/src/wallet/error.rs b/sdk/src/wallet/error.rs index 5da143f10a..e16408b6e7 100644 --- a/sdk/src/wallet/error.rs +++ b/sdk/src/wallet/error.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; +use crypto::keys::bip44::Bip44; use serde::{ ser::{SerializeMap, Serializer}, Serialize, @@ -12,16 +13,8 @@ use crate::types::block::{address::Bech32Address, payload::signed_transaction::T /// The wallet error type. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { - /// Account alias must be unique. - #[error("can't create account: account alias {0} already exists")] - AccountAliasAlreadyExists(String), - /// Account not found - #[error("account {0} not found")] - AccountNotFound(String), - /// Address not found in account - #[error("address {0} not found in account")] - AddressNotFoundInAccount(Bech32Address), /// Errors during backup creation or restoring #[error("backup failed {0}")] Backup(&'static str), @@ -34,6 +27,12 @@ pub enum Error { /// Client error. #[error("`{0}`")] Client(Box), + /// BIP44 coin type mismatch + #[error("BIP44 mismatch: {new_bip_path:?}, existing bip path is: {old_bip_path:?}")] + BipPathMismatch { + new_bip_path: Option, + old_bip_path: Option, + }, /// Funds are spread over too many outputs #[error("funds are spread over too many outputs {output_count}/{output_count_max}, consolidation required")] ConsolidationRequired { output_count: usize, output_count_max: u16 }, @@ -43,24 +42,25 @@ pub enum Error { /// Custom input error #[error("custom input error {0}")] CustomInput(String), - /// Failed to get remainder - #[error("failed to get remainder address")] - FailedToGetRemainder, /// Insufficient funds to send transaction. #[error("insufficient funds {available}/{required} available")] InsufficientFunds { available: u64, required: u64 }, - /// Invalid coin type, all accounts need to have the same coin type - #[error("invalid coin type for new account: {new_coin_type}, existing coin type is: {existing_coin_type}")] - InvalidCoinType { - new_coin_type: u32, - existing_coin_type: u32, - }, + /// Invalid event type. + #[cfg(feature = "events")] + #[cfg_attr(docsrs, doc(cfg(feature = "events")))] + #[error("invalid event type: {0}")] + InvalidEventType(u8), /// Invalid mnemonic error #[error("invalid mnemonic: {0}")] InvalidMnemonic(String), /// Invalid output kind. #[error("invalid output kind: {0}")] InvalidOutputKind(String), + /// Invalid Voting Power + #[cfg(feature = "participation")] + #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] + #[error("invalid voting power")] + InvalidVotingPower, /// IO error. (storage, backup, restore) #[error("`{0}`")] Io(#[from] std::io::Error), @@ -73,6 +73,9 @@ pub enum Error { /// Minting failed #[error("minting failed {0}")] MintingFailed(String), + /// Missing BIP path. + #[error("missing BIP path")] + MissingBipPath, /// Missing parameter. #[error("missing parameter: {0}")] MissingParameter(&'static str), @@ -115,10 +118,12 @@ pub enum Error { #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] #[error("voting error {0}")] Voting(String), - #[cfg(feature = "participation")] - #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] - #[error("invalid voting power")] - InvalidVotingPower, + /// Address not the wallet address + #[error("address {0} is not the wallet address")] + WalletAddressMismatch(Bech32Address), + /// Action requires the wallet to be Ed25519 address based + #[error("tried to perform an action that requires the wallet to be Ed25519 address based")] + NonEd25519Address, } // Serialize type with Display error diff --git a/sdk/src/wallet/events/mod.rs b/sdk/src/wallet/events/mod.rs index 7054e488f9..e908a0d1f9 100644 --- a/sdk/src/wallet/events/mod.rs +++ b/sdk/src/wallet/events/mod.rs @@ -9,12 +9,12 @@ use std::{ fmt::{Debug, Formatter, Result}, }; -pub use self::types::{Event, WalletEvent, WalletEventType}; +pub use self::types::{WalletEvent, WalletEventType}; type Handler = Arc; pub struct EventEmitter { - handlers: HashMap>>, + handlers: HashMap>>, } impl EventEmitter { @@ -29,7 +29,7 @@ impl EventEmitter { /// multiple listeners for a single event. pub fn on(&mut self, events: impl IntoIterator, handler: F) where - F: Fn(&Event) + 'static + Send + Sync, + F: Fn(&WalletEvent) + 'static + Send + Sync, { let mut events = events.into_iter().peekable(); let handler = Arc::new(handler); @@ -68,7 +68,7 @@ impl EventEmitter { /// Invokes all listeners of `event`, passing a reference to `payload` as an /// argument to each of them. - pub fn emit(&self, account_index: u32, event: WalletEvent) { + pub fn emit(&self, event: WalletEvent) { let event_type = match &event { WalletEvent::NewOutput(_) => WalletEventType::NewOutput, WalletEvent::SpentOutput(_) => WalletEventType::SpentOutput, @@ -78,7 +78,6 @@ impl EventEmitter { #[cfg(feature = "ledger_nano")] WalletEvent::LedgerAddressGeneration(_) => WalletEventType::LedgerAddressGeneration, }; - let event = Event { account_index, event }; if let Some(handlers) = self.handlers.get(&event_type) { for handler in handlers { handler(&event); @@ -119,7 +118,7 @@ mod tests { types::{TransactionInclusionEvent, TransactionProgressEvent, WalletEvent, WalletEventType}, EventEmitter, }; - use crate::{types::block::payload::signed_transaction::TransactionId, wallet::account::types::InclusionState}; + use crate::{types::block::payload::signed_transaction::TransactionId, wallet::types::InclusionState}; #[test] fn events() { @@ -150,48 +149,40 @@ mod tests { }); // emit events - emitter.emit(0, WalletEvent::ConsolidationRequired); - emitter.emit( - 0, - WalletEvent::TransactionProgress(TransactionProgressEvent::SelectingInputs), - ); - emitter.emit( - 0, - WalletEvent::TransactionInclusion(TransactionInclusionEvent { - transaction_id: TransactionId::from_str( - "0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000", - ) - .expect("invalid tx id"), - inclusion_state: InclusionState::Confirmed, - }), - ); + emitter.emit(WalletEvent::ConsolidationRequired); + emitter.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::SelectingInputs, + )); + emitter.emit(WalletEvent::TransactionInclusion(TransactionInclusionEvent { + transaction_id: TransactionId::from_str( + "0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000", + ) + .expect("invalid tx id"), + inclusion_state: InclusionState::Confirmed, + })); assert_eq!(3, event_counter.load(Ordering::SeqCst)); // remove handlers of single event emitter.clear([WalletEventType::ConsolidationRequired]); // emit event of removed type - emitter.emit(0, WalletEvent::ConsolidationRequired); + emitter.emit(WalletEvent::ConsolidationRequired); assert_eq!(3, event_counter.load(Ordering::SeqCst)); // remove handlers of all events emitter.clear([]); // emit events - emitter.emit( - 0, - WalletEvent::TransactionProgress(TransactionProgressEvent::SelectingInputs), - ); - emitter.emit( - 0, - WalletEvent::TransactionInclusion(TransactionInclusionEvent { - transaction_id: TransactionId::from_str( - "0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000", - ) - .expect("invalid tx id"), - inclusion_state: InclusionState::Confirmed, - }), - ); + emitter.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::SelectingInputs, + )); + emitter.emit(WalletEvent::TransactionInclusion(TransactionInclusionEvent { + transaction_id: TransactionId::from_str( + "0x2289d9981fb23cc5f4f6c2742685eeb480f8476089888aa886a18232bad8198900000000", + ) + .expect("invalid tx id"), + inclusion_state: InclusionState::Confirmed, + })); assert_eq!(3, event_counter.load(Ordering::SeqCst)); // listen to a single event @@ -202,7 +193,7 @@ mod tests { }); for _ in 0..1_000_000 { - emitter.emit(0, WalletEvent::ConsolidationRequired); + emitter.emit(WalletEvent::ConsolidationRequired); } assert_eq!(1_000_003, event_counter.load(Ordering::SeqCst)); } diff --git a/sdk/src/wallet/events/types.rs b/sdk/src/wallet/events/types.rs index 3b0f034502..7c60f5b8a0 100644 --- a/sdk/src/wallet/events/types.rs +++ b/sdk/src/wallet/events/types.rs @@ -13,18 +13,12 @@ use crate::{ payload::signed_transaction::{dto::SignedTransactionPayloadDto, TransactionId}, }, }, - wallet::account::types::{InclusionState, OutputDataDto}, + wallet::{ + types::{InclusionState, OutputDataDto}, + Error, + }, }; -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Event { - /// Associated account index. - pub account_index: u32, - /// The event - pub event: WalletEvent, -} - #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum WalletEvent { @@ -163,7 +157,7 @@ pub enum WalletEventType { } impl TryFrom for WalletEventType { - type Error = String; + type Error = Error; fn try_from(value: u8) -> Result { let event_type = match value { @@ -174,7 +168,7 @@ impl TryFrom for WalletEventType { 3 => Self::SpentOutput, 4 => Self::TransactionInclusion, 5 => Self::TransactionProgress, - _ => return Err(format!("invalid event type {value}")), + _ => return Err(Error::InvalidEventType(value)), }; Ok(event_type) } diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index 67671514ab..2da1120ffc 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -3,13 +3,18 @@ //! The IOTA Wallet Library -/// [`Account`]: crate::wallet::Account -/// The account module. Interaction with an Account happens via an [`Account`]. -pub mod account; +/// Constants used for the wallet and wallet operations. +pub(crate) mod constants; /// The core module. pub mod core; #[cfg(any(feature = "stronghold", feature = "storage"))] pub mod migration; +/// The wallet operations like address generation, syncing and creating transactions. +pub(crate) mod operations; +/// Types used in a wallet and returned from methods. +pub mod types; +/// Methods to update the wallet state. +pub(crate) mod update; /// The ClientOptions to build the iota_client for interactions with the IOTA Tangle. pub use crate::client::ClientBuilder as ClientOptions; @@ -27,19 +32,98 @@ pub mod storage; /// The module for spawning tasks on a thread pub(crate) mod task; +use std::collections::HashSet; + +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "participation")] +pub use self::operations::participation::{ParticipationEventWithNodes, ParticipationOverview}; +use self::types::TransactionWithMetadata; pub use self::{ - account::{ - operations::transaction::high_level::{ - minting::{create_native_token::CreateNativeTokenParams, mint_nfts::MintNftParams}, - send::SendParams, - send_native_token::SendNativeTokenParams, - send_nft::SendNftParams, - }, - Account, - }, core::{Wallet, WalletBuilder}, error::Error, + operations::{ + output_claiming::OutputsToClaim, + output_consolidation::ConsolidationParams, + syncing::{ + options::{AccountSyncOptions, NftSyncOptions, WalletSyncOptions}, + SyncOptions, + }, + transaction::{ + high_level::{ + create_account::CreateAccountParams, + minting::{ + create_native_token::{ + CreateNativeTokenParams, CreateNativeTokenTransactionDto, + PreparedCreateNativeTokenTransactionDto, + }, + mint_nfts::MintNftParams, + }, + send::SendParams, + send_native_token::SendNativeTokenParams, + send_nft::SendNftParams, + }, + prepare_output::{Assets, Features, OutputParams, ReturnStrategy, StorageDeposit, Unlocks}, + RemainderValueStrategy, TransactionOptions, + }, + }, + types::OutputDataDto, +}; +use crate::{ + types::{ + api::core::OutputWithMetadataResponse, + block::{ + output::{AccountId, AnchorId, DelegationId, FoundryId, NftId}, + payload::signed_transaction::{SignedTransactionPayload, TransactionId}, + }, + }, + wallet::types::InclusionState, }; /// The wallet Result type. pub type Result = std::result::Result; + +/// Options to filter outputs +#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct FilterOptions { + /// Filter all outputs where the booked milestone index is below the specified timestamp + pub lower_bound_booked_timestamp: Option, + /// Filter all outputs where the booked milestone index is above the specified timestamp + pub upper_bound_booked_timestamp: Option, + /// Filter all outputs for the provided types (Basic = 3, Account = 4, Foundry = 5, NFT = 6). + pub output_types: Option>, + /// Return all account outputs matching these IDs. + pub account_ids: Option>, + /// Return all anchor outputs matching these IDs. + pub anchor_ids: Option>, + /// Return all foundry outputs matching these IDs. + pub foundry_ids: Option>, + /// Return all nft outputs matching these IDs. + pub nft_ids: Option>, + /// Return all delegation outputs matching these IDs. + pub delegation_ids: Option>, +} + +pub(crate) fn build_transaction_from_payload_and_inputs( + tx_id: TransactionId, + tx_payload: SignedTransactionPayload, + inputs: Vec, +) -> crate::wallet::Result { + Ok(TransactionWithMetadata { + payload: tx_payload.clone(), + block_id: inputs.first().map(|i| *i.metadata.block_id()), + inclusion_state: InclusionState::Confirmed, + timestamp: 0, + // TODO use slot index since milestone_timestamp_spent is gone + // inputs + // .first() + // .and_then(|i| i.metadata.milestone_timestamp_spent.map(|t| t as u128 * 1000)) + // .unwrap_or_else(|| crate::utils::unix_timestamp_now().as_millis()), + transaction_id: tx_id, + network_id: tx_payload.transaction().network_id(), + incoming: true, + note: None, + inputs, + }) +} diff --git a/sdk/src/wallet/operations/balance.rs b/sdk/src/wallet/operations/balance.rs new file mode 100644 index 0000000000..1bd8e93d95 --- /dev/null +++ b/sdk/src/wallet/operations/balance.rs @@ -0,0 +1,325 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use primitive_types::U256; + +use crate::{ + client::secret::SecretManage, + types::block::output::{ + unlock_condition::UnlockCondition, FoundryId, MinimumOutputAmount, NativeTokensBuilder, Output, + }, + wallet::{ + core::WalletData, + operations::{helpers::time::can_output_be_unlocked_forever_from_now_on, output_claiming::OutputsToClaim}, + types::{Balance, NativeTokensBalance}, + Result, Wallet, + }, +}; + +impl Wallet +where + crate::wallet::Error: From, + crate::client::Error: From, +{ + /// Get the balance of the wallet. + pub async fn balance(&self) -> Result { + log::debug!("[BALANCE] balance"); + + let wallet_data = self.data().await; + + self.balance_inner(&wallet_data).await + } + + async fn balance_inner( + &self, + // addresses_with_unspent_outputs: impl Iterator + Send, + wallet_data: &WalletData, + ) -> Result { + let network_id = self.client().get_network_id().await?; + let storage_score_params = self.client().get_storage_score_parameters().await?; + let mut balance = Balance::default(); + let mut total_storage_cost = 0; + let mut total_native_tokens = NativeTokensBuilder::default(); + + #[cfg(feature = "participation")] + let voting_output = self.get_voting_output().await?; + + #[cfg(feature = "participation")] + { + if let Some(voting_output) = &voting_output { + if voting_output.output.as_basic().address() == wallet_data.address.inner() { + balance.base_coin.voting_power = voting_output.output.amount(); + } + } + } + + for (output_id, output_data) in &wallet_data.unspent_outputs { + // Check if output is from the network we're currently connected to + if output_data.network_id != network_id { + continue; + } + + let output = &output_data.output; + let storage_cost = output.minimum_amount(storage_score_params); + + // Add account and foundry outputs here because they can't have a + // [`StorageDepositReturnUnlockCondition`] or time related unlock conditions + match output { + Output::Account(output) => { + // Add amount + balance.base_coin.total += output.amount(); + // Add storage deposit + balance.required_storage_deposit.account += storage_cost; + if !wallet_data.locked_outputs.contains(output_id) { + total_storage_cost += storage_cost; + } + // Add native tokens + total_native_tokens.add_native_tokens(output.native_tokens().clone())?; + + let account_id = output.account_id_non_null(output_id); + balance.accounts.push(account_id); + } + Output::Foundry(output) => { + // Add amount + balance.base_coin.total += output.amount(); + // Add storage deposit + balance.required_storage_deposit.foundry += storage_cost; + if !wallet_data.locked_outputs.contains(output_id) { + total_storage_cost += storage_cost; + } + // Add native tokens + total_native_tokens.add_native_tokens(output.native_tokens().clone())?; + + balance.foundries.push(output.id()); + } + _ => { + // If there is only an [AddressUnlockCondition], then we can spend the output at any time + // without restrictions + if let [UnlockCondition::Address(_)] = output + .unlock_conditions() + .expect("output needs to have unlock conditions") + .as_ref() + { + // add nft_id for nft outputs + if let Output::Nft(output) = &output { + let nft_id = output.nft_id_non_null(output_id); + balance.nfts.push(nft_id); + } + + // Add amount + balance.base_coin.total += output.amount(); + + // Add storage deposit + if output.is_basic() { + balance.required_storage_deposit.basic += storage_cost; + if output + .native_tokens() + .map(|native_tokens| !native_tokens.is_empty()) + .unwrap_or(false) + && !wallet_data.locked_outputs.contains(output_id) + { + total_storage_cost += storage_cost; + } + } else if output.is_nft() { + balance.required_storage_deposit.nft += storage_cost; + if !wallet_data.locked_outputs.contains(output_id) { + total_storage_cost += storage_cost; + } + } + + // Add native tokens + if let Some(native_tokens) = output.native_tokens() { + total_native_tokens.add_native_tokens(native_tokens.clone())?; + } + } else { + // if we have multiple unlock conditions for basic or nft outputs, then we can't + // spend the balance at the moment or in the future + + let wallet_address = self.address().await.into_inner(); + let slot_index = self.client().get_slot_index().await?; + let is_claimable = self.claimable_outputs(OutputsToClaim::All).await?.contains(output_id); + + // For outputs that are expired or have a timelock unlock condition, but no expiration + // unlock condition and we then can unlock them, then + // they can never be not available for us anymore + // and should be added to the balance + if is_claimable { + // check if output can be unlocked always from now on, in that case it should be + // added to the total amount + let output_can_be_unlocked_now_and_in_future = can_output_be_unlocked_forever_from_now_on( + // We use the addresses with unspent outputs, because other addresses of + // the account without unspent + // outputs can't be related to this output + wallet_data.address.inner(), + output, + slot_index, + ); + + if output_can_be_unlocked_now_and_in_future { + // If output has a StorageDepositReturnUnlockCondition, the amount of it should + // be subtracted, because this part + // needs to be sent back + let amount = output + .unlock_conditions() + .and_then(|u| u.storage_deposit_return()) + .map_or_else( + || output.amount(), + |sdr| { + if &wallet_address == sdr.return_address() { + // sending to ourself, we get the full amount + output.amount() + } else { + // Sending to someone else + output.amount() - sdr.amount() + } + }, + ); + + // add nft_id for nft outputs + if let Output::Nft(output) = &output { + let nft_id = output.nft_id_non_null(output_id); + balance.nfts.push(nft_id); + } + + // Add amount + balance.base_coin.total += amount; + + // Add storage deposit + if output.is_basic() { + balance.required_storage_deposit.basic += storage_cost; + // Amount for basic outputs isn't added to total storage cost if there aren't + // native tokens, since we can + // spend it without burning. + if output + .native_tokens() + .map(|native_tokens| !native_tokens.is_empty()) + .unwrap_or(false) + && !wallet_data.locked_outputs.contains(output_id) + { + total_storage_cost += storage_cost; + } + } else if output.is_nft() { + balance.required_storage_deposit.nft += storage_cost; + if !wallet_data.locked_outputs.contains(output_id) { + total_storage_cost += storage_cost; + } + } + + // Add native tokens + if let Some(native_tokens) = output.native_tokens() { + total_native_tokens.add_native_tokens(native_tokens.clone())?; + } + } else { + // only add outputs that can't be locked now and at any point in the future + balance.potentially_locked_outputs.insert(*output_id, true); + } + } else { + // Don't add expired outputs that can't ever be unlocked by us + if let Some(expiration) = output + .unlock_conditions() + .expect("output needs to have unlock conditions") + .expiration() + { + // Not expired, could get unlockable when it's expired, so we insert it + if slot_index < expiration.slot_index() { + balance.potentially_locked_outputs.insert(*output_id, false); + } + } else { + balance.potentially_locked_outputs.insert(*output_id, false); + } + } + } + } + } + // } + } + // } + + self.finish( + balance, + wallet_data, + network_id, + total_storage_cost, + total_native_tokens, + ) + } + + fn finish( + &self, + mut balance: Balance, + wallet_data: &WalletData, + network_id: u64, + total_storage_cost: u64, + total_native_tokens: NativeTokensBuilder, + ) -> Result { + // for `available` get locked_outputs, sum outputs amount and subtract from total_amount + log::debug!("[BALANCE] locked outputs: {:#?}", wallet_data.locked_outputs); + + let mut locked_amount = 0; + let mut locked_native_tokens = NativeTokensBuilder::default(); + + for locked_output in &wallet_data.locked_outputs { + // Skip potentially_locked_outputs, as their amounts aren't added to the balance + if balance.potentially_locked_outputs.contains_key(locked_output) { + continue; + } + if let Some(output_data) = wallet_data.unspent_outputs.get(locked_output) { + // Only check outputs that are in this network + if output_data.network_id == network_id { + locked_amount += output_data.output.amount(); + if let Some(native_tokens) = output_data.output.native_tokens() { + locked_native_tokens.add_native_tokens(native_tokens.clone())?; + } + } + } + } + + log::debug!( + "[BALANCE] total_amount: {}, locked_amount: {}, total_storage_cost: {}", + balance.base_coin.total, + locked_amount, + total_storage_cost, + ); + + locked_amount += total_storage_cost; + + for native_token in total_native_tokens.finish_set()? { + // Check if some amount is currently locked + let locked_native_token_amount = locked_native_tokens.iter().find_map(|(id, amount)| { + if id == native_token.token_id() { + Some(amount) + } else { + None + } + }); + + let metadata = wallet_data + .native_token_foundries + .get(&FoundryId::from(*native_token.token_id())) + .and_then(|foundry| foundry.immutable_features().metadata()) + .cloned(); + + balance.native_tokens.push(NativeTokensBalance { + token_id: *native_token.token_id(), + total: native_token.amount(), + available: native_token.amount() - *locked_native_token_amount.unwrap_or(&U256::from(0u8)), + metadata, + }) + } + + #[cfg(not(feature = "participation"))] + { + balance.base_coin.available = balance.base_coin.total.saturating_sub(locked_amount); + } + #[cfg(feature = "participation")] + { + balance.base_coin.available = balance + .base_coin + .total + .saturating_sub(locked_amount) + .saturating_sub(balance.base_coin.voting_power); + } + + Ok(balance) + } +} diff --git a/sdk/src/wallet/account/operations/helpers/mod.rs b/sdk/src/wallet/operations/helpers/mod.rs similarity index 100% rename from sdk/src/wallet/account/operations/helpers/mod.rs rename to sdk/src/wallet/operations/helpers/mod.rs diff --git a/sdk/src/wallet/account/operations/helpers/time.rs b/sdk/src/wallet/operations/helpers/time.rs similarity index 60% rename from sdk/src/wallet/account/operations/helpers/time.rs rename to sdk/src/wallet/operations/helpers/time.rs index 7bf893f45a..ebd3675688 100644 --- a/sdk/src/wallet/account/operations/helpers/time.rs +++ b/sdk/src/wallet/operations/helpers/time.rs @@ -3,16 +3,12 @@ use crate::{ types::block::{address::Address, output::Output, slot::SlotIndex}, - wallet::account::types::{AddressWithUnspentOutputs, OutputData}, + wallet::types::OutputData, }; -// Check if an output can be unlocked by one of the account addresses at the current time +// Check if an output can be unlocked by the wallet address at the current time pub(crate) fn can_output_be_unlocked_now( - // We use the addresses with unspent outputs, because other addresses of the account without unspent outputs can't - // be related to this output - // TODO disambiguate these two parameters when we are done with Account changes https://github.com/iotaledger/iota-sdk/issues/647 - account_addresses: &[AddressWithUnspentOutputs], - account_and_nft_addresses: &[Address], + wallet_address: &Address, output_data: &OutputData, slot_index: SlotIndex, ) -> crate::wallet::Result { @@ -26,18 +22,13 @@ pub(crate) fn can_output_be_unlocked_now( .output .required_and_unlocked_address(slot_index, &output_data.output_id)?; - Ok(account_addresses - .iter() - .any(|a| a.address.inner == required_unlock_address) - || account_and_nft_addresses.iter().any(|a| *a == required_unlock_address)) + Ok(wallet_address == &required_unlock_address) } // Check if an output can be unlocked by one of the account addresses at the current time and at any // point in the future pub(crate) fn can_output_be_unlocked_forever_from_now_on( - // We use the addresses with unspent outputs, because other addresses of the account without unspent outputs can't - // be related to this output - account_addresses: &[AddressWithUnspentOutputs], + wallet_address: &Address, output: &Output, slot_index: SlotIndex, ) -> bool { @@ -50,7 +41,7 @@ pub(crate) fn can_output_be_unlocked_forever_from_now_on( // the return address belongs to the account if let Some(expiration) = unlock_conditions.expiration() { if let Some(return_address) = expiration.return_address_expired(slot_index) { - if !account_addresses.iter().any(|a| a.address.inner == *return_address) { + if wallet_address != return_address { return false; }; } else { diff --git a/sdk/src/wallet/account/operations/mod.rs b/sdk/src/wallet/operations/mod.rs similarity index 73% rename from sdk/src/wallet/account/operations/mod.rs rename to sdk/src/wallet/operations/mod.rs index 7df08edf2e..73736f248d 100644 --- a/sdk/src/wallet/account/operations/mod.rs +++ b/sdk/src/wallet/operations/mod.rs @@ -1,9 +1,7 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/// The module for the address generation -pub(crate) mod address_generation; -/// The module to get the accounts balance +/// The module to get the wallet's balance pub(crate) mod balance; /// Helper functions pub(crate) mod helpers; @@ -13,14 +11,12 @@ pub(crate) mod helpers; pub(crate) mod output_claiming; /// The module for the output consolidation pub(crate) mod output_consolidation; -/// The module to find additional addresses with unspent outputs -pub(crate) mod output_finder; /// The module for participation #[cfg(feature = "participation")] pub(crate) mod participation; /// The module for reissuing blocks or transactions pub(crate) mod reissue; -/// The module for synchronization of an account +/// The module for synchronization of the wallet pub(crate) mod syncing; /// The module for transactions pub(crate) mod transaction; diff --git a/sdk/src/wallet/account/operations/output_claiming.rs b/sdk/src/wallet/operations/output_claiming.rs similarity index 86% rename from sdk/src/wallet/account/operations/output_claiming.rs rename to sdk/src/wallet/operations/output_claiming.rs index 899020d90d..5b8dcffbff 100644 --- a/sdk/src/wallet/account/operations/output_claiming.rs +++ b/sdk/src/wallet/operations/output_claiming.rs @@ -8,17 +8,17 @@ use serde::{Deserialize, Serialize}; use crate::{ client::secret::SecretManage, types::block::{ - address::Address, + address::{Address, Ed25519Address}, output::{ unlock_condition::{AddressUnlockCondition, StorageDepositReturnUnlockCondition}, - BasicOutputBuilder, MinimumStorageDepositBasicOutput, NativeTokensBuilder, NftOutputBuilder, Output, - OutputId, + BasicOutput, BasicOutputBuilder, NativeTokensBuilder, NftOutputBuilder, Output, OutputId, }, slot::SlotIndex, }, - wallet::account::{ - operations::helpers::time::can_output_be_unlocked_now, types::TransactionWithMetadata, Account, OutputData, - TransactionOptions, + wallet::{ + operations::{helpers::time::can_output_be_unlocked_now, transaction::TransactionOptions}, + types::{OutputData, TransactionWithMetadata}, + Wallet, }, }; @@ -33,7 +33,7 @@ pub enum OutputsToClaim { All, } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -46,19 +46,19 @@ where /// additional inputs pub async fn claimable_outputs(&self, outputs_to_claim: OutputsToClaim) -> crate::wallet::Result> { log::debug!("[OUTPUT_CLAIMING] claimable_outputs"); - let account_details = self.details().await; + let wallet_data = self.data().await; let slot_index = self.client().get_slot_index().await?; // Get outputs for the claim let mut output_ids_to_claim: HashSet = HashSet::new(); - for (output_id, output_data) in account_details + for (output_id, output_data) in wallet_data .unspent_outputs .iter() .filter(|(_, o)| o.output.is_basic() || o.output.is_nft()) { // Don't use outputs that are locked for other transactions - if !account_details.locked_outputs.contains(output_id) && account_details.outputs.contains_key(output_id) { + if !wallet_data.locked_outputs.contains(output_id) && wallet_data.outputs.contains_key(output_id) { if let Some(unlock_conditions) = output_data.output.unlock_conditions() { // If there is a single [UnlockCondition], then it's an // [AddressUnlockCondition] and we own it already without @@ -67,9 +67,7 @@ where && can_output_be_unlocked_now( // We use the addresses with unspent outputs, because other addresses of the // account without unspent outputs can't be related to this output - &account_details.addresses_with_unspent_outputs, - // outputs controlled by an account or nft are currently not considered - &[], + wallet_data.address.inner(), output_data, slot_index, )? @@ -130,11 +128,11 @@ where log::debug!("[OUTPUT_CLAIMING] get_basic_outputs_for_additional_inputs"); #[cfg(feature = "participation")] let voting_output = self.get_voting_output().await?; - let account_details = self.details().await; + let wallet_data = self.data().await; // Get basic outputs only with AddressUnlockCondition and no other unlock condition let mut basic_outputs: Vec = Vec::new(); - for (output_id, output_data) in &account_details.unspent_outputs { + for (output_id, output_data) in &wallet_data.unspent_outputs { #[cfg(feature = "participation")] if let Some(ref voting_output) = voting_output { // Remove voting output from inputs, because we don't want to spent it to claim something else. @@ -143,8 +141,8 @@ where } } // Don't use outputs that are locked for other transactions - if !account_details.locked_outputs.contains(output_id) { - if let Some(output) = account_details.outputs.get(output_id) { + if !wallet_data.locked_outputs.contains(output_id) { + if let Some(output) = wallet_data.outputs.get(output_id) { if let Output::Basic(basic_output) = &output.output { if basic_output.unlock_conditions().len() == 1 { // Store outputs with [`AddressUnlockCondition`] alone, because they could be used as @@ -205,15 +203,14 @@ where log::debug!("[OUTPUT_CLAIMING] claim_outputs_internal"); let slot_index = self.client().get_slot_index().await?; - let rent_structure = self.client().get_rent_structure().await?; - let token_supply = self.client().get_token_supply().await?; + let storage_score_params = self.client().get_storage_score_parameters().await?; - let account_details = self.details().await; + let wallet_data = self.data().await; let mut outputs_to_claim = Vec::new(); for output_id in output_ids_to_claim { - if let Some(output_data) = account_details.unspent_outputs.get(&output_id) { - if !account_details.locked_outputs.contains(&output_id) { + if let Some(output_data) = wallet_data.unspent_outputs.get(&output_id) { + if !wallet_data.locked_outputs.contains(&output_id) { outputs_to_claim.push(output_data.clone()); } } @@ -225,12 +222,8 @@ where )); } - let first_account_address = account_details - .public_addresses - .first() - .ok_or(crate::wallet::Error::FailedToGetRemainder)? - .clone(); - drop(account_details); + let wallet_address = wallet_data.address.clone(); + drop(wallet_data); let mut additional_inputs_used = HashSet::new(); @@ -252,7 +245,7 @@ where .iter() .map(|i| i.output.amount()) .sum::() - >= MinimumStorageDepositBasicOutput::new(rent_structure, token_supply).finish()?; + >= BasicOutput::minimum_amount(&Address::from(Ed25519Address::null()), storage_score_params); // check native tokens for output_data in &outputs_to_claim { @@ -277,23 +270,19 @@ where let nft_output = if !enough_amount_for_basic_output { // Only update address and nft id if we have no additional inputs which can provide the storage - // deposit for the remaining amount and possible NTs + // deposit for the remaining amount and possible native tokens NftOutputBuilder::from(nft_output) .with_nft_id(nft_output.nft_id_non_null(&output_data.output_id)) - .with_unlock_conditions([AddressUnlockCondition::new( - first_account_address.address.inner.clone(), - )]) - .finish_output(token_supply)? + .with_unlock_conditions([AddressUnlockCondition::new(wallet_address.clone())]) + .finish_output()? } else { NftOutputBuilder::from(nft_output) - .with_minimum_storage_deposit(rent_structure) + .with_minimum_amount(storage_score_params) .with_nft_id(nft_output.nft_id_non_null(&output_data.output_id)) - .with_unlock_conditions([AddressUnlockCondition::new( - first_account_address.address.inner.clone(), - )]) + .with_unlock_conditions([AddressUnlockCondition::new(wallet_address.clone())]) // Set native tokens empty, we will collect them from all inputs later .with_native_tokens([]) - .finish_output(token_supply)? + .finish_output()? }; // Add required amount for the new output @@ -302,25 +291,29 @@ where } } + // TODO: rework native tokens let option_native_token = if new_native_tokens.is_empty() { None } else { Some(new_native_tokens.clone().finish()?) }; - // Check if the new amount is enough for the storage deposit, otherwise increase it to this + // Check if the new amount is enough for the storage deposit, otherwise increase it with a minimal basic output + // amount let mut required_amount = if !enough_amount_for_basic_output { required_amount_for_nfts } else { required_amount_for_nfts - + MinimumStorageDepositBasicOutput::new(rent_structure, token_supply) - .with_native_tokens(option_native_token) + + BasicOutputBuilder::new_with_minimum_amount(storage_score_params) + .add_unlock_condition(AddressUnlockCondition::new(Ed25519Address::null())) + .with_native_tokens(option_native_token.into_iter().flatten()) .finish()? + .amount() }; let mut additional_inputs = Vec::new(); if available_amount < required_amount { - // Sort by amount so we use as less as possible + // Sort by amount so we use as little as possible possible_additional_inputs.sort_by_key(|o| o.output.amount()); // add more inputs @@ -333,9 +326,11 @@ where // Recalculate every time, because new inputs can also add more native tokens, which would increase // the required storage deposit required_amount = required_amount_for_nfts - + MinimumStorageDepositBasicOutput::new(rent_structure, token_supply) + + BasicOutputBuilder::new_with_minimum_amount(storage_score_params) + .add_unlock_condition(AddressUnlockCondition::new(Ed25519Address::null())) .with_native_token(option_native_token) - .finish()?; + .finish()? + .amount(); if available_amount < required_amount { if !additional_inputs_used.contains(&output_data.output_id) { @@ -365,7 +360,7 @@ where outputs_to_send.push( BasicOutputBuilder::new_with_amount(return_amount) .add_unlock_condition(AddressUnlockCondition::new(return_address)) - .finish_output(token_supply)?, + .finish_output()?, ); } @@ -373,9 +368,9 @@ where if available_amount - required_amount_for_nfts > 0 { outputs_to_send.push( BasicOutputBuilder::new_with_amount(available_amount - required_amount_for_nfts) - .add_unlock_condition(AddressUnlockCondition::new(first_account_address.address.inner.clone())) + .add_unlock_condition(AddressUnlockCondition::new(wallet_address)) .with_native_tokens(new_native_tokens.finish()?) - .finish_output(token_supply)?, + .finish_output()?, ); } else if !new_native_tokens.finish()?.is_empty() { return Err(crate::client::api::input_selection::Error::InsufficientAmount { diff --git a/sdk/src/wallet/account/operations/output_consolidation.rs b/sdk/src/wallet/operations/output_consolidation.rs similarity index 88% rename from sdk/src/wallet/account/operations/output_consolidation.rs rename to sdk/src/wallet/operations/output_consolidation.rs index 970f59e473..30121bf0d9 100644 --- a/sdk/src/wallet/account/operations/output_consolidation.rs +++ b/sdk/src/wallet/operations/output_consolidation.rs @@ -8,11 +8,17 @@ use crate::client::secret::{ledger_nano::LedgerSecretManager, DowncastSecretMana use crate::{ client::{api::PreparedTransactionData, secret::SecretManage}, types::block::{ - address::Bech32Address, + address::{Address, Bech32Address}, input::INPUT_COUNT_MAX, output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NativeTokensBuilder, Output}, slot::SlotIndex, }, + wallet::{ + constants::DEFAULT_OUTPUT_CONSOLIDATION_THRESHOLD, + operations::{helpers::time::can_output_be_unlocked_now, transaction::TransactionOptions}, + types::{OutputData, TransactionWithMetadata}, + Result, Wallet, + }, }; // Constants for the calculation of the amount of inputs we can use with a ledger nano @@ -25,16 +31,7 @@ const INPUT_SIZE: usize = 43; const MIN_OUTPUT_SIZE_IN_ESSENCE: usize = 46; #[cfg(feature = "ledger_nano")] -use crate::wallet::account::constants::DEFAULT_LEDGER_OUTPUT_CONSOLIDATION_THRESHOLD; -use crate::wallet::{ - account::{ - constants::DEFAULT_OUTPUT_CONSOLIDATION_THRESHOLD, - operations::helpers::time::can_output_be_unlocked_now, - types::{OutputData, TransactionWithMetadata}, - Account, AddressWithUnspentOutputs, TransactionOptions, - }, - Result, -}; +use crate::wallet::constants::DEFAULT_LEDGER_OUTPUT_CONSOLIDATION_THRESHOLD; #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -68,7 +65,7 @@ impl ConsolidationParams { } } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -77,7 +74,7 @@ where &self, output_data: &OutputData, slot_index: SlotIndex, - account_addresses: &[AddressWithUnspentOutputs], + wallet_address: &Address, ) -> Result { Ok(if let Output::Basic(basic_output) = &output_data.output { let unlock_conditions = basic_output.unlock_conditions(); @@ -96,7 +93,7 @@ where return Ok(false); } - can_output_be_unlocked_now(account_addresses, &[], output_data, slot_index)? + can_output_be_unlocked_now(wallet_address, output_data, slot_index)? } else { false }) @@ -131,12 +128,12 @@ where #[cfg(feature = "participation")] let voting_output = self.get_voting_output().await?; let slot_index = self.client().get_slot_index().await?; - let token_supply = self.client().get_token_supply().await?; let mut outputs_to_consolidate = Vec::new(); - let account_details = self.details().await; - let account_addresses = &account_details.addresses_with_unspent_outputs[..]; + let wallet_data = self.data().await; - for (output_id, output_data) in account_details.unspent_outputs() { + let wallet_address = &wallet_data.address; + + for (output_id, output_data) in &wallet_data.unspent_outputs { #[cfg(feature = "participation")] if let Some(ref voting_output) = voting_output { // Remove voting output from inputs, because we want to keep its features and not consolidate it. @@ -144,15 +141,15 @@ where continue; } } - let is_locked_output = account_details.locked_outputs.contains(output_id); - let should_consolidate_output = - self.should_consolidate_output(output_data, slot_index, account_addresses)?; + + let is_locked_output = wallet_data.locked_outputs.contains(output_id); + let should_consolidate_output = self.should_consolidate_output(output_data, slot_index, wallet_address)?; if !is_locked_output && should_consolidate_output { outputs_to_consolidate.push(output_data.clone()); } } - drop(account_details); + drop(wallet_data); #[allow(clippy::option_if_let_else)] let output_threshold = match params.output_threshold { @@ -160,8 +157,8 @@ where None => { #[cfg(feature = "ledger_nano")] { - use crate::wallet::account::SecretManager; - let secret_manager = self.wallet.secret_manager.read().await; + use crate::client::secret::SecretManager; + let secret_manager = self.secret_manager.read().await; if secret_manager .downcast::() .or_else(|| { @@ -200,8 +197,8 @@ where #[cfg(feature = "ledger_nano")] let max_inputs = { - use crate::wallet::account::SecretManager; - let secret_manager = self.wallet.secret_manager.read().await; + use crate::client::secret::SecretManager; + let secret_manager = self.secret_manager.read().await; if let Some(ledger) = secret_manager.downcast::().or_else(|| { secret_manager.downcast::().and_then(|s| { if let SecretManager::LedgerNano(n) = s { @@ -255,7 +252,7 @@ where .unwrap_or_else(|| outputs_to_consolidate[0].address.clone()), )) .with_native_tokens(total_native_tokens.finish()?) - .finish_output(token_supply)?]; + .finish_output()?]; let options = Some(TransactionOptions { custom_inputs: Some(custom_inputs), diff --git a/sdk/src/wallet/account/operations/participation/event.rs b/sdk/src/wallet/operations/participation/event.rs similarity index 83% rename from sdk/src/wallet/account/operations/participation/event.rs rename to sdk/src/wallet/operations/participation/event.rs index c0033cdfc4..841a527c21 100644 --- a/sdk/src/wallet/account/operations/participation/event.rs +++ b/sdk/src/wallet/operations/participation/event.rs @@ -8,15 +8,16 @@ use crate::{ types::api::plugins::participation::types::{ ParticipationEventId, ParticipationEventStatus, ParticipationEventType, }, - wallet::account::{ + wallet::{ operations::participation::ParticipationEventWithNodes, - types::participation::ParticipationEventRegistrationOptions, Account, + types::participation::ParticipationEventRegistrationOptions, Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { /// Stores participation information for the given events locally and returns them all. /// @@ -60,12 +61,10 @@ where data: event_data, nodes: vec![options.node.clone()], }; - let account_index = self.details().await.index; - self.wallet - .storage_manager + self.storage_manager .read() .await - .insert_participation_event(account_index, event_with_node.clone()) + .insert_participation_event(event_with_node.clone()) .await?; registered_participation_events.insert(event_id, event_with_node.clone()); } @@ -75,13 +74,7 @@ where /// Removes a previously registered participation event from local storage. pub async fn deregister_participation_event(&self, id: &ParticipationEventId) -> crate::wallet::Result<()> { - let account_index = self.details().await.index; - self.wallet - .storage_manager - .read() - .await - .remove_participation_event(account_index, id) - .await?; + self.storage_manager.read().await.remove_participation_event(id).await?; Ok(()) } @@ -90,13 +83,11 @@ where &self, id: ParticipationEventId, ) -> crate::wallet::Result> { - let account_index = self.details().await.index; Ok(self - .wallet .storage_manager .read() .await - .get_participation_events(account_index) + .get_participation_events() .await? .get(&id) .cloned()) @@ -106,13 +97,7 @@ where pub async fn get_participation_events( &self, ) -> crate::wallet::Result> { - let account_index = self.details().await.index; - self.wallet - .storage_manager - .read() - .await - .get_participation_events(account_index) - .await + self.storage_manager.read().await.get_participation_events().await } /// Retrieves IDs of all events tracked by the client options node. diff --git a/sdk/src/wallet/account/operations/participation/mod.rs b/sdk/src/wallet/operations/participation/mod.rs similarity index 91% rename from sdk/src/wallet/account/operations/participation/mod.rs rename to sdk/src/wallet/operations/participation/mod.rs index 0eb247c087..cd7154580d 100644 --- a/sdk/src/wallet/account/operations/participation/mod.rs +++ b/sdk/src/wallet/operations/participation/mod.rs @@ -25,15 +25,12 @@ use crate::{ }, block::output::{unlock_condition::UnlockCondition, Output, OutputId}, }, - wallet::{ - account::{Account, OutputData}, - task, Result, - }, + wallet::{task, types::OutputData, Result, Wallet}, }; /// An object containing an account's entire participation overview. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AccountParticipationOverview { +pub struct ParticipationOverview { /// Output participations for events. pub participations: HashMap>, } @@ -49,32 +46,32 @@ pub struct ParticipationEventWithNodes { pub nodes: Vec, } -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { - /// Calculates the voting overview of an account. If event_ids are provided, only return outputs and tracked + /// Calculates the voting overview of a wallet. If event_ids are provided, only return outputs and tracked /// participations for them. pub async fn get_participation_overview( &self, event_ids: Option>, - ) -> Result { + ) -> Result { log::debug!("[get_participation_overview]"); // TODO: Could use the address endpoint in the future when https://github.com/iotaledger/inx-participation/issues/50 is done. let mut spent_cached_outputs = self - .wallet .storage_manager .read() .await - .get_cached_participation_output_status(self.details().await.index) + .get_cached_participation_output_status() .await?; let restored_spent_cached_outputs_len = spent_cached_outputs.len(); log::debug!( "[get_participation_overview] restored_spent_cached_outputs_len: {}", restored_spent_cached_outputs_len ); - let outputs = self.outputs(None).await?; + let outputs = self.outputs(None).await; let participation_outputs = outputs .into_iter() .filter(|output_data| { @@ -216,15 +213,14 @@ where ); // Only store updated data if new outputs got added if spent_cached_outputs.len() > restored_spent_cached_outputs_len { - self.wallet - .storage_manager + self.storage_manager .read() .await - .set_cached_participation_output_status(self.details().await.index, &spent_cached_outputs) + .set_cached_participation_output_status(&spent_cached_outputs) .await?; } - Ok(AccountParticipationOverview { participations }) + Ok(ParticipationOverview { participations }) } /// Returns the voting output ("PARTICIPATION" tag). @@ -234,7 +230,7 @@ where log::debug!("[get_voting_output]"); Ok(self .unspent_outputs(None) - .await? + .await .iter() .filter(|output_data| is_valid_participation_output(&output_data.output)) .max_by_key(|output_data| output_data.output.amount()) @@ -245,14 +241,7 @@ where /// If event isn't found, the client from the account will be returned. pub(crate) async fn get_client_for_event(&self, id: &ParticipationEventId) -> crate::wallet::Result { log::debug!("[get_client_for_event]"); - let account_index = self.details().await.index; - let events = self - .wallet - .storage_manager - .read() - .await - .get_participation_events(account_index) - .await?; + let events = self.storage_manager.read().await.get_participation_events().await?; let event_with_nodes = match events.get(id) { Some(event_with_nodes) => event_with_nodes, @@ -277,14 +266,7 @@ where let latest_milestone_index = 0; // let latest_milestone_index = self.client().get_info().await?.node_info.status.latest_milestone.index; - let account_index = self.details().await.index; - let events = self - .wallet - .storage_manager - .read() - .await - .get_participation_events(account_index) - .await?; + let events = self.storage_manager.read().await.get_participation_events().await?; for participation in participations.participations.clone().iter() { if let Some(event_with_nodes) = events.get(&participation.event_id) { diff --git a/sdk/src/wallet/account/operations/participation/voting.rs b/sdk/src/wallet/operations/participation/voting.rs similarity index 96% rename from sdk/src/wallet/account/operations/participation/voting.rs rename to sdk/src/wallet/operations/participation/voting.rs index 59427f0a28..cb6f097d1a 100644 --- a/sdk/src/wallet/account/operations/participation/voting.rs +++ b/sdk/src/wallet/operations/participation/voting.rs @@ -13,13 +13,10 @@ use crate::{ payload::TaggedDataPayload, }, }, - wallet::{ - account::{types::TransactionWithMetadata, Account, TransactionOptions}, - Result, - }, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Result, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -108,7 +105,7 @@ where Feature::Tag(TagFeature::new(PARTICIPATION_TAG)?), Feature::Metadata(MetadataFeature::new(participation_bytes.clone())?), ]) - .finish_output(self.client().get_token_supply().await?)?; + .finish_output()?; self.prepare_transaction( [new_output], @@ -180,7 +177,7 @@ where Feature::Tag(TagFeature::new(PARTICIPATION_TAG)?), Feature::Metadata(MetadataFeature::new(participation_bytes.clone())?), ]) - .finish_output(self.client().get_token_supply().await?)?; + .finish_output()?; self.prepare_transaction( [new_output], diff --git a/sdk/src/wallet/account/operations/participation/voting_power.rs b/sdk/src/wallet/operations/participation/voting_power.rs similarity index 83% rename from sdk/src/wallet/account/operations/participation/voting_power.rs rename to sdk/src/wallet/operations/participation/voting_power.rs index 068582c767..f2457cfe75 100644 --- a/sdk/src/wallet/account/operations/participation/voting_power.rs +++ b/sdk/src/wallet/operations/participation/voting_power.rs @@ -14,13 +14,10 @@ use crate::{ payload::TaggedDataPayload, }, }, - wallet::{ - account::{types::TransactionWithMetadata, Account, TransactionOptions}, - Error, Result, - }, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Error, Result, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -53,17 +50,14 @@ where /// Prepares the transaction for /// [Account::increase_voting_power()](crate::wallet::Account::increase_voting_power). pub async fn prepare_increase_voting_power(&self, amount: u64) -> Result { - let token_supply = self.client().get_token_supply().await?; - let (new_output, tx_options) = match self.get_voting_output().await? { Some(current_output_data) => { let output = current_output_data.output.as_basic(); let new_amount = output.amount().checked_add(amount).ok_or(Error::InvalidVotingPower)?; - let (new_output, tagged_data_payload) = self - .new_voting_output_and_tagged_data(output, new_amount, token_supply) - .await?; + let (new_output, tagged_data_payload) = + self.new_voting_output_and_tagged_data(output, new_amount).await?; ( new_output, @@ -77,17 +71,9 @@ where } None => ( BasicOutputBuilder::new_with_amount(amount) - .add_unlock_condition(AddressUnlockCondition::new( - self.public_addresses() - .await - .into_iter() - .next() - .expect("account needs to have a public address") - .address - .inner, - )) + .add_unlock_condition(AddressUnlockCondition::new(self.address().await)) .add_feature(TagFeature::new(PARTICIPATION_TAG)?) - .finish_output(token_supply)?, + .finish_output()?, None, ), }; @@ -113,7 +99,6 @@ where /// Prepares the transaction for /// [Account::decrease_voting_power()](crate::wallet::Account::decrease_voting_power). pub async fn prepare_decrease_voting_power(&self, amount: u64) -> Result { - let token_supply = self.client().get_token_supply().await?; let current_output_data = self .get_voting_output() .await? @@ -122,18 +107,11 @@ where // If the amount to decrease is the amount of the output, then we just remove the features. let (new_output, tagged_data_payload) = if amount == output.amount() { - ( - BasicOutputBuilder::from(output) - .clear_features() - .finish_output(token_supply)?, - None, - ) + (BasicOutputBuilder::from(output).clear_features().finish_output()?, None) } else { let new_amount = output.amount().checked_sub(amount).ok_or(Error::InvalidVotingPower)?; - let (new_output, tagged_data_payload) = self - .new_voting_output_and_tagged_data(output, new_amount, token_supply) - .await?; + let (new_output, tagged_data_payload) = self.new_voting_output_and_tagged_data(output, new_amount).await?; (new_output, Some(tagged_data_payload)) }; @@ -155,7 +133,6 @@ where &self, output: &BasicOutput, amount: u64, - token_supply: u64, ) -> Result<(Output, TaggedDataPayload)> { let mut output_builder = BasicOutputBuilder::from(output).with_amount(amount); let mut participation_bytes = output.features().metadata().map(|m| m.data()).unwrap_or(&[]); @@ -174,7 +151,7 @@ where }; Ok(( - output_builder.finish_output(token_supply)?, + output_builder.finish_output()?, TaggedDataPayload::new(PARTICIPATION_TAG.as_bytes().to_vec(), participation_bytes.to_vec())?, )) } diff --git a/sdk/src/wallet/account/operations/reissue.rs b/sdk/src/wallet/operations/reissue.rs similarity index 94% rename from sdk/src/wallet/account/operations/reissue.rs rename to sdk/src/wallet/operations/reissue.rs index 2eff243a9f..ec1c120f36 100644 --- a/sdk/src/wallet/account/operations/reissue.rs +++ b/sdk/src/wallet/operations/reissue.rs @@ -1,8 +1,6 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crypto::keys::bip44::Bip44; - use crate::{ client::{ secret::{SecretManage, SignBlock}, @@ -15,16 +13,13 @@ use crate::{ BlockId, }, }, - wallet::{ - account::{types::InclusionState, Account}, - Error, - }, + wallet::{types::InclusionState, Error, Wallet}, }; const DEFAULT_REISSUE_UNTIL_INCLUDED_INTERVAL: u64 = 1; const DEFAULT_REISSUE_UNTIL_INCLUDED_MAX_AMOUNT: u64 = 40; -impl Account +impl Wallet where Error: From, crate::client::Error: From, @@ -40,7 +35,7 @@ where log::debug!("[reissue_transaction_until_included]"); let protocol_parameters = self.client().get_protocol_parameters().await?; - let transaction = self.details().await.transactions.get(transaction_id).cloned(); + let transaction = self.data().await.transactions.get(transaction_id).cloned(); if let Some(transaction) = transaction { if transaction.inclusion_state == InclusionState::Confirmed { @@ -68,7 +63,7 @@ where .await? .sign_ed25519( &*self.get_secret_manager().read().await, - Bip44::new(self.wallet.coin_type()), + self.bip_path().await.ok_or(Error::MissingBipPath)?, ) .await? .id(&protocol_parameters), @@ -116,7 +111,7 @@ where .await? .sign_ed25519( &*self.get_secret_manager().read().await, - Bip44::new(self.wallet.coin_type()), + self.bip_path().await.ok_or(Error::MissingBipPath)?, ) .await?; block_ids.push(reissued_block.id(&protocol_parameters)); diff --git a/sdk/src/wallet/operations/syncing/addresses/mod.rs b/sdk/src/wallet/operations/syncing/addresses/mod.rs new file mode 100644 index 0000000000..2952ebb296 --- /dev/null +++ b/sdk/src/wallet/operations/syncing/addresses/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2021 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod output_ids; +mod outputs; diff --git a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/account_foundry.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/account_foundry.rs similarity index 90% rename from sdk/src/wallet/account/operations/syncing/addresses/output_ids/account_foundry.rs rename to sdk/src/wallet/operations/syncing/addresses/output_ids/account_foundry.rs index 795ee91c95..779227f649 100644 --- a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/account_foundry.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/account_foundry.rs @@ -13,18 +13,16 @@ use crate::{ block::{ address::{AccountAddress, Bech32Address, ToBech32Ext}, output::{Output, OutputId}, - ConvertTo, }, }, - wallet::{ - account::{Account, SyncOptions}, - task, - }, + utils::ConvertTo, + wallet::{operations::syncing::SyncOptions, task, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { /// Returns output ids of account outputs pub(crate) async fn get_account_and_foundry_output_ids( @@ -37,12 +35,12 @@ where let mut output_ids = self .client() - .account_output_ids(AccountOutputQueryParameters::new().unlockable_by_address(bech32_address)) + .account_output_ids(AccountOutputQueryParameters::new().address(bech32_address)) .await? .items; // Get all results - if sync_options.alias.foundry_outputs { + if sync_options.account.foundry_outputs { let foundry_output_ids = self.get_foundry_output_ids(&output_ids).await?; output_ids.extend(foundry_output_ids); } @@ -72,7 +70,7 @@ where let client = self.client().clone(); tasks.push(Box::pin(task::spawn(async move { client - .foundry_output_ids(FoundryOutputQueryParameters::new().account_address(account_bech32_address)) + .foundry_output_ids(FoundryOutputQueryParameters::new().account(account_bech32_address)) .await .map_err(From::from) }))); diff --git a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/basic.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/basic.rs similarity index 90% rename from sdk/src/wallet/account/operations/syncing/addresses/output_ids/basic.rs rename to sdk/src/wallet/operations/syncing/addresses/output_ids/basic.rs index 54178ae70d..4374d0c859 100644 --- a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/basic.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/basic.rs @@ -3,11 +3,12 @@ use crate::{ client::{node_api::indexer::query_parameters::BasicOutputQueryParameters, secret::SecretManage}, - types::block::{address::Bech32Address, output::OutputId, ConvertTo}, - wallet::Account, + types::block::{address::Bech32Address, output::OutputId}, + utils::ConvertTo, + wallet::Wallet, }; -impl Account +impl Wallet where crate::wallet::Error: From, { diff --git a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/mod.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/mod.rs similarity index 78% rename from sdk/src/wallet/account/operations/syncing/addresses/output_ids/mod.rs rename to sdk/src/wallet/operations/syncing/addresses/output_ids/mod.rs index f9560262d9..c48e876b16 100644 --- a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/mod.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/mod.rs @@ -16,45 +16,41 @@ use crate::{ node_api::indexer::query_parameters::{FoundryOutputQueryParameters, OutputQueryParameters}, secret::SecretManage, }, - types::block::{ - address::{Address, Bech32Address}, - output::OutputId, - }, - wallet::account::{ + types::block::{address::Bech32Address, output::OutputId}, + wallet::{ constants::PARALLEL_REQUESTS_AMOUNT, operations::syncing::SyncOptions, - types::address::AddressWithUnspentOutputs, Account, + types::address::AddressWithUnspentOutputs, Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { /// Returns output ids for outputs that are directly (Ed25519 address in AddressUnlockCondition) or indirectly /// (account/nft address in AddressUnlockCondition and the account/nft output is controlled with the Ed25519 /// address) connected to pub(crate) async fn get_output_ids_for_address( &self, - address: &Address, + address: &Bech32Address, sync_options: &SyncOptions, ) -> crate::wallet::Result> { - let bech32_address = Bech32Address::new(self.client().get_bech32_hrp().await?, address.clone()); - if sync_options.sync_only_most_basic_outputs { let output_ids = self - .get_basic_output_ids_with_address_unlock_condition_only(bech32_address.clone()) + .get_basic_output_ids_with_address_unlock_condition_only(address.clone()) .await?; return Ok(output_ids); } - // If interested in alias, basic, NFT and foundry outputs, get them all at once - if (address.is_ed25519() && sync_options.account.all_outputs()) + // If interested in account, basic, NFT and foundry outputs, get them all at once + if (address.is_ed25519() && sync_options.wallet.all_outputs()) || (address.is_nft() && sync_options.nft.all_outputs()) - || (address.is_account() && sync_options.alias.all_outputs()) + || (address.is_account() && sync_options.account.all_outputs()) { return Ok(self .client() - .output_ids(OutputQueryParameters::new().unlockable_by_address(bech32_address.clone())) + .output_ids(OutputQueryParameters::new().unlockable_by_address(address.clone())) .await? .items); } @@ -65,9 +61,9 @@ where #[cfg(not(target_family = "wasm"))] let mut tasks = Vec::new(); - if (address.is_ed25519() && sync_options.account.basic_outputs) + if (address.is_ed25519() && sync_options.wallet.basic_outputs) || (address.is_nft() && sync_options.nft.basic_outputs) - || (address.is_account() && sync_options.alias.basic_outputs) + || (address.is_account() && sync_options.account.basic_outputs) { // basic outputs #[cfg(target_family = "wasm")] @@ -82,7 +78,7 @@ where { tasks.push( async { - let bech32_address = bech32_address.clone(); + let bech32_address = address.clone(); let account = self.clone(); tokio::spawn(async move { account @@ -96,24 +92,21 @@ where } } - if (address.is_ed25519() && sync_options.account.nft_outputs) + if (address.is_ed25519() && sync_options.wallet.nft_outputs) || (address.is_nft() && sync_options.nft.nft_outputs) - || (address.is_account() && sync_options.alias.nft_outputs) + || (address.is_account() && sync_options.account.nft_outputs) { // nfts #[cfg(target_family = "wasm")] { - results.push( - self.get_nft_output_ids_with_any_unlock_condition(bech32_address.clone()) - .await, - ) + results.push(self.get_nft_output_ids_with_any_unlock_condition(address.clone()).await) } #[cfg(not(target_family = "wasm"))] { tasks.push( async { - let bech32_address = bech32_address.clone(); + let bech32_address = address.clone(); let account = self.clone(); tokio::spawn(async move { account @@ -127,15 +120,15 @@ where } } - if (address.is_ed25519() && sync_options.account.account_outputs) + if (address.is_ed25519() && sync_options.wallet.account_outputs) || (address.is_nft() && sync_options.nft.account_outputs) - || (address.is_account() && sync_options.alias.account_outputs) + || (address.is_account() && sync_options.account.account_outputs) { // accounts and foundries #[cfg(target_family = "wasm")] { results.push( - self.get_account_and_foundry_output_ids(bech32_address.clone(), sync_options) + self.get_account_and_foundry_output_ids(address.clone(), sync_options) .await, ) } @@ -144,7 +137,7 @@ where { tasks.push( async { - let bech32_address = bech32_address.clone(); + let bech32_address = address.clone(); let sync_options = sync_options.clone(); let account = self.clone(); tokio::spawn(async move { @@ -157,13 +150,13 @@ where .boxed(), ); } - } else if address.is_account() && sync_options.alias.foundry_outputs { + } else if address.is_account() && sync_options.account.foundry_outputs { // foundries #[cfg(target_family = "wasm")] { results.push(Ok(self .client() - .foundry_output_ids(FoundryOutputQueryParameters::new().account_address(bech32_address)) + .foundry_output_ids(FoundryOutputQueryParameters::new().account(address)) .await? .items)) } @@ -172,11 +165,11 @@ where { tasks.push( async { - let bech32_address = bech32_address.clone(); + let bech32_address = address.clone(); let client = self.client().clone(); tokio::spawn(async move { Ok(client - .foundry_output_ids(FoundryOutputQueryParameters::new().account_address(bech32_address)) + .foundry_output_ids(FoundryOutputQueryParameters::new().account(bech32_address)) .await? .items) }) @@ -200,12 +193,12 @@ where Ok(output_ids.into_iter().collect()) } - /// Get the current output ids for provided addresses and only returns addresses that have unspent outputs and + /// Get the current output ids and only returns addresses that have unspent outputs and /// return spent outputs separated pub(crate) async fn get_output_ids_for_addresses( &self, - options: &SyncOptions, addresses_with_unspent_outputs: Vec, + options: &SyncOptions, ) -> crate::wallet::Result<(Vec, Vec)> { log::debug!("[SYNC] start get_output_ids_for_addresses"); let address_output_ids_start_time = Instant::now(); @@ -213,6 +206,7 @@ where let mut addresses_with_outputs = Vec::new(); // spent outputs or account/nft/foundries that don't get synced anymore, because of other sync options let mut spent_or_not_anymore_synced_outputs = Vec::new(); + // We split the addresses into chunks so we don't get timeouts if we have thousands for addresses_chunk in &mut addresses_with_unspent_outputs .chunks(PARALLEL_REQUESTS_AMOUNT) @@ -235,12 +229,12 @@ where { let mut tasks = Vec::new(); for address in addresses_chunk { - let account = self.clone(); + let wallet = self.clone(); let sync_options = options.clone(); tasks.push(async move { tokio::spawn(async move { - let output_ids = account - .get_output_ids_for_address(&address.address.inner, &sync_options) + let output_ids = wallet + .get_output_ids_for_address(&address.address, &sync_options) .await?; crate::wallet::Result::Ok((address, output_ids)) }) @@ -255,8 +249,8 @@ where let (mut address, output_ids): (AddressWithUnspentOutputs, Vec) = res?; // only return addresses with outputs if !output_ids.is_empty() { - // outputs we had before, but now not anymore, got spent or are account/nft/foundries that don't get - // synced anymore because of other sync options + // outputs we had before, but now not anymore, got spent or are account/nft/foundries that don't + // get synced anymore because of other sync options for output_id in address.output_ids { if !output_ids.contains(&output_id) { spent_or_not_anymore_synced_outputs.push(output_id); @@ -265,8 +259,8 @@ where address.output_ids = output_ids; addresses_with_outputs.push(address); } else { - // outputs we had before, but now not anymore, got spent or are account/nft/foundries that don't get - // synced anymore because of other sync options + // outputs we had before, but now not anymore, got spent or are account/nft/foundries that don't + // get synced anymore because of other sync options spent_or_not_anymore_synced_outputs.extend(address.output_ids); } } diff --git a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/nft.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/nft.rs similarity index 84% rename from sdk/src/wallet/account/operations/syncing/addresses/output_ids/nft.rs rename to sdk/src/wallet/operations/syncing/addresses/output_ids/nft.rs index af53f1f4b8..5430bcb009 100644 --- a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/nft.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/nft.rs @@ -3,11 +3,12 @@ use crate::{ client::{node_api::indexer::query_parameters::NftOutputQueryParameters, secret::SecretManage}, - types::block::{address::Bech32Address, output::OutputId, ConvertTo}, - wallet::Account, + types::block::{address::Bech32Address, output::OutputId}, + utils::ConvertTo, + wallet::Wallet, }; -impl Account +impl Wallet where crate::wallet::Error: From, { diff --git a/sdk/src/wallet/account/operations/syncing/addresses/outputs.rs b/sdk/src/wallet/operations/syncing/addresses/outputs.rs similarity index 83% rename from sdk/src/wallet/account/operations/syncing/addresses/outputs.rs rename to sdk/src/wallet/operations/syncing/addresses/outputs.rs index 615cf2b89e..fadc42acd4 100644 --- a/sdk/src/wallet/account/operations/syncing/addresses/outputs.rs +++ b/sdk/src/wallet/operations/syncing/addresses/outputs.rs @@ -6,16 +6,17 @@ use instant::Instant; use crate::{ client::secret::SecretManage, wallet::{ - account::{ - constants::PARALLEL_REQUESTS_AMOUNT, types::address::AddressWithUnspentOutputs, Account, OutputData, - }, + constants::PARALLEL_REQUESTS_AMOUNT, task, + types::{address::AddressWithUnspentOutputs, OutputData}, + Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { /// Get outputs from addresses pub(crate) async fn get_outputs_from_address_output_ids( @@ -35,12 +36,12 @@ where { let mut tasks = Vec::new(); for address in addresses_chunk { - let account = self.clone(); + let wallet = self.clone(); tasks.push(async move { task::spawn(async move { - let output_responses = account.get_outputs(address.output_ids.clone()).await?; + let output_responses = wallet.get_outputs(address.output_ids.clone()).await?; - let outputs = account + let outputs = wallet .output_response_to_output_data(output_responses, &address) .await?; crate::wallet::Result::Ok((address, outputs)) diff --git a/sdk/src/wallet/account/operations/syncing/foundries.rs b/sdk/src/wallet/operations/syncing/foundries.rs similarity index 83% rename from sdk/src/wallet/account/operations/syncing/foundries.rs rename to sdk/src/wallet/operations/syncing/foundries.rs index 96c5998d37..166c4fd64f 100644 --- a/sdk/src/wallet/account/operations/syncing/foundries.rs +++ b/sdk/src/wallet/operations/syncing/foundries.rs @@ -6,12 +6,13 @@ use std::collections::HashSet; use crate::{ client::secret::SecretManage, types::block::output::{FoundryId, Output}, - wallet::{task, Account}, + wallet::{task, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { pub(crate) async fn request_and_store_foundry_outputs( &self, @@ -19,7 +20,7 @@ where ) -> crate::wallet::Result<()> { log::debug!("[SYNC] request_and_store_foundry_outputs"); - let mut foundries = self.details().await.native_token_foundries().clone(); + let mut foundries = self.data().await.native_token_foundries.clone(); let results = futures::future::try_join_all(foundry_ids.into_iter().filter(|f| !foundries.contains_key(f)).map( |foundry_id| { @@ -45,8 +46,8 @@ where } } - let mut account_details = self.details_mut().await; - account_details.native_token_foundries = foundries; + let mut wallet_data = self.data_mut().await; + wallet_data.native_token_foundries = foundries; Ok(()) } diff --git a/sdk/src/wallet/account/operations/syncing/mod.rs b/sdk/src/wallet/operations/syncing/mod.rs similarity index 75% rename from sdk/src/wallet/account/operations/syncing/mod.rs rename to sdk/src/wallet/operations/syncing/mod.rs index 6ef9d1ada6..770e333f91 100644 --- a/sdk/src/wallet/account/operations/syncing/mod.rs +++ b/sdk/src/wallet/operations/syncing/mod.rs @@ -13,17 +13,17 @@ pub use self::options::SyncOptions; use crate::{ client::secret::SecretManage, types::block::{ - address::{AccountAddress, Address, NftAddress, ToBech32Ext}, + address::{AccountAddress, Address, Bech32Address, NftAddress, ToBech32Ext}, output::{FoundryId, Output, OutputId, OutputMetadata}, }, - wallet::account::{ + wallet::{ constants::MIN_SYNC_INTERVAL, - types::{AddressWithUnspentOutputs, OutputData}, - Account, Balance, + types::{AddressWithUnspentOutputs, Balance, OutputData}, + Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -33,9 +33,8 @@ where pub async fn set_default_sync_options(&self, options: SyncOptions) -> crate::wallet::Result<()> { #[cfg(feature = "storage")] { - let index = *self.details().await.index(); - let storage_manager = self.wallet.storage_manager.read().await; - storage_manager.set_default_sync_options(index, &options).await?; + let storage_manager = self.storage_manager.read().await; + storage_manager.set_default_sync_options(&options).await?; } *self.default_sync_options.lock().await = options; @@ -47,7 +46,7 @@ where self.default_sync_options.lock().await.clone() } - /// Sync the account by fetching new information from the nodes. Will also reissue pending transactions + /// Sync the wallet by fetching new information from the nodes. Will also reissue pending transactions /// if necessary. A custom default can be set using set_default_sync_options. pub async fn sync(&self, options: Option) -> crate::wallet::Result { let options = match options { @@ -67,8 +66,8 @@ where "[SYNC] synced within the latest {} ms, only calculating balance", MIN_SYNC_INTERVAL ); - // Calculate the balance because if we created a transaction in the meantime, the amount for the inputs is - // not available anymore + // Calculate the balance because if we created a transaction in the meantime, the amount for the inputs + // is not available anymore return self.balance().await; } @@ -96,14 +95,25 @@ where async fn sync_internal(&self, options: &SyncOptions) -> crate::wallet::Result<()> { log::debug!("[SYNC] sync_internal"); - let addresses_to_sync = self.get_addresses_to_sync(options).await?; - log::debug!("[SYNC] addresses_to_sync {}", addresses_to_sync.len()); + let wallet_address_with_unspent_outputs = AddressWithUnspentOutputs { + address: self.address().await, + output_ids: self + .unspent_outputs(None) + .await + .into_iter() + .map(|data| data.output_id) + .collect::>(), + internal: false, + key_index: 0, + }; - let (spent_or_not_synced_output_ids, addresses_with_unspent_outputs, outputs_data): ( - Vec, + let address_to_sync = vec![wallet_address_with_unspent_outputs]; + + let (addresses_with_unspent_outputs, spent_or_not_synced_output_ids, outputs_data): ( Vec, + Vec, Vec, - ) = self.request_outputs_recursively(addresses_to_sync, options).await?; + ) = self.request_outputs_recursively(address_to_sync, options).await?; // Request possible spent outputs log::debug!("[SYNC] spent_or_not_synced_outputs: {spent_or_not_synced_output_ids:?}"); @@ -145,65 +155,68 @@ where self.request_and_store_foundry_outputs(native_token_foundry_ids).await?; } - // Updates account with balances, output ids, outputs - self.update_account( - addresses_with_unspent_outputs, - outputs_data, - spent_or_unsynced_output_metadata_map, - options, - ) - .await + // Updates wallet with balances, output ids, outputs + self.update_after_sync(outputs_data, spent_or_unsynced_output_metadata_map) + .await } - // First request all outputs directly related to the ed25519 addresses, then for each nft and account output we got, + // First request all outputs directly related to the wallet address, then for each nft and account output we got, // request all outputs that are related to their account/nft addresses in a loop until no new account or nft outputs - // is found + // are found. async fn request_outputs_recursively( &self, addresses_to_sync: Vec, options: &SyncOptions, - ) -> crate::wallet::Result<(Vec, Vec, Vec)> { + ) -> crate::wallet::Result<(Vec, Vec, Vec)> { // Cache the account and nft address with the related ed2559 address, so we can update the account address with // the new output ids - let mut new_account_and_nft_addresses = HashMap::new(); - let (mut spent_or_not_synced_output_ids, mut addresses_with_unspent_outputs, mut outputs_data) = - (Vec::new(), Vec::new(), Vec::new()); + + let mut new_account_and_nft_addresses: HashMap = HashMap::new(); + let mut spent_or_not_synced_output_ids = Vec::new(); + let mut addresses_with_unspent_outputs = Vec::new(); + let mut outputs_data = Vec::new(); + + let bech32_hrp = self.client().get_bech32_hrp().await?; loop { let new_outputs_data = if new_account_and_nft_addresses.is_empty() { - // Get outputs for addresses and add them also the the addresses_with_unspent_outputs - let (addresses_with_output_ids, spent_or_not_synced_output_ids_inner) = self - .get_output_ids_for_addresses(options, addresses_to_sync.clone()) + // Get outputs for the addresses and add them also the the addresses_with_unspent_outputs + let (unspent_output_ids, spent_or_not_synced_output_ids_inner) = self + .get_output_ids_for_addresses(addresses_to_sync.clone(), options) .await?; + spent_or_not_synced_output_ids = spent_or_not_synced_output_ids_inner; + // Get outputs for addresses and add them also the the addresses_with_unspent_outputs - let (addresses_with_unspent_outputs_inner, outputs_data_inner) = self - .get_outputs_from_address_output_ids(addresses_with_output_ids) - .await?; + let (addresses_with_unspent_outputs_inner, outputs_data_inner) = + self.get_outputs_from_address_output_ids(unspent_output_ids).await?; + addresses_with_unspent_outputs = addresses_with_unspent_outputs_inner; outputs_data.extend(outputs_data_inner.clone()); outputs_data_inner } else { - let bech32_hrp = self.client().get_bech32_hrp().await?; let mut new_outputs_data = Vec::new(); - for (account_or_nft_address, ed25519_address) in new_account_and_nft_addresses { + for (account_or_nft_address, output_address) in &new_account_and_nft_addresses { let output_ids = self - .get_output_ids_for_address(&account_or_nft_address, options) + .get_output_ids_for_address( + &Bech32Address::new(bech32_hrp, account_or_nft_address.clone()), + options, + ) .await?; // Update address with unspent outputs let address_with_unspent_outputs = addresses_with_unspent_outputs .iter_mut() - .find(|a| a.address.inner == ed25519_address) + .find(|address| address.address.inner() == output_address) .ok_or_else(|| { - crate::wallet::Error::AddressNotFoundInAccount(ed25519_address.to_bech32(bech32_hrp)) + crate::wallet::Error::WalletAddressMismatch(output_address.clone().to_bech32(bech32_hrp)) })?; address_with_unspent_outputs.output_ids.extend(output_ids.clone()); let new_outputs_data_inner = self.get_outputs(output_ids).await?; let outputs_data_inner = self - .output_response_to_output_data(new_outputs_data_inner, address_with_unspent_outputs) + .output_response_to_output_data(new_outputs_data_inner, &address_with_unspent_outputs) .await?; outputs_data.extend(outputs_data_inner.clone()); @@ -213,7 +226,8 @@ where }; // Clear, so we only get new addresses - new_account_and_nft_addresses = HashMap::new(); + new_account_and_nft_addresses.clear(); + // Add new account and nft addresses for output_data in new_outputs_data { match output_data.output { @@ -242,12 +256,13 @@ where // synced afterwards, so we filter these unspent outputs here. Maybe the spent_or_not_synced_output_ids can be // calculated more efficient in the future, by comparing the new and old outputs only at this point. Then this // retain isn't needed anymore. + let unspent_output_ids: HashSet = HashSet::from_iter(outputs_data.iter().map(|o| o.output_id)); spent_or_not_synced_output_ids.retain(|o| !unspent_output_ids.contains(o)); Ok(( - spent_or_not_synced_output_ids, addresses_with_unspent_outputs, + spent_or_not_synced_output_ids, outputs_data, )) } diff --git a/sdk/src/wallet/account/operations/syncing/options.rs b/sdk/src/wallet/operations/syncing/options.rs similarity index 74% rename from sdk/src/wallet/account/operations/syncing/options.rs rename to sdk/src/wallet/operations/syncing/options.rs index ae52169ee2..38e733c1e1 100644 --- a/sdk/src/wallet/account/operations/syncing/options.rs +++ b/sdk/src/wallet/operations/syncing/options.rs @@ -3,9 +3,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::block::address::Bech32Address; - -const DEFAULT_ADDRESS_START_INDEX: u32 = 0; const DEFAULT_FORCE_SYNCING: bool = false; const DEFAULT_SYNC_INCOMING_TRANSACTIONS: bool = false; const DEFAULT_SYNC_ONLY_MOST_BASIC_OUTPUTS: bool = false; @@ -16,18 +13,6 @@ const DEFAULT_SYNC_NATIVE_TOKEN_FOUNDRIES: bool = false; #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SyncOptions { - /// Specific Bech32 encoded addresses of the account to sync, if addresses are provided, then `address_start_index` - /// will be ignored - #[serde(default)] - pub addresses: Vec, - /// Address index from which to start syncing addresses. 0 by default, using a higher index will be faster because - /// addresses with a lower index will be skipped, but could result in a wrong balance for that reason - #[serde(default = "default_address_start_index")] - pub address_start_index: u32, - /// Address index from which to start syncing internal addresses. 0 by default, using a higher index will be faster - /// because addresses with a lower index will be skipped, but could result in a wrong balance for that reason - #[serde(default = "default_address_start_index")] - pub address_start_index_internal: u32, /// Usually syncing is skipped if it's called in between 200ms, because there can only be new changes every /// milestone and calling it twice "at the same time" will not return new data /// When this to true, we will sync anyways, even if it's called 0ms after the las sync finished. @@ -40,18 +25,17 @@ pub struct SyncOptions { /// Checks pending transactions and reissues them if necessary. #[serde(default = "default_sync_pending_transactions")] pub sync_pending_transactions: bool, - /// Specifies what outputs should be synced for the ed25519 addresses from the account. + /// Specifies what outputs should be synced for the ed25519 address from the wallet. #[serde(default)] - pub account: AccountSyncOptions, + pub wallet: WalletSyncOptions, /// Specifies what outputs should be synced for the address of an account output. #[serde(default)] - // TODO Rename when we are done with Account changes https://github.com/iotaledger/iota-sdk/issues/647. - pub alias: AliasSyncOptions, + pub account: AccountSyncOptions, /// Specifies what outputs should be synced for the address of an nft output. #[serde(default)] pub nft: NftSyncOptions, /// Specifies if only basic outputs with an AddressUnlockCondition alone should be synced, will overwrite - /// `account`, `alias` and `nft` options. + /// `wallet`, `account` and `nft` options. #[serde(default = "default_sync_only_most_basic_outputs")] pub sync_only_most_basic_outputs: bool, /// Sync native token foundries, so their metadata can be returned in the balance. @@ -59,10 +43,6 @@ pub struct SyncOptions { pub sync_native_token_foundries: bool, } -fn default_address_start_index() -> u32 { - DEFAULT_ADDRESS_START_INDEX -} - fn default_force_syncing() -> bool { DEFAULT_FORCE_SYNCING } @@ -86,13 +66,10 @@ fn default_sync_native_token_foundries() -> bool { impl Default for SyncOptions { fn default() -> Self { Self { - addresses: Vec::new(), - address_start_index: default_address_start_index(), - address_start_index_internal: default_address_start_index(), sync_incoming_transactions: default_sync_incoming_transactions(), sync_pending_transactions: default_sync_pending_transactions(), + wallet: WalletSyncOptions::default(), account: AccountSyncOptions::default(), - alias: AliasSyncOptions::default(), nft: NftSyncOptions::default(), sync_only_most_basic_outputs: default_sync_only_most_basic_outputs(), sync_native_token_foundries: default_sync_native_token_foundries(), @@ -101,16 +78,16 @@ impl Default for SyncOptions { } } -/// Sync options for Ed25519 addresses from the account +/// Sync options for Ed25519 addresses from the wallet #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] -pub struct AccountSyncOptions { +pub struct WalletSyncOptions { pub basic_outputs: bool, pub nft_outputs: bool, pub account_outputs: bool, } -impl Default for AccountSyncOptions { +impl Default for WalletSyncOptions { fn default() -> Self { Self { basic_outputs: true, @@ -120,7 +97,7 @@ impl Default for AccountSyncOptions { } } -impl AccountSyncOptions { +impl WalletSyncOptions { pub(crate) fn all_outputs(&self) -> bool { self.basic_outputs && self.nft_outputs && self.account_outputs } @@ -129,14 +106,14 @@ impl AccountSyncOptions { /// Sync options for addresses from account outputs #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] -pub struct AliasSyncOptions { +pub struct AccountSyncOptions { pub basic_outputs: bool, pub nft_outputs: bool, pub account_outputs: bool, pub foundry_outputs: bool, } -impl Default for AliasSyncOptions { +impl Default for AccountSyncOptions { // Sync only foundries fn default() -> Self { Self { @@ -148,7 +125,7 @@ impl Default for AliasSyncOptions { } } -impl AliasSyncOptions { +impl AccountSyncOptions { pub(crate) fn all_outputs(&self) -> bool { self.basic_outputs && self.nft_outputs && self.account_outputs && self.foundry_outputs } diff --git a/sdk/src/wallet/account/operations/syncing/outputs.rs b/sdk/src/wallet/operations/syncing/outputs.rs similarity index 83% rename from sdk/src/wallet/account/operations/syncing/outputs.rs rename to sdk/src/wallet/operations/syncing/outputs.rs index a0de9f0462..ce4e3c3928 100644 --- a/sdk/src/wallet/account/operations/syncing/outputs.rs +++ b/sdk/src/wallet/operations/syncing/outputs.rs @@ -16,14 +16,16 @@ use crate::{ }, }, wallet::{ - account::{build_transaction_from_payload_and_inputs, types::OutputData, Account, AddressWithUnspentOutputs}, - task, + build_transaction_from_payload_and_inputs, task, + types::{AddressWithUnspentOutputs, OutputData}, + Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { /// Convert OutputWithMetadataResponse to OutputData with the network_id added pub(crate) async fn output_response_to_output_data( @@ -34,23 +36,27 @@ where log::debug!("[SYNC] convert output_responses"); // store outputs with network_id let network_id = self.client().get_network_id().await?; - let account_details = self.details().await; + let wallet_data = self.data().await; Ok(outputs_with_meta .into_iter() .map(|output_with_meta| { // check if we know the transaction that created this output and if we created it (if we store incoming // transactions separated, then this check wouldn't be required) - let remainder = account_details + let remainder = wallet_data .transactions .get(output_with_meta.metadata().transaction_id()) .map_or(false, |tx| !tx.incoming); - // BIP 44 (HD wallets) and 4218 is the registered index for IOTA https://github.com/satoshilabs/slips/blob/master/slip-0044.md - let chain = Bip44::new(account_details.coin_type) - .with_account(account_details.index) - .with_change(associated_address.internal as _) - .with_address_index(associated_address.key_index); + let chain = wallet_data.bip_path.map_or(None, |bip_path| { + // BIP 44 (HD wallets) and 4218 is the registered index for IOTA https://github.com/satoshilabs/slips/blob/master/slip-0044.md + Some( + Bip44::new(bip_path.coin_type) + .with_account(bip_path.account) + .with_change(associated_address.internal as _) + .with_address_index(associated_address.key_index), + ) + }); OutputData { output_id: output_with_meta.metadata().output_id().to_owned(), @@ -60,7 +66,7 @@ where address: associated_address.address.inner.clone(), network_id, remainder, - chain: Some(chain), + chain, } }) .collect()) @@ -77,10 +83,10 @@ where let mut outputs = Vec::new(); let mut unknown_outputs = Vec::new(); let mut unspent_outputs = Vec::new(); - let mut account_details = self.details_mut().await; + let mut wallet_data = self.data_mut().await; for output_id in output_ids { - match account_details.outputs.get_mut(&output_id) { + match wallet_data.outputs.get_mut(&output_id) { // set unspent Some(output_data) => { output_data.is_spent = false; @@ -96,10 +102,10 @@ where // known output is unspent, so insert it to the unspent outputs again, because if it was an // account/nft/foundry output it could have been removed when syncing without them for (output_id, output_data) in unspent_outputs { - account_details.unspent_outputs.insert(output_id, output_data); + wallet_data.unspent_outputs.insert(output_id, output_data); } - drop(account_details); + drop(wallet_data); if !unknown_outputs.is_empty() { outputs.extend(self.client().get_outputs_with_metadata(&unknown_outputs).await?); @@ -122,15 +128,13 @@ where ) -> crate::wallet::Result<()> { log::debug!("[SYNC] request_incoming_transaction_data"); - let account_details = self.details().await; + let wallet_data = self.data().await; transaction_ids.retain(|transaction_id| { - !(account_details.transactions.contains_key(transaction_id) - || account_details.incoming_transactions.contains_key(transaction_id) - || account_details - .inaccessible_incoming_transactions - .contains(transaction_id)) + !(wallet_data.transactions.contains_key(transaction_id) + || wallet_data.incoming_transactions.contains_key(transaction_id) + || wallet_data.inaccessible_incoming_transactions.contains(transaction_id)) }); - drop(account_details); + drop(wallet_data); // Limit parallel requests to 100, to avoid timeouts let results = @@ -184,19 +188,15 @@ where .await?; // Update account with new transactions - let mut account_details = self.details_mut().await; + let mut wallet_data = self.data_mut().await; for (transaction_id, txn) in results.into_iter().flatten() { if let Some(transaction) = txn { - account_details - .incoming_transactions - .insert(transaction_id, transaction); + wallet_data.incoming_transactions.insert(transaction_id, transaction); } else { log::debug!("[SYNC] adding {transaction_id} to inaccessible_incoming_transactions"); // Save transactions that weren't found by the node to avoid requesting them endlessly. // Will be cleared when new client options are provided. - account_details - .inaccessible_incoming_transactions - .insert(transaction_id); + wallet_data.inaccessible_incoming_transactions.insert(transaction_id); } } diff --git a/sdk/src/wallet/account/operations/syncing/transactions.rs b/sdk/src/wallet/operations/syncing/transactions.rs similarity index 92% rename from sdk/src/wallet/account/operations/syncing/transactions.rs rename to sdk/src/wallet/operations/syncing/transactions.rs index ce855c8ed7..15febb3bfb 100644 --- a/sdk/src/wallet/account/operations/syncing/transactions.rs +++ b/sdk/src/wallet/operations/syncing/transactions.rs @@ -7,9 +7,10 @@ use crate::{ api::core::TransactionState, block::{input::Input, output::OutputId, BlockId}, }, - wallet::account::{ + wallet::{ + core::WalletData, types::{InclusionState, TransactionWithMetadata}, - Account, AccountDetails, + Wallet, }, }; @@ -18,7 +19,7 @@ use crate::{ // also revalidate that the locked outputs needs to be there, maybe there was a conflict or the transaction got // confirmed, then they should get removed -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -29,13 +30,13 @@ where /// be synced again pub(crate) async fn sync_pending_transactions(&self) -> crate::wallet::Result { log::debug!("[SYNC] sync pending transactions"); - let account_details = self.details().await; + let wallet_data = self.data().await; // only set to true if a transaction got confirmed for which we don't have an output // (transaction_output.is_none()) let mut confirmed_unknown_output = false; - if account_details.pending_transactions.is_empty() { + if wallet_data.pending_transactions.is_empty() { return Ok(confirmed_unknown_output); } @@ -48,9 +49,9 @@ where let mut output_ids_to_unlock = Vec::new(); let mut transactions_to_reissue = Vec::new(); - for transaction_id in &account_details.pending_transactions { + for transaction_id in &wallet_data.pending_transactions { log::debug!("[SYNC] sync pending transaction {transaction_id}"); - let transaction = account_details + let transaction = wallet_data .transactions .get(transaction_id) // panic during development to easier detect if something is wrong, should be handled different later @@ -64,14 +65,14 @@ where // check if we have an output (remainder, if not sending to an own address) that got created by this // transaction, if that's the case, then the transaction got confirmed - let transaction_output = account_details + let transaction_output = wallet_data .outputs .keys() .find(|o| o.transaction_id() == transaction_id); if let Some(transaction_output) = transaction_output { // Save to unwrap, we just got the output - let confirmed_output_data = account_details.outputs.get(transaction_output).expect("output exists"); + let confirmed_output_data = wallet_data.outputs.get(transaction_output).expect("output exists"); log::debug!( "[SYNC] confirmed transaction {transaction_id} in block {}", confirmed_output_data.metadata.block_id() @@ -90,7 +91,7 @@ where let mut input_got_spent = false; for input in transaction.payload.transaction().inputs() { let Input::Utxo(input) = input; - if let Some(input) = account_details.outputs.get(input.output_id()) { + if let Some(input) = wallet_data.outputs.get(input.output_id()) { if input.is_spent { input_got_spent = true; } @@ -152,7 +153,7 @@ where // no need to reissue if one input got spent if input_got_spent { process_transaction_with_unknown_state( - &account_details, + &wallet_data, transaction, &mut updated_transactions, &mut output_ids_to_unlock, @@ -171,7 +172,7 @@ where // no need to reissue if one input got spent if input_got_spent { process_transaction_with_unknown_state( - &account_details, + &wallet_data, transaction, &mut updated_transactions, &mut output_ids_to_unlock, @@ -197,7 +198,7 @@ where } } } - drop(account_details); + drop(wallet_data); for mut transaction in transactions_to_reissue { log::debug!("[SYNC] reissue transaction"); @@ -207,7 +208,7 @@ where } // updates account with balances, output ids, outputs - self.update_account_with_transactions(updated_transactions, spent_output_ids, output_ids_to_unlock) + self.update_with_transactions(updated_transactions, spent_output_ids, output_ids_to_unlock) .await?; Ok(confirmed_unknown_output) @@ -235,7 +236,7 @@ fn updated_transaction_and_outputs( // When a transaction got pruned, the inputs and outputs are also not available, then this could mean that it was // confirmed and the created outputs got also already spent and pruned or the inputs got spent in another transaction fn process_transaction_with_unknown_state( - account: &AccountDetails, + wallet_data: &WalletData, mut transaction: TransactionWithMetadata, updated_transactions: &mut Vec, output_ids_to_unlock: &mut Vec, @@ -243,7 +244,7 @@ fn process_transaction_with_unknown_state( let mut all_inputs_spent = true; for input in transaction.payload.transaction().inputs() { let Input::Utxo(input) = input; - if let Some(output_data) = account.outputs.get(input.output_id()) { + if let Some(output_data) = wallet_data.outputs.get(input.output_id()) { if !output_data.metadata.is_spent() { // unspent output needs to be made available again output_ids_to_unlock.push(*input.output_id()); diff --git a/sdk/src/wallet/account/operations/transaction/build_transaction.rs b/sdk/src/wallet/operations/transaction/build_transaction.rs similarity index 95% rename from sdk/src/wallet/account/operations/transaction/build_transaction.rs rename to sdk/src/wallet/operations/transaction/build_transaction.rs index 4fdf996c9a..a390abebc7 100644 --- a/sdk/src/wallet/account/operations/transaction/build_transaction.rs +++ b/sdk/src/wallet/operations/transaction/build_transaction.rs @@ -12,10 +12,10 @@ use crate::{ input::{Input, UtxoInput}, payload::signed_transaction::Transaction, }, - wallet::account::{operations::transaction::TransactionOptions, Account}, + wallet::{operations::transaction::TransactionOptions, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, { diff --git a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/melt_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs similarity index 91% rename from sdk/src/wallet/account/operations/transaction/high_level/burning_melting/melt_native_token.rs rename to sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs index 7ade02fbfc..24f3963e2a 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/melt_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs @@ -10,12 +10,13 @@ use crate::{ TokenScheme, }, wallet::{ - account::{operations::transaction::TransactionWithMetadata, types::OutputData, Account, TransactionOptions}, - Error, + operations::transaction::TransactionOptions, + types::{OutputData, TransactionWithMetadata}, + Error, Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -51,7 +52,6 @@ where let foundry_id = FoundryId::from(token_id); let account_id = *foundry_id.account_address().account_id(); - let token_supply = self.client().get_token_supply().await?; let (existing_account_output_data, existing_foundry_output) = self .find_account_and_foundry_output_data(account_id, foundry_id) @@ -65,7 +65,7 @@ where // Create the new account output with updated amount. let account_output = AccountOutputBuilder::from(account_output) .with_account_id(account_id) - .finish_output(token_supply)?; + .finish_output()?; let TokenScheme::Simple(token_scheme) = existing_foundry_output.token_scheme(); let outputs = [ @@ -76,7 +76,7 @@ where token_scheme.melted_tokens() + melt_amount, token_scheme.maximum_supply(), )?)) - .finish_output(token_supply)?, + .finish_output()?, ]; // Input selection will detect that we're melting native tokens and add the required inputs if available self.prepare_transaction(outputs, options).await @@ -94,7 +94,7 @@ where let mut existing_account_output_data = None; let mut existing_foundry_output = None; - for (output_id, output_data) in self.details().await.unspent_outputs().iter() { + for (output_id, output_data) in self.data().await.unspent_outputs.iter() { match &output_data.output { Output::Account(output) => { if output.account_id_non_null(output_id) == account_id { diff --git a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs b/sdk/src/wallet/operations/transaction/high_level/burning_melting/mod.rs similarity index 94% rename from sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs rename to sdk/src/wallet/operations/transaction/high_level/burning_melting/mod.rs index 2d7d200ff9..809a4ee45e 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/burning_melting/mod.rs +++ b/sdk/src/wallet/operations/transaction/high_level/burning_melting/mod.rs @@ -3,15 +3,12 @@ use crate::{ client::api::{input_selection::Burn, PreparedTransactionData}, - wallet::{ - account::{types::TransactionWithMetadata, TransactionOptions}, - Account, - }, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet}, }; pub(crate) mod melt_native_token; -impl Account { +impl Wallet { /// A generic function that can be used to burn native tokens, nfts, foundries and accounts. /// /// Note that burning **native tokens** doesn't require the foundry output which minted them, but will not increase diff --git a/sdk/src/wallet/account/operations/transaction/high_level/create_account.rs b/sdk/src/wallet/operations/transaction/high_level/create_account.rs similarity index 86% rename from sdk/src/wallet/account/operations/transaction/high_level/create_account.rs rename to sdk/src/wallet/operations/transaction/high_level/create_account.rs index 1eaeca7ca6..bc8b1d5134 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/create_account.rs +++ b/sdk/src/wallet/operations/transaction/high_level/create_account.rs @@ -12,15 +12,19 @@ use crate::{ }, }, utils::serde::option_prefix_hex_bytes, - wallet::account::{types::TransactionWithMetadata, Account, OutputData, TransactionOptions}, + wallet::{ + operations::transaction::TransactionOptions, + types::{OutputData, TransactionWithMetadata}, + Wallet, + }, }; /// Params `create_account_output()` #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreateAccountParams { - /// Bech32 encoded address which will control the account. Default will use the first - /// ed25519 address of the wallet account + /// Bech32 encoded address which will control the account. Default will use the + /// ed25519 wallet address pub address: Option, /// Immutable account metadata #[serde(default, with = "option_prefix_hex_bytes")] @@ -30,7 +34,7 @@ pub struct CreateAccountParams { pub metadata: Option>, } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -70,27 +74,18 @@ where options: impl Into> + Send, ) -> crate::wallet::Result { log::debug!("[TRANSACTION] prepare_create_account_output"); - let rent_structure = self.client().get_rent_structure().await?; - let token_supply = self.client().get_token_supply().await?; + let storage_score_params = self.client().get_storage_score_parameters().await?; let address = match params.as_ref().and_then(|options| options.address.as_ref()) { Some(bech32_address) => { self.client().bech32_hrp_matches(bech32_address.hrp()).await?; bech32_address.inner().clone() } - None => { - self.public_addresses() - .await - .into_iter() - .next() - .expect("first address is generated during account creation") - .address - .inner - } + None => self.address().await.inner().clone(), }; let mut account_output_builder = - AccountOutputBuilder::new_with_minimum_storage_deposit(rent_structure, AccountId::null()) + AccountOutputBuilder::new_with_minimum_amount(storage_score_params, AccountId::null()) .with_foundry_counter(0) .add_unlock_condition(AddressUnlockCondition::new(address.clone())); if let Some(CreateAccountParams { @@ -108,7 +103,7 @@ where } } - let outputs = [account_output_builder.finish_output(token_supply)?]; + let outputs = [account_output_builder.finish_output()?]; self.prepare_transaction(outputs, options).await } @@ -116,9 +111,9 @@ where /// Gets an existing account output. pub(crate) async fn get_account_output(&self, account_id: Option) -> Option<(AccountId, OutputData)> { log::debug!("[get_account_output]"); - self.details() + self.data() .await - .unspent_outputs() + .unspent_outputs .values() .find_map(|output_data| match &output_data.output { Output::Account(account_output) => { diff --git a/sdk/src/wallet/account/operations/transaction/high_level/minting/create_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs similarity index 93% rename from sdk/src/wallet/account/operations/transaction/high_level/minting/create_native_token.rs rename to sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs index 7daaa703d7..479e291aaf 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/minting/create_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs @@ -16,9 +16,10 @@ use crate::{ AccountOutputBuilder, FoundryId, FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, TokenScheme, }, }, - wallet::account::{ + wallet::{ + operations::transaction::TransactionOptions, types::{TransactionWithMetadata, TransactionWithMetadataDto}, - Account, TransactionOptions, + Wallet, }, }; @@ -85,7 +86,7 @@ impl From<&PreparedCreateNativeTokenTransaction> for PreparedCreateNativeTokenTr } } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -132,8 +133,7 @@ where options: impl Into> + Send, ) -> crate::wallet::Result { log::debug!("[TRANSACTION] create_native_token"); - let rent_structure = self.client().get_rent_structure().await?; - let token_supply = self.client().get_token_supply().await?; + let storage_score_params = self.client().get_storage_score_parameters().await?; let (account_id, account_output) = self .get_account_output(params.account_id) @@ -155,10 +155,10 @@ where let token_id = TokenId::from(foundry_id); let outputs = [ - new_account_output_builder.finish_output(token_supply)?, + new_account_output_builder.finish_output()?, { - let mut foundry_builder = FoundryOutputBuilder::new_with_minimum_storage_deposit( - rent_structure, + let mut foundry_builder = FoundryOutputBuilder::new_with_minimum_amount( + storage_score_params, account_output.foundry_counter() + 1, TokenScheme::Simple(SimpleTokenScheme::new( params.circulating_supply, @@ -174,7 +174,7 @@ where foundry_builder = foundry_builder.add_immutable_feature(MetadataFeature::new(foundry_metadata)?) } - foundry_builder.finish_output(token_supply)? + foundry_builder.finish_output()? }, // Native Tokens will be added automatically in the remainder output in try_select_inputs() ]; diff --git a/sdk/src/wallet/account/operations/transaction/high_level/minting/mint_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs similarity index 88% rename from sdk/src/wallet/account/operations/transaction/high_level/minting/mint_native_token.rs rename to sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs index 5f3e7f0f83..a3f7fa7076 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/minting/mint_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs @@ -8,13 +8,10 @@ use crate::{ types::block::output::{ AccountOutputBuilder, FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, TokenScheme, }, - wallet::{ - account::{types::TransactionWithMetadata, Account, TransactionOptions}, - Error, - }, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Error, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -60,9 +57,8 @@ where log::debug!("[TRANSACTION] mint_native_token"); let mint_amount = mint_amount.into(); - let account_details = self.details().await; - let token_supply = self.client().get_token_supply().await?; - let existing_foundry_output = account_details.unspent_outputs().values().find(|output_data| { + let wallet_data = self.data().await; + let existing_foundry_output = wallet_data.unspent_outputs.values().find(|output_data| { if let Output::Foundry(output) = &output_data.output { TokenId::new(*output.id()) == token_id } else { @@ -85,7 +81,7 @@ where } // Get the account output that controls the foundry output - let existing_account_output = account_details.unspent_outputs().values().find(|output_data| { + let existing_account_output = wallet_data.unspent_outputs.values().find(|output_data| { if let Output::Account(output) = &output_data.output { output.account_id_non_null(&output_data.output_id) == **foundry_output.account_address() } else { @@ -99,7 +95,7 @@ where return Err(Error::MintingFailed("account output is not available".to_string())); }; - drop(account_details); + drop(wallet_data); let account_output = if let Output::Account(account_output) = existing_account_output.output { account_output @@ -129,8 +125,8 @@ where FoundryOutputBuilder::from(&foundry_output).with_token_scheme(updated_token_scheme); let outputs = [ - new_account_output_builder.finish_output(token_supply)?, - new_foundry_output_builder.finish_output(token_supply)?, + new_account_output_builder.finish_output()?, + new_foundry_output_builder.finish_output()?, // Native Tokens will be added automatically in the remainder output in try_select_inputs() ]; diff --git a/sdk/src/wallet/account/operations/transaction/high_level/minting/mint_nfts.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs similarity index 89% rename from sdk/src/wallet/account/operations/transaction/high_level/minting/mint_nfts.rs rename to sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs index 63c02d1614..0ed8a2cb89 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/minting/mint_nfts.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs @@ -13,11 +13,11 @@ use crate::{ unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, }, - ConvertTo, }, + utils::ConvertTo, wallet::{ - account::{operations::transaction::TransactionWithMetadata, Account, TransactionOptions}, - Error as WalletError, + operations::transaction::{TransactionOptions, TransactionWithMetadata}, + Wallet, }, }; @@ -26,7 +26,7 @@ use crate::{ #[serde(rename_all = "camelCase")] pub struct MintNftParams { /// Bech32 encoded address to which the NFT will be minted. Default will use the - /// first address of the account. + /// address of the wallet. #[getset(get = "pub")] address: Option, /// NFT sender feature. @@ -109,7 +109,7 @@ impl MintNftParams { } } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -161,9 +161,8 @@ where I::IntoIter: Send, { log::debug!("[TRANSACTION] prepare_mint_nfts"); - let rent_structure = self.client().get_rent_structure().await?; - let token_supply = self.client().get_token_supply().await?; - let account_addresses = self.addresses().await; + let storage_score_params = self.client().get_storage_score_parameters().await?; + let wallet_address = self.address().await.into_inner(); let mut outputs = Vec::new(); for MintNftParams { @@ -178,18 +177,13 @@ where let address = match address { Some(address) => { self.client().bech32_hrp_matches(address.hrp()).await?; - address + address.inner().clone() } - // todo other error message - None => account_addresses - .first() - .ok_or(WalletError::FailedToGetRemainder)? - .address - .clone(), + None => wallet_address.clone(), }; // NftId needs to be set to 0 for the creation - let mut nft_builder = NftOutputBuilder::new_with_minimum_storage_deposit(rent_structure, NftId::null()) + let mut nft_builder = NftOutputBuilder::new_with_minimum_amount(storage_score_params, NftId::null()) // Address which will own the nft .add_unlock_condition(AddressUnlockCondition::new(address)); @@ -213,7 +207,7 @@ where nft_builder = nft_builder.add_immutable_feature(MetadataFeature::new(immutable_metadata)?); } - outputs.push(nft_builder.finish_output(token_supply)?); + outputs.push(nft_builder.finish_output()?); } self.prepare_transaction(outputs, options).await diff --git a/sdk/src/wallet/account/operations/transaction/high_level/minting/mod.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mod.rs similarity index 100% rename from sdk/src/wallet/account/operations/transaction/high_level/minting/mod.rs rename to sdk/src/wallet/operations/transaction/high_level/minting/mod.rs diff --git a/sdk/src/wallet/account/operations/transaction/high_level/mod.rs b/sdk/src/wallet/operations/transaction/high_level/mod.rs similarity index 100% rename from sdk/src/wallet/account/operations/transaction/high_level/mod.rs rename to sdk/src/wallet/operations/transaction/high_level/mod.rs diff --git a/sdk/src/wallet/account/operations/transaction/high_level/send.rs b/sdk/src/wallet/operations/transaction/high_level/send.rs similarity index 69% rename from sdk/src/wallet/account/operations/transaction/high_level/send.rs rename to sdk/src/wallet/operations/transaction/high_level/send.rs index 6732705fcc..accd247f79 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/send.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send.rs @@ -7,23 +7,18 @@ use serde::{Deserialize, Serialize}; use crate::{ client::{api::PreparedTransactionData, secret::SecretManage}, types::block::{ - address::Bech32Address, + address::{Bech32Address, ToBech32Ext}, output::{ - unlock_condition::{ - AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, - }, - BasicOutputBuilder, MinimumStorageDepositBasicOutput, + unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, + BasicOutputBuilder, MinimumOutputAmount, }, slot::SlotIndex, - ConvertTo, }, - utils::serde::string, + utils::{serde::string, ConvertTo}, wallet::{ - account::{ - constants::DEFAULT_EXPIRATION_SLOTS, operations::transaction::TransactionWithMetadata, Account, - TransactionOptions, - }, - Error, + constants::DEFAULT_EXPIRATION_SLOTS, + operations::transaction::{TransactionOptions, TransactionWithMetadata}, + Error, Wallet, }, }; @@ -39,7 +34,7 @@ pub struct SendParams { address: Bech32Address, /// Bech32 encoded return address, to which the storage deposit will be returned if one is necessary /// given the provided amount. If a storage deposit is needed and a return address is not provided, it will - /// default to the first address of the account. + /// default to the address of the wallet. #[getset(get = "pub")] return_address: Option, /// Expiration in slot indices, after which the output will be available for the sender again, if not spent by the @@ -78,7 +73,7 @@ impl SendParams { } } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -141,14 +136,11 @@ where { log::debug!("[TRANSACTION] prepare_send"); let options = options.into(); - let rent_structure = self.client().get_rent_structure().await?; - let token_supply = self.client().get_token_supply().await?; + let storage_score_params = self.client().get_storage_score_parameters().await?; + + let wallet_address = self.address().await; - let account_addresses = self.addresses().await; - let default_return_address = account_addresses - .into_iter() - .next() - .ok_or(Error::FailedToGetRemainder)?; + let default_return_address = wallet_address.to_bech32(self.client().get_bech32_hrp().await?); let slot_index = self.client().get_slot_index().await?; @@ -172,19 +164,15 @@ where Ok::<_, Error>(return_address) }) .transpose()? - .unwrap_or_else(|| default_return_address.address.clone()); + .unwrap_or_else(|| default_return_address.clone()); // Get the minimum required amount for an output assuming it does not need a storage deposit. - let output = BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) + let output = BasicOutputBuilder::new_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(token_supply)?; - - if amount >= output.amount() { - outputs.push( - BasicOutputBuilder::from(output.as_basic()) - .with_amount(amount) - .finish_output(token_supply)?, - ) + .finish()?; + + if amount >= output.minimum_amount(storage_score_params) { + outputs.push(output.into()) } else { let expiration_slot_index = expiration .map_or(slot_index + DEFAULT_EXPIRATION_SLOTS, |expiration_slot_index| { @@ -192,35 +180,22 @@ where }); // Since it does need a storage deposit, calculate how much that should be - let storage_deposit_amount = MinimumStorageDepositBasicOutput::new(rent_structure, token_supply) - .with_storage_deposit_return()? - .with_expiration()? - .finish()?; + let output = BasicOutputBuilder::from(&output) + .add_unlock_condition(ExpirationUnlockCondition::new( + return_address.clone(), + expiration_slot_index, + )?) + .with_sufficient_storage_deposit(return_address, storage_score_params)? + .finish_output()?; if !options.as_ref().map(|o| o.allow_micro_amount).unwrap_or_default() { return Err(Error::InsufficientFunds { available: amount, - required: amount + storage_deposit_amount, + required: output.amount(), }); } - outputs.push( - // Add address_and_amount.amount+storage_deposit_amount, so receiver can get - // address_and_amount.amount - BasicOutputBuilder::from(output.as_basic()) - .with_amount(amount + storage_deposit_amount) - .add_unlock_condition( - // We send the storage_deposit_amount back to the sender, so only the additional amount is - // sent - StorageDepositReturnUnlockCondition::new( - return_address.clone(), - storage_deposit_amount, - token_supply, - )?, - ) - .add_unlock_condition(ExpirationUnlockCondition::new(return_address, expiration_slot_index)?) - .finish_output(token_supply)?, - ) + outputs.push(output) } } diff --git a/sdk/src/wallet/account/operations/transaction/high_level/send_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/send_native_token.rs similarity index 80% rename from sdk/src/wallet/account/operations/transaction/high_level/send_native_token.rs rename to sdk/src/wallet/operations/transaction/high_level/send_native_token.rs index b05ae7d615..35f065f2cb 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/send_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_native_token.rs @@ -8,22 +8,21 @@ use serde::{Deserialize, Serialize}; use crate::{ client::{api::PreparedTransactionData, secret::SecretManage}, types::block::{ - address::Bech32Address, + address::{Bech32Address, ToBech32Ext}, output::{ unlock_condition::{ AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, }, - BasicOutputBuilder, MinimumStorageDepositBasicOutput, NativeToken, TokenId, + BasicOutputBuilder, NativeToken, TokenId, + }, slot::SlotIndex, - ConvertTo, }, + utils::ConvertTo, wallet::{ - account::{ - constants::DEFAULT_EXPIRATION_SLOTS, operations::transaction::TransactionWithMetadata, Account, - TransactionOptions, - }, - Error, Result, + constants::DEFAULT_EXPIRATION_SLOTS, + operations::transaction::{TransactionOptions, TransactionWithMetadata}, + Error, Result, Wallet, }, }; @@ -38,7 +37,7 @@ pub struct SendNativeTokenParams { #[getset(get = "pub")] native_token: (TokenId, U256), /// Bech32 encoded address return address, to which the storage deposit will be returned. Default will use the - /// first address of the account + /// address of the wallet. #[getset(get = "pub")] return_address: Option, /// Expiration in slot indices, after which the output will be available for the sender again, if not spent by the @@ -77,7 +76,7 @@ impl SendNativeTokenParams { } } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -127,15 +126,17 @@ where where I::IntoIter: Send, { +<<<<<<<< HEAD:sdk/src/wallet/operations/transaction/high_level/send_native_token.rs log::debug!("[TRANSACTION] prepare_send_native_token"); let rent_structure = self.client().get_rent_structure().await?; let token_supply = self.client().get_token_supply().await?; +======== + log::debug!("[TRANSACTION] prepare_send_native_tokens"); + let storage_score_params = self.client().get_storage_score_parameters().await?; +>>>>>>>> 2.0:sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs - let account_addresses = self.addresses().await; - let default_return_address = account_addresses - .into_iter() - .next() - .ok_or(Error::FailedToGetRemainder)?; + let wallet_address = self.address().await; + let default_return_address = wallet_address.to_bech32(self.client().get_bech32_hrp().await?); let slot_index = self.client().get_slot_index().await?; @@ -159,10 +160,11 @@ where Ok::<_, Error>(addr) }) .transpose()? - .unwrap_or_else(|| default_return_address.address.clone()); + .unwrap_or_else(|| default_return_address.clone()); let native_token = NativeToken::new(native_token.0, native_token.1)?; +<<<<<<<< HEAD:sdk/src/wallet/operations/transaction/high_level/send_native_token.rs // get minimum required amount for such an output, so we don't lock more than required // We have to check it for every output individually, because different address types and amount of // different native token require a different storage deposit @@ -172,12 +174,15 @@ where .with_expiration()? .finish()?; +======== +>>>>>>>> 2.0:sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs let expiration_slot_index = expiration .map_or(slot_index + DEFAULT_EXPIRATION_SLOTS, |expiration_slot_index| { slot_index + expiration_slot_index }); outputs.push( +<<<<<<<< HEAD:sdk/src/wallet/operations/transaction/high_level/send_native_token.rs BasicOutputBuilder::new_with_amount(storage_deposit_amount) .with_native_token(native_token) .add_unlock_condition(AddressUnlockCondition::new(address)) @@ -192,6 +197,17 @@ where ) .add_unlock_condition(ExpirationUnlockCondition::new(return_address, expiration_slot_index)?) .finish_output(token_supply)?, +======== + BasicOutputBuilder::new_with_amount(0) + .with_native_tokens(native_tokens) + .add_unlock_condition(AddressUnlockCondition::new(address)) + .add_unlock_condition(ExpirationUnlockCondition::new( + return_address.clone(), + expiration_slot_index, + )?) + .with_sufficient_storage_deposit(return_address, storage_score_params)? + .finish_output()?, +>>>>>>>> 2.0:sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs ) } diff --git a/sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs b/sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs new file mode 100644 index 0000000000..4c2f47f3c1 --- /dev/null +++ b/sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs @@ -0,0 +1,220 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use getset::Getters; +use primitive_types::U256; +use serde::{Deserialize, Serialize}; + +use crate::{ + client::{api::PreparedTransactionData, secret::SecretManage}, + types::block::{ + address::{Bech32Address, ToBech32Ext}, + output::{ +<<<<<<<< HEAD:sdk/src/wallet/operations/transaction/high_level/send_native_token.rs + unlock_condition::{ + AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, + }, + BasicOutputBuilder, MinimumStorageDepositBasicOutput, NativeToken, TokenId, +======== + unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, + BasicOutputBuilder, NativeToken, NativeTokens, TokenId, +>>>>>>>> 2.0:sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs + }, + slot::SlotIndex, + }, + utils::ConvertTo, + wallet::{ + constants::DEFAULT_EXPIRATION_SLOTS, + operations::transaction::{TransactionOptions, TransactionWithMetadata}, + Error, Result, Wallet, + }, +}; + +/// Params for `send_native_token()` +#[derive(Debug, Clone, Serialize, Deserialize, Getters)] +#[serde(rename_all = "camelCase")] +pub struct SendNativeTokenParams { + /// Bech32 encoded address + #[getset(get = "pub")] + address: Bech32Address, + /// Native token + #[getset(get = "pub")] + native_token: (TokenId, U256), + /// Bech32 encoded address return address, to which the storage deposit will be returned. Default will use the + /// address of the wallet. + #[getset(get = "pub")] + return_address: Option, + /// Expiration in slot indices, after which the output will be available for the sender again, if not spent by the + /// receiver before. Default is [`DEFAULT_EXPIRATION_SLOTS`] slots. + #[getset(get = "pub")] + expiration: Option, +} + +impl SendNativeTokenParams { + /// Creates a new instance of [`SendNativeTokenParams`] + pub fn new(address: impl ConvertTo, native_token: (TokenId, U256)) -> Result { + Ok(Self { + address: address.convert()?, + native_token, + return_address: None, + expiration: None, + }) + } + + /// Set the return address and try convert to [`Bech32Address`] + pub fn try_with_return_address(mut self, return_address: impl ConvertTo) -> Result { + self.return_address = Some(return_address.convert()?); + Ok(self) + } + + /// Set the return address + pub fn with_return_address(mut self, return_address: impl Into>) -> Self { + self.return_address = return_address.into(); + self + } + + /// Set the expiration in seconds + pub fn with_expiration(mut self, expiration: Option) -> Self { + self.expiration = expiration; + self + } +} + +impl Wallet +where + crate::wallet::Error: From, + crate::client::Error: From, +{ + /// Sends a native token in basic outputs with a [`StorageDepositReturnUnlockCondition`] and an + /// [`ExpirationUnlockCondition`], so that the storage deposit is returned to the sender and the sender gets access + /// to the output again after a predefined time (default 1 day). + /// Calls [Account::send_outputs()](crate::wallet::Account::send_outputs) internally. The options may define the + /// remainder value strategy or custom inputs. Note that the address needs to be bech32-encoded. + /// ```ignore + /// let params = [SendNativeTokenParams { + /// address: "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu".to_string(), + /// native_token: ( + /// TokenId::from_str("08e68f7616cd4948efebc6a77c4f93aed770ac53860100000000000000000000000000000000")?, + /// U256::from(50), + /// ), + /// ..Default::default() + /// }]; + /// + /// let tx = account.send_native_token(params, None).await?; + /// println!("Transaction created: {}", tx.transaction_id); + /// if let Some(block_id) = tx.block_id { + /// println!("Block sent: {}", block_id); + /// } + /// ``` + pub async fn send_native_token + Send>( + &self, + params: I, + options: impl Into> + Send, + ) -> crate::wallet::Result + where + I::IntoIter: Send, + { + let options = options.into(); + let prepared_transaction = self.prepare_send_native_token(params, options.clone()).await?; + + self.sign_and_submit_transaction(prepared_transaction, options).await + } + + /// Prepares the transaction for + /// [Account::send_native_token()](crate::wallet::Account::send_native_token). + pub async fn prepare_send_native_token + Send>( + &self, + params: I, + options: impl Into> + Send, + ) -> crate::wallet::Result + where + I::IntoIter: Send, + { +<<<<<<<< HEAD:sdk/src/wallet/operations/transaction/high_level/send_native_token.rs + log::debug!("[TRANSACTION] prepare_send_native_token"); + let rent_structure = self.client().get_rent_structure().await?; + let token_supply = self.client().get_token_supply().await?; +======== + log::debug!("[TRANSACTION] prepare_send_native_tokens"); + let storage_score_params = self.client().get_storage_score_parameters().await?; +>>>>>>>> 2.0:sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs + + let wallet_address = self.address().await; + let default_return_address = wallet_address.to_bech32(self.client().get_bech32_hrp().await?); + + let slot_index = self.client().get_slot_index().await?; + + let mut outputs = Vec::new(); + for SendNativeTokenParams { + address, + native_token, + return_address, + expiration, + } in params + { + self.client().bech32_hrp_matches(address.hrp()).await?; + let return_address = return_address + .map(|addr| { + if address.hrp() != addr.hrp() { + Err(crate::client::Error::Bech32HrpMismatch { + provided: addr.hrp().to_string(), + expected: address.hrp().to_string(), + })?; + } + Ok::<_, Error>(addr) + }) + .transpose()? + .unwrap_or_else(|| default_return_address.clone()); + + let native_token = NativeToken::new(native_token.0, native_token.1)?; + +<<<<<<<< HEAD:sdk/src/wallet/operations/transaction/high_level/send_native_token.rs + // get minimum required amount for such an output, so we don't lock more than required + // We have to check it for every output individually, because different address types and amount of + // different native token require a different storage deposit + let storage_deposit_amount = MinimumStorageDepositBasicOutput::new(rent_structure, token_supply) + .with_native_token(native_token.clone()) + .with_storage_deposit_return()? + .with_expiration()? + .finish()?; + +======== +>>>>>>>> 2.0:sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs + let expiration_slot_index = expiration + .map_or(slot_index + DEFAULT_EXPIRATION_SLOTS, |expiration_slot_index| { + slot_index + expiration_slot_index + }); + + outputs.push( +<<<<<<<< HEAD:sdk/src/wallet/operations/transaction/high_level/send_native_token.rs + BasicOutputBuilder::new_with_amount(storage_deposit_amount) + .with_native_token(native_token) + .add_unlock_condition(AddressUnlockCondition::new(address)) + .add_unlock_condition( + // We send the full storage_deposit_amount back to the sender, so only the native token is + // sent + StorageDepositReturnUnlockCondition::new( + return_address.clone(), + storage_deposit_amount, + token_supply, + )?, + ) + .add_unlock_condition(ExpirationUnlockCondition::new(return_address, expiration_slot_index)?) + .finish_output(token_supply)?, +======== + BasicOutputBuilder::new_with_amount(0) + .with_native_tokens(native_tokens) + .add_unlock_condition(AddressUnlockCondition::new(address)) + .add_unlock_condition(ExpirationUnlockCondition::new( + return_address.clone(), + expiration_slot_index, + )?) + .with_sufficient_storage_deposit(return_address, storage_score_params)? + .finish_output()?, +>>>>>>>> 2.0:sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs + ) + } + + self.prepare_transaction(outputs, options).await + } +} diff --git a/sdk/src/wallet/account/operations/transaction/high_level/send_nft.rs b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs similarity index 92% rename from sdk/src/wallet/account/operations/transaction/high_level/send_nft.rs rename to sdk/src/wallet/operations/transaction/high_level/send_nft.rs index f82386bfe6..33212d22a5 100644 --- a/sdk/src/wallet/account/operations/transaction/high_level/send_nft.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs @@ -9,9 +9,12 @@ use crate::{ types::block::{ address::Bech32Address, output::{unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, Output}, - ConvertTo, }, - wallet::account::{operations::transaction::TransactionWithMetadata, Account, TransactionOptions}, + utils::ConvertTo, + wallet::{ + operations::transaction::{TransactionOptions, TransactionWithMetadata}, + Wallet, + }, }; /// Params for `send_nft()` @@ -39,7 +42,7 @@ impl SendNftParams { } } -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -92,8 +95,7 @@ where { log::debug!("[TRANSACTION] prepare_send_nft"); - let unspent_outputs = self.unspent_outputs(None).await?; - let token_supply = self.client().get_token_supply().await?; + let unspent_outputs = self.unspent_outputs(None).await; let mut outputs = Vec::new(); @@ -113,7 +115,7 @@ where let nft_builder = NftOutputBuilder::from(nft_output) .with_nft_id(nft_id) .with_unlock_conditions([AddressUnlockCondition::new(address)]); - outputs.push(nft_builder.finish_output(token_supply)?); + outputs.push(nft_builder.finish_output()?); } } else { return Err(crate::wallet::Error::NftNotFoundInUnspentOutputs); diff --git a/sdk/src/wallet/account/operations/transaction/input_selection.rs b/sdk/src/wallet/operations/transaction/input_selection.rs similarity index 85% rename from sdk/src/wallet/account/operations/transaction/input_selection.rs rename to sdk/src/wallet/operations/transaction/input_selection.rs index fb7dd9e0f1..71e437b38c 100644 --- a/sdk/src/wallet/account/operations/transaction/input_selection.rs +++ b/sdk/src/wallet/operations/transaction/input_selection.rs @@ -15,16 +15,18 @@ use crate::{ output::{Output, OutputId}, slot::SlotIndex, }, - wallet::account::{ - operations::helpers::time::can_output_be_unlocked_forever_from_now_on, Account, AccountDetails, OutputData, + wallet::{ + core::WalletData, operations::helpers::time::can_output_be_unlocked_forever_from_now_on, types::OutputData, + Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, + crate::client::Error: From, { - /// Selects inputs for a transaction and locks them in the account, so they don't get used again + /// Selects inputs for a transaction and locks them in the wallet, so they don't get used again pub(crate) async fn select_inputs( &self, outputs: Vec, @@ -38,26 +40,18 @@ where #[cfg(feature = "participation")] let voting_output = self.get_voting_output().await?; // lock so the same inputs can't be selected in multiple transactions - let mut account_details = self.details_mut().await; + let mut wallet_data = self.data_mut().await; let protocol_parameters = self.client().get_protocol_parameters().await?; #[cfg(feature = "events")] - self.emit( - account_details.index, - WalletEvent::TransactionProgress(TransactionProgressEvent::SelectingInputs), - ) + self.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::SelectingInputs, + )) .await; let slot_index = self.client().get_slot_index().await?; #[allow(unused_mut)] - let mut forbidden_inputs = account_details.locked_outputs.clone(); - - let addresses = account_details - .public_addresses() - .iter() - .chain(account_details.internal_addresses().iter()) - .map(|address| address.address.as_ref().clone()) - .collect::>(); + let mut forbidden_inputs = wallet_data.locked_outputs.clone(); // Prevent consuming the voting output if not actually wanted #[cfg(feature = "participation")] @@ -73,8 +67,8 @@ where // Filter inputs to not include inputs that require additional outputs for storage deposit return or could be // still locked. let available_outputs_signing_data = filter_inputs( - &account_details, - account_details.unspent_outputs.values(), + &wallet_data, + wallet_data.unspent_outputs.values(), slot_index, custom_inputs.as_ref(), mandatory_inputs.as_ref(), @@ -85,7 +79,7 @@ where if let Some(custom_inputs) = custom_inputs { // Check that no input got already locked for input in custom_inputs.iter() { - if account_details.locked_outputs.contains(input) { + if wallet_data.locked_outputs.contains(input) { return Err(crate::wallet::Error::CustomInput(format!( "provided custom input {input} is already used in another transaction", ))); @@ -95,7 +89,7 @@ where let mut input_selection = InputSelection::new( available_outputs_signing_data, outputs, - addresses, + Some(wallet_data.address.clone().into_inner()), protocol_parameters.clone(), ) .with_required_inputs(custom_inputs) @@ -113,14 +107,14 @@ where // lock outputs so they don't get used by another transaction for output in &selected_transaction_data.inputs { - account_details.locked_outputs.insert(*output.output_id()); + wallet_data.locked_outputs.insert(*output.output_id()); } return Ok(selected_transaction_data); } else if let Some(mandatory_inputs) = mandatory_inputs { // Check that no input got already locked for input in mandatory_inputs.iter() { - if account_details.locked_outputs.contains(input) { + if wallet_data.locked_outputs.contains(input) { return Err(crate::wallet::Error::CustomInput(format!( "provided custom input {input} is already used in another transaction", ))); @@ -130,7 +124,7 @@ where let mut input_selection = InputSelection::new( available_outputs_signing_data, outputs, - addresses, + Some(wallet_data.address.clone().into_inner()), protocol_parameters.clone(), ) .with_required_inputs(mandatory_inputs) @@ -148,12 +142,12 @@ where // lock outputs so they don't get used by another transaction for output in &selected_transaction_data.inputs { - account_details.locked_outputs.insert(*output.output_id()); + wallet_data.locked_outputs.insert(*output.output_id()); } // lock outputs so they don't get used by another transaction for output in &selected_transaction_data.inputs { - account_details.locked_outputs.insert(*output.output_id()); + wallet_data.locked_outputs.insert(*output.output_id()); } return Ok(selected_transaction_data); @@ -162,7 +156,7 @@ where let mut input_selection = InputSelection::new( available_outputs_signing_data, outputs, - addresses, + Some(wallet_data.address.clone().into_inner()), protocol_parameters.clone(), ) .with_forbidden_inputs(forbidden_inputs); @@ -195,7 +189,7 @@ where // lock outputs so they don't get used by another transaction for output in &selected_transaction_data.inputs { log::debug!("[TRANSACTION] locking: {}", output.output_id()); - account_details.locked_outputs.insert(*output.output_id()); + wallet_data.locked_outputs.insert(*output.output_id()); } Ok(selected_transaction_data) @@ -222,7 +216,7 @@ where /// | [Address, StorageDepositReturn, expired Expiration] | yes | #[allow(clippy::too_many_arguments)] fn filter_inputs( - account: &AccountDetails, + wallet_data: &WalletData, available_outputs: Values<'_, OutputId, OutputData>, slot_index: SlotIndex, custom_inputs: Option<&HashSet>, @@ -241,7 +235,7 @@ fn filter_inputs( let output_can_be_unlocked_now_and_in_future = can_output_be_unlocked_forever_from_now_on( // We use the addresses with unspent outputs, because other addresses of the // account without unspent outputs can't be related to this output - &account.addresses_with_unspent_outputs, + &wallet_data.address.inner, &output_data.output, slot_index, ); @@ -252,7 +246,7 @@ fn filter_inputs( } } - if let Some(available_input) = output_data.input_signing_data(account, slot_index)? { + if let Some(available_input) = output_data.input_signing_data(wallet_data, slot_index)? { available_outputs_signing_data.push(available_input); } } diff --git a/sdk/src/wallet/account/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs similarity index 91% rename from sdk/src/wallet/account/operations/transaction/mod.rs rename to sdk/src/wallet/operations/transaction/mod.rs index 693526df20..78cfcdfbf7 100644 --- a/sdk/src/wallet/account/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -24,13 +24,13 @@ use crate::{ payload::signed_transaction::SignedTransactionPayload, }, }, - wallet::account::{ + wallet::{ types::{InclusionState, TransactionWithMetadata}, - Account, + Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -73,7 +73,7 @@ where // Check if the outputs have enough amount to cover the storage deposit for output in &outputs { - output.verify_storage_deposit(protocol_parameters.rent_structure(), protocol_parameters.token_supply())?; + output.verify_storage_deposit(protocol_parameters.storage_score_parameters())?; } self.finish_transaction(outputs, options).await @@ -95,7 +95,7 @@ where .await } - /// Signs a transaction, submit it to a node and store it in the account + /// Signs a transaction, submit it to a node and store it in the wallet pub async fn sign_and_submit_transaction( &self, prepared_transaction_data: PreparedTransactionData, @@ -116,7 +116,7 @@ where .await } - /// Validates the transaction, submit it to a node and store it in the account + /// Validates the transaction, submit it to a node and store it in the wallet pub async fn submit_and_store_transaction( &self, signed_transaction_data: SignedTransactionData, @@ -179,14 +179,15 @@ where inputs, }; - let mut account_details = self.details_mut().await; + let mut wallet_data = self.data_mut().await; - account_details.transactions.insert(transaction_id, transaction.clone()); - account_details.pending_transactions.insert(transaction_id); + wallet_data.transactions.insert(transaction_id, transaction.clone()); + wallet_data.pending_transactions.insert(transaction_id); #[cfg(feature = "storage")] { - log::debug!("[TRANSACTION] storing account {}", account_details.index()); - self.save(Some(&account_details)).await?; + // TODO: maybe better to use the wallt address as identifier now? + log::debug!("[TRANSACTION] storing wallet"); + self.save(Some(&wallet_data)).await?; } Ok(transaction) @@ -194,10 +195,10 @@ where // unlock outputs async fn unlock_inputs(&self, inputs: &[InputSigningData]) -> crate::wallet::Result<()> { - let mut account_details = self.details_mut().await; + let mut wallet_data = self.data_mut().await; for input_signing_data in inputs { let output_id = input_signing_data.output_id(); - account_details.locked_outputs.remove(output_id); + wallet_data.locked_outputs.remove(output_id); log::debug!( "[TRANSACTION] Unlocked output {} because of transaction error", output_id diff --git a/sdk/src/wallet/account/operations/transaction/options.rs b/sdk/src/wallet/operations/transaction/options.rs similarity index 95% rename from sdk/src/wallet/account/operations/transaction/options.rs rename to sdk/src/wallet/operations/transaction/options.rs index a41072122c..7be4a75b9e 100644 --- a/sdk/src/wallet/account/operations/transaction/options.rs +++ b/sdk/src/wallet/operations/transaction/options.rs @@ -35,8 +35,6 @@ pub struct TransactionOptions { pub enum RemainderValueStrategy { /// Keep the remainder value on the source address. ReuseAddress, - /// Move the remainder value to a change address. - ChangeAddress, /// Move the remainder value to any specified address. CustomAddress(Address), } diff --git a/sdk/src/wallet/account/operations/transaction/prepare_output.rs b/sdk/src/wallet/operations/transaction/prepare_output.rs similarity index 79% rename from sdk/src/wallet/account/operations/transaction/prepare_output.rs rename to sdk/src/wallet/operations/transaction/prepare_output.rs index bf834a247c..d34d8c1aa7 100644 --- a/sdk/src/wallet/account/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -6,26 +6,28 @@ use serde::{Deserialize, Serialize}; use crate::{ client::secret::SecretManage, types::block::{ - address::{Address, Bech32Address}, + address::{Address, Bech32Address, Ed25519Address}, output::{ feature::{IssuerFeature, MetadataFeature, NativeTokenFeature, SenderFeature, TagFeature}, unlock_condition::{ AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition, }, - BasicOutputBuilder, MinimumStorageDepositBasicOutput, NativeToken, NftId, NftOutputBuilder, Output, Rent, - RentStructure, UnlockCondition, + BasicOutput, BasicOutputBuilder, MinimumOutputAmount, NativeToken, NftId, NftOutputBuilder, Output, + StorageScoreParameters, UnlockCondition, }, slot::SlotIndex, Error, }, utils::serde::string, - wallet::account::{ - operations::transaction::RemainderValueStrategy, types::OutputData, Account, TransactionOptions, + wallet::{ + operations::transaction::{RemainderValueStrategy, TransactionOptions}, + types::OutputData, + Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -43,16 +45,15 @@ where ) -> crate::wallet::Result { log::debug!("[OUTPUT] prepare_output {params:?}"); let transaction_options = transaction_options.into(); - let token_supply = self.client().get_token_supply().await?; self.client().bech32_hrp_matches(params.recipient_address.hrp()).await?; - let rent_structure = self.client().get_rent_structure().await?; + let storage_score_params = self.client().get_storage_score_parameters().await?; let nft_id = params.assets.as_ref().and_then(|a| a.nft_id); let (mut first_output_builder, existing_nft_output_data) = self - .create_initial_output_builder(params.recipient_address, nft_id, rent_structure) + .create_initial_output_builder(params.recipient_address, nft_id, storage_score_params) .await?; if let Some(features) = params.features { @@ -101,8 +102,8 @@ where // Build output with minimum required storage deposit so we can use the amount in the next step let first_output = first_output_builder - .with_minimum_storage_deposit(rent_structure) - .finish_output(token_supply)?; + .with_minimum_amount(storage_score_params) + .finish_output()?; let mut second_output_builder = if nft_id.is_some() { OutputBuilder::Nft(NftOutputBuilder::from(first_output.as_nft())) @@ -110,10 +111,11 @@ where OutputBuilder::Basic(BasicOutputBuilder::from(first_output.as_basic())) }; - let min_storage_deposit_basic_output = - MinimumStorageDepositBasicOutput::new(rent_structure, token_supply).finish()?; + // TODO: Probably not good to use ed25519 always here, even if technically it's the same for now.. + let min_amount_basic_output = + BasicOutput::minimum_amount(&Address::from(Ed25519Address::null()), storage_score_params); - let min_required_storage_deposit = first_output.rent_cost(rent_structure); + let min_required_storage_deposit = first_output.minimum_amount(storage_score_params); if params.amount > min_required_storage_deposit { second_output_builder = second_output_builder.with_amount(params.amount); @@ -134,19 +136,18 @@ where second_output_builder = second_output_builder.add_unlock_condition(StorageDepositReturnUnlockCondition::new( remainder_address.clone(), - // Return minimum storage deposit - min_storage_deposit_basic_output, - token_supply, - )?); + // Return minimum amount + min_amount_basic_output, + )); // Update output amount, so recipient still gets the provided amount - let new_amount = params.amount + min_storage_deposit_basic_output; + let new_amount = params.amount + min_amount_basic_output; // new_amount could be not enough because we added the storage deposit return unlock condition, so we // need to check the min required storage deposit again let min_storage_deposit_new_amount = second_output_builder .clone() - .with_minimum_storage_deposit(rent_structure) - .finish_output(token_supply)? + .with_minimum_amount(storage_score_params) + .finish_output()? .amount(); if new_amount < min_storage_deposit_new_amount { @@ -156,10 +157,9 @@ where second_output_builder = second_output_builder.replace_unlock_condition(StorageDepositReturnUnlockCondition::new( remainder_address.clone(), - // Return minimum storage deposit - min_storage_deposit_basic_output + additional_required_amount, - token_supply, - )?); + // Return minimum amount + min_amount_basic_output + additional_required_amount, + )); } else { // new_amount is enough second_output_builder = second_output_builder.with_amount(new_amount); @@ -167,7 +167,7 @@ where } } - let third_output = second_output_builder.clone().finish_output(token_supply)?; + let third_output = second_output_builder.clone().finish_output()?; let mut final_amount = third_output.amount(); // Now we have to make sure that our output also works with our available balance, without leaving < // min_storage_deposit_basic_output for a remainder (if not 0) @@ -175,7 +175,7 @@ where // If we're sending an existing NFT, its minimum required storage deposit is not part of the available base_coin // balance, so we add it here if let Some(existing_nft_output_data) = existing_nft_output_data { - available_base_coin += existing_nft_output_data.output.rent_cost(rent_structure); + available_base_coin += existing_nft_output_data.output.minimum_amount(storage_score_params); } if final_amount > available_base_coin { @@ -190,7 +190,7 @@ where if final_amount < available_base_coin { let remaining_balance = available_base_coin - final_amount; - if remaining_balance < min_storage_deposit_basic_output { + if remaining_balance < min_amount_basic_output { // not enough for remainder if params .storage_deposit @@ -212,22 +212,21 @@ where second_output_builder = second_output_builder.replace_unlock_condition(StorageDepositReturnUnlockCondition::new( remainder_address, - // Return minimum storage deposit + // Return minimum amount new_sdr_amount, - token_supply, - )?); + )); } } else { // Would leave dust behind, so return what's required for a remainder return Err(crate::wallet::Error::InsufficientFunds { available: available_base_coin, - required: available_base_coin + min_storage_deposit_basic_output - remaining_balance, + required: available_base_coin + min_amount_basic_output - remaining_balance, }); } } } - Ok(second_output_builder.finish_output(token_supply)?) + Ok(second_output_builder.finish_output()?) } // Create the initial output builder for prepare_output() @@ -235,21 +234,18 @@ where &self, recipient_address: Bech32Address, nft_id: Option, - rent_structure: RentStructure, + params: StorageScoreParameters, ) -> crate::wallet::Result<(OutputBuilder, Option)> { let (mut first_output_builder, existing_nft_output_data) = if let Some(nft_id) = &nft_id { if nft_id.is_null() { // Mint a new NFT output ( - OutputBuilder::Nft(NftOutputBuilder::new_with_minimum_storage_deposit( - rent_structure, - *nft_id, - )), + OutputBuilder::Nft(NftOutputBuilder::new_with_minimum_amount(params, *nft_id)), None, ) } else { // Transition an existing NFT output - let unspent_nft_output = self.unspent_nft_output(nft_id).await?; + let unspent_nft_output = self.unspent_nft_output(nft_id).await; // Find nft output from the inputs let mut first_output_builder = if let Some(nft_output_data) = &unspent_nft_output { @@ -265,7 +261,7 @@ where } } else { ( - OutputBuilder::Basic(BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure)), + OutputBuilder::Basic(BasicOutputBuilder::new_with_minimum_amount(params)), None, ) }; @@ -283,35 +279,15 @@ where ) -> crate::wallet::Result
{ let transaction_options = transaction_options.into(); - let remainder_address = match &transaction_options { - Some(options) => { - match &options.remainder_value_strategy { - RemainderValueStrategy::ReuseAddress => { - // select_inputs will select an address from the inputs if it's none - None - } - RemainderValueStrategy::ChangeAddress => { - let remainder_address = self.generate_remainder_address().await?; - Some(remainder_address.address.inner) - } - RemainderValueStrategy::CustomAddress(address) => Some(address.clone()), - } + Ok(if let Some(options) = &transaction_options { + match &options.remainder_value_strategy { + // TODO is this correct? It was None before the accounts removal + RemainderValueStrategy::ReuseAddress => self.address().await.into_inner(), + RemainderValueStrategy::CustomAddress(address) => address.clone(), } - None => None, - }; - let remainder_address = match remainder_address { - Some(address) => address, - None => { - self.addresses() - .await - .into_iter() - .next() - .ok_or(crate::wallet::Error::FailedToGetRemainder)? - .address - .inner - } - }; - Ok(remainder_address) + } else { + self.address().await.into_inner() + }) } } @@ -365,10 +341,10 @@ pub struct StorageDeposit { #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[non_exhaustive] pub enum ReturnStrategy { - // A storage deposit return unlock condition will be added with the required minimum storage deposit + // A storage deposit return unlock condition will be added with the required minimum amount #[default] Return, - // The recipient address will get the additional amount to reach the minimum storage deposit gifted + // The recipient address will get the additional amount to reach the minimum amount gifted Gift, } @@ -429,13 +405,13 @@ impl OutputBuilder { } self } - fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self { + fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { match self { Self::Basic(b) => { - self = Self::Basic(b.with_minimum_storage_deposit(rent_structure)); + self = Self::Basic(b.with_minimum_amount(params)); } Self::Nft(b) => { - self = Self::Nft(b.with_minimum_storage_deposit(rent_structure)); + self = Self::Nft(b.with_minimum_amount(params)); } } self @@ -447,10 +423,10 @@ impl OutputBuilder { self } - fn finish_output(self, token_supply: u64) -> Result { + fn finish_output(self) -> Result { match self { - Self::Basic(b) => b.finish_output(token_supply), - Self::Nft(b) => b.finish_output(token_supply), + Self::Basic(b) => b.finish_output(), + Self::Nft(b) => b.finish_output(), } } } diff --git a/sdk/src/wallet/account/operations/transaction/prepare_transaction.rs b/sdk/src/wallet/operations/transaction/prepare_transaction.rs similarity index 69% rename from sdk/src/wallet/account/operations/transaction/prepare_transaction.rs rename to sdk/src/wallet/operations/transaction/prepare_transaction.rs index b6c6d67304..fcf9a1f96d 100644 --- a/sdk/src/wallet/account/operations/transaction/prepare_transaction.rs +++ b/sdk/src/wallet/operations/transaction/prepare_transaction.rs @@ -14,13 +14,13 @@ use crate::{ input::INPUT_COUNT_RANGE, output::{Output, OUTPUT_COUNT_RANGE}, }, - wallet::account::{ + wallet::{ operations::transaction::{RemainderValueStrategy, TransactionOptions}, - Account, + Wallet, }, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -35,12 +35,11 @@ where let options = options.into(); let outputs = outputs.into(); let prepare_transaction_start_time = Instant::now(); - let rent_structure = self.client().get_rent_structure().await?; - let token_supply = self.client().get_token_supply().await?; + let storage_score_params = self.client().get_storage_score_parameters().await?; // Check if the outputs have enough amount to cover the storage deposit for output in &outputs { - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; } let is_burn_present = options.as_ref().map(|options| options.burn.is_some()).unwrap_or(false); @@ -71,35 +70,12 @@ where } } - let remainder_address = match &options { - Some(options) => { - match &options.remainder_value_strategy { - RemainderValueStrategy::ReuseAddress => { - // select_inputs will select an address from the inputs if it's none - None - } - RemainderValueStrategy::ChangeAddress => { - let remainder_address = self.generate_remainder_address().await?; - #[cfg(feature = "events")] - { - let account_index = self.details().await.index; - self.emit( - account_index, - WalletEvent::TransactionProgress( - TransactionProgressEvent::GeneratingRemainderDepositAddress(AddressData { - address: remainder_address.address.clone(), - }), - ), - ) - .await; - } - Some(remainder_address.address.inner) - } - RemainderValueStrategy::CustomAddress(address) => Some(address.clone()), - } - } - None => None, - }; + let remainder_address = options + .as_ref() + .and_then(|options| match &options.remainder_value_strategy { + RemainderValueStrategy::ReuseAddress => None, + RemainderValueStrategy::CustomAddress(address) => Some(address.clone()), + }); let selected_transaction_data = self .select_inputs( diff --git a/sdk/src/wallet/account/operations/transaction/sign_transaction.rs b/sdk/src/wallet/operations/transaction/sign_transaction.rs similarity index 74% rename from sdk/src/wallet/account/operations/transaction/sign_transaction.rs rename to sdk/src/wallet/operations/transaction/sign_transaction.rs index e8f6742c58..2421bda089 100644 --- a/sdk/src/wallet/account/operations/transaction/sign_transaction.rs +++ b/sdk/src/wallet/operations/transaction/sign_transaction.rs @@ -19,10 +19,10 @@ use crate::{ }, secret::SecretManage, }, - wallet::account::{operations::transaction::SignedTransactionPayload, Account}, + wallet::{operations::transaction::SignedTransactionPayload, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -35,16 +35,15 @@ where log::debug!("[TRANSACTION] sign_transaction"); log::debug!("[TRANSACTION] prepared_transaction_data {prepared_transaction_data:?}"); #[cfg(feature = "events")] - self.emit( - self.details().await.index, - WalletEvent::TransactionProgress(TransactionProgressEvent::SigningTransaction), - ) + self.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::SigningTransaction, + )) .await; #[cfg(all(feature = "events", feature = "ledger_nano"))] { - use crate::wallet::account::SecretManager; - let secret_manager = self.wallet.secret_manager.read().await; + use crate::client::secret::SecretManager; + let secret_manager = self.secret_manager.read().await; if let Some(ledger) = secret_manager.downcast::().or_else(|| { secret_manager.downcast::().and_then(|s| { if let SecretManager::LedgerNano(n) = s { @@ -57,20 +56,18 @@ where let ledger_nano_status = ledger.get_ledger_nano_status().await; if let Some(buffer_size) = ledger_nano_status.buffer_size() { if needs_blind_signing(prepared_transaction_data, buffer_size) { - self.emit( - self.details().await.index, - WalletEvent::TransactionProgress(TransactionProgressEvent::PreparedTransactionSigningHash( + self.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::PreparedTransactionSigningHash( prepared_transaction_data.transaction.signing_hash().to_string(), - )), - ) + ), + )) .await; } else { - self.emit( - self.details().await.index, - WalletEvent::TransactionProgress(TransactionProgressEvent::PreparedTransaction(Box::new( - PreparedTransactionDataDto::from(prepared_transaction_data), + self.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::PreparedTransaction(Box::new(PreparedTransactionDataDto::from( + prepared_transaction_data, ))), - ) + )) .await; } } @@ -78,7 +75,6 @@ where } let unlocks = match self - .wallet .secret_manager .read() .await diff --git a/sdk/src/wallet/account/operations/transaction/submit_transaction.rs b/sdk/src/wallet/operations/transaction/submit_transaction.rs similarity index 70% rename from sdk/src/wallet/account/operations/transaction/submit_transaction.rs rename to sdk/src/wallet/operations/transaction/submit_transaction.rs index 28b4bc015b..4ce30c6a8f 100644 --- a/sdk/src/wallet/account/operations/transaction/submit_transaction.rs +++ b/sdk/src/wallet/operations/transaction/submit_transaction.rs @@ -1,17 +1,15 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crypto::keys::bip44::Bip44; - #[cfg(feature = "events")] use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; use crate::{ client::secret::{SecretManage, SignBlock}, types::block::{payload::Payload, BlockId}, - wallet::account::{operations::transaction::SignedTransactionPayload, Account}, + wallet::{operations::transaction::SignedTransactionPayload, Error, Wallet}, }; -impl Account +impl Wallet where crate::wallet::Error: From, crate::client::Error: From, @@ -22,8 +20,6 @@ where transaction_payload: SignedTransactionPayload, ) -> crate::wallet::Result { log::debug!("[TRANSACTION] send_payload"); - #[cfg(feature = "events")] - let account_index = self.details().await.index; let block = self .client() @@ -31,16 +27,13 @@ where .await? .sign_ed25519( &*self.get_secret_manager().read().await, - Bip44::new(self.wallet.coin_type()), + self.bip_path().await.ok_or(Error::MissingBipPath)?, ) .await?; #[cfg(feature = "events")] - self.emit( - account_index, - WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting), - ) - .await; + self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)) + .await; let block_id = self.client().post_block(&block).await?; log::debug!("[TRANSACTION] submitted block {}", block_id); Ok(block_id) diff --git a/sdk/src/wallet/storage/constants.rs b/sdk/src/wallet/storage/constants.rs index ae75d94881..e50e1a3f15 100644 --- a/sdk/src/wallet/storage/constants.rs +++ b/sdk/src/wallet/storage/constants.rs @@ -15,18 +15,15 @@ pub const fn default_storage_path() -> &'static str { DEFAULT_STORAGE_PATH } -pub(crate) const WALLET_INDEXATION_KEY: &str = "iota-wallet-account-manager"; - -pub(crate) const SECRET_MANAGER_KEY: &str = "secret_manager"; - -pub(crate) const ACCOUNTS_INDEXATION_KEY: &str = "iota-wallet-accounts"; -pub(crate) const ACCOUNT_INDEXATION_KEY: &str = "iota-wallet-account-"; - -pub(crate) const ACCOUNT_SYNC_OPTIONS: &str = "sync-options"; - pub(crate) const DATABASE_SCHEMA_VERSION: u8 = 1; pub(crate) const DATABASE_SCHEMA_VERSION_KEY: &str = "database-schema-version"; +pub(crate) const WALLET_DATA_KEY: &str = "wallet-data"; +pub(crate) const WALLET_BUILDER_KEY: &str = "wallet-builder"; +pub(crate) const WALLET_SYNC_OPTIONS: &str = "wallet-sync-options"; + +pub(crate) const SECRET_MANAGER_KEY: &str = "secret-manager"; + #[cfg(feature = "participation")] pub(crate) const PARTICIPATION_EVENTS: &str = "participation-events"; #[cfg(feature = "participation")] diff --git a/sdk/src/wallet/storage/manager.rs b/sdk/src/wallet/storage/manager.rs index b8a9c30560..4da7b634e4 100644 --- a/sdk/src/wallet/storage/manager.rs +++ b/sdk/src/wallet/storage/manager.rs @@ -1,15 +1,15 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use futures::{StreamExt, TryStreamExt}; use zeroize::Zeroizing; use crate::{ client::storage::StorageAdapter, types::TryFromDto, wallet::{ - account::{AccountDetails, AccountDetailsDto, SyncOptions}, + core::{WalletData, WalletDataDto}, migration::migrate, + operations::syncing::SyncOptions, storage::{constants::*, DynStorageAdapter, Storage}, }, }; @@ -18,8 +18,6 @@ use crate::{ #[derive(Debug)] pub(crate) struct StorageManager { pub(crate) storage: Storage, - // account indexes for accounts in the database - account_indexes: Vec, } impl StorageManager { @@ -46,70 +44,30 @@ impl StorageManager { .await?; }; - let account_indexes = storage.get(ACCOUNTS_INDEXATION_KEY).await?.unwrap_or_default(); - - let storage_manager = Self { - storage, - account_indexes, - }; + let storage_manager = Self { storage }; Ok(storage_manager) } - pub(crate) async fn get_accounts(&mut self) -> crate::wallet::Result> { - if let Some(account_indexes) = self.get(ACCOUNTS_INDEXATION_KEY).await? { - if self.account_indexes.is_empty() { - self.account_indexes = account_indexes; - } + pub(crate) async fn load_wallet_data(&mut self) -> crate::wallet::Result> { + if let Some(dto) = self.get::(WALLET_DATA_KEY).await? { + Ok(Some(WalletData::try_from_dto(dto)?)) } else { - return Ok(Vec::new()); + Ok(None) } - - futures::stream::iter(&self.account_indexes) - .filter_map(|account_index| async { - let account_index = *account_index; - let key = format!("{ACCOUNT_INDEXATION_KEY}{account_index}"); - self.get::(&key).await.transpose() - }) - .map(|res| AccountDetails::try_from_dto(res?)) - .try_collect::>() - .await } - pub(crate) async fn save_account(&mut self, account: &AccountDetails) -> crate::wallet::Result<()> { - // Only add account index if not already present - if !self.account_indexes.contains(account.index()) { - self.account_indexes.push(*account.index()); - } - - self.set(ACCOUNTS_INDEXATION_KEY, &self.account_indexes).await?; - self.set( - &format!("{ACCOUNT_INDEXATION_KEY}{}", account.index()), - &AccountDetailsDto::from(account), - ) - .await - } - - pub(crate) async fn remove_account(&mut self, account_index: u32) -> crate::wallet::Result<()> { - self.delete(&format!("{ACCOUNT_INDEXATION_KEY}{account_index}")).await?; - self.account_indexes.retain(|a| a != &account_index); - self.set(ACCOUNTS_INDEXATION_KEY, &self.account_indexes).await + pub(crate) async fn save_wallet_data(&mut self, wallet_data: &WalletData) -> crate::wallet::Result<()> { + self.set(WALLET_DATA_KEY, &WalletDataDto::from(wallet_data)).await } - pub(crate) async fn set_default_sync_options( - &self, - account_index: u32, - sync_options: &SyncOptions, - ) -> crate::wallet::Result<()> { - let key = format!("{ACCOUNT_INDEXATION_KEY}{account_index}-{ACCOUNT_SYNC_OPTIONS}"); + pub(crate) async fn set_default_sync_options(&self, sync_options: &SyncOptions) -> crate::wallet::Result<()> { + let key = format!("{WALLET_DATA_KEY}-{WALLET_SYNC_OPTIONS}"); self.set(&key, &sync_options).await } - pub(crate) async fn get_default_sync_options( - &self, - account_index: u32, - ) -> crate::wallet::Result> { - let key = format!("{ACCOUNT_INDEXATION_KEY}{account_index}-{ACCOUNT_SYNC_OPTIONS}"); + pub(crate) async fn get_default_sync_options(&self) -> crate::wallet::Result> { + let key = format!("{WALLET_DATA_KEY}-{WALLET_SYNC_OPTIONS}"); self.get(&key).await } } @@ -164,23 +122,19 @@ mod tests { } #[tokio::test] - async fn save_remove_account() { + async fn save_load_wallet_data() { let mut storage_manager = StorageManager::new(Memory::default(), None).await.unwrap(); - assert!(storage_manager.get_accounts().await.unwrap().is_empty()); - - let account_details = AccountDetails::mock(); + assert!(storage_manager.load_wallet_data().await.unwrap().is_none()); - storage_manager.save_account(&account_details).await.unwrap(); - let accounts = storage_manager.get_accounts().await.unwrap(); - assert_eq!(accounts.len(), 1); - assert_eq!(accounts[0].alias(), "Alice"); + let wallet_data = WalletData::mock(); - storage_manager.remove_account(0).await.unwrap(); - assert!(storage_manager.get_accounts().await.unwrap().is_empty()); + storage_manager.save_wallet_data(&wallet_data).await.unwrap(); + let wallet = storage_manager.load_wallet_data().await.unwrap(); + assert!(matches!(wallet, Some(data) if data.alias == Some("Alice".to_string()))); } #[tokio::test] - async fn save_get_wallet_data() { + async fn save_load_wallet_builder() { let storage_manager = StorageManager::new(Memory::default(), None).await.unwrap(); assert!( WalletBuilder::::load(&storage_manager) diff --git a/sdk/src/wallet/storage/participation.rs b/sdk/src/wallet/storage/participation.rs index c5851d4614..0691874a91 100644 --- a/sdk/src/wallet/storage/participation.rs +++ b/sdk/src/wallet/storage/participation.rs @@ -11,7 +11,7 @@ use crate::{ block::output::OutputId, }, wallet::{ - account::operations::participation::ParticipationEventWithNodes, + operations::participation::ParticipationEventWithNodes, storage::constants::{PARTICIPATION_CACHED_OUTPUTS, PARTICIPATION_EVENTS}, }, }; @@ -19,40 +19,29 @@ use crate::{ impl StorageManager { pub(crate) async fn insert_participation_event( &self, - account_index: u32, event_with_nodes: ParticipationEventWithNodes, ) -> crate::wallet::Result<()> { log::debug!("insert_participation_event {}", event_with_nodes.id); let mut events = self .storage - .get::>(&format!( - "{PARTICIPATION_EVENTS}{account_index}" - )) + .get::>(PARTICIPATION_EVENTS) .await? .unwrap_or_default(); events.insert(event_with_nodes.id, event_with_nodes); - self.storage - .set(&format!("{PARTICIPATION_EVENTS}{account_index}"), &events) - .await?; + self.storage.set(PARTICIPATION_EVENTS, &events).await?; Ok(()) } - pub(crate) async fn remove_participation_event( - &self, - account_index: u32, - id: &ParticipationEventId, - ) -> crate::wallet::Result<()> { + pub(crate) async fn remove_participation_event(&self, id: &ParticipationEventId) -> crate::wallet::Result<()> { log::debug!("remove_participation_event {id}"); let mut events = match self .storage - .get::>(&format!( - "{PARTICIPATION_EVENTS}{account_index}" - )) + .get::>(PARTICIPATION_EVENTS) .await? { Some(events) => events, @@ -61,38 +50,27 @@ impl StorageManager { events.remove(id); - self.storage - .set(&format!("{PARTICIPATION_EVENTS}{account_index}"), &events) - .await?; + self.storage.set(PARTICIPATION_EVENTS, &events).await?; Ok(()) } pub(crate) async fn get_participation_events( &self, - account_index: u32, ) -> crate::wallet::Result> { log::debug!("get_participation_events"); - Ok(self - .storage - .get(&format!("{PARTICIPATION_EVENTS}{account_index}")) - .await? - .unwrap_or_default()) + Ok(self.storage.get(PARTICIPATION_EVENTS).await?.unwrap_or_default()) } pub(crate) async fn set_cached_participation_output_status( &self, - account_index: u32, outputs_participation: &HashMap, ) -> crate::wallet::Result<()> { log::debug!("set_cached_participation"); self.storage - .set( - &format!("{PARTICIPATION_CACHED_OUTPUTS}{account_index}"), - outputs_participation, - ) + .set(PARTICIPATION_CACHED_OUTPUTS, outputs_participation) .await?; Ok(()) @@ -100,13 +78,12 @@ impl StorageManager { pub(crate) async fn get_cached_participation_output_status( &self, - account_index: u32, ) -> crate::wallet::Result> { log::debug!("get_cached_participation"); Ok(self .storage - .get(&format!("{PARTICIPATION_CACHED_OUTPUTS}{account_index}")) + .get(PARTICIPATION_CACHED_OUTPUTS) .await? .unwrap_or_default()) } @@ -122,16 +99,16 @@ mod tests { #[tokio::test] async fn insert_get_remove_participation_event() { let storage_manager = StorageManager::new(Memory::default(), None).await.unwrap(); - assert!(storage_manager.get_participation_events(0).await.unwrap().is_empty()); + assert!(storage_manager.get_participation_events().await.unwrap().is_empty()); let event_with_nodes = ParticipationEventWithNodes::mock(); let event_with_nodes_id = event_with_nodes.id; storage_manager - .insert_participation_event(0, event_with_nodes.clone()) + .insert_participation_event(event_with_nodes.clone()) .await .unwrap(); - let participation_events = storage_manager.get_participation_events(0).await.unwrap(); + let participation_events = storage_manager.get_participation_events().await.unwrap(); let mut expected = HashMap::new(); expected.insert(event_with_nodes_id, event_with_nodes); @@ -139,10 +116,10 @@ mod tests { assert_eq!(participation_events, expected); storage_manager - .remove_participation_event(0, &event_with_nodes_id) + .remove_participation_event(&event_with_nodes_id) .await .unwrap(); - assert!(storage_manager.get_participation_events(0).await.unwrap().is_empty()); + assert!(storage_manager.get_participation_events().await.unwrap().is_empty()); } #[tokio::test] @@ -150,7 +127,7 @@ mod tests { let storage_manager = StorageManager::new(Memory::default(), None).await.unwrap(); assert!( storage_manager - .get_cached_participation_output_status(0) + .get_cached_participation_output_status() .await .unwrap() .is_empty() @@ -166,12 +143,12 @@ mod tests { .collect::>(); storage_manager - .set_cached_participation_output_status(0, &outputs_participation) + .set_cached_participation_output_status(&outputs_participation) .await .unwrap(); assert_eq!( - storage_manager.get_cached_participation_output_status(0).await.unwrap(), + storage_manager.get_cached_participation_output_status().await.unwrap(), outputs_participation ); } diff --git a/sdk/src/wallet/account/types/address.rs b/sdk/src/wallet/types/address.rs similarity index 93% rename from sdk/src/wallet/account/types/address.rs rename to sdk/src/wallet/types/address.rs index a1539ad157..2eced539d4 100644 --- a/sdk/src/wallet/account/types/address.rs +++ b/sdk/src/wallet/types/address.rs @@ -6,9 +6,12 @@ use std::hash::Hash; use getset::{Getters, Setters}; use serde::{Deserialize, Serialize}; -use crate::types::{ - self, - block::{address::Bech32Address, output::OutputId, ConvertTo}, +use crate::{ + types::{ + self, + block::{address::Bech32Address, output::OutputId}, + }, + utils::ConvertTo, }; /// A BIP44 address. diff --git a/sdk/src/wallet/account/types/balance.rs b/sdk/src/wallet/types/balance.rs similarity index 98% rename from sdk/src/wallet/account/types/balance.rs rename to sdk/src/wallet/types/balance.rs index 6709ebc2b2..180ae01bd3 100644 --- a/sdk/src/wallet/account/types/balance.rs +++ b/sdk/src/wallet/types/balance.rs @@ -12,8 +12,8 @@ use crate::{ utils::serde::string, }; -/// The balance of an account, returned from [`crate::wallet::account::Account::sync()`] and -/// [`crate::wallet::account::Account::balance()`]. +/// The balance of the wallet, returned from [`crate::wallet::core::Wallet::sync()`] and +/// [`crate::wallet::core::Wallet::balance()`]. #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Getters)] #[serde(rename_all = "camelCase")] #[getset(get = "pub")] diff --git a/sdk/src/wallet/account/types/mod.rs b/sdk/src/wallet/types/mod.rs similarity index 72% rename from sdk/src/wallet/account/types/mod.rs rename to sdk/src/wallet/types/mod.rs index fbb9edf36a..cc70a0a417 100644 --- a/sdk/src/wallet/account/types/mod.rs +++ b/sdk/src/wallet/types/mod.rs @@ -1,7 +1,7 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/// Address types used in the account +/// Address types used in the wallet pub(crate) mod address; pub(crate) mod balance; #[cfg(feature = "participation")] @@ -10,7 +10,7 @@ pub mod participation; use std::str::FromStr; use crypto::keys::bip44::Bip44; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; pub use self::{ address::{AddressWithUnspentOutputs, Bip44Address}, @@ -30,7 +30,7 @@ use crate::{ TryFromDto, }, utils::serde::bip44::option_bip44, - wallet::account::AccountDetails, + wallet::core::WalletData, }; /// An output with metadata @@ -43,7 +43,7 @@ pub struct OutputData { pub output: Output, /// If an output is spent pub is_spent: bool, - /// Associated account address. + /// Associated wallet address. pub address: Address, /// Network ID pub network_id: u64, @@ -55,7 +55,7 @@ pub struct OutputData { impl OutputData { pub fn input_signing_data( &self, - account: &AccountDetails, + wallet_data: &WalletData, slot_index: SlotIndex, ) -> crate::wallet::Result> { let (unlock_address, _unlocked_account_or_nft_address) = @@ -64,17 +64,10 @@ impl OutputData { let chain = if unlock_address == self.address { self.chain } else if let Address::Ed25519(_) = unlock_address { - if let Some(address) = account - .addresses_with_unspent_outputs - .iter() - .find(|a| a.address.inner == unlock_address) - { - Some( - Bip44::new(account.coin_type) - .with_account(account.index) - .with_change(address.internal as _) - .with_address_index(address.key_index), - ) + if wallet_data.address.inner() == &unlock_address { + // TODO #1279: do we need a check to make sure that `wallet_data.address` and `wallet_data.bip_path` are + // never conflicting? + wallet_data.bip_path } else { return Ok(None); } @@ -129,18 +122,14 @@ impl From<&OutputData> for OutputDataDto { } } -impl TryFromDto for OutputData { - type Dto = OutputDataDto; +impl TryFrom for OutputData { type Error = BlockError; - fn try_from_dto_with_params_inner( - dto: Self::Dto, - params: crate::types::ValidationParams<'_>, - ) -> Result { + fn try_from(dto: OutputDataDto) -> Result { Ok(Self { output_id: dto.output_id, metadata: dto.metadata, - output: Output::try_from_dto_with_params(dto.output, params)?, + output: Output::try_from(dto.output)?, is_spent: dto.is_spent, address: dto.address, network_id: dto @@ -277,74 +266,3 @@ impl FromStr for OutputKind { Ok(kind) } } - -/// The account identifier. -#[derive(Debug, Clone, Serialize, Eq, PartialEq, Hash)] -#[serde(untagged)] -#[non_exhaustive] -pub enum AccountIdentifier { - /// Account alias as identifier. - Alias(String), - /// An index identifier. - Index(u32), -} - -// Custom deserialize because the index could also be encoded as String -impl<'de> Deserialize<'de> for AccountIdentifier { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use serde::de::Error; - use serde_json::Value; - let v = Value::deserialize(deserializer)?; - Ok(match v.as_u64() { - Some(number) => { - let index: u32 = - u32::try_from(number).map_err(|_| D::Error::custom("account index is greater than u32::MAX"))?; - Self::Index(index) - } - None => { - let alias_or_index_str = v - .as_str() - .ok_or_else(|| D::Error::custom("account identifier is not a number or string"))?; - Self::from(alias_or_index_str) - } - }) - } -} - -// When the identifier is a string. -impl From<&str> for AccountIdentifier { - fn from(value: &str) -> Self { - u32::from_str(value).map_or_else(|_| Self::Alias(value.to_string()), Self::Index) - } -} - -impl From for AccountIdentifier { - fn from(value: String) -> Self { - Self::from(value.as_str()) - } -} - -impl From<&String> for AccountIdentifier { - fn from(value: &String) -> Self { - Self::from(value.as_str()) - } -} - -// When the identifier is an index. -impl From for AccountIdentifier { - fn from(value: u32) -> Self { - Self::Index(value) - } -} - -impl core::fmt::Display for AccountIdentifier { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Alias(alias) => alias.fmt(f), - Self::Index(index) => index.fmt(f), - } - } -} diff --git a/sdk/src/wallet/account/types/participation.rs b/sdk/src/wallet/types/participation.rs similarity index 100% rename from sdk/src/wallet/account/types/participation.rs rename to sdk/src/wallet/types/participation.rs diff --git a/sdk/src/wallet/update.rs b/sdk/src/wallet/update.rs new file mode 100644 index 0000000000..77ce6f8622 --- /dev/null +++ b/sdk/src/wallet/update.rs @@ -0,0 +1,208 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use crate::{ + client::secret::SecretManage, + types::{ + api::core::OutputWithMetadataResponse, + block::output::{OutputId, OutputMetadata}, + }, + wallet::{ + types::{InclusionState, OutputData, TransactionWithMetadata}, + Wallet, + }, +}; +#[cfg(feature = "events")] +use crate::{ + types::block::payload::signed_transaction::dto::SignedTransactionPayloadDto, + wallet::{ + events::types::{NewOutputEvent, SpentOutputEvent, TransactionInclusionEvent, WalletEvent}, + types::OutputDataDto, + }, +}; + +impl Wallet +where + crate::wallet::Error: From, + crate::client::Error: From, +{ + /// Set the alias for the wallet. + pub async fn set_alias(&self, alias: &str) -> crate::wallet::Result<()> { + let mut wallet_data = self.data_mut().await; + wallet_data.alias = Some(alias.to_string()); + #[cfg(feature = "storage")] + self.save(Some(&wallet_data)).await?; + Ok(()) + } + + /// Update wallet with newly synced data and emit events for outputs. + pub(crate) async fn update_after_sync( + &self, + unspent_outputs: Vec, + spent_or_unsynced_output_metadata_map: HashMap>, + ) -> crate::wallet::Result<()> { + log::debug!("[SYNC] Update wallet with new synced transactions"); + + let network_id = self.client().get_network_id().await?; + let mut wallet_data = self.data_mut().await; + + // Update spent outputs + for (output_id, output_metadata_response_opt) in spent_or_unsynced_output_metadata_map { + // If we got the output response and it's still unspent, skip it + if let Some(output_metadata_response) = output_metadata_response_opt { + if output_metadata_response.is_spent() { + wallet_data.unspent_outputs.remove(&output_id); + if let Some(output_data) = wallet_data.outputs.get_mut(&output_id) { + output_data.metadata = output_metadata_response; + } + } else { + // not spent, just not synced, skip + continue; + } + } + + if let Some(output) = wallet_data.outputs.get(&output_id) { + // Could also be outputs from other networks after we switched the node, so we check that first + if output.network_id == network_id { + log::debug!("[SYNC] Spent output {}", output_id); + wallet_data.locked_outputs.remove(&output_id); + wallet_data.unspent_outputs.remove(&output_id); + // Update spent data fields + if let Some(output_data) = wallet_data.outputs.get_mut(&output_id) { + output_data.metadata.set_spent(true); + output_data.is_spent = true; + #[cfg(feature = "events")] + { + self.emit(WalletEvent::SpentOutput(Box::new(SpentOutputEvent { + output: OutputDataDto::from(&*output_data), + }))) + .await; + } + } + } + } + } + + // Add new synced outputs + for output_data in unspent_outputs { + // Insert output, if it's unknown emit the NewOutputEvent + if wallet_data + .outputs + .insert(output_data.output_id, output_data.clone()) + .is_none() + { + #[cfg(feature = "events")] + { + let transaction = wallet_data + .incoming_transactions + .get(output_data.output_id.transaction_id()); + self.emit(WalletEvent::NewOutput(Box::new(NewOutputEvent { + output: OutputDataDto::from(&output_data), + transaction: transaction + .as_ref() + .map(|tx| SignedTransactionPayloadDto::from(&tx.payload)), + transaction_inputs: transaction.as_ref().map(|tx| { + tx.inputs + .clone() + .into_iter() + .map(OutputWithMetadataResponse::from) + .collect() + }), + }))) + .await; + } + }; + if !output_data.is_spent { + wallet_data.unspent_outputs.insert(output_data.output_id, output_data); + } + } + + #[cfg(feature = "storage")] + { + log::debug!("[SYNC] storing wallet with new synced data"); + self.save(Some(&wallet_data)).await?; + } + Ok(()) + } + + /// Update wallet with newly synced transactions. + pub(crate) async fn update_with_transactions( + &self, + updated_transactions: Vec, + spent_output_ids: Vec, + output_ids_to_unlock: Vec, + ) -> crate::wallet::Result<()> { + log::debug!("[SYNC] Update wallet with new synced transactions"); + + let mut wallet_data = self.data_mut().await; + + for transaction in updated_transactions { + match transaction.inclusion_state { + InclusionState::Confirmed | InclusionState::Conflicting | InclusionState::UnknownPruned => { + let transaction_id = transaction.payload.transaction().id(); + wallet_data.pending_transactions.remove(&transaction_id); + log::debug!( + "[SYNC] inclusion_state of {transaction_id} changed to {:?}", + transaction.inclusion_state + ); + #[cfg(feature = "events")] + { + self.emit(WalletEvent::TransactionInclusion(TransactionInclusionEvent { + transaction_id, + inclusion_state: transaction.inclusion_state, + })) + .await; + } + } + _ => {} + } + wallet_data + .transactions + .insert(transaction.payload.transaction().id(), transaction.clone()); + } + + for output_to_unlock in &spent_output_ids { + if let Some(output) = wallet_data.outputs.get_mut(output_to_unlock) { + output.is_spent = true; + } + wallet_data.locked_outputs.remove(output_to_unlock); + wallet_data.unspent_outputs.remove(output_to_unlock); + log::debug!("[SYNC] Unlocked spent output {}", output_to_unlock); + } + + for output_to_unlock in &output_ids_to_unlock { + wallet_data.locked_outputs.remove(output_to_unlock); + log::debug!( + "[SYNC] Unlocked unspent output {} because of a conflicting transaction", + output_to_unlock + ); + } + + #[cfg(feature = "storage")] + { + log::debug!("[SYNC] storing wallet with new synced transactions"); + self.save(Some(&wallet_data)).await?; + } + Ok(()) + } + + /// Update the wallet address with a possible new Bech32 HRP and clear the inaccessible incoming transactions. + pub(crate) async fn update_bech32_hrp(&self) -> crate::wallet::Result<()> { + let bech32_hrp = self.client().get_bech32_hrp().await?; + log::debug!("updating wallet data with new bech32 hrp: {}", bech32_hrp); + let mut wallet_data = self.data_mut().await; + wallet_data.address.hrp = bech32_hrp; + + wallet_data.inaccessible_incoming_transactions.clear(); + + #[cfg(feature = "storage")] + { + log::debug!("[save] wallet data with updated bech32 hrp",); + self.save(Some(&wallet_data)).await?; + } + + Ok(()) + } +} diff --git a/sdk/tests/client/input_selection/account_outputs.rs b/sdk/tests/client/input_selection/account_outputs.rs index 4ccb35debf..46be201a92 100644 --- a/sdk/tests/client/input_selection/account_outputs.rs +++ b/sdk/tests/client/input_selection/account_outputs.rs @@ -17,7 +17,7 @@ use crate::client::{ addresses, build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, Build::{Account, Basic}, ACCOUNT_ID_0, ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ACCOUNT_1, BECH32_ADDRESS_ED25519_0, - BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_NFT_1, TOKEN_SUPPLY, + BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_NFT_1, }; #[test] @@ -470,7 +470,7 @@ fn account_in_output_and_sender() { Basic(1_000_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), ]); let account_output = AccountOutputBuilder::from(inputs[0].output.as_account()) - .finish_output(TOKEN_SUPPLY) + .finish_output() .unwrap(); let mut outputs = build_outputs([Basic( 1_000_000, diff --git a/sdk/tests/client/input_selection/basic_outputs.rs b/sdk/tests/client/input_selection/basic_outputs.rs index b2be93d408..b267e0219d 100644 --- a/sdk/tests/client/input_selection/basic_outputs.rs +++ b/sdk/tests/client/input_selection/basic_outputs.rs @@ -1105,44 +1105,45 @@ fn two_inputs_remainder_3() { }); } -#[test] -fn another_input_required_to_cover_remainder_rent() { - let protocol_parameters = protocol_parameters(); - - let inputs = build_inputs([ - Basic(500_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), - Basic(600_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), - Basic(700_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), - ]); - let outputs = build_outputs([Basic( - 1_000_000, - BECH32_ADDRESS_ED25519_0, - None, - None, - None, - None, - None, - None, - )]); +// TODO: re-enabled when rent is figured out +// #[test] +// fn another_input_required_to_cover_remainder_rent() { +// let protocol_parameters = protocol_parameters(); - let selected = InputSelection::new( - inputs.clone(), - outputs.clone(), - addresses([BECH32_ADDRESS_ED25519_0]), - protocol_parameters, - ) - .select() - .unwrap(); +// let inputs = build_inputs([ +// Basic(500_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), +// Basic(600_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), +// Basic(700_000, BECH32_ADDRESS_ED25519_0, None, None, None, None, None, None), +// ]); +// let outputs = build_outputs([Basic( +// 1_000_000, +// BECH32_ADDRESS_ED25519_0, +// None, +// None, +// None, +// None, +// None, +// None, +// )]); - assert!(unsorted_eq(&selected.inputs, &inputs)); - assert_eq!(selected.outputs.len(), 2); - assert!(selected.outputs.contains(&outputs[0])); - selected.outputs.iter().for_each(|output| { - if !outputs.contains(output) { - assert!(is_remainder_or_return(output, 800_000, BECH32_ADDRESS_ED25519_0, None)); - } - }); -} +// let selected = InputSelection::new( +// inputs.clone(), +// outputs.clone(), +// addresses([BECH32_ADDRESS_ED25519_0]), +// protocol_parameters, +// ) +// .select() +// .unwrap(); + +// assert!(unsorted_eq(&selected.inputs, &inputs)); +// assert_eq!(selected.outputs.len(), 2); +// assert!(selected.outputs.contains(&outputs[0])); +// selected.outputs.iter().for_each(|output| { +// if !outputs.contains(output) { +// assert!(is_remainder_or_return(output, 800_000, BECH32_ADDRESS_ED25519_0, None)); +// } +// }); +// } #[test] fn sender_already_selected() { diff --git a/sdk/tests/client/input_selection/foundry_outputs.rs b/sdk/tests/client/input_selection/foundry_outputs.rs index 7d4c9a2775..afd4f7f687 100644 --- a/sdk/tests/client/input_selection/foundry_outputs.rs +++ b/sdk/tests/client/input_selection/foundry_outputs.rs @@ -23,7 +23,7 @@ use pretty_assertions::assert_eq; use crate::client::{ addresses, build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, Build::{Account, Basic, Foundry}, - ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ED25519_0, TOKEN_SUPPLY, + ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ED25519_0, }; #[test] @@ -208,7 +208,7 @@ fn melt_native_tokens() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .with_foundry_counter(1) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); inputs.push(InputSigningData { output: account_output, @@ -261,7 +261,7 @@ fn destroy_foundry_with_account_state_transition() { ]); let account_output = AccountOutputBuilder::from(inputs[0].output.as_account()) .with_amount(103_100) - .finish_output(TOKEN_SUPPLY) + .finish_output() .unwrap(); // Account output gets the amount from the foundry output added let outputs = [account_output]; @@ -397,7 +397,7 @@ fn simple_foundry_transition_basic_not_needed() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .with_foundry_counter(1) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); inputs.push(InputSigningData { output: account_output, @@ -463,7 +463,7 @@ fn simple_foundry_transition_basic_not_needed_with_remainder() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .with_foundry_counter(1) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); inputs.push(InputSigningData { output: account_output, @@ -604,7 +604,7 @@ fn mint_and_burn_at_the_same_time() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .with_foundry_counter(1) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); inputs.push(InputSigningData { output: account_output, @@ -657,7 +657,7 @@ fn take_amount_from_account_and_foundry_to_fund_basic() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .with_foundry_counter(1) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); inputs.push(InputSigningData { output: account_output, @@ -848,7 +848,7 @@ fn foundry_in_outputs_and_required() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .with_foundry_counter(1) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); inputs.push(InputSigningData { output: account_output, @@ -905,7 +905,7 @@ fn melt_and_burn_native_tokens() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .with_foundry_counter(1) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); inputs.push(InputSigningData { output: account_output, diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/input_selection/nft_outputs.rs index 31140a4be9..02503fd963 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/input_selection/nft_outputs.rs @@ -1162,13 +1162,14 @@ fn changed_immutable_metadata() { #[cfg(not(feature = "irc_27"))] let metadata = [1, 2, 3]; - let nft_output = NftOutputBuilder::new_with_minimum_storage_deposit(protocol_parameters.rent_structure(), nft_id_1) - .with_immutable_features(MetadataFeature::try_from(metadata)) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .finish_output(protocol_parameters.token_supply()) - .unwrap(); + let nft_output = + NftOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters(), nft_id_1) + .with_immutable_features(MetadataFeature::try_from(metadata)) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(); let inputs = [InputSigningData { output: nft_output.clone(), @@ -1188,9 +1189,9 @@ fn changed_immutable_metadata() { // New nft output with changed immutable metadata feature let updated_nft_output = NftOutputBuilder::from(nft_output.as_nft()) - .with_minimum_storage_deposit(protocol_parameters.rent_structure()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) .with_immutable_features(MetadataFeature::try_from(metadata)) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let outputs = [updated_nft_output]; diff --git a/sdk/tests/client/input_signing_data.rs b/sdk/tests/client/input_signing_data.rs index 956c1301f6..491cf180f5 100644 --- a/sdk/tests/client/input_signing_data.rs +++ b/sdk/tests/client/input_signing_data.rs @@ -9,31 +9,25 @@ use iota_sdk::{ constants::SHIMMER_COIN_TYPE, secret::types::{InputSigningData, InputSigningDataDto}, }, - types::{ - block::{ - address::Address, - output::{unlock_condition::AddressUnlockCondition, BasicOutput, OutputId, OutputMetadata}, - payload::signed_transaction::TransactionId, - protocol::protocol_parameters, - slot::SlotCommitmentId, - BlockId, - }, - TryFromDto, + types::block::{ + address::Address, + output::{unlock_condition::AddressUnlockCondition, BasicOutput, OutputId, OutputMetadata}, + payload::signed_transaction::TransactionId, + slot::SlotCommitmentId, + BlockId, }, }; use pretty_assertions::assert_eq; #[test] fn input_signing_data_conversion() { - let protocol_parameters = protocol_parameters(); - let bip44_chain = Bip44::new(SHIMMER_COIN_TYPE); let output = BasicOutput::build_with_amount(1_000_000) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32("rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy").unwrap(), )) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let input_signing_data = InputSigningData { @@ -68,8 +62,7 @@ fn input_signing_data_conversion() { let input_signing_data_dto = InputSigningDataDto::from(&input_signing_data); assert_eq!(input_signing_data_dto.chain.as_ref(), Some(&bip44_chain)); - let restored_input_signing_data = - InputSigningData::try_from_dto_with_params(input_signing_data_dto.clone(), &protocol_parameters).unwrap(); + let restored_input_signing_data = InputSigningData::try_from(input_signing_data_dto.clone()).unwrap(); assert_eq!(input_signing_data, restored_input_signing_data); let input_signing_data_dto_json = serde_json::json!({ @@ -113,8 +106,7 @@ fn input_signing_data_conversion() { serde_json::from_value::(input_signing_data_dto_json).unwrap(); assert_eq!(restored_input_signing_data_dto.chain.as_ref(), Some(&bip44_chain)); - let restored_input_signing_data = - InputSigningData::try_from_dto_with_params(restored_input_signing_data_dto, &protocol_parameters).unwrap(); + let restored_input_signing_data = InputSigningData::try_from(restored_input_signing_data_dto).unwrap(); assert!(restored_input_signing_data.output.is_basic()); assert_eq!(restored_input_signing_data.chain, Some(bip44_chain)); } diff --git a/sdk/tests/client/mod.rs b/sdk/tests/client/mod.rs index 32172f569b..258b3ddc32 100644 --- a/sdk/tests/client/mod.rs +++ b/sdk/tests/client/mod.rs @@ -39,7 +39,6 @@ use iota_sdk::{ }, }; -const TOKEN_SUPPLY: u64 = 1_813_620_509_061_365; const ACCOUNT_ID_0: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; const ACCOUNT_ID_1: &str = "0x1111111111111111111111111111111111111111111111111111111111111111"; const ACCOUNT_ID_2: &str = "0x2222222222222222222222222222222222222222222222222222222222222222"; @@ -110,8 +109,7 @@ fn build_basic_output( } if let Some((address, amount)) = sdruc { - builder = builder - .add_unlock_condition(StorageDepositReturnUnlockCondition::new(address, amount, TOKEN_SUPPLY).unwrap()); + builder = builder.add_unlock_condition(StorageDepositReturnUnlockCondition::new(address, amount)); } if let Some(timelock) = timelock { @@ -122,7 +120,7 @@ fn build_basic_output( builder = builder.add_unlock_condition(ExpirationUnlockCondition::new(address, timestamp).unwrap()); } - builder.finish_output(TOKEN_SUPPLY).unwrap() + builder.finish_output().unwrap() } #[allow(clippy::too_many_arguments)] @@ -147,15 +145,14 @@ fn build_nft_output( } if let Some((address, amount)) = sdruc { - builder = builder - .add_unlock_condition(StorageDepositReturnUnlockCondition::new(address, amount, TOKEN_SUPPLY).unwrap()); + builder = builder.add_unlock_condition(StorageDepositReturnUnlockCondition::new(address, amount)); } if let Some((address, timestamp)) = expiration { builder = builder.add_unlock_condition(ExpirationUnlockCondition::new(address, timestamp).unwrap()); } - builder.finish_output(TOKEN_SUPPLY).unwrap() + builder.finish_output().unwrap() } #[allow(clippy::too_many_arguments)] @@ -177,7 +174,7 @@ fn build_account_output( builder = builder.add_immutable_feature(IssuerFeature::new(bech32_issuer)); } - builder.finish_output(TOKEN_SUPPLY).unwrap() + builder.finish_output().unwrap() } fn build_foundry_output( @@ -200,7 +197,7 @@ fn build_foundry_output( ); } - builder.finish_output(TOKEN_SUPPLY).unwrap() + builder.finish_output().unwrap() } fn build_output_inner(build: Build) -> (Output, Option) { diff --git a/sdk/tests/client/node_api/indexer.rs b/sdk/tests/client/node_api/indexer.rs index 1ab8a7ae4d..d873f1400e 100644 --- a/sdk/tests/client/node_api/indexer.rs +++ b/sdk/tests/client/node_api/indexer.rs @@ -64,8 +64,8 @@ // .await?[0]; // let nft_output = -// NftOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.rent_structure(), NftId::null()) -// .with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new(address))]) +// NftOutputBuilder::new_with_minimum_storage_deposit(*protocol_parameters.storage_score_parameters(), +// NftId::null()) .with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new(address))]) // .finish_output(protocol_parameters.token_supply())?; // let block = client @@ -121,7 +121,7 @@ // ); // let foundry_output = FoundryOutputBuilder::new_with_minimum_storage_deposit( -// *protocol_parameters.rent_structure(), +// *protocol_parameters.storage_score_parameters(), // alias_output_0.as_alias().foundry_counter() + 1, // TokenScheme::Simple(SimpleTokenScheme::new(100, 0, 500)?), // ) diff --git a/sdk/tests/types/address/mod.rs b/sdk/tests/types/address/mod.rs index a19cff845d..fff6ac7d57 100644 --- a/sdk/tests/types/address/mod.rs +++ b/sdk/tests/types/address/mod.rs @@ -4,6 +4,7 @@ mod account; mod bech32; mod ed25519; +mod multi; mod nft; mod restricted; diff --git a/sdk/tests/types/address/multi.rs b/sdk/tests/types/address/multi.rs new file mode 100644 index 0000000000..ea767e5f69 --- /dev/null +++ b/sdk/tests/types/address/multi.rs @@ -0,0 +1,57 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use iota_sdk::types::block::address::{Address, ToBech32Ext}; + +#[test] +fn bech32() { + // Test from https://github.com/iotaledger/tips/blob/tip52/tips/TIP-0052/tip-0052.md#bech32 + + let multi_address_json = serde_json::json!({ + "type": 40, + "addresses": [ + { + "address": { + "type": 0, + "pubKeyHash": "0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" + }, + "weight": 1 + }, + { + "address": { + "type": 0, + "pubKeyHash": "0x53fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" + }, + "weight": 1 + }, + { + "address": { + "type": 0, + "pubKeyHash": "0x54fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" + }, + "weight": 1 + }, + { + "address": { + "type": 8, + "accountId": "0x55fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" + }, + "weight": 2 + }, + { + "address": { + "type": 16, + "nftId": "0x56fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649" + }, + "weight": 3 + } + ], + "threshold": 2 + }); + let multi_address = serde_json::from_value::
(multi_address_json).unwrap(); + + assert_eq!( + multi_address.to_bech32_unchecked("iota"), + "iota19qq0ezu97zl76wqnpdxxleuf55gk0eqhscjtdgqm5sqwav6gcarz6vvesnk" + ); +} diff --git a/sdk/tests/types/block_id.rs b/sdk/tests/types/block_id.rs index 87152765ca..b788f4cd29 100644 --- a/sdk/tests/types/block_id.rs +++ b/sdk/tests/types/block_id.rs @@ -62,7 +62,7 @@ fn memory_layout() { // TODO: re-enable below tests when source is updated // fn protocol_parameters() -> ProtocolParameters { -// ProtocolParameters::new(3, "test", "rms", RentStructure::default(), 0, 1695275822, 10, 0).unwrap() +// ProtocolParameters::new(3, "test", "rms", StorageScoreParameters::default(), 0, 1695275822, 10, 0).unwrap() // } // #[test] diff --git a/sdk/tests/types/mod.rs b/sdk/tests/types/mod.rs index 0faf8400da..66bf093d08 100644 --- a/sdk/tests/types/mod.rs +++ b/sdk/tests/types/mod.rs @@ -13,9 +13,9 @@ mod output_id; mod parents; mod payload; mod protocol; -mod rent; mod signed_transaction_payload; mod slot; +mod storage_score; mod tagged_data_payload; mod transaction; mod transaction_id; diff --git a/sdk/tests/types/output/account.rs b/sdk/tests/types/output/account.rs index d8742d2aec..57f5b2474c 100644 --- a/sdk/tests/types/output/account.rs +++ b/sdk/tests/types/output/account.rs @@ -1,17 +1,14 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::{ - block::{ - output::{AccountOutput, Feature, Output, Rent}, - protocol::protocol_parameters, - rand::output::{ - feature::{rand_issuer_feature, rand_metadata_feature, rand_sender_feature}, - rand_account_id, rand_account_output, - unlock_condition::rand_address_unlock_condition_different_from_account_id, - }, +use iota_sdk::types::block::{ + output::{AccountOutput, Feature, MinimumOutputAmount}, + protocol::protocol_parameters, + rand::output::{ + feature::{rand_issuer_feature, rand_metadata_feature, rand_sender_feature}, + rand_account_id, rand_account_output, + unlock_condition::rand_address_unlock_condition_different_from_account_id, }, - ValidationParams, }; use packable::PackableExt; use pretty_assertions::assert_eq; @@ -54,16 +51,16 @@ fn builder() { let metadata = rand_metadata_feature(); let output = builder - .with_minimum_storage_deposit(protocol_parameters.rent_structure()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) .add_unlock_condition(rand_address_unlock_condition_different_from_account_id(&account_id)) .with_features([Feature::from(metadata.clone()), sender_1.clone().into()]) .with_immutable_features([Feature::from(metadata.clone()), issuer_1.clone().into()]) - .finish_with_params(ValidationParams::default().with_protocol_parameters(protocol_parameters.clone())) + .finish() .unwrap(); assert_eq!( output.amount(), - Output::Account(output.clone()).rent_cost(protocol_parameters.rent_structure()) + output.minimum_amount(protocol_parameters.storage_score_parameters()) ); assert_eq!(output.features().metadata(), Some(&metadata)); assert_eq!(output.features().sender(), Some(&sender_1)); diff --git a/sdk/tests/types/output/basic.rs b/sdk/tests/types/output/basic.rs index d2dd05e845..59967e4c01 100644 --- a/sdk/tests/types/output/basic.rs +++ b/sdk/tests/types/output/basic.rs @@ -1,20 +1,17 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::{ - block::{ - output::{BasicOutput, Feature, FoundryId, NativeToken, Output, Rent, SimpleTokenScheme, TokenId}, - protocol::protocol_parameters, - rand::{ - address::rand_account_address, - output::{ - feature::{rand_metadata_feature, rand_sender_feature}, - rand_basic_output, - unlock_condition::rand_address_unlock_condition, - }, +use iota_sdk::types::block::{ + output::{BasicOutput, Feature, FoundryId, MinimumOutputAmount, NativeToken, Output, SimpleTokenScheme, TokenId}, + protocol::protocol_parameters, + rand::{ + address::rand_account_address, + output::{ + feature::{rand_metadata_feature, rand_sender_feature}, + rand_basic_output, + unlock_condition::rand_address_unlock_condition, }, }, - ValidationParams, }; use packable::PackableExt; use pretty_assertions::assert_eq; @@ -51,15 +48,15 @@ fn builder() { let metadata = rand_metadata_feature(); let output = builder - .with_minimum_storage_deposit(protocol_parameters.rent_structure()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) .add_unlock_condition(rand_address_unlock_condition()) .with_features([Feature::from(metadata.clone()), sender_1.clone().into()]) - .finish_with_params(ValidationParams::default().with_protocol_parameters(protocol_parameters.clone())) + .finish() .unwrap(); assert_eq!( output.amount(), - Output::Basic(output.clone()).rent_cost(protocol_parameters.rent_structure()) + output.minimum_amount(protocol_parameters.storage_score_parameters()) ); assert_eq!(output.features().metadata(), Some(&metadata)); assert_eq!(output.features().sender(), Some(&sender_1)); diff --git a/sdk/tests/types/output/foundry.rs b/sdk/tests/types/output/foundry.rs index 03e6b364fe..68b1a23ff2 100644 --- a/sdk/tests/types/output/foundry.rs +++ b/sdk/tests/types/output/foundry.rs @@ -3,8 +3,8 @@ use iota_sdk::types::block::{ output::{ - unlock_condition::ImmutableAccountAddressUnlockCondition, FoundryId, FoundryOutput, NativeToken, Output, Rent, - SimpleTokenScheme, TokenId, + unlock_condition::ImmutableAccountAddressUnlockCondition, FoundryId, FoundryOutput, MinimumOutputAmount, + NativeToken, Output, SimpleTokenScheme, TokenId, }, protocol::protocol_parameters, rand::{ @@ -52,14 +52,14 @@ fn builder() { assert!(output.immutable_features().is_empty()); let output = builder - .with_minimum_storage_deposit(protocol_parameters.rent_structure()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(rand_account_address())) - .finish_with_params(&protocol_parameters) + .finish() .unwrap(); assert_eq!( output.amount(), - Output::Foundry(output).rent_cost(protocol_parameters.rent_structure()) + output.minimum_amount(protocol_parameters.storage_score_parameters()) ); } diff --git a/sdk/tests/types/output/nft.rs b/sdk/tests/types/output/nft.rs index 6bbdb7a11f..658bb05b96 100644 --- a/sdk/tests/types/output/nft.rs +++ b/sdk/tests/types/output/nft.rs @@ -48,14 +48,14 @@ fn builder() { assert!(output.immutable_features().is_empty()); let output = builder - .with_minimum_storage_deposit(protocol_parameters.rent_structure()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) .add_unlock_condition(rand_address_unlock_condition()) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(); assert_eq!( output.amount(), - Output::Nft(output).rent_cost(protocol_parameters.rent_structure()) + output.minimum_amount(protocol_parameters.storage_score_parameters()) ); } diff --git a/sdk/tests/types/payload.rs b/sdk/tests/types/payload.rs index 58e4e8a6ff..b8a2ddafee 100644 --- a/sdk/tests/types/payload.rs +++ b/sdk/tests/types/payload.rs @@ -34,7 +34,7 @@ fn transaction() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(&protocol_parameters) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) diff --git a/sdk/tests/types/signed_transaction_payload.rs b/sdk/tests/types/signed_transaction_payload.rs index 09e4762cb7..eef9a27afb 100644 --- a/sdk/tests/types/signed_transaction_payload.rs +++ b/sdk/tests/types/signed_transaction_payload.rs @@ -39,7 +39,7 @@ fn builder_too_few_unlocks() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -75,7 +75,7 @@ fn builder_too_many_unlocks() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -114,7 +114,7 @@ fn pack_unpack_valid() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -155,7 +155,7 @@ fn getters() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) diff --git a/sdk/tests/types/rent.rs b/sdk/tests/types/storage_score.rs similarity index 94% rename from sdk/tests/types/rent.rs rename to sdk/tests/types/storage_score.rs index 3d86541aa5..94e2db354b 100644 --- a/sdk/tests/types/rent.rs +++ b/sdk/tests/types/storage_score.rs @@ -1,7 +1,7 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -// TODO: Re-enable when rent is figured out +// TODO: Re-enable when storage score is figured out // use iota_sdk::types::block::{ // output::{Output, Rent}, diff --git a/sdk/tests/types/transaction.rs b/sdk/tests/types/transaction.rs index 81f5e492a1..5e696ac391 100644 --- a/sdk/tests/types/transaction.rs +++ b/sdk/tests/types/transaction.rs @@ -42,7 +42,7 @@ fn build_valid() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -67,7 +67,7 @@ fn build_valid_with_payload() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -93,7 +93,7 @@ fn build_valid_add_inputs_outputs() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -119,7 +119,7 @@ fn build_invalid_payload_kind() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -158,7 +158,7 @@ fn build_invalid_input_count_low() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -184,7 +184,7 @@ fn build_invalid_input_count_high() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -228,7 +228,7 @@ fn build_invalid_output_count_high() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -255,7 +255,7 @@ fn build_invalid_duplicate_utxo() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -280,7 +280,7 @@ fn build_invalid_accumulated_output() { let output1 = Output::Basic( BasicOutput::build_with_amount(amount1) .add_unlock_condition(AddressUnlockCondition::new(address1)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -290,7 +290,7 @@ fn build_invalid_accumulated_output() { let output2 = Output::Basic( BasicOutput::build_with_amount(amount2) .add_unlock_condition(AddressUnlockCondition::new(address2)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); @@ -315,7 +315,7 @@ fn getters() { let outputs = [Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), )]; let payload = Payload::from(rand_tagged_data_payload()); @@ -343,12 +343,12 @@ fn duplicate_output_nft() { let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let nft_id = NftId::from(bytes); let nft = NftOutput::build_with_amount(1_000_000, nft_id) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -374,12 +374,12 @@ fn duplicate_output_nft_null() { let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let nft_id = NftId::null(); let nft = NftOutput::build_with_amount(1_000_000, nft_id) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -402,12 +402,12 @@ fn duplicate_output_account() { let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let account_id = AccountId::from(bytes); let account = AccountOutput::build_with_amount(1_000_000, account_id) .add_unlock_condition(AddressUnlockCondition::new(address.clone())) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -433,7 +433,7 @@ fn duplicate_output_foundry() { let amount = 1_000_000; let basic = BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let account_id = AccountId::from(bytes); let token_scheme = TokenScheme::Simple(SimpleTokenScheme::new(70, 0, 100).unwrap()); @@ -444,7 +444,7 @@ fn duplicate_output_foundry() { .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(AccountAddress::from( account_id, ))) - .finish_output(protocol_parameters.token_supply()) + .finish_output() .unwrap(); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -470,7 +470,7 @@ fn transactions_capabilities() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(&protocol_parameters) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) diff --git a/sdk/tests/types/unlock/mod.rs b/sdk/tests/types/unlock/mod.rs index 8917cb07f3..dc352f534b 100644 --- a/sdk/tests/types/unlock/mod.rs +++ b/sdk/tests/types/unlock/mod.rs @@ -8,7 +8,7 @@ mod signature; use iota_sdk::types::block::{ rand::signature::rand_signature, - unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, + unlock::{AccountUnlock, AnchorUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, Error, }; use packable::bounded::TryIntoBoundedU16Error; @@ -19,7 +19,8 @@ fn kind() { assert_eq!(Unlock::from(SignatureUnlock::from(rand_signature())).kind(), 0); assert_eq!(Unlock::from(ReferenceUnlock::new(0).unwrap()).kind(), 1); assert_eq!(Unlock::from(AccountUnlock::new(0).unwrap()).kind(), 2); - assert_eq!(Unlock::from(NftUnlock::new(0).unwrap()).kind(), 3); + assert_eq!(Unlock::from(AnchorUnlock::new(0).unwrap()).kind(), 3); + assert_eq!(Unlock::from(NftUnlock::new(0).unwrap()).kind(), 4); } #[test] diff --git a/sdk/tests/types/unlock/nft.rs b/sdk/tests/types/unlock/nft.rs index 356c0d9c71..70204ae4ca 100644 --- a/sdk/tests/types/unlock/nft.rs +++ b/sdk/tests/types/unlock/nft.rs @@ -7,7 +7,7 @@ use pretty_assertions::assert_eq; #[test] fn kind() { - assert_eq!(NftUnlock::KIND, 3); + assert_eq!(NftUnlock::KIND, 4); } #[test] diff --git a/sdk/tests/wallet/account_recovery.rs b/sdk/tests/wallet/account_recovery.rs deleted file mode 100644 index 9113d188ba..0000000000 --- a/sdk/tests/wallet/account_recovery.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -// Tests for recovering accounts from mnemonic without a backup - -use std::time::Duration; - -use iota_sdk::{ - client::{ - api::GetAddressesOptions, - constants::SHIMMER_COIN_TYPE, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, - Client, - }, - wallet::Result, -}; -use pretty_assertions::assert_eq; - -use crate::wallet::common::{make_wallet, setup, tear_down}; - -#[ignore] -#[tokio::test] -async fn account_recovery_empty() -> Result<()> { - let storage_path = "test-storage/account_recovery_empty"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - let accounts = wallet.recover_accounts(0, 2, 2, None).await?; - - // accounts should be empty if no account was created before and no account was found with balance - assert_eq!(0, accounts.len()); - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn account_recovery_existing_accounts() -> Result<()> { - let storage_path = "test-storage/account_recovery_existing_accounts"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - // create two accounts - wallet.create_account().finish().await?; - wallet.create_account().finish().await?; - - let accounts = wallet.recover_accounts(0, 2, 2, None).await?; - - // accounts should still be ordered - for (index, account) in accounts.iter().enumerate() { - assert_eq!(&(index as u32), account.details().await.index()); - } - // accounts should be 2 because we created 2 accounts before and no new account was found with balance - assert_eq!(2, accounts.len()); - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn account_recovery_with_balance_and_empty_addresses() -> Result<()> { - let storage_path = "test-storage/account_recovery_with_balance_and_empty_addresses"; - setup(storage_path)?; - - let mnemonic = Client::generate_mnemonic()?; - let client = Client::builder() - .with_node(crate::wallet::common::NODE_LOCAL)? - .finish() - .await?; - - let secret_manager = SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic.clone())?); - - let addresses = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::from_client(&client) - .await? - .with_coin_type(SHIMMER_COIN_TYPE) - .with_account_index(2) - .with_range(2..3), - ) - .await?; - - // Add funds to the address with account index 2 and address key_index 2, so recover works - iota_sdk::client::request_funds_from_faucet(crate::wallet::common::FAUCET_URL, &addresses[0]).await?; - - // Wait for faucet transaction - tokio::time::sleep(Duration::new(10, 0)).await; - - let wallet = make_wallet(storage_path, Some(mnemonic), None).await?; - - let accounts = wallet.recover_accounts(0, 3, 2, None).await?; - - // accounts should still be ordered - for (index, account) in accounts.iter().enumerate() { - assert_eq!(&(index as u32), account.details().await.index()); - } - // accounts should be 3 because account with index 2 has balance - assert_eq!(3, accounts.len()); - - let account_with_balance = accounts[2].details().await; - // should have 3 addresses - assert_eq!(3, account_with_balance.public_addresses().len()); - tear_down(storage_path) -} diff --git a/sdk/tests/wallet/accounts.rs b/sdk/tests/wallet/accounts.rs deleted file mode 100644 index cfa755691c..0000000000 --- a/sdk/tests/wallet/accounts.rs +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use iota_sdk::wallet::Result; -use pretty_assertions::assert_eq; -#[cfg(feature = "stronghold")] -use { - iota_sdk::client::{ - constants::SHIMMER_COIN_TYPE, - secret::{stronghold::StrongholdSecretManager, SecretManager}, - }, - iota_sdk::wallet::{ClientOptions, Wallet}, -}; - -use crate::wallet::common::{make_wallet, setup, tear_down}; - -#[tokio::test] -async fn account_ordering() -> Result<()> { - let storage_path = "test-storage/account_ordering"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - for _ in 0..100 { - let _account = wallet.create_account().finish().await?; - } - std::fs::remove_dir_all("test-storage/account_ordering").ok(); - #[cfg(debug_assertions)] - wallet.verify_integrity().await?; - tear_down(storage_path) -} - -#[cfg(feature = "storage")] -#[tokio::test] -async fn remove_latest_account() -> Result<()> { - let storage_path = "test-storage/remove_latest_account"; - setup(storage_path)?; - - let recreated_account_index = { - let wallet = make_wallet(storage_path, None, None).await?; - - // Create two accounts. - let first_account = wallet.create_account().finish().await?; - let _second_account = wallet.create_account().finish().await?; - assert!(wallet.get_accounts().await.unwrap().len() == 2); - - // Remove `second_account`. - wallet - .remove_latest_account() - .await - .expect("cannot remove latest account"); - - // Check if the `second_account` was removed successfully. - let accounts = wallet.get_accounts().await.unwrap(); - assert!(accounts.len() == 1); - assert_eq!( - *accounts.get(0).unwrap().details().await.index(), - *first_account.details().await.index() - ); - - // Remove `first_account`. - wallet - .remove_latest_account() - .await - .expect("cannot remove latest account"); - - // Check if the `first_account` was removed successfully. All accounts should be removed. - let accounts = wallet.get_accounts().await.unwrap(); - assert!(accounts.is_empty()); - - // Try remove another time (even if there is nothing to remove). - wallet - .remove_latest_account() - .await - .expect("cannot remove latest account"); - - let accounts = wallet.get_accounts().await.unwrap(); - assert!(accounts.is_empty()); - - // Recreate a new account and return their index. - - let recreated_account = wallet.create_account().finish().await?; - assert_eq!(wallet.get_accounts().await.unwrap().len(), 1); - let recreated_account_index = *recreated_account.details().await.index(); - - recreated_account_index - }; - - // Restore dropped `Wallet` from above. - let wallet = make_wallet(storage_path, None, None).await?; - - let accounts = wallet.get_accounts().await.unwrap(); - - // Check if accounts with `recreated_account_index` exist. - assert_eq!(accounts.len(), 1); - assert_eq!( - *accounts.get(0).unwrap().details().await.index(), - recreated_account_index - ); - - #[cfg(debug_assertions)] - wallet.verify_integrity().await?; - - tear_down(storage_path) -} - -#[tokio::test] -async fn account_alias_already_exists() -> Result<()> { - let storage_path = "test-storage/account_alias_already_exists"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - let _account = wallet.create_account().with_alias("Alice").finish().await?; - assert!(&wallet.create_account().with_alias("Alice").finish().await.is_err()); - assert!(&wallet.create_account().with_alias("alice").finish().await.is_err()); - assert!(&wallet.create_account().with_alias("ALICE").finish().await.is_err()); - // Other alias works - assert!(&wallet.create_account().with_alias("Bob").finish().await.is_ok()); - - tear_down(storage_path) -} - -#[tokio::test] -async fn account_rename_alias() -> Result<()> { - let storage_path = "test-storage/account_rename_alias"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - let account = wallet.create_account().with_alias("Alice").finish().await?; - - assert_eq!(account.alias().await, "Alice".to_string()); - assert_eq!(account.details().await.alias(), "Alice"); - - // rename account - account.set_alias("Bob").await?; - - assert_eq!(account.alias().await, "Bob".to_string()); - assert_eq!(account.details().await.alias(), "Bob"); - - tear_down(storage_path) -} - -#[tokio::test] -async fn account_first_address_exists() -> Result<()> { - let storage_path = "test-storage/account_first_address_exists"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - let account = wallet.create_account().with_alias("Alice").finish().await?; - - // When the account is generated, the first public address also gets generated and added to it - assert_eq!(account.addresses().await.len(), 1); - // First address is a public address - assert_eq!(account.addresses().await.first().unwrap().internal(), &false); - - tear_down(storage_path) -} - -#[cfg(feature = "stronghold")] -#[tokio::test] -async fn account_creation_stronghold() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/account_creation_stronghold"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node("http://localhost:14265")?; - let mnemonic = crypto::keys::bip39::Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_owned()); - - // Create directory before, because stronghold would panic otherwise - std::fs::create_dir_all(storage_path).ok(); - let stronghold_secret_manager = StrongholdSecretManager::builder() - .password("some_hopefully_secure_password".to_owned()) - .build("test-storage/account_creation_stronghold/test.stronghold")?; - stronghold_secret_manager.store_mnemonic(mnemonic).await?; - let secret_manager = SecretManager::Stronghold(stronghold_secret_manager); - - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(secret_manager) - .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE); - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - let _account = wallet.create_account().finish().await?; - - tear_down(storage_path) -} diff --git a/sdk/tests/wallet/address_generation.rs b/sdk/tests/wallet/address_generation.rs index 7c2258a6b7..eccd3aa430 100644 --- a/sdk/tests/wallet/address_generation.rs +++ b/sdk/tests/wallet/address_generation.rs @@ -3,6 +3,7 @@ #[cfg(feature = "stronghold")] use crypto::keys::bip39::Mnemonic; +use crypto::keys::bip44::Bip44; #[cfg(feature = "stronghold")] use iota_sdk::client::secret::stronghold::StrongholdSecretManager; #[cfg(feature = "ledger_nano")] @@ -34,7 +35,7 @@ async fn wallet_address_generation_mnemonic() -> Result<()> { let mut wallet_builder = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_client_options(client_options) - .with_coin_type(IOTA_COIN_TYPE); + .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); #[cfg(feature = "storage")] { @@ -73,7 +74,7 @@ async fn wallet_address_generation_stronghold() -> Result<()> { let mut wallet_builder = Wallet::builder() .with_secret_manager(SecretManager::Stronghold(secret_manager)) .with_client_options(client_options) - .with_coin_type(IOTA_COIN_TYPE); + .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); #[cfg(feature = "storage")] { wallet_builder = wallet_builder.with_storage_path(storage_path); @@ -106,7 +107,7 @@ async fn wallet_address_generation_ledger() -> Result<()> { let mut wallet_builder = Wallet::builder() .with_secret_manager(SecretManager::LedgerNano(secret_manager)) .with_client_options(client_options) - .with_coin_type(IOTA_COIN_TYPE); + .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); #[cfg(feature = "storage")] { @@ -129,7 +130,7 @@ async fn wallet_address_generation_ledger() -> Result<()> { wallet .listen([WalletEventType::LedgerAddressGeneration], move |event| { - if let WalletEvent::LedgerAddressGeneration(address) = &event.event { + if let WalletEvent::LedgerAddressGeneration(address) = event { sender .try_send(address.address.clone()) .expect("too many LedgerAddressGeneration events"); @@ -176,30 +177,30 @@ async fn wallet_address_generation_ledger() -> Result<()> { tear_down(storage_path) } -#[tokio::test] -async fn wallet_address_generation_placeholder() -> Result<()> { - let storage_path = "test-storage/wallet_address_generation_placeholder"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(SecretManager::Placeholder) - .with_client_options(client_options) - .with_coin_type(IOTA_COIN_TYPE); - - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - if let Err(Error::Client(error)) = wallet.generate_ed25519_address(0, 0, None).await { - assert!(matches!(*error, ClientError::PlaceholderSecretManager)) - } else { - panic!("expected PlaceholderSecretManager") - } - - tear_down(storage_path) -} +// #[tokio::test] +// async fn wallet_address_generation_placeholder() -> Result<()> { +// let storage_path = "test-storage/wallet_address_generation_placeholder"; +// setup(storage_path)?; + +// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; + +// #[allow(unused_mut)] +// let mut wallet_builder = Wallet::builder() +// .with_secret_manager(SecretManager::Placeholder) +// .with_client_options(client_options) +// .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); + +// #[cfg(feature = "storage")] +// { +// wallet_builder = wallet_builder.with_storage_path(storage_path); +// } +// let wallet = wallet_builder.finish().await?; + +// if let Err(Error::Client(error)) = wallet.generate_ed25519_address(0, 0, None).await { +// assert!(matches!(*error, ClientError::PlaceholderSecretManager)) +// } else { +// panic!("expected PlaceholderSecretManager") +// } + +// tear_down(storage_path) +// } diff --git a/sdk/tests/wallet/backup_restore.rs b/sdk/tests/wallet/backup_restore.rs index e4b283e82e..7fd0ceec97 100644 --- a/sdk/tests/wallet/backup_restore.rs +++ b/sdk/tests/wallet/backup_restore.rs @@ -1,571 +1,577 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::path::PathBuf; - -use crypto::keys::bip39::Mnemonic; -use iota_sdk::{ - client::{ - constants::{IOTA_COIN_TYPE, SHIMMER_COIN_TYPE}, - node_manager::node::{Node, NodeDto}, - secret::{mnemonic::MnemonicSecretManager, stronghold::StrongholdSecretManager, SecretManager}, - }, - wallet::{ClientOptions, Result, Wallet}, -}; -use pretty_assertions::assert_eq; -use url::Url; - -use crate::wallet::common::{setup, tear_down, NODE_LOCAL, NODE_OTHER}; - -// Backup and restore with Stronghold -#[tokio::test] -async fn backup_and_restore() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/backup_and_restore"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - - let stronghold_password = "some_hopefully_secure_password".to_owned(); - - // Create directory if not existing, because stronghold panics otherwise - std::fs::create_dir_all(storage_path).ok(); - let stronghold = StrongholdSecretManager::builder() - .password(stronghold_password.clone()) - .build("test-storage/backup_and_restore/1.stronghold")?; - - stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(client_options.clone()) - .with_coin_type(SHIMMER_COIN_TYPE) - .with_storage_path("test-storage/backup_and_restore/1") - .finish() - .await?; - - let account = wallet.create_account().with_alias("Alice").finish().await?; - - wallet - .backup( - PathBuf::from("test-storage/backup_and_restore/backup.stronghold"), - stronghold_password.clone(), - ) - .await?; - - // restore from backup - - let stronghold = StrongholdSecretManager::builder().build("test-storage/backup_and_restore/2.stronghold")?; - - let restore_wallet = Wallet::builder() - .with_storage_path("test-storage/backup_and_restore/2") - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) - // Build with a different coin type, to check if it gets replaced by the one from the backup - .with_coin_type(IOTA_COIN_TYPE) - .finish() - .await?; - - // Wrong password fails - restore_wallet - .restore_backup( - PathBuf::from("test-storage/backup_and_restore/backup.stronghold"), - "wrong password".to_owned(), - None, - None, - ) - .await - .unwrap_err(); - - // Correct password works, even after trying with a wrong one before - restore_wallet - .restore_backup( - PathBuf::from("test-storage/backup_and_restore/backup.stronghold"), - stronghold_password, - None, - None, - ) - .await?; - - // Validate restored data - - // Restored coin type is used - let new_account = restore_wallet.create_account().finish().await?; - assert_eq!(new_account.details().await.coin_type(), &SHIMMER_COIN_TYPE); - - // compare restored client options - let client_options = restore_wallet.client_options().await; - let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); - assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); - - // Get account - let recovered_account = restore_wallet.get_account("Alice").await?; - assert_eq!(account.addresses().await, recovered_account.addresses().await); - - // secret manager is the same - assert_eq!( - account.generate_ed25519_addresses(1, None).await?, - recovered_account.generate_ed25519_addresses(1, None).await? - ); - tear_down(storage_path) -} - -// Backup and restore with Stronghold and MnemonicSecretManager -#[tokio::test] -async fn backup_and_restore_mnemonic_secret_manager() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/backup_and_restore_mnemonic_secret_manager"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - - let secret_manager = MnemonicSecretManager::try_from_mnemonic( - "inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_owned(), - )?; - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) - .with_client_options(client_options.clone()) - .with_coin_type(SHIMMER_COIN_TYPE) - .with_storage_path("test-storage/backup_and_restore_mnemonic_secret_manager/1") - .finish() - .await?; - - let account = wallet.create_account().with_alias("Alice").finish().await?; - - let stronghold_password = "some_hopefully_secure_password".to_owned(); - - // Create directory if not existing, because stronghold panics otherwise - std::fs::create_dir_all(storage_path).ok(); - wallet - .backup( - PathBuf::from("test-storage/backup_and_restore_mnemonic_secret_manager/backup.stronghold"), - stronghold_password.clone(), - ) - .await?; - - // restore from backup - - let secret_manager = MnemonicSecretManager::try_from_mnemonic( - "inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_owned(), - )?; - - let restore_wallet = Wallet::builder() - .with_storage_path("test-storage/backup_and_restore_mnemonic_secret_manager/2") - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) - // Build with a different coin type, to check if it gets replaced by the one from the backup - .with_coin_type(IOTA_COIN_TYPE) - .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) - .finish() - .await?; - - restore_wallet - .restore_backup( - PathBuf::from("test-storage/backup_and_restore_mnemonic_secret_manager/backup.stronghold"), - stronghold_password, - None, - None, - ) - .await?; - - // Validate restored data - - // Restored coin type is used - let new_account = restore_wallet.create_account().finish().await?; - assert_eq!(new_account.details().await.coin_type(), &SHIMMER_COIN_TYPE); - - // compare restored client options - let client_options = restore_wallet.client_options().await; - let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); - assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); - - // Get account - let recovered_account = restore_wallet.get_account("Alice").await?; - assert_eq!(account.addresses().await, recovered_account.addresses().await); - - // secret manager is the same - assert_eq!( - account.generate_ed25519_addresses(1, None).await?, - recovered_account.generate_ed25519_addresses(1, None).await? - ); - tear_down(storage_path) -} - -// Backup and restore with Stronghold -#[tokio::test] -async fn backup_and_restore_different_coin_type() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/backup_and_restore_different_coin_type"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - - let stronghold_password = "some_hopefully_secure_password".to_owned(); - - // Create directory if not existing, because stronghold panics otherwise - std::fs::create_dir_all(storage_path).ok(); - let stronghold = StrongholdSecretManager::builder() - .password(stronghold_password.clone()) - .build("test-storage/backup_and_restore_different_coin_type/1.stronghold")?; - - stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(client_options.clone()) - .with_coin_type(SHIMMER_COIN_TYPE) - .with_storage_path("test-storage/backup_and_restore_different_coin_type/1") - .finish() - .await?; - - // Create one account - wallet.create_account().with_alias("Alice").finish().await?; - - wallet - .backup( - PathBuf::from("test-storage/backup_and_restore_different_coin_type/backup.stronghold"), - stronghold_password.clone(), - ) - .await?; - - // restore from backup - - let stronghold = - StrongholdSecretManager::builder().build("test-storage/backup_and_restore_different_coin_type/2.stronghold")?; - - let restore_wallet = Wallet::builder() - .with_storage_path("test-storage/backup_and_restore_different_coin_type/2") - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) - // Build with a different coin type, to check if it gets replaced by the one from the backup - .with_coin_type(IOTA_COIN_TYPE) - .finish() - .await?; - - // restore with ignore_if_coin_type_mismatch: Some(true) to not overwrite the coin type - restore_wallet - .restore_backup( - PathBuf::from("test-storage/backup_and_restore_different_coin_type/backup.stronghold"), - stronghold_password, - Some(true), - None, - ) - .await?; - - // Validate restored data - - // No accounts restored, because the coin type was different - assert!(restore_wallet.get_accounts().await?.is_empty()); - - // Restored coin type is not used and it's still the same one - let new_account = restore_wallet.create_account().finish().await?; - assert_eq!(new_account.details().await.coin_type(), &IOTA_COIN_TYPE); - // secret manager is the same - assert_eq!( - new_account.first_address_bech32().await, - "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" - ); - - // compare restored client options - let client_options = restore_wallet.client_options().await; - let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_OTHER).unwrap())); - assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); - - tear_down(storage_path) -} - -// Backup and restore with Stronghold -#[tokio::test] -async fn backup_and_restore_same_coin_type() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/backup_and_restore_same_coin_type"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - - let stronghold_password = "some_hopefully_secure_password".to_owned(); - - // Create directory if not existing, because stronghold panics otherwise - std::fs::create_dir_all(storage_path).ok(); - let stronghold = StrongholdSecretManager::builder() - .password(stronghold_password.clone()) - .build("test-storage/backup_and_restore_same_coin_type/1.stronghold")?; - - stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(client_options.clone()) - .with_coin_type(SHIMMER_COIN_TYPE) - .with_storage_path("test-storage/backup_and_restore_same_coin_type/1") - .finish() - .await?; - - // Create one account - let account_before_backup = wallet.create_account().with_alias("Alice").finish().await?; - - wallet - .backup( - PathBuf::from("test-storage/backup_and_restore_same_coin_type/backup.stronghold"), - stronghold_password.clone(), - ) - .await?; - - // restore from backup - - let stronghold = - StrongholdSecretManager::builder().build("test-storage/backup_and_restore_same_coin_type/2.stronghold")?; - - let restore_wallet = Wallet::builder() - .with_storage_path("test-storage/backup_and_restore_same_coin_type/2") - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) - // Build with same coin type - .with_coin_type(SHIMMER_COIN_TYPE) - .finish() - .await?; - - // restore with ignore_if_coin_type_mismatch: Some(true) to not overwrite the coin type - restore_wallet - .restore_backup( - PathBuf::from("test-storage/backup_and_restore_same_coin_type/backup.stronghold"), - stronghold_password, - Some(true), - None, - ) - .await?; - - // Validate restored data - - // The account is restored, because the coin type is the same - let restored_accounts = restore_wallet.get_accounts().await?; - assert_eq!(restored_accounts.len(), 1); - - // addresses are still there - assert_eq!( - restored_accounts[0].addresses().await, - account_before_backup.addresses().await - ); - - // compare client options, they are not restored - let client_options = restore_wallet.client_options().await; - let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_OTHER).unwrap())); - assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); - - tear_down(storage_path) -} - -// Backup and restore with Stronghold -#[tokio::test] -async fn backup_and_restore_different_coin_type_dont_ignore() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/backup_and_restore_different_coin_type_dont_ignore"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_OTHER)?; - - let stronghold_password = "some_hopefully_secure_password".to_owned(); - - // Create directory if not existing, because stronghold panics otherwise - std::fs::create_dir_all(storage_path).ok(); - let stronghold = StrongholdSecretManager::builder() - .password(stronghold_password.clone()) - .build("test-storage/backup_and_restore_different_coin_type_dont_ignore/1.stronghold")?; - - stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(client_options.clone()) - .with_coin_type(SHIMMER_COIN_TYPE) - .with_storage_path("test-storage/backup_and_restore_different_coin_type_dont_ignore/1") - .finish() - .await?; - - // Create one account - let account = wallet.create_account().with_alias("Alice").finish().await?; - - wallet - .backup( - PathBuf::from("test-storage/backup_and_restore_different_coin_type_dont_ignore/backup.stronghold"), - stronghold_password.clone(), - ) - .await?; - - // restore from backup - - let stronghold = StrongholdSecretManager::builder() - .build("test-storage/backup_and_restore_different_coin_type_dont_ignore/2.stronghold")?; - - let restore_wallet = Wallet::builder() - .with_storage_path("test-storage/backup_and_restore_different_coin_type_dont_ignore/2") - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(ClientOptions::new().with_node(NODE_LOCAL)?) - // Build with a different coin type, to check if it gets replaced by the one from the backup - .with_coin_type(IOTA_COIN_TYPE) - .finish() - .await?; - - // restore with ignore_if_coin_type_mismatch: Some(true) to not overwrite the coin type - restore_wallet - .restore_backup( - PathBuf::from("test-storage/backup_and_restore_different_coin_type_dont_ignore/backup.stronghold"), - stronghold_password, - Some(false), - None, - ) - .await?; - - // Validate restored data - - // No accounts restored, because the coin type was different - let restored_account = restore_wallet.get_account("Alice").await?; - assert_eq!( - account.first_address_bech32().await, - restored_account.first_address_bech32().await, - ); - - // Restored coin type is used - let new_account = restore_wallet.create_account().finish().await?; - assert_eq!(new_account.details().await.coin_type(), &SHIMMER_COIN_TYPE); - // secret manager is restored - assert_eq!( - new_account.first_address_bech32().await, - "smr1qzvjvjyqxgfx4f0m3xhn2rj24e03dwsmjz082735y3wx88v2gudu2afedhu" - ); - - // compare client options, they are not restored - let client_options = restore_wallet.client_options().await; - let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); - assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); - - tear_down(storage_path) -} - -#[tokio::test] -async fn backup_and_restore_bech32_hrp_mismatch() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/backup_and_restore_bech32_hrp_mismatch"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - - let stronghold_password = "some_hopefully_secure_password".to_owned(); - - // Create directory if not existing, because stronghold panics otherwise - std::fs::create_dir_all(storage_path).ok(); - let stronghold = StrongholdSecretManager::builder() - .password(stronghold_password.clone()) - .build("test-storage/backup_and_restore_bech32_hrp_mismatch/1.stronghold")?; - - stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); - - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(client_options.clone()) - .with_coin_type(SHIMMER_COIN_TYPE) - .with_storage_path("test-storage/backup_and_restore_bech32_hrp_mismatch/1") - .finish() - .await?; - - let account = wallet.create_account().with_alias("Alice").finish().await?; - - wallet - .backup( - PathBuf::from("test-storage/backup_and_restore_bech32_hrp_mismatch/backup.stronghold"), - stronghold_password.clone(), - ) - .await?; - - // restore from backup - - let stronghold = - StrongholdSecretManager::builder().build("test-storage/backup_and_restore_bech32_hrp_mismatch/2.stronghold")?; - - let restore_wallet = Wallet::builder() - .with_storage_path("test-storage/backup_and_restore_bech32_hrp_mismatch/2") - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) - // Build with a different coin type, to check if it gets replaced by the one from the backup - .with_coin_type(IOTA_COIN_TYPE) - .finish() - .await?; - - restore_wallet - .restore_backup( - PathBuf::from("test-storage/backup_and_restore_bech32_hrp_mismatch/backup.stronghold"), - stronghold_password, - None, - Some(iota_sdk::types::block::address::Hrp::from_str_unchecked("otherhrp")), - ) - .await?; - - // Validate restored data - - // compare restored client options - let client_options = restore_wallet.client_options().await; - let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); - assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); - - // No restored accounts because the bech32 hrp was different - let restored_accounts = restore_wallet.get_accounts().await?; - assert!(restored_accounts.is_empty()); - - // Restored coin type is used - let new_account = restore_wallet.create_account().finish().await?; - assert_eq!(new_account.details().await.coin_type(), &SHIMMER_COIN_TYPE); - - // secret manager is the same - assert_eq!( - account.generate_ed25519_addresses(1, None).await?, - new_account.generate_ed25519_addresses(1, None).await? - ); - tear_down(storage_path) -} - -// Restore a Stronghold snapshot without secret manager data -#[tokio::test] -async fn restore_no_secret_manager_data() -> Result<()> { - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let storage_path = "test-storage/restore_no_secret_manager_data"; - setup(storage_path)?; - - let stronghold = StrongholdSecretManager::builder().build(storage_path.to_string() + "/wallet.stronghold")?; - - let restore_wallet = Wallet::builder() - .with_storage_path(storage_path) - .with_secret_manager(SecretManager::Stronghold(stronghold)) - .with_client_options(ClientOptions::new().with_node(NODE_LOCAL)?) - .with_coin_type(IOTA_COIN_TYPE) - .finish() - .await?; - - let stronghold_password = "some_hopefully_secure_password".to_owned(); - - restore_wallet - .restore_backup( - PathBuf::from("./tests/wallet/fixtures/no_secret_manager_data.stronghold"), - stronghold_password.clone(), - None, - None, - ) - .await?; - - restore_wallet.set_stronghold_password(stronghold_password).await?; - - // Backup is restored also without any secret manager data inside and the seed is available - // Backup was created with mnemonic: "inhale gorilla deny three celery song category owner lottery rent author - // wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak" - assert_eq!( - restore_wallet.generate_ed25519_address(0, 0, None).await?.to_string(), - "0xc2ece328eb3d9bbc51d471dd17fd3665aa8c6bae63c78d64c13977efbb8b011e" - ); - tear_down(storage_path) -} +// use std::path::PathBuf; + +// use crypto::keys::bip39::Mnemonic; +// use iota_sdk::{ +// client::{ +// api::GetAddressesOptions, +// constants::{IOTA_COIN_TYPE, SHIMMER_COIN_TYPE}, +// node_manager::node::{Node, NodeDto}, +// secret::{mnemonic::MnemonicSecretManager, stronghold::StrongholdSecretManager, SecretManager}, +// }, +// crypto::keys::bip44::Bip44, +// wallet::{ClientOptions, Result, Wallet}, +// }; +// use pretty_assertions::assert_eq; +// use url::Url; + +// use crate::wallet::common::{setup, tear_down, NODE_LOCAL, NODE_OTHER}; + +// // Backup and restore with Stronghold +// #[tokio::test] +// async fn backup_and_restore() -> Result<()> { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + +// let storage_path = "test-storage/backup_and_restore"; +// setup(storage_path)?; + +// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; + +// let stronghold_password = "some_hopefully_secure_password".to_owned(); + +// // Create directory if not existing, because stronghold panics otherwise +// std::fs::create_dir_all(storage_path).ok(); +// let stronghold = StrongholdSecretManager::builder() +// .password(stronghold_password.clone()) +// .build("test-storage/backup_and_restore/1.stronghold")?; + +// stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); + +// let wallet = Wallet::builder() +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(client_options.clone()) +// .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) +// .with_storage_path("test-storage/backup_and_restore/1") +// .finish() +// .await?; + +// wallet +// .backup( +// PathBuf::from("test-storage/backup_and_restore/backup.stronghold"), +// stronghold_password.clone(), +// ) +// .await?; + +// // restore from backup + +// let stronghold = StrongholdSecretManager::builder().build("test-storage/backup_and_restore/2.stronghold")?; + +// let restored_wallet = Wallet::builder() +// .with_storage_path("test-storage/backup_and_restore/2") +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) +// // Build with a different coin type, to check if it gets replaced by the one from the backup +// .with_bip_path(Bip44::new(IOTA_COIN_TYPE)) +// .finish() +// .await?; + +// // Wrong password fails +// restored_wallet +// .restore_backup( +// PathBuf::from("test-storage/backup_and_restore/backup.stronghold"), +// "wrong password".to_owned(), +// None, +// None, +// ) +// .await +// .unwrap_err(); + +// // Correct password works, even after trying with a wrong one before +// restored_wallet +// .restore_backup( +// PathBuf::from("test-storage/backup_and_restore/backup.stronghold"), +// stronghold_password, +// None, +// None, +// ) +// .await?; + +// // Validate restored data + +// // Restored coin type is used +// assert_eq!(restored_wallet.bip_path().await.unwrap().coin_type, SHIMMER_COIN_TYPE); + +// // compare restored client options +// let client_options = restored_wallet.client_options().await; +// let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); +// assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); + +// assert_eq!(wallet.address().await, restored_wallet.address().await); + +// // secret manager is the same +// assert_eq!( +// wallet +// .get_secret_manager() +// .read() +// .await +// .generate_ed25519_addresses(GetAddressesOptions { +// coin_type: SHIMMER_COIN_TYPE, +// range: 0..1, +// ..Default::default() +// }) +// .await?, +// restored_wallet +// .get_secret_manager() +// .read() +// .await +// .generate_ed25519_addresses(GetAddressesOptions { +// coin_type: SHIMMER_COIN_TYPE, +// range: 0..1, +// ..Default::default() +// }) +// .await?, +// ); +// tear_down(storage_path) +// } + +// // Backup and restore with Stronghold and MnemonicSecretManager +// #[tokio::test] +// async fn backup_and_restore_mnemonic_secret_manager() -> Result<()> { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + +// let storage_path = "test-storage/backup_and_restore_mnemonic_secret_manager"; +// setup(storage_path)?; + +// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; + +// let secret_manager = MnemonicSecretManager::try_from_mnemonic( +// "inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain +// glad warm early rain clutch slab august bleak".to_owned(), )?; + +// let wallet = Wallet::builder() +// .with_secret_manager(SecretManager::Mnemonic(secret_manager)) +// .with_client_options(client_options.clone()) +// .with_coin_type(SHIMMER_COIN_TYPE) +// .with_storage_path("test-storage/backup_and_restore_mnemonic_secret_manager/1") +// .finish() +// .await?; + +// let stronghold_password = "some_hopefully_secure_password".to_owned(); + +// // Create directory if not existing, because stronghold panics otherwise +// std::fs::create_dir_all(storage_path).ok(); +// wallet +// .backup( +// PathBuf::from("test-storage/backup_and_restore_mnemonic_secret_manager/backup.stronghold"), +// stronghold_password.clone(), +// ) +// .await?; + +// // restore from backup + +// let secret_manager = MnemonicSecretManager::try_from_mnemonic( +// "inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain +// glad warm early rain clutch slab august bleak".to_owned(), )?; + +// let restore_wallet = Wallet::builder() +// .with_storage_path("test-storage/backup_and_restore_mnemonic_secret_manager/2") +// .with_secret_manager(SecretManager::Mnemonic(secret_manager)) +// // Build with a different coin type, to check if it gets replaced by the one from the backup +// .with_coin_type(IOTA_COIN_TYPE) +// .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) +// .finish() +// .await?; + +// restore_wallet +// .restore_backup( +// PathBuf::from("test-storage/backup_and_restore_mnemonic_secret_manager/backup.stronghold"), +// stronghold_password, +// None, +// None, +// ) +// .await?; + +// // Validate restored data + +// // Restored coin type is used +// let new_wallet = restore_wallet; +// assert_eq!(new_wallet.data().await.coin_type(), &SHIMMER_COIN_TYPE); + +// // compare restored client options +// let client_options = restore_wallet.client_options().await; +// let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); +// assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); + +// // Get wallet +// let recovered_wallet = restore_wallet; +// assert_eq!(wallet.address().await, recovered_wallet.address().await); + +// // secret manager is the same +// assert_eq!( +// wallet.generate_ed25519_addresses(1, None).await?, +// recovered_wallet.generate_ed25519_addresses(1, None).await? +// ); +// tear_down(storage_path) +// } + +// // Backup and restore with Stronghold +// #[tokio::test] +// async fn backup_and_restore_different_coin_type() -> Result<()> { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + +// let storage_path = "test-storage/backup_and_restore_different_coin_type"; +// setup(storage_path)?; + +// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; + +// let stronghold_password = "some_hopefully_secure_password".to_owned(); + +// // Create directory if not existing, because stronghold panics otherwise +// std::fs::create_dir_all(storage_path).ok(); +// let stronghold = StrongholdSecretManager::builder() +// .password(stronghold_password.clone()) +// .build("test-storage/backup_and_restore_different_coin_type/1.stronghold")?; + +// stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); + +// let wallet = Wallet::builder() +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(client_options.clone()) +// .with_coin_type(SHIMMER_COIN_TYPE) +// .with_storage_path("test-storage/backup_and_restore_different_coin_type/1") +// .finish() +// .await?; + +// wallet +// .backup( +// PathBuf::from("test-storage/backup_and_restore_different_coin_type/backup.stronghold"), +// stronghold_password.clone(), +// ) +// .await?; + +// // restore from backup + +// let stronghold = +// StrongholdSecretManager::builder().build("test-storage/backup_and_restore_different_coin_type/2.stronghold")? +// ; + +// let restore_wallet = Wallet::builder() +// .with_storage_path("test-storage/backup_and_restore_different_coin_type/2") +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) +// // Build with a different coin type, to check if it gets replaced by the one from the backup +// .with_coin_type(IOTA_COIN_TYPE) +// .finish() +// .await?; + +// // restore with ignore_if_coin_type_mismatch: Some(true) to not overwrite the coin type +// restore_wallet +// .restore_backup( +// PathBuf::from("test-storage/backup_and_restore_different_coin_type/backup.stronghold"), +// stronghold_password, +// Some(true), +// None, +// ) +// .await?; + +// // Validate restored data + +// // No wallet restored, because the coin type was different +// assert!(restore_wallet.get_wallet_data().await?.is_empty()); + +// // Restored coin type is not used and it's still the same one +// let new_wallet = restore_wallet; +// assert_eq!(new_wallet.data().await.coin_type(), &IOTA_COIN_TYPE); +// // secret manager is the same +// assert_eq!( +// new_wallet.address().await, +// "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" +// ); + +// // compare restored client options +// let client_options = restore_wallet.client_options().await; +// let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_OTHER).unwrap())); +// assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); + +// tear_down(storage_path) +// } + +// // Backup and restore with Stronghold +// #[tokio::test] +// async fn backup_and_restore_same_coin_type() -> Result<()> { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + +// let storage_path = "test-storage/backup_and_restore_same_coin_type"; +// setup(storage_path)?; + +// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; + +// let stronghold_password = "some_hopefully_secure_password".to_owned(); + +// // Create directory if not existing, because stronghold panics otherwise +// std::fs::create_dir_all(storage_path).ok(); +// let stronghold = StrongholdSecretManager::builder() +// .password(stronghold_password.clone()) +// .build("test-storage/backup_and_restore_same_coin_type/1.stronghold")?; + +// stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); + +// let wallet = Wallet::builder() +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(client_options.clone()) +// .with_coin_type(SHIMMER_COIN_TYPE) +// .with_storage_path("test-storage/backup_and_restore_same_coin_type/1") +// .finish() +// .await?; + +// let wallet_before_backup = wallet; + +// wallet +// .backup( +// PathBuf::from("test-storage/backup_and_restore_same_coin_type/backup.stronghold"), +// stronghold_password.clone(), +// ) +// .await?; + +// // restore from backup + +// let stronghold = +// StrongholdSecretManager::builder().build("test-storage/backup_and_restore_same_coin_type/2.stronghold")?; + +// let restore_wallet = Wallet::builder() +// .with_storage_path("test-storage/backup_and_restore_same_coin_type/2") +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) +// // Build with same coin type +// .with_coin_type(SHIMMER_COIN_TYPE) +// .finish() +// .await?; + +// // restore with ignore_if_coin_type_mismatch: Some(true) to not overwrite the coin type +// restore_wallet +// .restore_backup( +// PathBuf::from("test-storage/backup_and_restore_same_coin_type/backup.stronghold"), +// stronghold_password, +// Some(true), +// None, +// ) +// .await?; + +// // Validate restored data + +// // The wallet is restored, because the coin type is the same +// let restored_wallet = restore_wallet.get_wallet_data().await?; +// assert!(restored_wallet.is_some()); + +// // addresses are still there +// assert_eq!( +// restored_wallet.address().await, +// wallet_before_backup.address().await +// ); + +// // compare client options, they are not restored +// let client_options = restore_wallet.client_options().await; +// let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_OTHER).unwrap())); +// assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); + +// tear_down(storage_path) +// } + +// // Backup and restore with Stronghold +// #[tokio::test] +// async fn backup_and_restore_different_coin_type_dont_ignore() -> Result<()> { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + +// let storage_path = "test-storage/backup_and_restore_different_coin_type_dont_ignore"; +// setup(storage_path)?; + +// let client_options = ClientOptions::new().with_node(NODE_OTHER)?; + +// let stronghold_password = "some_hopefully_secure_password".to_owned(); + +// // Create directory if not existing, because stronghold panics otherwise +// std::fs::create_dir_all(storage_path).ok(); +// let stronghold = StrongholdSecretManager::builder() +// .password(stronghold_password.clone()) +// .build("test-storage/backup_and_restore_different_coin_type_dont_ignore/1.stronghold")?; + +// stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); + +// let wallet = Wallet::builder() +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(client_options.clone()) +// .with_coin_type(SHIMMER_COIN_TYPE) +// .with_storage_path("test-storage/backup_and_restore_different_coin_type_dont_ignore/1") +// .finish() +// .await?; + +// wallet +// .backup( +// PathBuf::from("test-storage/backup_and_restore_different_coin_type_dont_ignore/backup.stronghold"), +// stronghold_password.clone(), +// ) +// .await?; + +// // restore from backup + +// let stronghold = StrongholdSecretManager::builder() +// .build("test-storage/backup_and_restore_different_coin_type_dont_ignore/2.stronghold")?; + +// let restore_wallet = Wallet::builder() +// .with_storage_path("test-storage/backup_and_restore_different_coin_type_dont_ignore/2") +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(ClientOptions::new().with_node(NODE_LOCAL)?) +// // Build with a different coin type, to check if it gets replaced by the one from the backup +// .with_coin_type(IOTA_COIN_TYPE) +// .finish() +// .await?; + +// // restore with ignore_if_coin_type_mismatch: Some(true) to not overwrite the coin type +// restore_wallet +// .restore_backup( +// PathBuf::from("test-storage/backup_and_restore_different_coin_type_dont_ignore/backup.stronghold"), +// stronghold_password, +// Some(false), +// None, +// ) +// .await?; + +// // Validate restored data + +// // No wallet restored, because the coin type was different +// let restored_wallet = restore_wallet.get_wallet_data().await?; +// assert_eq!( +// wallet.address().await, +// restored_wallet.address().await, +// ); + +// // TODO: Restored coin type is used +// let new_wallet = restore_wallet; +// assert_eq!(new_wallet.data().await.coin_type(), &SHIMMER_COIN_TYPE); +// // secret manager is restored +// assert_eq!( +// new_wallet.address().await, +// "smr1qzvjvjyqxgfx4f0m3xhn2rj24e03dwsmjz082735y3wx88v2gudu2afedhu" +// ); + +// // compare client options, they are not restored +// let client_options = restore_wallet.client_options().await; +// let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); +// assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); + +// tear_down(storage_path) +// } + +// #[tokio::test] +// async fn backup_and_restore_bech32_hrp_mismatch() -> Result<()> { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + +// let storage_path = "test-storage/backup_and_restore_bech32_hrp_mismatch"; +// setup(storage_path)?; + +// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; + +// let stronghold_password = "some_hopefully_secure_password".to_owned(); + +// // Create directory if not existing, because stronghold panics otherwise +// std::fs::create_dir_all(storage_path).ok(); +// let stronghold = StrongholdSecretManager::builder() +// .password(stronghold_password.clone()) +// .build("test-storage/backup_and_restore_bech32_hrp_mismatch/1.stronghold")?; + +// stronghold.store_mnemonic(Mnemonic::from("inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak".to_string())).await.unwrap(); + +// let wallet = Wallet::builder() +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(client_options.clone()) +// .with_coin_type(SHIMMER_COIN_TYPE) +// .with_storage_path("test-storage/backup_and_restore_bech32_hrp_mismatch/1") +// .finish() +// .await?; + +// wallet +// .backup( +// PathBuf::from("test-storage/backup_and_restore_bech32_hrp_mismatch/backup.stronghold"), +// stronghold_password.clone(), +// ) +// .await?; + +// // restore from backup + +// let stronghold = +// StrongholdSecretManager::builder().build("test-storage/backup_and_restore_bech32_hrp_mismatch/2.stronghold")? +// ; + +// let restore_wallet = Wallet::builder() +// .with_storage_path("test-storage/backup_and_restore_bech32_hrp_mismatch/2") +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(ClientOptions::new().with_node(NODE_OTHER)?) +// // Build with a different coin type, to check if it gets replaced by the one from the backup +// .with_coin_type(IOTA_COIN_TYPE) +// .finish() +// .await?; + +// restore_wallet +// .restore_backup( +// PathBuf::from("test-storage/backup_and_restore_bech32_hrp_mismatch/backup.stronghold"), +// stronghold_password, +// None, +// Some(iota_sdk::types::block::address::Hrp::from_str_unchecked("otherhrp")), +// ) +// .await?; + +// // Validate restored data + +// // compare restored client options +// let client_options = restore_wallet.client_options().await; +// let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); +// assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); + +// // No restored wallet because the bech32 hrp was different +// let restored_wallet = restore_wallet.get_wallet_data().await?; +// assert!(restored_wallet.is_empty()); + +// // Restored coin type is used +// let new_wallet = restore_wallet; +// assert_eq!(new_wallet.details().await.coin_type(), &SHIMMER_COIN_TYPE); + +// // secret manager is the same +// assert_eq!( +// wallet.generate_ed25519_addresses(1, None).await?, +// new_wallet.generate_ed25519_addresses(1, None).await? +// ); +// tear_down(storage_path) +// } + +// // Restore a Stronghold snapshot without secret manager data +// #[tokio::test] +// async fn restore_no_secret_manager_data() -> Result<()> { +// iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + +// let storage_path = "test-storage/restore_no_secret_manager_data"; +// setup(storage_path)?; + +// let stronghold = StrongholdSecretManager::builder().build(storage_path.to_string() + "/wallet.stronghold")?; + +// let restore_wallet = Wallet::builder() +// .with_storage_path(storage_path) +// .with_secret_manager(SecretManager::Stronghold(stronghold)) +// .with_client_options(ClientOptions::new().with_node(NODE_LOCAL)?) +// .with_coin_type(IOTA_COIN_TYPE) +// .finish() +// .await?; + +// let stronghold_password = "some_hopefully_secure_password".to_owned(); + +// restore_wallet +// .restore_backup( +// PathBuf::from("./tests/wallet/fixtures/no_secret_manager_data.stronghold"), +// stronghold_password.clone(), +// None, +// None, +// ) +// .await?; + +// restore_wallet.set_stronghold_password(stronghold_password).await?; + +// // Backup is restored also without any secret manager data inside and the seed is available +// // Backup was created with mnemonic: "inhale gorilla deny three celery song category owner lottery rent author +// // wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak" +// assert_eq!( +// restore_wallet.generate_ed25519_address(0, 0, None).await?.to_string(), +// "0xc2ece328eb3d9bbc51d471dd17fd3665aa8c6bae63c78d64c13977efbb8b011e" +// ); +// tear_down(storage_path) +// } diff --git a/sdk/tests/wallet/balance.rs b/sdk/tests/wallet/balance.rs index 9c9256e373..bc926936e7 100644 --- a/sdk/tests/wallet/balance.rs +++ b/sdk/tests/wallet/balance.rs @@ -7,11 +7,11 @@ use iota_sdk::{ unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, BasicOutputBuilder, UnlockCondition, }, - wallet::{account::types::Balance, Result}, + wallet::{types::Balance, Result}, }; use pretty_assertions::assert_eq; -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; +use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[test] fn balance_add_assign() { @@ -90,32 +90,35 @@ fn balance_add_assign() { #[ignore] #[tokio::test] async fn balance_expiration() -> Result<()> { - let storage_path = "test-storage/balance_expiration"; - setup(storage_path)?; + let storage_path_0 = "test-storage/balance_expiration_0"; + let storage_path_1 = "test-storage/balance_expiration_1"; + let storage_path_2 = "test-storage/balance_expiration_2"; + setup(storage_path_0)?; + setup(storage_path_1)?; + setup(storage_path_2)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; + let wallet_2 = make_wallet(storage_path_2, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - let account_2 = wallet.create_account().finish().await?; + request_funds(&wallet_0).await?; let slots_until_expired = 20; - let token_supply = account_0.client().get_token_supply().await?; let outputs = [BasicOutputBuilder::new_with_amount(1_000_000) // Send to account 1 with expiration to account 2, both have no amount yet .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(account_1.first_address_bech32().await)), + UnlockCondition::Address(AddressUnlockCondition::new(wallet_1.address().await)), UnlockCondition::Expiration(ExpirationUnlockCondition::new( - account_2.first_address_bech32().await, - account_0.client().get_slot_index().await? + slots_until_expired, + wallet_2.address().await, + wallet_0.client().get_slot_index().await? + slots_until_expired, )?), ]) - .with_features([SenderFeature::new(account_0.first_address_bech32().await)]) - .finish_output(token_supply)?]; + .with_features([SenderFeature::new(wallet_0.address().await)]) + .finish_output()?]; - let balance_before_tx = account_0.balance().await?; - let tx = account_0.send_outputs(outputs, None).await?; - let balance_after_tx = account_0.balance().await?; + let balance_before_tx = wallet_0.balance().await?; + let tx = wallet_0.send_outputs(outputs, None).await?; + let balance_after_tx = wallet_0.balance().await?; // Total doesn't change before syncing after tx got confirmed assert_eq!( balance_before_tx.base_coin().total(), @@ -123,18 +126,18 @@ async fn balance_expiration() -> Result<()> { ); assert_eq!(balance_after_tx.base_coin().available(), 0); - account_0 + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - // Account 1 balance before expiration - let balance = account_1.sync(None).await?; + // Wallet 1 balance before expiration + let balance = wallet_1.sync(None).await?; assert_eq!(balance.potentially_locked_outputs().len(), 1); assert_eq!(balance.base_coin().total(), 0); assert_eq!(balance.base_coin().available(), 0); - // Account 2 balance before expiration - let balance = account_2.sync(None).await?; + // Wallet 2 balance before expiration + let balance = wallet_2.sync(None).await?; assert_eq!(balance.potentially_locked_outputs().len(), 1); assert_eq!(balance.base_coin().total(), 0); assert_eq!(balance.base_coin().available(), 0); @@ -143,47 +146,46 @@ async fn balance_expiration() -> Result<()> { // TODO wait for slots, not seconds tokio::time::sleep(std::time::Duration::from_secs(slots_until_expired as u64)).await; - // Account 1 balance after expiration - let balance = account_1.sync(None).await?; + // Wallet 1 balance after expiration + let balance = wallet_1.sync(None).await?; assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!(balance.base_coin().total(), 0); assert_eq!(balance.base_coin().available(), 0); - // Account 2 balance after expiration - let balance = account_2.sync(None).await?; + // Wallet 2 balance after expiration + let balance = wallet_2.sync(None).await?; assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!(balance.base_coin().total(), 1_000_000); assert_eq!(balance.base_coin().available(), 1_000_000); // It's possible to send the expired output let outputs = [BasicOutputBuilder::new_with_amount(1_000_000) - // Send to account 1 with expiration to account 2, both have no amount yet - .with_unlock_conditions([AddressUnlockCondition::new( - account_1.addresses().await[0].clone().into_bech32(), - )]) - .finish_output(token_supply)?]; - let _tx = account_2.send_outputs(outputs, None).await?; - - tear_down(storage_path) + // Send to wallet 1 with expiration to wallet 2, both have no amount yet + .with_unlock_conditions([AddressUnlockCondition::new(wallet_1.address().await)]) + .finish_output()?]; + let _tx = wallet_2.send_outputs(outputs, None).await?; + + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + tear_down(storage_path_2)?; + Ok(()) } #[ignore] #[tokio::test] -async fn addresses_balance() -> Result<()> { - let storage_path = "test-storage/addresses_balance"; - setup(storage_path)?; +async fn balance_transfer() -> Result<()> { + let storage_path_0 = "test-storage/addresses_balance_0"; + let storage_path_1 = "test-storage/addresses_balance_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - let addresses_0 = account_0.addresses_with_unspent_outputs().await?; - let acc_1_addr = &account_1.generate_ed25519_addresses(1, None).await?[0]; + request_funds(&wallet_0).await?; - let balance_0 = account_0 - .addresses_balance(addresses_0.iter().map(|a| a.address().clone()).collect()) - .await?; - let balance_0_sync = account_0.balance().await?; + let balance_0 = wallet_0.balance().await?; + let balance_0_sync = wallet_0.sync(None).await?; let to_send = balance_0.base_coin().available(); // Check if 0 has balance and sync() and address_balance() match @@ -191,54 +193,30 @@ async fn addresses_balance() -> Result<()> { assert_eq!(balance_0, balance_0_sync); // Make sure 1 is empty - let balance_1 = account_1.sync(None).await?; + let balance_1 = wallet_1.sync(None).await?; assert_eq!(balance_1.base_coin().available(), 0); // Send to 1 - let tx = account_0.send(to_send, acc_1_addr.address().clone(), None).await?; + let tx = wallet_0.send(to_send, wallet_1.address().await, None).await?; + // Balance should update without sync - let balance_0 = account_0 - .addresses_balance(addresses_0.iter().map(|a| a.address().clone()).collect()) - .await?; - let balance_0_sync = account_0.balance().await?; + let balance_0 = wallet_0.balance().await?; + let balance_0_sync = wallet_0.sync(None).await?; assert_eq!(balance_0.base_coin().available(), 0); assert_eq!(balance_0, balance_0_sync); - account_0 + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - account_1.sync(None).await?; // Balance should have transferred entirely - let balance_1 = account_1.addresses_balance(vec![acc_1_addr.address().clone()]).await?; - let balance_1_sync = account_1.balance().await?; + let balance_1_sync = wallet_1.sync(None).await?; assert!(balance_1.base_coin().available() > 0); assert_eq!(balance_1, balance_1_sync); - // Internal transfer on account 1 - let acc_1_addr_2 = &account_1.generate_ed25519_addresses(1, None).await?[0]; - - let tx = account_1 - .send(to_send / 2, acc_1_addr_2.address().clone(), None) - .await?; - account_1 - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - let balance_1_sync = account_1.sync(None).await?; - - // Check the new address - let balance_1 = account_1 - .addresses_balance(vec![acc_1_addr_2.address().clone()]) - .await?; - assert_eq!(to_send / 2, balance_1.base_coin().available()); - - // Check old and new together - let balance_1_total = account_1 - .addresses_balance(vec![acc_1_addr.address().clone(), acc_1_addr_2.address().clone()]) - .await?; - assert_eq!(balance_1_total, balance_1_sync); - - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + Ok(()) } #[ignore] @@ -250,36 +228,37 @@ async fn balance_voting_power() -> Result<()> { let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; let faucet_amount = 100_000_000_000; - let balance = account.balance().await?; + let balance = wallet.balance().await?; assert_eq!(balance.base_coin().total(), faucet_amount); assert_eq!(balance.base_coin().available(), faucet_amount); let voting_power = 1_000_000; // Only use a part as voting power - let tx = account.increase_voting_power(voting_power).await?; - account + let tx = wallet.increase_voting_power(voting_power).await?; + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; assert_eq!(balance.base_coin().total(), faucet_amount); assert_eq!(balance.base_coin().available(), faucet_amount - voting_power); - let account_voting_power = account.get_voting_power().await?; - assert_eq!(account_voting_power, voting_power); + let wallet_voting_power = wallet.get_voting_power().await?; + assert_eq!(wallet_voting_power, voting_power); // Increase voting power to total amount - let tx = account.increase_voting_power(faucet_amount - voting_power).await?; - account + let tx = wallet.increase_voting_power(faucet_amount - voting_power).await?; + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; assert_eq!(balance.base_coin().total(), faucet_amount); assert_eq!(balance.base_coin().available(), 0); - let account_voting_power = account.get_voting_power().await?; - assert_eq!(account_voting_power, faucet_amount); + let wallet_voting_power = wallet.get_voting_power().await?; + assert_eq!(wallet_voting_power, faucet_amount); - tear_down(storage_path) + tear_down(storage_path)?; + Ok(()) } diff --git a/sdk/tests/wallet/bech32_hrp_validation.rs b/sdk/tests/wallet/bech32_hrp_validation.rs index 1128ecd699..5271a0bbb3 100644 --- a/sdk/tests/wallet/bech32_hrp_validation.rs +++ b/sdk/tests/wallet/bech32_hrp_validation.rs @@ -4,7 +4,7 @@ use iota_sdk::{ client::Error as ClientError, types::block::address::{Bech32Address, ToBech32Ext}, - wallet::{account::OutputParams, Error, Result, SendParams}, + wallet::{Error, OutputParams, Result, SendParams}, }; use pretty_assertions::assert_eq; @@ -18,20 +18,18 @@ async fn bech32_hrp_send_amount() -> Result<()> { let wallet = make_wallet(storage_path, None, None).await?; - let account = wallet.create_account().finish().await?; - - let error = account + let error = wallet .send_with_params( [SendParams::new( 1_000_000, - Bech32Address::try_new("wronghrp", account.first_address_bech32().await)?, + Bech32Address::try_new("wronghrp", wallet.address().await)?, )?], None, ) .await .unwrap_err(); - let bech32_hrp = account.client().get_bech32_hrp().await?; + let bech32_hrp = wallet.client().get_bech32_hrp().await?; match error { Error::Client(error) => match *error { @@ -54,16 +52,11 @@ async fn bech32_hrp_prepare_output() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let account = wallet.create_account().finish().await?; - let error = account + let error = wallet .prepare_output( OutputParams { - recipient_address: account.addresses().await[0] - .clone() - .into_bech32() - .into_inner() - .to_bech32_unchecked("wronghrp"), + recipient_address: wallet.address().await.to_bech32_unchecked("wronghrp"), amount: 1_000_000, assets: None, features: None, @@ -75,7 +68,7 @@ async fn bech32_hrp_prepare_output() -> Result<()> { .await .unwrap_err(); - let bech32_hrp = account.client().get_bech32_hrp().await?; + let bech32_hrp = wallet.client().get_bech32_hrp().await?; match error { Error::Client(error) => match *error { diff --git a/sdk/tests/wallet/burn_outputs.rs b/sdk/tests/wallet/burn_outputs.rs index a7c07c4898..dff9359506 100644 --- a/sdk/tests/wallet/burn_outputs.rs +++ b/sdk/tests/wallet/burn_outputs.rs @@ -7,12 +7,12 @@ use iota_sdk::{ unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, NativeToken, NftId, NftOutputBuilder, OutputId, UnlockCondition, }, - wallet::{Account, CreateNativeTokenParams, MintNftParams, Result}, + wallet::{CreateNativeTokenParams, MintNftParams, Result, Wallet}, U256, }; use pretty_assertions::assert_eq; -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; +use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] @@ -21,19 +21,19 @@ async fn mint_and_burn_nft() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; let nft_options = [MintNftParams::new() - .with_address(account.first_address_bech32().await) + .with_address(wallet.address().await) .with_metadata(b"some nft metadata".to_vec()) .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; - let transaction = account.mint_nfts(nft_options, None).await.unwrap(); - account + let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await.unwrap(); + let balance = wallet.sync(None).await.unwrap(); let output_id = OutputId::new(transaction.transaction_id, 0u16).unwrap(); let nft_id = NftId::from(&output_id); @@ -42,11 +42,11 @@ async fn mint_and_burn_nft() -> Result<()> { println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); assert!(search.is_some()); - let transaction = account.burn(nft_id, None).await.unwrap(); - account + let transaction = wallet.burn(nft_id, None).await.unwrap(); + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await.unwrap(); + let balance = wallet.sync(None).await.unwrap(); let search = balance.nfts().iter().find(|&balance_nft_id| *balance_nft_id == nft_id); println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); assert!(search.is_none()); @@ -60,40 +60,33 @@ async fn mint_and_burn_expired_nft() -> Result<()> { let storage_path = "test-storage/mint_and_burn_expired_nft"; setup(storage_path)?; - let wallet = make_wallet(storage_path, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - let token_supply = account_0.client().get_token_supply().await?; + let wallet_0 = make_wallet(storage_path, None, None).await?; + let wallet_1 = make_wallet(storage_path, None, None).await?; + request_funds(&wallet_0).await?; let amount = 1_000_000; let outputs = [NftOutputBuilder::new_with_amount(amount, NftId::null()) .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new( - account_0.addresses().await[0].clone().into_bech32().into_inner(), - )), + UnlockCondition::Address(AddressUnlockCondition::new(wallet_0.address().await)), // immediately expired to account_1 - UnlockCondition::Expiration(ExpirationUnlockCondition::new( - account_1.addresses().await[0].clone().into_bech32().into_inner(), - 1, - )?), + UnlockCondition::Expiration(ExpirationUnlockCondition::new(wallet_1.address().await, 1)?), ]) - .finish_output(token_supply)?]; + .finish_output()?]; - let transaction = account_0.send_outputs(outputs, None).await?; - account_0 + let transaction = wallet_0.send_outputs(outputs, None).await?; + wallet_0 .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; let output_id = OutputId::new(transaction.transaction_id, 0u16)?; let nft_id = NftId::from(&output_id); - account_1.sync(None).await?; - let transaction = account_1.burn(nft_id, None).await?; - account_1 + wallet_1.sync(None).await?; + let transaction = wallet_1.burn(nft_id, None).await?; + wallet_1 .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account_1.sync(None).await?; + let balance = wallet_1.sync(None).await?; // After burning the amount is available on account_1 assert_eq!(balance.base_coin().available(), amount); @@ -107,16 +100,16 @@ async fn create_and_melt_native_token() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; // First create an account output, this needs to be done only once, because an account can have many foundry outputs - let transaction = account.create_account_output(None, None).await?; + let transaction = wallet.create_account_output(None, None).await?; // Wait for transaction to get included - account + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - account.sync(None).await?; + wallet.sync(None).await?; let circulating_supply = U256::from(60i32); let params = CreateNativeTokenParams { @@ -126,32 +119,32 @@ async fn create_and_melt_native_token() -> Result<()> { foundry_metadata: None, }; - let create_transaction = account.create_native_token(params, None).await.unwrap(); + let create_transaction = wallet.create_native_token(params, None).await.unwrap(); - account + wallet .reissue_transaction_until_included(&create_transaction.transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await.unwrap(); + let balance = wallet.sync(None).await.unwrap(); let search = balance .native_tokens() .iter() .find(|token| token.token_id() == &create_transaction.token_id && token.available() == circulating_supply); - println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); + println!("wallet balance -> {}", serde_json::to_string(&balance).unwrap()); assert!(search.is_some()); // Melt some of the circulating supply let melt_amount = U256::from(40i32); - let transaction = account + let transaction = wallet .melt_native_token(create_transaction.token_id, melt_amount, None) .await .unwrap(); - account + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await.unwrap(); - println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); + let balance = wallet.sync(None).await.unwrap(); + println!("wallet balance -> {}", serde_json::to_string(&balance).unwrap()); let search = balance.native_tokens().iter().find(|token| { (token.token_id() == &create_transaction.token_id) && (token.available() == circulating_supply - melt_amount) @@ -160,16 +153,16 @@ async fn create_and_melt_native_token() -> Result<()> { // Then melt the rest of the supply let melt_amount = circulating_supply - melt_amount; - let transaction = account + let transaction = wallet .melt_native_token(create_transaction.token_id, melt_amount, None) .await .unwrap(); - account + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await.unwrap(); - println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); + let balance = wallet.sync(None).await.unwrap(); + println!("wallet balance -> {}", serde_json::to_string(&balance).unwrap()); let search = balance .native_tokens() @@ -178,47 +171,47 @@ async fn create_and_melt_native_token() -> Result<()> { assert!(search.is_none()); // Call to run tests in sequence - destroy_foundry(account).await?; - destroy_account(account).await?; + destroy_foundry(&wallet).await?; + destroy_account(&wallet).await?; tear_down(storage_path) } -async fn destroy_foundry(account: &Account) -> Result<()> { - let balance = account.sync(None).await?; - println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); +async fn destroy_foundry(wallet: &Wallet) -> Result<()> { + let balance = wallet.sync(None).await?; + println!("wallet balance -> {}", serde_json::to_string(&balance).unwrap()); // Let's burn the first foundry we can find, although we may not find the required account output so maybe not a // good idea let foundry_id = *balance.foundries().first().unwrap(); - let transaction = account.burn(foundry_id, None).await.unwrap(); - account + let transaction = wallet.burn(foundry_id, None).await.unwrap(); + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await.unwrap(); + let balance = wallet.sync(None).await.unwrap(); let search = balance .foundries() .iter() .find(|&balance_foundry_id| *balance_foundry_id == foundry_id); - println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); + println!("wallet balance -> {}", serde_json::to_string(&balance).unwrap()); assert!(search.is_none()); Ok(()) } -async fn destroy_account(account: &Account) -> Result<()> { - let balance = account.sync(None).await.unwrap(); +async fn destroy_account(wallet: &Wallet) -> Result<()> { + let balance = wallet.sync(None).await.unwrap(); println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); // Let's destroy the first account we can find let account_id = *balance.accounts().first().unwrap(); println!("account_id -> {account_id}"); - let transaction = account.burn(account_id, None).await.unwrap(); - account + let transaction = wallet.burn(account_id, None).await.unwrap(); + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await.unwrap(); + let balance = wallet.sync(None).await.unwrap(); let search = balance .accounts() .iter() @@ -237,17 +230,17 @@ async fn create_and_burn_native_tokens() -> Result<()> { let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; let native_token_amount = U256::from(100); - let tx = account.create_account_output(None, None).await?; - account + let tx = wallet.create_account_output(None, None).await?; + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - account.sync(None).await?; + wallet.sync(None).await?; - let create_tx = account + let create_tx = wallet .create_native_token( CreateNativeTokenParams { account_id: None, @@ -258,18 +251,18 @@ async fn create_and_burn_native_tokens() -> Result<()> { None, ) .await?; - account + wallet .reissue_transaction_until_included(&create_tx.transaction.transaction_id, None, None) .await?; - account.sync(None).await?; + wallet.sync(None).await?; - let tx = account + let tx = wallet .burn(NativeToken::new(create_tx.token_id, native_token_amount)?, None) .await?; - account + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; assert!(balance.native_tokens().is_empty()); @@ -283,34 +276,34 @@ async fn mint_and_burn_nft_with_account() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; - let tx = account.create_account_output(None, None).await?; - account + let tx = wallet.create_account_output(None, None).await?; + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - account.sync(None).await?; + wallet.sync(None).await?; let nft_options = [MintNftParams::new() .with_metadata(b"some nft metadata".to_vec()) .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; - let nft_tx = account.mint_nfts(nft_options, None).await.unwrap(); - account + let nft_tx = wallet.mint_nfts(nft_options, None).await.unwrap(); + wallet .reissue_transaction_until_included(&nft_tx.transaction_id, None, None) .await?; let output_id = OutputId::new(nft_tx.transaction_id, 0u16).unwrap(); let nft_id = NftId::from(&output_id); - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; let account_id = balance.accounts().first().unwrap(); - let burn_tx = account + let burn_tx = wallet .burn(Burn::new().add_nft(nft_id).add_account(*account_id), None) .await?; - account + wallet .reissue_transaction_until_included(&burn_tx.transaction_id, None, None) .await?; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; assert!(balance.accounts().is_empty()); assert!(balance.nfts().is_empty()); diff --git a/sdk/tests/wallet/claim_outputs.rs b/sdk/tests/wallet/claim_outputs.rs index e0386080f9..f5ab50de12 100644 --- a/sdk/tests/wallet/claim_outputs.rs +++ b/sdk/tests/wallet/claim_outputs.rs @@ -6,32 +6,33 @@ use iota_sdk::{ unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, BasicOutputBuilder, NativeToken, NftId, NftOutputBuilder, UnlockCondition, }, - wallet::{ - account::{OutputsToClaim, TransactionOptions}, - CreateNativeTokenParams, Result, SendNativeTokenParams, SendParams, - }, + wallet::{CreateNativeTokenParams, OutputsToClaim, Result, SendNativeTokenParams, SendParams, TransactionOptions}, U256, }; use pretty_assertions::assert_eq; -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; +use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] async fn claim_2_basic_micro_outputs() -> Result<()> { - let storage_path = "test-storage/claim_2_basic_micro_outputs"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_2_basic_micro_outputs_0"; + let storage_path_1 = "test-storage/claim_2_basic_micro_outputs_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let accounts = create_accounts_with_funds(&wallet, 2).await?; + request_funds(&wallet_0).await?; + request_funds(&wallet_1).await?; let micro_amount = 1; - let tx = accounts[1] + let tx = wallet_1 .send_with_params( [ - SendParams::new(micro_amount, accounts[0].first_address_bech32().await)?, - SendParams::new(micro_amount, accounts[0].first_address_bech32().await)?, + SendParams::new(micro_amount, wallet_0.address().await)?, + SendParams::new(micro_amount, wallet_0.address().await)?, ], TransactionOptions { allow_micro_amount: true, @@ -40,48 +41,55 @@ async fn claim_2_basic_micro_outputs() -> Result<()> { ) .await?; - accounts[1] + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; // Claim with account 0 - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 2); let base_coin_amount_before_claiming = balance.base_coin().available(); - let tx = accounts[0] - .claim_outputs(accounts[0].claimable_outputs(OutputsToClaim::MicroTransactions).await?) + let tx = wallet_0 + .claim_outputs(wallet_0.claimable_outputs(OutputsToClaim::MicroTransactions).await?) .await?; - accounts[0] + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!( balance.base_coin().available(), base_coin_amount_before_claiming + 2 * micro_amount ); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] #[tokio::test] async fn claim_1_of_2_basic_outputs() -> Result<()> { - let storage_path = "test-storage/claim_1_of_2_basic_outputs"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_1_of_2_basic_outputs_0"; + let storage_path_1 = "test-storage/claim_1_of_2_basic_outputs_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_0, None, None).await?; - let accounts = create_accounts_with_funds(&wallet, 2).await?; + request_funds(&wallet_0).await?; + request_funds(&wallet_1).await?; let amount = 10; - let tx = accounts[1] + let tx = wallet_1 .send_with_params( [ - SendParams::new(amount, accounts[0].first_address_bech32().await)?, - SendParams::new(0, accounts[0].first_address_bech32().await)?, + SendParams::new(amount, wallet_0.address().await)?, + SendParams::new(0, wallet_0.address().await)?, ], TransactionOptions { allow_micro_amount: true, @@ -90,106 +98,114 @@ async fn claim_1_of_2_basic_outputs() -> Result<()> { ) .await?; - accounts[1] + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; // Claim with account 0 - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 2); let base_coin_amount_before_claiming = balance.base_coin().available(); - let tx = accounts[0] - .claim_outputs(accounts[0].claimable_outputs(OutputsToClaim::Amount).await?) + let tx = wallet_0 + .claim_outputs(wallet_0.claimable_outputs(OutputsToClaim::Amount).await?) .await?; - accounts[0] + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 1); assert_eq!( balance.base_coin().available(), base_coin_amount_before_claiming + amount ); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] #[tokio::test] async fn claim_2_basic_outputs_no_outputs_in_claim_account() -> Result<()> { - let storage_path = "test-storage/claim_2_basic_outputs_no_outputs_in_claim_account"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_2_basic_outputs_no_outputs_in_claim_account_0"; + let storage_path_1 = "test-storage/claim_2_basic_outputs_no_outputs_in_claim_account_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; + request_funds(&wallet_0).await?; - let token_supply = account_0.client().get_token_supply().await?; - let rent_structure = account_0.client().get_rent_structure().await?; + let storage_score_params = wallet_0.client().get_storage_score_parameters().await?; // TODO more fitting value - let expiration_slot = account_0.client().get_slot_index().await? + 86400; + let expiration_slot = wallet_0.client().get_slot_index().await? + 86400; - let output = BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - .add_unlock_condition(AddressUnlockCondition::new(account_1.first_address_bech32().await)) + let output = BasicOutputBuilder::new_with_minimum_amount(storage_score_params) + .add_unlock_condition(AddressUnlockCondition::new(wallet_1.address().await)) .add_unlock_condition(ExpirationUnlockCondition::new( - account_0.first_address_bech32().await, + wallet_0.address().await, expiration_slot, )?) - .finish_output(token_supply)?; + .finish_output()?; let amount = output.amount(); let outputs = vec![output; 2]; - let tx = account_0.send_outputs(outputs, None).await?; + let tx = wallet_0.send_outputs(outputs, None).await?; - account_0 + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - // Claim with account 1 - let balance = account_1.sync(None).await.unwrap(); + // Claim with wallet 1 + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 2); let base_coin_amount_before_claiming = balance.base_coin().available(); - let tx = account_1 - .claim_outputs(account_1.claimable_outputs(OutputsToClaim::All).await?) + let tx = wallet_1 + .claim_outputs(wallet_1.claimable_outputs(OutputsToClaim::All).await?) .await?; - account_1 + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account_1.sync(None).await.unwrap(); + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!( balance.base_coin().available(), base_coin_amount_before_claiming + 2 * amount ); - tear_down(storage_path) + tear_down(storage_path_0) } #[ignore] #[tokio::test] async fn claim_2_native_tokens() -> Result<()> { - let storage_path = "test-storage/claim_2_native_tokens"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_2_native_tokens_0"; + let storage_path_1 = "test-storage/claim_2_native_tokens_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let accounts = create_accounts_with_funds(&wallet, 2).await?; + request_funds(&wallet_0).await?; + request_funds(&wallet_1).await?; let native_token_amount = U256::from(100); - let tx = accounts[1].create_account_output(None, None).await?; - accounts[1] + let tx = wallet_1.create_account_output(None, None).await?; + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - accounts[1].sync(None).await?; + wallet_1.sync(None).await?; - let create_tx_0 = accounts[1] + let create_tx_0 = wallet_1 .create_native_token( CreateNativeTokenParams { account_id: None, @@ -200,12 +216,12 @@ async fn claim_2_native_tokens() -> Result<()> { None, ) .await?; - accounts[1] + wallet_1 .reissue_transaction_until_included(&create_tx_0.transaction.transaction_id, None, None) .await?; - accounts[1].sync(None).await?; + wallet_1.sync(None).await?; - let create_tx_1 = accounts[1] + let create_tx_1 = wallet_1 .create_native_token( CreateNativeTokenParams { account_id: None, @@ -216,42 +232,36 @@ async fn claim_2_native_tokens() -> Result<()> { None, ) .await?; - accounts[1] + wallet_1 .reissue_transaction_until_included(&create_tx_1.transaction.transaction_id, None, None) .await?; - accounts[1].sync(None).await?; + wallet_1.sync(None).await?; - let tx = accounts[1] + let tx = wallet_1 .send_native_token( [ - SendNativeTokenParams::new( - accounts[0].first_address_bech32().await, - [(create_tx_0.token_id, native_token_amount)], - )?, - SendNativeTokenParams::new( - accounts[0].first_address_bech32().await, - [(create_tx_1.token_id, native_token_amount)], - )?, + SendNativeTokenParams::new(wallet_0.address().await, [(create_tx_0.token_id, native_token_amount)])?, + SendNativeTokenParams::new(wallet_0.address().await, [(create_tx_1.token_id, native_token_amount)])?, ], None, ) .await?; - accounts[1] + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; // Claim with account 0 - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 2); - let tx = accounts[0] - .claim_outputs(accounts[0].claimable_outputs(OutputsToClaim::NativeTokens).await?) + let tx = wallet_0 + .claim_outputs(wallet_0.claimable_outputs(OutputsToClaim::NativeTokens).await?) .await?; - accounts[0] + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!(balance.native_tokens().len(), 2); let native_token_0 = balance @@ -267,29 +277,34 @@ async fn claim_2_native_tokens() -> Result<()> { .unwrap(); assert_eq!(native_token_1.total(), native_token_amount); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] #[tokio::test] async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { - let storage_path = "test-storage/claim_2_native_tokens_no_outputs_in_claim_account"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_2_native_tokens_no_outputs_in_claim_account_0"; + let storage_path_1 = "test-storage/claim_2_native_tokens_no_outputs_in_claim_account_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; + request_funds(&wallet_0).await?; let native_token_amount = U256::from(100); - let tx = account_0.create_account_output(None, None).await?; - account_0 + let tx = wallet_0.create_account_output(None, None).await?; + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - account_0.sync(None).await?; + wallet_0.sync(None).await?; - let create_tx_0 = account_0 + let create_tx_0 = wallet_0 .create_native_token( CreateNativeTokenParams { account_id: None, @@ -300,12 +315,12 @@ async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { None, ) .await?; - account_0 + wallet_0 .reissue_transaction_until_included(&create_tx_0.transaction.transaction_id, None, None) .await?; - account_0.sync(None).await?; + wallet_0.sync(None).await?; - let create_tx_1 = account_0 + let create_tx_1 = wallet_0 .create_native_token( CreateNativeTokenParams { account_id: None, @@ -316,53 +331,52 @@ async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { None, ) .await?; - account_0 + wallet_0 .reissue_transaction_until_included(&create_tx_1.transaction.transaction_id, None, None) .await?; - account_0.sync(None).await?; + wallet_0.sync(None).await?; - let rent_structure = account_0.client().get_rent_structure().await?; - let token_supply = account_0.client().get_token_supply().await?; + let storage_score_params = wallet_0.client().get_storage_score_parameters().await?; - let tx = account_0 + let tx = wallet_0 .send_outputs( [ - BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - .add_unlock_condition(AddressUnlockCondition::new(account_1.first_address_bech32().await)) + BasicOutputBuilder::new_with_minimum_amount(storage_score_params) + .add_unlock_condition(AddressUnlockCondition::new(wallet_1.address().await)) .add_unlock_condition(ExpirationUnlockCondition::new( - account_0.first_address_bech32().await, - account_0.client().get_slot_index().await? + 5000, + wallet_0.address().await, + wallet_0.client().get_slot_index().await? + 5000, )?) .add_native_token(NativeToken::new(create_tx_0.token_id, native_token_amount)?) - .finish_output(token_supply)?, - BasicOutputBuilder::new_with_minimum_storage_deposit(rent_structure) - .add_unlock_condition(AddressUnlockCondition::new(account_1.first_address_bech32().await)) + .finish_output()?, + BasicOutputBuilder::new_with_minimum_amount(storage_score_params) + .add_unlock_condition(AddressUnlockCondition::new(wallet_1.address().await)) .add_unlock_condition(ExpirationUnlockCondition::new( - account_0.first_address_bech32().await, - account_0.client().get_slot_index().await? + 5000, + wallet_0.address().await, + wallet_0.client().get_slot_index().await? + 5000, )?) .add_native_token(NativeToken::new(create_tx_1.token_id, native_token_amount)?) - .finish_output(token_supply)?, + .finish_output()?, ], None, ) .await?; - account_0 + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; // Claim with account 1 - let balance = account_1.sync(None).await.unwrap(); + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 2); - let tx = account_1 - .claim_outputs(account_1.claimable_outputs(OutputsToClaim::NativeTokens).await?) + let tx = wallet_1 + .claim_outputs(wallet_1.claimable_outputs(OutputsToClaim::NativeTokens).await?) .await?; - account_1 + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account_1.sync(None).await.unwrap(); + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!(balance.native_tokens().len(), 2); let native_token_0 = balance @@ -378,137 +392,152 @@ async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { .unwrap(); assert_eq!(native_token_1.total(), native_token_amount); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] #[tokio::test] async fn claim_2_nft_outputs() -> Result<()> { - let storage_path = "test-storage/claim_2_nft_outputs"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_2_nft_outputs_0"; + let storage_path_1 = "test-storage/claim_2_nft_outputs_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let accounts = create_accounts_with_funds(&wallet, 2).await?; + request_funds(&wallet_0).await?; + request_funds(&wallet_1).await?; - let token_supply = accounts[1].client().get_token_supply().await?; let outputs = [ // address of the owner of the NFT NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(accounts[0].first_address_bech32().await)), + UnlockCondition::Address(AddressUnlockCondition::new(wallet_0.address().await)), UnlockCondition::Expiration(ExpirationUnlockCondition::new( - accounts[1].first_address_bech32().await, - accounts[1].client().get_slot_index().await? + 5000, + wallet_1.address().await, + wallet_1.client().get_slot_index().await? + 5000, )?), ]) - .finish_output(token_supply)?, + .finish_output()?, NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(accounts[0].first_address_bech32().await)), + UnlockCondition::Address(AddressUnlockCondition::new(wallet_0.address().await)), UnlockCondition::Expiration(ExpirationUnlockCondition::new( - accounts[1].first_address_bech32().await, - accounts[1].client().get_slot_index().await? + 5000, + wallet_1.address().await, + wallet_1.client().get_slot_index().await? + 5000, )?), ]) - .finish_output(token_supply)?, + .finish_output()?, ]; - let tx = accounts[1].send_outputs(outputs, None).await?; - accounts[1] + let tx = wallet_1.send_outputs(outputs, None).await?; + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; // Claim with account 0 - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 2); - let tx = accounts[0] - .claim_outputs(accounts[0].claimable_outputs(OutputsToClaim::Nfts).await?) + let tx = wallet_0 + .claim_outputs(wallet_0.claimable_outputs(OutputsToClaim::Nfts).await?) .await?; - accounts[0] + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = accounts[0].sync(None).await.unwrap(); + let balance = wallet_0.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!(balance.nfts().len(), 2); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] #[tokio::test] async fn claim_2_nft_outputs_no_outputs_in_claim_account() -> Result<()> { - let storage_path = "test-storage/claim_2_nft_outputs_no_outputs_in_claim_account"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_2_nft_outputs_no_outputs_in_claim_wallet_0"; + let storage_path_1 = "test-storage/claim_2_nft_outputs_no_outputs_in_claim_wallet_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; + request_funds(&wallet_0).await?; - let token_supply = account_0.client().get_token_supply().await?; let outputs = [ // address of the owner of the NFT NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(account_1.first_address_bech32().await)), + UnlockCondition::Address(AddressUnlockCondition::new(wallet_1.address().await)), UnlockCondition::Expiration(ExpirationUnlockCondition::new( - account_0.first_address_bech32().await, - account_0.client().get_slot_index().await? + 5000, + wallet_0.address().await, + wallet_0.client().get_slot_index().await? + 5000, )?), ]) - .finish_output(token_supply)?, + .finish_output()?, NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(account_1.first_address_bech32().await)), + UnlockCondition::Address(AddressUnlockCondition::new(wallet_1.address().await)), UnlockCondition::Expiration(ExpirationUnlockCondition::new( - account_0.first_address_bech32().await, - account_0.client().get_slot_index().await? + 5000, + wallet_0.address().await, + wallet_0.client().get_slot_index().await? + 5000, )?), ]) - .finish_output(token_supply)?, + .finish_output()?, ]; - let tx = account_0.send_outputs(outputs, None).await?; - account_0 + let tx = wallet_0.send_outputs(outputs, None).await?; + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - // Claim with account 1 - let balance = account_1.sync(None).await.unwrap(); + // Claim with wallet 1 + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 2); - let tx = account_1 - .claim_outputs(account_1.claimable_outputs(OutputsToClaim::Nfts).await?) + let tx = wallet_1 + .claim_outputs(wallet_1.claimable_outputs(OutputsToClaim::Nfts).await?) .await?; - account_1 + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account_1.sync(None).await.unwrap(); + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 0); assert_eq!(balance.nfts().len(), 2); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] #[tokio::test] async fn claim_basic_micro_output_error() -> Result<()> { - let storage_path = "test-storage/claim_basic_micro_output_error"; - setup(storage_path)?; + let storage_path_0 = "test-storage/claim_basic_micro_output_error_0"; + let storage_path_1 = "test-storage/claim_basic_micro_output_error_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; + request_funds(&wallet_0).await?; let micro_amount = 1; - let tx = account_0 + let tx = wallet_0 .send_with_params( - [SendParams::new(micro_amount, account_1.first_address_bech32().await)?], + [SendParams::new(micro_amount, wallet_1.address().await)?], TransactionOptions { allow_micro_amount: true, ..Default::default() @@ -516,18 +545,21 @@ async fn claim_basic_micro_output_error() -> Result<()> { ) .await?; - account_0 + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; // Try claim with account 1 will fail since it has no funds to cover the storage deposit - let balance = account_1.sync(None).await.unwrap(); + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.potentially_locked_outputs().len(), 1); - let result = account_1 - .claim_outputs(account_1.claimable_outputs(OutputsToClaim::MicroTransactions).await?) + let result = wallet_1 + .claim_outputs(wallet_1.claimable_outputs(OutputsToClaim::MicroTransactions).await?) .await; assert!(matches!(result, Err(iota_sdk::wallet::Error::InsufficientFunds { .. }))); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } diff --git a/sdk/tests/wallet/common/mod.rs b/sdk/tests/wallet/common/mod.rs index 732a0abb41..bca29f4e3d 100644 --- a/sdk/tests/wallet/common/mod.rs +++ b/sdk/tests/wallet/common/mod.rs @@ -3,7 +3,7 @@ mod constants; -use crypto::keys::bip39::Mnemonic; +use crypto::keys::{bip39::Mnemonic, bip44::Bip44}; use iota_sdk::{ client::{ constants::SHIMMER_COIN_TYPE, @@ -11,7 +11,7 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, Client, }, - wallet::{Account, ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Result, Wallet}, }; pub use self::constants::*; @@ -27,7 +27,7 @@ pub use self::constants::*; /// /// Returns: /// -/// An Wallet +/// A Wallet #[allow(dead_code, unused_variables)] pub(crate) async fn make_wallet(storage_path: &str, mnemonic: Option, node: Option<&str>) -> Result { let client_options = ClientOptions::new().with_node(node.unwrap_or(NODE_LOCAL))?; @@ -38,7 +38,8 @@ pub(crate) async fn make_wallet(storage_path: &str, mnemonic: Option, let mut wallet_builder = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE); + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)); + #[cfg(feature = "storage")] { wallet_builder = wallet_builder.with_storage_path(storage_path); @@ -58,7 +59,7 @@ pub(crate) async fn make_ledger_nano_wallet(storage_path: &str, node: Option<&st let mut wallet_builder = Wallet::builder() .with_secret_manager(SecretManager::LedgerNano(secret_manager)) .with_client_options(client_options) - .with_coin_type(SHIMMER_COIN_TYPE); + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)); #[cfg(feature = "storage")] { wallet_builder = wallet_builder.with_storage_path(storage_path); @@ -67,28 +68,20 @@ pub(crate) async fn make_ledger_nano_wallet(storage_path: &str, node: Option<&st wallet_builder.finish().await } -/// Create `amount` new accounts, request funds from the faucet and sync the accounts afterwards until the faucet output -/// is available. Returns the new accounts. +/// Request funds from the faucet and sync the wallet. #[allow(dead_code)] -pub(crate) async fn create_accounts_with_funds(wallet: &Wallet, amount: usize) -> Result> { - let mut new_accounts = Vec::new(); - 'accounts: for _ in 0..amount { - let account = wallet.create_account().finish().await?; - request_funds_from_faucet(FAUCET_URL, &account.first_address_bech32().await).await?; +pub(crate) async fn request_funds(wallet: &Wallet) -> Result<()> { + request_funds_from_faucet(FAUCET_URL, &wallet.address().await).await?; - // Continue only after funds are received - for _ in 0..30 { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - let balance = account.sync(None).await?; - if balance.base_coin().available() > 0 { - new_accounts.push(account); - continue 'accounts; - } + // Continue only after funds are received + for _ in 0..30 { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + let balance = wallet.sync(None).await?; + if balance.base_coin().available() > 0 { + return Ok(()); } - panic!("Faucet no longer wants to hand over coins"); } - - Ok(new_accounts) + panic!("Faucet no longer wants to hand over coins"); } #[allow(dead_code)] diff --git a/sdk/tests/wallet/consolidation.rs b/sdk/tests/wallet/consolidation.rs index c3fc3e8a7b..64b92e0753 100644 --- a/sdk/tests/wallet/consolidation.rs +++ b/sdk/tests/wallet/consolidation.rs @@ -1,51 +1,53 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::wallet::{account::ConsolidationParams, Result, SendParams}; +use iota_sdk::wallet::{ConsolidationParams, Result, SendParams}; use pretty_assertions::assert_eq; -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; +use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] async fn consolidation() -> Result<()> { - let storage_path = "test-storage/consolidation"; - setup(storage_path)?; + let storage_path_0 = "test-storage/consolidation_0"; + let storage_path_1 = "test-storage/consolidation_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; + request_funds(&wallet_0).await?; - // Send 10 outputs to account_1 + // Send 10 outputs to wallet_1 let amount = 1_000_000; - let tx = account_0 - .send_with_params( - vec![SendParams::new(amount, account_1.first_address_bech32().await)?; 10], - None, - ) + let tx = wallet_0 + .send_with_params(vec![SendParams::new(amount, wallet_1.address().await)?; 10], None) .await?; - account_0 + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account_1.sync(None).await.unwrap(); + let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.base_coin().available(), 10 * amount); - assert_eq!(account_1.unspent_outputs(None).await?.len(), 10); + assert_eq!(wallet_1.unspent_outputs(None).await.len(), 10); - let tx = account_1 + let tx = wallet_1 .consolidate_outputs(ConsolidationParams::new().with_force(true)) .await?; - account_1 + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account_1.sync(None).await.unwrap(); + let balance = wallet_1.sync(None).await.unwrap(); // Balance still the same assert_eq!(balance.base_coin().available(), 10 * amount); // Only one unspent output - assert_eq!(account_1.unspent_outputs(None).await?.len(), 1); + assert_eq!(wallet_1.unspent_outputs(None).await.len(), 1); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } diff --git a/sdk/tests/wallet/core.rs b/sdk/tests/wallet/core.rs index 3afe917b18..8723afe641 100644 --- a/sdk/tests/wallet/core.rs +++ b/sdk/tests/wallet/core.rs @@ -13,6 +13,7 @@ use iota_sdk::{ constants::IOTA_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, + crypto::keys::bip44::Bip44, types::block::address::Bech32Address, wallet::{ClientOptions, Result, Wallet}, }; @@ -56,70 +57,65 @@ async fn update_client_options() -> Result<()> { tear_down(storage_path) } -#[cfg(feature = "storage")] -#[tokio::test] -async fn different_seed() -> Result<()> { - let storage_path = "test-storage/different_seed"; - setup(storage_path)?; +// #[cfg(feature = "storage")] +// #[tokio::test] +// async fn different_seed() -> Result<()> { +// let storage_path = "test-storage/different_seed"; +// setup(storage_path)?; - let wallet = make_wallet(storage_path, None, None).await?; - let _account = wallet.create_account().with_alias("Alice").finish().await?; +// let wallet = make_wallet(storage_path, None, None).await?; - drop(_account); - drop(wallet); +// drop(wallet); - // Recreate Wallet with a different mnemonic - let wallet = make_wallet(storage_path, None, None).await?; - - // Generating a new account needs to return an error, because the seed from the secret_manager is different - assert!(wallet.create_account().with_alias("Bob").finish().await.is_err()); +// // Recreate Wallet with a different mnemonic +// // Generating a new wallet needs to return an error, because the seed from the secret_manager is different +// assert!(make_wallet(storage_path, None, None).await.is_err()); - tear_down(storage_path) -} +// tear_down(storage_path) +// } #[cfg(feature = "storage")] #[tokio::test] -async fn changed_coin_type() -> Result<()> { +async fn changed_bip_path() -> Result<()> { + use iota_sdk::crypto::keys::bip44::Bip44; + let storage_path = "test-storage/changed_coin_type"; setup(storage_path)?; let mnemonic = Mnemonic::from(DEFAULT_MNEMONIC.to_owned()); let wallet = make_wallet(storage_path, Some(mnemonic.clone()), None).await?; - let _account = wallet.create_account().with_alias("Alice").finish().await?; - drop(_account); drop(wallet); let err = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( mnemonic.clone(), )?)) - .with_coin_type(IOTA_COIN_TYPE) + .with_bip_path(Bip44::new(IOTA_COIN_TYPE)) .with_storage_path(storage_path) .finish() .await; // Building the wallet with another coin type needs to return an error, because a different coin type was used in // the existing account - assert!(matches!( - err, - Err(Error::InvalidCoinType { - new_coin_type: IOTA_COIN_TYPE, - existing_coin_type: SHIMMER_COIN_TYPE - }) - )); + let mismatch_err: Result = Err(Error::BipPathMismatch { + new_bip_path: Some(Bip44::new(IOTA_COIN_TYPE)), + old_bip_path: Some(Bip44::new(SHIMMER_COIN_TYPE)), + }); + assert!(matches!(err, mismatch_err)); // Building the wallet with the same coin type still works - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( - mnemonic, - )?)) - .with_storage_path(storage_path) - .finish() - .await?; - // Also still possible to create a new account - assert!(wallet.create_account().with_alias("Bob").finish().await.is_ok()); + assert!( + Wallet::builder() + .with_secret_manager(SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( + mnemonic, + )?)) + .with_storage_path(storage_path) + .finish() + .await + .is_ok() + ); tear_down(storage_path) } @@ -130,11 +126,10 @@ async fn shimmer_coin_type() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, Some(Mnemonic::from(DEFAULT_MNEMONIC.to_owned())), None).await?; - let account = wallet.create_account().finish().await?; // Creating a new account with providing a coin type will use the Shimmer coin type with shimmer testnet bech32 hrp assert_eq!( - Bech32Address::try_new("smr", account.first_address_bech32().await)?.to_string(), + Bech32Address::try_new("smr", wallet.address().await)?.to_string(), // Address generated with bip32 path: [44, 4219, 0, 0, 0] "smr1qq724zgvdujt3jdcd3xzsuqq7wl9pwq3dvsa5zvx49rj9tme8cat65xq7jz" ); @@ -154,7 +149,7 @@ async fn iota_coin_type() -> Result<()> { let mut wallet_builder = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_client_options(client_options) - .with_coin_type(IOTA_COIN_TYPE); + .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); #[cfg(feature = "storage")] { @@ -162,11 +157,9 @@ async fn iota_coin_type() -> Result<()> { } let wallet = wallet_builder.finish().await?; - let account = wallet.create_account().finish().await?; - // Creating a new account with providing a coin type will use the iota coin type with shimmer testnet bech32 hrp assert_eq!( - Bech32Address::try_new("smr", account.first_address_bech32().await)?.to_string(), + Bech32Address::try_new("smr", wallet.address().await)?.to_string(), // Address generated with bip32 path: [44, 4218, 0, 0, 0] "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" ); diff --git a/sdk/tests/wallet/error.rs b/sdk/tests/wallet/error.rs index a80fd657b9..037ab03582 100644 --- a/sdk/tests/wallet/error.rs +++ b/sdk/tests/wallet/error.rs @@ -6,12 +6,21 @@ use pretty_assertions::assert_eq; #[test] fn stringified_error() { - let error = Error::AccountNotFound("0".into()); + // testing a unit-type-like error + let error = Error::MissingBipPath; assert_eq!( &serde_json::to_string(&error).unwrap(), - "{\"type\":\"accountNotFound\",\"error\":\"account 0 not found\"}" + "{\"type\":\"missingBipPath\",\"error\":\"missing BIP path\"}" ); + // testing a tuple-like error + let error = Error::InvalidMnemonic("nilly willy".to_string()); + assert_eq!( + serde_json::to_string(&error).unwrap(), + "{\"type\":\"invalidMnemonic\",\"error\":\"invalid mnemonic: nilly willy\"}" + ); + + // testing a struct-like error let error = Error::NoOutputsToConsolidate { available_outputs: 0, consolidation_threshold: 0, @@ -20,10 +29,4 @@ fn stringified_error() { &serde_json::to_string(&error).unwrap(), "{\"type\":\"noOutputsToConsolidate\",\"error\":\"nothing to consolidate: available outputs: 0, consolidation threshold: 0\"}" ); - - let error = Error::FailedToGetRemainder; - assert_eq!( - &serde_json::to_string(&error).unwrap(), - "{\"type\":\"failedToGetRemainder\",\"error\":\"failed to get remainder address\"}" - ); } diff --git a/sdk/tests/wallet/events.rs b/sdk/tests/wallet/events.rs index b0e59eb4ce..41abdac43c 100644 --- a/sdk/tests/wallet/events.rs +++ b/sdk/tests/wallet/events.rs @@ -15,11 +15,11 @@ use iota_sdk::{ }, }, wallet::{ - account::types::{InclusionState, OutputData, OutputDataDto}, events::types::{ AddressData, NewOutputEvent, SpentOutputEvent, TransactionInclusionEvent, TransactionProgressEvent, WalletEvent, }, + types::{InclusionState, OutputData, OutputDataDto}, }, }; use pretty_assertions::assert_eq; @@ -95,7 +95,7 @@ fn wallet_events_serde() { let output = Output::Basic( BasicOutput::build_with_amount(amount) .add_unlock_condition(AddressUnlockCondition::new(address)) - .finish_with_params(protocol_parameters.token_supply()) + .finish() .unwrap(), ); let transaction = Transaction::builder(protocol_parameters.network_id()) diff --git a/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs b/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs index 5862ce3aef..1bd1747ecb 100644 --- a/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs +++ b/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs @@ -12,6 +12,7 @@ use iota_sdk::{ stronghold::{Error as StrongholdError, StrongholdAdapter}, Error as ClientError, }, + crypto::keys::bip44::Bip44, wallet::{ClientOptions, Error as WalletError, Wallet}, }; use pretty_assertions::assert_eq; @@ -88,7 +89,7 @@ async fn stronghold_snapshot_v2_v3_migration() { .with_secret_manager(stronghold_secret_manager) .with_client_options(ClientOptions::new().with_node(NODE_LOCAL).unwrap()) // Build with a different coin type, to check if it gets replaced by the one from the backup - .with_coin_type(IOTA_COIN_TYPE) + .with_bip_path(Bip44::new(IOTA_COIN_TYPE)) .finish() .await .unwrap(); diff --git a/sdk/tests/wallet/mod.rs b/sdk/tests/wallet/mod.rs index 60058449ce..0cb359191e 100644 --- a/sdk/tests/wallet/mod.rs +++ b/sdk/tests/wallet/mod.rs @@ -1,8 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod account_recovery; -mod accounts; mod address_generation; #[cfg(all(feature = "stronghold", feature = "storage"))] mod backup_restore; diff --git a/sdk/tests/wallet/native_tokens.rs b/sdk/tests/wallet/native_tokens.rs index b0bc9bd239..a392e8fdd4 100644 --- a/sdk/tests/wallet/native_tokens.rs +++ b/sdk/tests/wallet/native_tokens.rs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::{ - wallet::{account::SyncOptions, CreateNativeTokenParams, Result}, + wallet::{CreateNativeTokenParams, Result, SyncOptions}, U256, }; use pretty_assertions::assert_eq; -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; +use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] @@ -17,15 +17,15 @@ async fn create_and_mint_native_token() -> Result<()> { let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; - let tx = account.create_account_output(None, None).await?; - account + let tx = wallet.create_account_output(None, None).await?; + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - account.sync(None).await?; + wallet.sync(None).await?; - let create_tx = account + let create_tx = wallet .create_native_token( CreateNativeTokenParams { account_id: None, @@ -36,10 +36,10 @@ async fn create_and_mint_native_token() -> Result<()> { None, ) .await?; - account + wallet .reissue_transaction_until_included(&create_tx.transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; assert_eq!(balance.native_tokens().len(), 1); assert_eq!( balance @@ -51,11 +51,11 @@ async fn create_and_mint_native_token() -> Result<()> { U256::from(50) ); - let tx = account.mint_native_token(create_tx.token_id, 50, None).await?; - account + let tx = wallet.mint_native_token(create_tx.token_id, 50, None).await?; + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account.sync(None).await?; + let balance = wallet.sync(None).await?; assert_eq!(balance.native_tokens().len(), 1); assert_eq!( balance @@ -78,17 +78,17 @@ async fn native_token_foundry_metadata() -> Result<()> { let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; - let tx = account.create_account_output(None, None).await?; - account + let tx = wallet.create_account_output(None, None).await?; + wallet .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - account.sync(None).await?; + wallet.sync(None).await?; let foundry_metadata = [1, 3, 3, 7]; - let create_tx = account + let create_tx = wallet .create_native_token( CreateNativeTokenParams { account_id: None, @@ -99,11 +99,11 @@ async fn native_token_foundry_metadata() -> Result<()> { None, ) .await?; - account + wallet .reissue_transaction_until_included(&create_tx.transaction.transaction_id, None, None) .await?; // Sync native_token_foundries to get the metadata - let balance = account + let balance = wallet .sync(Some(SyncOptions { sync_native_token_foundries: true, ..Default::default() diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index 1f58006536..8a2e294a03 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -6,17 +6,14 @@ use std::str::FromStr; use iota_sdk::{ types::block::{ address::{Address, Bech32Address, ToBech32Ext}, - output::{MinimumStorageDepositBasicOutput, NativeToken, NftId, Output, Rent, TokenId}, + output::{BasicOutput, MinimumOutputAmount, NativeToken, NftId, TokenId}, slot::SlotIndex, }, - wallet::{ - account::{Assets, Features, OutputParams, ReturnStrategy, StorageDeposit, Unlocks}, - MintNftParams, Result, - }, + wallet::{Assets, Features, MintNftParams, OutputParams, Result, ReturnStrategy, StorageDeposit, Unlocks}, }; use pretty_assertions::assert_eq; -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; +use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] @@ -25,14 +22,14 @@ async fn output_preparation() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; let recipient_address_bech32 = String::from("rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"); // Roundtrip to get the correct bech32 HRP let recipient_address = - Address::try_from_bech32(&recipient_address_bech32)?.to_bech32(account.client().get_bech32_hrp().await?); + Address::try_from_bech32(&recipient_address_bech32)?.to_bech32(wallet.client().get_bech32_hrp().await?); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -51,7 +48,7 @@ async fn output_preparation() -> Result<()> { let sdr = output.unlock_conditions().unwrap().storage_deposit_return().unwrap(); assert_eq!(sdr.amount(), 46300); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -72,7 +69,7 @@ async fn output_preparation() -> Result<()> { TokenId::from_str("0x08847bd287c912fadedb6bf38900bda9f2d377b75b2a0bece8738699f56ebca4130100000000")?, 10, )?; - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -93,7 +90,7 @@ async fn output_preparation() -> Result<()> { assert_eq!(output.unlock_conditions().unwrap().len(), 1); assert_eq!(output.native_tokens().unwrap().first(), Some(&native_token)); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -118,7 +115,7 @@ async fn output_preparation() -> Result<()> { assert_eq!(output.features().unwrap().len(), 2); // only send 1 with metadata feature - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -146,7 +143,7 @@ async fn output_preparation() -> Result<()> { // metadata and tag features assert_eq!(output.features().unwrap().len(), 2); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -171,7 +168,7 @@ async fn output_preparation() -> Result<()> { // metadata and tag features assert_eq!(output.features().unwrap().len(), 2); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -199,8 +196,8 @@ async fn output_preparation() -> Result<()> { // metadata and tag features assert_eq!(output.features().unwrap().len(), 2); - // Error if this NftId is not in the account - let error = account + // Error if this NftId is not in the wallet + let error = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -224,7 +221,7 @@ async fn output_preparation() -> Result<()> { _ => panic!("should return NftNotFoundInUnspentOutputs error"), } - if let Ok(output) = account + if let Ok(output) = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -252,13 +249,11 @@ async fn output_preparation() -> Result<()> { let issuer_and_sender_address_bech32 = Bech32Address::try_from_str("rms1qq724zgvdujt3jdcd3xzsuqq7wl9pwq3dvsa5zvx49rj9tme8cat6qptyfm")?; // Roundtrip to get the correct bech32 HRP - let issuer_and_sender_address = issuer_and_sender_address_bech32 - .into_inner() - .to_bech32(account.client().get_bech32_hrp().await?); + let issuer_and_sender_address = issuer_and_sender_address_bech32.to_bech32(wallet.client().get_bech32_hrp().await?); let expected_address = issuer_and_sender_address.inner(); // sender address present when building basic output - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -288,7 +283,7 @@ async fn output_preparation() -> Result<()> { assert_eq!(features.sender().unwrap().address(), expected_address); // error when adding issuer when building basic output - let error = account + let error = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -313,7 +308,7 @@ async fn output_preparation() -> Result<()> { } // issuer and sender address present when building nft output - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -357,7 +352,7 @@ async fn output_preparation() -> Result<()> { assert!(conditions.is_expired(2)); // nft with expiration - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -388,7 +383,7 @@ async fn output_preparation() -> Result<()> { // address, sdr, expiration assert_eq!(output.unlock_conditions().unwrap().len(), 3); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -409,9 +404,9 @@ async fn output_preparation() -> Result<()> { None, ) .await?; - let rent_structure = wallet.client().get_rent_structure().await?; - let minimum_storage_deposit = output.rent_cost(rent_structure); - assert_eq!(output.amount(), minimum_storage_deposit); + let storage_score_params = wallet.client().get_storage_score_parameters().await?; + let minimum_amount = output.minimum_amount(storage_score_params); + assert_eq!(output.amount(), minimum_amount); assert_eq!(output.amount(), 187900); let sdr = output.unlock_conditions().unwrap().storage_deposit_return().unwrap(); assert_eq!(sdr.amount(), 145300); @@ -431,17 +426,16 @@ async fn output_preparation_sdr() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let account = &create_accounts_with_funds(&wallet, 1).await?[0]; + request_funds(&wallet).await?; - let rent_structure = account.client().get_rent_structure().await?; - let token_supply = account.client().get_token_supply().await?; + let storage_score_params = wallet.client().get_storage_score_parameters().await?; let recipient_address_bech32 = String::from("rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"); // Roundtrip to get the correct bech32 HRP let recipient_address = - Address::try_from_bech32(&recipient_address_bech32)?.to_bech32(account.client().get_bech32_hrp().await?); + Address::try_from_bech32(&recipient_address_bech32)?.to_bech32(wallet.client().get_bech32_hrp().await?); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -455,14 +449,14 @@ async fn output_preparation_sdr() -> Result<()> { ) .await?; // Check if the output has enough amount to cover the storage deposit - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; assert_eq!(output.amount(), 50601); // address and sdr unlock condition assert_eq!(output.unlock_conditions().unwrap().len(), 2); let sdr = output.unlock_conditions().unwrap().storage_deposit_return().unwrap(); assert_eq!(sdr.amount(), 42600); - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -476,7 +470,7 @@ async fn output_preparation_sdr() -> Result<()> { ) .await?; // Check if the output has enough amount to cover the storage deposit - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; assert_eq!(output.amount(), 85199); // address and sdr unlock condition assert_eq!(output.unlock_conditions().unwrap().len(), 2); @@ -484,7 +478,7 @@ async fn output_preparation_sdr() -> Result<()> { assert_eq!(sdr.amount(), 42600); // ReturnStrategy::Return provided - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -501,7 +495,7 @@ async fn output_preparation_sdr() -> Result<()> { ) .await?; // Check if the output has enough amount to cover the storage deposit - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; assert_eq!(output.amount(), 85199); // address and sdr unlock condition assert_eq!(output.unlock_conditions().unwrap().len(), 2); @@ -509,7 +503,7 @@ async fn output_preparation_sdr() -> Result<()> { assert_eq!(sdr.amount(), 42600); // ReturnStrategy::Gift provided - let output = account + let output = wallet .prepare_output( OutputParams { recipient_address: recipient_address.clone(), @@ -526,7 +520,7 @@ async fn output_preparation_sdr() -> Result<()> { ) .await?; // Check if the output has enough amount to cover the storage deposit - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; // The additional 1 amount will be added, because the storage deposit should be gifted and not returned assert_eq!(output.amount(), 42600); // storage deposit gifted, only address unlock condition @@ -542,29 +536,28 @@ async fn prepare_nft_output_features_update() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let accounts = &create_accounts_with_funds(&wallet, 1).await?; - let addresses = accounts[0].addresses().await; - let address = addresses[0].address(); + request_funds(&wallet).await?; + let wallet_address = wallet.address().await; let nft_options = [MintNftParams::new() - .with_address(address.clone()) - .with_sender(address.clone()) + .with_address(wallet_address.clone()) + .with_sender(wallet_address.clone()) .with_metadata(b"some nft metadata".to_vec()) .with_tag(b"some nft tag".to_vec()) - .with_issuer(address.clone()) + .with_issuer(wallet_address.clone()) .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; - let transaction = accounts[0].mint_nfts(nft_options, None).await.unwrap(); - accounts[0] + let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let nft_id = *accounts[0].sync(None).await?.nfts().first().unwrap(); + let nft_id = *wallet.sync(None).await?.nfts().first().unwrap(); - let nft = accounts[0] + let nft = wallet .prepare_output( OutputParams { - recipient_address: address.clone(), + recipient_address: wallet_address, amount: 1_000_000, assets: Some(Assets { native_tokens: None, @@ -586,10 +579,7 @@ async fn prepare_nft_output_features_update() -> Result<()> { .clone(); assert_eq!(nft.amount(), 1_000_000); - assert_eq!( - nft.address(), - accounts[0].addresses().await[0].clone().into_bech32().as_ref() - ); + assert_eq!(nft.address(), wallet.address().await.inner()); assert!(nft.features().sender().is_none()); assert!(nft.features().tag().is_none()); assert_eq!(nft.features().metadata().unwrap().data(), &[42]); @@ -599,7 +589,7 @@ async fn prepare_nft_output_features_update() -> Result<()> { ); assert_eq!( nft.immutable_features().issuer().unwrap().address(), - accounts[0].addresses().await[0].clone().into_bech32().as_ref() + wallet.address().await.inner() ); tear_down(storage_path) @@ -608,27 +598,26 @@ async fn prepare_nft_output_features_update() -> Result<()> { #[ignore] #[tokio::test] async fn prepare_output_remainder_dust() -> Result<()> { - let storage_path = "test-storage/prepare_output_remainder_dust"; - setup(storage_path)?; + let storage_path_0 = "test-storage/prepare_output_remainder_dust_0"; + let storage_path_1 = "test-storage/prepare_output_remainder_dust_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; - let accounts = &create_accounts_with_funds(&wallet, 2).await?; - let account = &accounts[0]; - let addresses = &accounts[1].addresses().await; - let address = addresses[0].address(); + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + let wallet_1 = make_wallet(storage_path_1, None, None).await?; + request_funds(&wallet_0).await?; + request_funds(&wallet_1).await?; - let rent_structure = account.client().get_rent_structure().await?; - let token_supply = account.client().get_token_supply().await?; + let storage_score_params = wallet_0.client().get_storage_score_parameters().await?; - let balance = account.sync(None).await?; - let minimum_required_storage_deposit = - MinimumStorageDepositBasicOutput::new(rent_structure, token_supply).finish()?; + let balance = wallet_0.sync(None).await?; + let minimum_amount = BasicOutput::minimum_amount(&*wallet_1.address().await, storage_score_params); // Send away most balance so we can test with leaving dust - let output = account + let output = wallet_0 .prepare_output( OutputParams { - recipient_address: address.clone(), + recipient_address: wallet_1.address().await, amount: balance.base_coin().available() - 63900, assets: None, features: None, @@ -638,18 +627,18 @@ async fn prepare_output_remainder_dust() -> Result<()> { None, ) .await?; - let transaction = account.send_outputs(vec![output], None).await?; - account + let transaction = wallet_0.send_outputs(vec![output], None).await?; + wallet_0 .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let balance = account.sync(None).await?; + let balance = wallet_0.sync(None).await?; // 63900 left - let output = account + let output = wallet_0 .prepare_output( OutputParams { - recipient_address: address.clone(), - amount: minimum_required_storage_deposit - 1, // Leave less than min. deposit + recipient_address: wallet_1.address().await, + amount: minimum_amount - 1, // Leave less than min. deposit assets: None, features: None, unlocks: None, @@ -663,17 +652,17 @@ async fn prepare_output_remainder_dust() -> Result<()> { .await?; // Check if the output has enough amount to cover the storage deposit - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; // The left over 21299 is too small to keep, so we donate it assert_eq!(output.amount(), balance.base_coin().available()); // storage deposit gifted, only address unlock condition assert_eq!(output.unlock_conditions().unwrap().len(), 1); - let result = account + let result = wallet_0 .prepare_output( OutputParams { - recipient_address: address.clone(), - amount: minimum_required_storage_deposit - 1, // Leave less than min. deposit + recipient_address: wallet_1.address().await, + amount: minimum_amount - 1, // Leave less than min. deposit assets: None, features: None, unlocks: None, @@ -689,10 +678,10 @@ async fn prepare_output_remainder_dust() -> Result<()> { matches!(result, Err(iota_sdk::wallet::Error::InsufficientFunds{available, required}) if available == balance.base_coin().available() && required == 85199) ); - let output = account + let output = wallet_0 .prepare_output( OutputParams { - recipient_address: address.clone(), + recipient_address: wallet_1.address().await, amount: 100, // leave more behind than min. deposit assets: None, features: None, @@ -707,16 +696,16 @@ async fn prepare_output_remainder_dust() -> Result<()> { .await?; // Check if the output has enough amount to cover the storage deposit - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; // We use excess if leftover is too small, so amount == all available balance assert_eq!(output.amount(), 63900); // storage deposit gifted, only address unlock condition assert_eq!(output.unlock_conditions().unwrap().len(), 1); - let output = account + let output = wallet_0 .prepare_output( OutputParams { - recipient_address: address.clone(), + recipient_address: wallet_1.address().await, amount: 100, // leave more behind than min. deposit assets: None, features: None, @@ -731,7 +720,7 @@ async fn prepare_output_remainder_dust() -> Result<()> { .await?; // Check if the output has enough amount to cover the storage deposit - output.verify_storage_deposit(rent_structure, token_supply)?; + output.verify_storage_deposit(storage_score_params)?; // We use excess if leftover is too small, so amount == all available balance assert_eq!(output.amount(), 63900); // storage deposit returned, address and SDR unlock condition @@ -740,45 +729,47 @@ async fn prepare_output_remainder_dust() -> Result<()> { let sdr = output.unlock_conditions().unwrap().storage_deposit_return().unwrap(); assert_eq!(sdr.amount(), 63900 - 100); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] #[tokio::test] async fn prepare_output_only_single_nft() -> Result<()> { - let storage_path = "test-storage/prepare_output_only_single_nft"; - setup(storage_path)?; + let storage_path_0 = "test-storage/prepare_output_only_single_nft_0"; + let storage_path_1 = "test-storage/prepare_output_only_single_nft_1"; + setup(storage_path_0)?; + setup(storage_path_1)?; - let wallet = make_wallet(storage_path, None, None).await?; - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - // Create second account without funds, so it only gets the NFT - let account_1 = wallet.create_account().finish().await?; - let addresses = &account_0.addresses().await; - let account_0_address = addresses[0].address(); - let addresses = &account_1.addresses().await; - let account_1_address = addresses[0].address(); - - // Send NFT to second account - let tx = account_0 - .mint_nfts( - [MintNftParams::new().try_with_address(account_1_address.clone())?], - None, - ) + let wallet_0 = make_wallet(storage_path_0, None, None).await?; + request_funds(&wallet_0).await?; + + // Create second wallet without funds, so it only gets the NFT + let wallet_1 = make_wallet(storage_path_1, None, None).await?; + + let wallet_0_address = wallet_0.address().await; + let wallet_1_address = wallet_1.address().await; + + // Send NFT to second wallet + let tx = wallet_0 + .mint_nfts([MintNftParams::new().try_with_address(wallet_1_address)?], None) .await?; - account_0 + wallet_0 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - let balance = account_1.sync(None).await?; + let balance = wallet_1.sync(None).await?; assert_eq!(balance.nfts().len(), 1); - let nft_data = &account_1.unspent_outputs(None).await?[0]; + let nft_data = &wallet_1.unspent_outputs(None).await[0]; let nft_id = *balance.nfts().first().unwrap(); - // Send NFT back to first account - let output = account_1 + // Send NFT back to first wallet + let output = wallet_1 .prepare_output( OutputParams { - recipient_address: account_0_address.clone(), + recipient_address: wallet_0_address, amount: nft_data.output.amount(), assets: Some(Assets { native_tokens: None, @@ -791,21 +782,24 @@ async fn prepare_output_only_single_nft() -> Result<()> { None, ) .await?; - let tx = account_1.send_outputs([output], None).await?; - account_1 + let tx = wallet_1.send_outputs([output], None).await?; + wallet_1 .reissue_transaction_until_included(&tx.transaction_id, None, None) .await?; - // account_0 now has the NFT - let balance_0 = account_0.sync(None).await?; + // wallet_0 now has the NFT + let balance_0 = wallet_0.sync(None).await?; assert_eq!(*balance_0.nfts().first().unwrap(), nft_id); - // account_1 has no NFT and also no base coin amount - let balance_1 = account_1.sync(None).await?; + // wallet_1 has no NFT and also no base coin amount + let balance_1 = wallet_1.sync(None).await?; assert!(balance_1.nfts().is_empty()); assert_eq!(balance_1.base_coin().total(), 0); - tear_down(storage_path) + tear_down(storage_path_0)?; + tear_down(storage_path_1)?; + + Ok(()) } #[ignore] @@ -815,9 +809,8 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { setup(storage_path)?; let wallet = make_wallet(storage_path, None, None).await?; - let accounts = &create_accounts_with_funds(&wallet, 1).await?; - let addresses = accounts[0].addresses().await; - let address = addresses[0].address(); + request_funds(&wallet).await?; + let address = wallet.address().await; let nft_options = [MintNftParams::new() .with_address(address.clone()) @@ -827,17 +820,17 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { .with_issuer(address.clone()) .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; - let transaction = accounts[0].mint_nfts(nft_options, None).await.unwrap(); - accounts[0] + let transaction = wallet.mint_nfts(nft_options, None).await.unwrap(); + wallet .reissue_transaction_until_included(&transaction.transaction_id, None, None) .await?; - let nft_id = *accounts[0].sync(None).await?.nfts().first().unwrap(); + let nft_id = *wallet.sync(None).await?.nfts().first().unwrap(); - let nft = accounts[0] + let nft = wallet .prepare_output( OutputParams { - recipient_address: address.clone(), + recipient_address: address, amount: 0, assets: Some(Assets { native_tokens: None, @@ -856,15 +849,12 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { .as_nft() .clone(); - let rent_structure = wallet.client().get_rent_structure().await?; - let minimum_storage_deposit = Output::Nft(nft.clone()).rent_cost(rent_structure); + let storage_score_params = wallet.client().get_storage_score_parameters().await?; + let minimum_storage_deposit = nft.minimum_amount(storage_score_params); assert_eq!(nft.amount(), minimum_storage_deposit); assert_eq!(nft.amount(), 52300); - assert_eq!( - nft.address(), - accounts[0].addresses().await[0].clone().into_bech32().as_ref() - ); + assert_eq!(nft.address(), wallet.address().await.inner()); assert!(nft.features().is_empty()); assert_eq!( nft.immutable_features().metadata().unwrap().data(), @@ -872,7 +862,7 @@ async fn prepare_existing_nft_output_gift() -> Result<()> { ); assert_eq!( nft.immutable_features().issuer().unwrap().address(), - accounts[0].addresses().await[0].clone().into_bech32().as_ref() + wallet.address().await.inner() ); tear_down(storage_path) diff --git a/sdk/tests/wallet/syncing.rs b/sdk/tests/wallet/syncing.rs index 35d0c08fb4..6a417f379d 100644 --- a/sdk/tests/wallet/syncing.rs +++ b/sdk/tests/wallet/syncing.rs @@ -1,232 +1,227 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::{ - types::block::output::{ - unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition}, - AccountId, AccountOutputBuilder, BasicOutputBuilder, NftId, NftOutputBuilder, UnlockCondition, - }, - wallet::{account::SyncOptions, Result}, -}; -use pretty_assertions::assert_eq; - -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; - -#[tokio::test] -#[cfg(feature = "rocksdb")] -async fn updated_default_sync_options() -> Result<()> { - let storage_path = "test-storage/updated_default_sync_options"; - setup(storage_path)?; - - let default_sync = SyncOptions::default(); - - let wallet = make_wallet(storage_path, None, None).await?; - let account = wallet.create_account().finish().await?; - - assert_eq!(default_sync, account.default_sync_options().await); - - let custom_options = SyncOptions { - address_start_index: 10, - ..Default::default() - }; - account.set_default_sync_options(custom_options.clone()).await?; - assert_eq!(custom_options, account.default_sync_options().await); - - drop(account); - drop(wallet); - - let wallet = make_wallet(storage_path, None, None).await?; - let account = wallet.get_account(0).await?; - - assert_eq!(custom_options, account.default_sync_options().await); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn sync_only_most_basic_outputs() -> Result<()> { - let storage_path = "test-storage/sync_only_most_basic_outputs"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - let account_1_address = account_1.first_address_bech32().await; - - let token_supply = account_0.client().get_token_supply().await?; - // Only one basic output without further unlock conditions - let outputs = [ - BasicOutputBuilder::new_with_amount(1_000_000) - .with_unlock_conditions([AddressUnlockCondition::new(account_1_address.clone())]) - .finish_output(token_supply)?, - BasicOutputBuilder::new_with_amount(1_000_000) - .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(account_1_address.clone())), - UnlockCondition::Expiration(ExpirationUnlockCondition::new( - account_1_address.clone(), - // Already expired - account_0.client().get_slot_index().await? - 5000, - )?), - ]) - .finish_output(token_supply)?, - BasicOutputBuilder::new_with_amount(1_000_000) - .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(account_1_address.clone())), - UnlockCondition::Expiration(ExpirationUnlockCondition::new( - account_1_address.clone(), - // Not expired - account_0.client().get_slot_index().await? + 5000, - )?), - ]) - .finish_output(token_supply)?, - BasicOutputBuilder::new_with_amount(1_000_000) - .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(account_1_address.clone())), - UnlockCondition::StorageDepositReturn(StorageDepositReturnUnlockCondition::new( - account_1_address.clone(), - 1_000_000, - token_supply, - )?), - ]) - .finish_output(token_supply)?, - NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) - .with_unlock_conditions([AddressUnlockCondition::new(account_1_address.clone())]) - .finish_output(token_supply)?, - NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) - .with_unlock_conditions([ - UnlockCondition::Address(AddressUnlockCondition::new(account_1_address.clone())), - UnlockCondition::Expiration(ExpirationUnlockCondition::new( - account_1_address.clone(), - account_0.client().get_slot_index().await? + 5000, - )?), - ]) - .finish_output(token_supply)?, - AccountOutputBuilder::new_with_amount(1_000_000, AccountId::null()) - .with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new( - account_1_address.clone(), - ))]) - .finish_output(token_supply)?, - ]; - - let tx = account_0.send_outputs(outputs, None).await?; - account_0 - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - - // Sync with sync_only_most_basic_outputs: true, only the first output should be synced - let balance = account_1 - .sync(Some(SyncOptions { - sync_only_most_basic_outputs: true, - ..Default::default() - })) - .await?; - assert_eq!(balance.potentially_locked_outputs().len(), 0); - assert_eq!(balance.nfts().len(), 0); - assert_eq!(balance.accounts().len(), 0); - let unspent_outputs = account_1.unspent_outputs(None).await?; - assert_eq!(unspent_outputs.len(), 1); - unspent_outputs.into_iter().for_each(|output_data| { - assert!(output_data.output.is_basic()); - assert_eq!(output_data.output.unlock_conditions().unwrap().len(), 1); - assert_eq!( - output_data - .output - .unlock_conditions() - .unwrap() - .address() - .unwrap() - .address(), - account_1_address.as_ref() - ); - }); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn sync_incoming_transactions() -> Result<()> { - let storage_path = "test-storage/sync_incoming_transactions"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - let account_1_address = account_1.first_address_bech32().await; - - let token_supply = account_0.client().get_token_supply().await?; - - let outputs = [ - BasicOutputBuilder::new_with_amount(750_000) - .with_unlock_conditions([AddressUnlockCondition::new(account_1_address.clone())]) - .finish_output(token_supply)?, - BasicOutputBuilder::new_with_amount(250_000) - .with_unlock_conditions([AddressUnlockCondition::new(account_1_address)]) - .finish_output(token_supply)?, - ]; - - let tx = account_0.send_outputs(outputs, None).await?; - account_0 - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - - account_1 - .sync(Some(SyncOptions { - sync_incoming_transactions: true, - ..Default::default() - })) - .await?; - let incoming_transactions = account_1.incoming_transactions().await; - assert_eq!(incoming_transactions.len(), 1); - let incoming_tx = account_1.get_incoming_transaction(&tx.transaction_id).await.unwrap(); - assert_eq!(incoming_tx.inputs.len(), 1); - let transaction = incoming_tx.payload.transaction(); - - // 2 created outputs plus remainder - assert_eq!(transaction.outputs().len(), 3); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -#[cfg(feature = "storage")] -async fn background_syncing() -> Result<()> { - let storage_path = "test-storage/background_syncing"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - wallet.start_background_syncing(None, None).await?; - - let account = wallet.create_account().finish().await?; - - iota_sdk::client::request_funds_from_faucet( - crate::wallet::common::FAUCET_URL, - &account.first_address_bech32().await, - ) - .await?; - - for _ in 0..30 { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - let balance = account.balance().await?; - if balance.base_coin().available() > 0 { - break; - } - } - - // Balance should be != 0 without calling account.sync() - let balance = account.balance().await?; - if balance.base_coin().available() == 0 { - panic!("Faucet no longer wants to hand over coins or background syncing failed"); - } - - wallet.stop_background_syncing().await?; - - tear_down(storage_path) -} +// use iota_sdk::{ +// types::block::output::{ +// unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition}, +// AccountId, AccountOutputBuilder, BasicOutputBuilder, NftId, NftOutputBuilder, UnlockCondition, +// }, +// wallet::{Result, SyncOptions}, +// }; +// use pretty_assertions::assert_eq; + +// use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; + +// #[tokio::test] +// #[cfg(feature = "rocksdb")] +// async fn updated_default_sync_options() -> Result<()> { +// let storage_path = "test-storage/updated_default_sync_options"; +// setup(storage_path)?; + +// let default_sync = SyncOptions::default(); + +// let wallet = make_wallet(storage_path, None, None).await?; + +// assert_eq!(default_sync, wallet.default_sync_options().await); + +// let custom_options = SyncOptions { +// sync_only_most_basic_outputs: true, +// ..Default::default() +// }; +// wallet.set_default_sync_options(custom_options.clone()).await?; +// assert_eq!(custom_options, wallet.default_sync_options().await); + +// drop(wallet); + +// let wallet = make_wallet(storage_path, None, None).await?; + +// assert_eq!(custom_options, wallet.default_sync_options().await); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// async fn sync_only_most_basic_outputs() -> Result<()> { +// let storage_path_0 = "test-storage/sync_only_most_basic_outputs_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/sync_only_most_basic_outputs_1"; +// setup(storage_path_1)?; + +// let wallet_0 = create_wallet_with_funds(storage_path_0, None, None, 1).await?; +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// let wallet_1_address = wallet_1.address().await; + +// let token_supply = wallet_0.client().get_token_supply().await?; +// // Only one basic output without further unlock conditions +// let outputs = [ +// BasicOutputBuilder::new_with_amount(1_000_000) +// .with_unlock_conditions([AddressUnlockCondition::new(wallet_1_address.clone())]) +// .finish_output(token_supply)?, +// BasicOutputBuilder::new_with_amount(1_000_000) +// .with_unlock_conditions([ +// UnlockCondition::Address(AddressUnlockCondition::new(wallet_1_address.clone())), +// UnlockCondition::Expiration(ExpirationUnlockCondition::new( +// wallet_1_address.clone(), +// // Already expired +// wallet_0.client().get_slot_index().await? - 5000, +// )?), +// ]) +// .finish_output(token_supply)?, +// BasicOutputBuilder::new_with_amount(1_000_000) +// .with_unlock_conditions([ +// UnlockCondition::Address(AddressUnlockCondition::new(wallet_1_address.clone())), +// UnlockCondition::Expiration(ExpirationUnlockCondition::new( +// wallet_1_address.clone(), +// // Not expired +// wallet_0.client().get_slot_index().await? + 5000, +// )?), +// ]) +// .finish_output(token_supply)?, +// BasicOutputBuilder::new_with_amount(1_000_000) +// .with_unlock_conditions([ +// UnlockCondition::Address(AddressUnlockCondition::new(wallet_1_address.clone())), +// UnlockCondition::StorageDepositReturn(StorageDepositReturnUnlockCondition::new( +// wallet_1_address.clone(), +// 1_000_000, +// token_supply, +// )?), +// ]) +// .finish_output(token_supply)?, +// NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) +// .with_unlock_conditions([AddressUnlockCondition::new(wallet_1_address.clone())]) +// .finish_output(token_supply)?, +// NftOutputBuilder::new_with_amount(1_000_000, NftId::null()) +// .with_unlock_conditions([ +// UnlockCondition::Address(AddressUnlockCondition::new(wallet_1_address.clone())), +// UnlockCondition::Expiration(ExpirationUnlockCondition::new( +// wallet_1_address.clone(), +// wallet_0.client().get_slot_index().await? + 5000, +// )?), +// ]) +// .finish_output(token_supply)?, +// AccountOutputBuilder::new_with_amount(1_000_000, AccountId::null()) +// .with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new( +// wallet_1_address.clone(), +// ))]) +// .finish_output(token_supply)?, +// ]; + +// let tx = wallet_0.send_outputs(outputs, None).await?; +// wallet_0 +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; + +// // Sync with sync_only_most_basic_outputs: true, only the first output should be synced +// let balance = wallet_1 +// .sync(Some(SyncOptions { +// sync_only_most_basic_outputs: true, +// ..Default::default() +// })) +// .await?; +// assert_eq!(balance.potentially_locked_outputs().len(), 0); +// assert_eq!(balance.nfts().len(), 0); +// assert_eq!(balance.accounts().len(), 0); +// let unspent_outputs = wallet_1.unspent_outputs(None).await?; +// assert_eq!(unspent_outputs.len(), 1); +// unspent_outputs.into_iter().for_each(|output_data| { +// assert!(output_data.output.is_basic()); +// assert_eq!(output_data.output.unlock_conditions().unwrap().len(), 1); +// assert_eq!( +// output_data +// .output +// .unlock_conditions() +// .unwrap() +// .address() +// .unwrap() +// .address(), +// wallet_1_address.as_ref() +// ); +// }); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// async fn sync_incoming_transactions() -> Result<()> { +// let storage_path_0 = "test-storage/sync_incoming_transactions_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/sync_incoming_transactions_1"; +// setup(storage_path_1)?; + +// let wallet_0 = create_wallet_with_funds(storage_path_0, None, None, 1).await?; +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// let wallet_1_address = wallet_1.address().await; + +// let token_supply = wallet_0.client().get_token_supply().await?; + +// let outputs = [ +// BasicOutputBuilder::new_with_amount(750_000) +// .with_unlock_conditions([AddressUnlockCondition::new(wallet_1_address.clone())]) +// .finish_output(token_supply)?, +// BasicOutputBuilder::new_with_amount(250_000) +// .with_unlock_conditions([AddressUnlockCondition::new(wallet_1_address)]) +// .finish_output(token_supply)?, +// ]; + +// let tx = wallet_0.send_outputs(outputs, None).await?; +// wallet_0 +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; + +// wallet_1 +// .sync(Some(SyncOptions { +// sync_incoming_transactions: true, +// ..Default::default() +// })) +// .await?; +// let incoming_transactions = wallet_1.incoming_transactions().await; +// assert_eq!(incoming_transactions.len(), 1); +// let incoming_tx = wallet_1.get_incoming_transaction(&tx.transaction_id).await.unwrap(); +// assert_eq!(incoming_tx.inputs.len(), 1); +// let transaction = incoming_tx.payload.transaction(); + +// // 2 created outputs plus remainder +// assert_eq!(transaction.outputs().len(), 3); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// #[cfg(feature = "storage")] +// async fn background_syncing() -> Result<()> { +// let storage_path = "test-storage/background_syncing"; +// setup(storage_path)?; + +// let wallet = make_wallet(storage_path, None, None).await?; + +// wallet.start_background_syncing(None, None).await?; + +// iota_sdk::client::request_funds_from_faucet( +// crate::wallet::common::FAUCET_URL, +// &wallet.address().await, +// ) +// .await?; + +// for _ in 0..30 { +// tokio::time::sleep(std::time::Duration::from_secs(2)).await; +// let balance = wallet.balance().await?; +// if balance.base_coin().available() > 0 { +// break; +// } +// } + +// // Balance should be != 0 without calling wallet.sync() +// let balance = wallet.balance().await?; +// if balance.base_coin().available() == 0 { +// panic!("Faucet no longer wants to hand over coins or background syncing failed"); +// } + +// wallet.stop_background_syncing().await?; + +// tear_down(storage_path) +// } diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index 3b7ff019a3..42e1419288 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -1,299 +1,312 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::wallet::{account::TransactionOptions, MintNftParams, Result, SendNftParams, SendParams}; +use iota_sdk::wallet::{MintNftParams, Result, SendNftParams, SendParams, TransactionOptions}; use pretty_assertions::assert_eq; -use crate::wallet::common::{create_accounts_with_funds, make_wallet, setup, tear_down}; - -#[ignore] -#[tokio::test] -async fn send_amount() -> Result<()> { - let storage_path = "test-storage/send_amount"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - let amount = 1_000_000; - let tx = account_0 - .send_with_params([SendParams::new(amount, account_1.first_address_bech32().await)?], None) - .await?; - - account_0 - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - - let balance = account_1.sync(None).await.unwrap(); - assert_eq!(balance.base_coin().available(), amount); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn send_amount_127_outputs() -> Result<()> { - let storage_path = "test-storage/send_amount_127_outputs"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - let amount = 1_000_000; - let tx = account_0 - .send_with_params( - vec![ - SendParams::new( - amount, - account_1.first_address_bech32().await, - )?; - // Only 127, because we need one remainder - 127 - ], - None, - ) - .await?; - - account_0 - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - - let balance = account_1.sync(None).await.unwrap(); - assert_eq!(balance.base_coin().available(), 127 * amount); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn send_amount_custom_input() -> Result<()> { - let storage_path = "test-storage/send_amount_custom_input"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - // Send 10 outputs to account_1 - let amount = 1_000_000; - let tx = account_0 - .send_with_params( - vec![SendParams::new(amount, account_1.first_address_bech32().await)?; 10], - None, - ) - .await?; - - account_0 - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - - let balance = account_1.sync(None).await.unwrap(); - assert_eq!(balance.base_coin().available(), 10 * amount); - - // Send back with custom provided input - let custom_input = &account_1.unspent_outputs(None).await?[5]; - let tx = account_1 - .send_with_params( - [SendParams::new(amount, account_0.first_address_bech32().await)?], - Some(TransactionOptions { - custom_inputs: Some(vec![custom_input.output_id]), - ..Default::default() - }), - ) - .await?; - - assert_eq!(tx.inputs.len(), 1); - assert_eq!(tx.inputs.first().unwrap().metadata.output_id(), &custom_input.output_id); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn send_nft() -> Result<()> { - let storage_path = "test-storage/send_nft"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - let accounts = &create_accounts_with_funds(&wallet, 2).await?; - - let nft_options = [MintNftParams::new() - .with_address(accounts[0].first_address_bech32().await) - .with_metadata(b"some nft metadata".to_vec()) - .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; - - let transaction = accounts[0].mint_nfts(nft_options, None).await.unwrap(); - accounts[0] - .reissue_transaction_until_included(&transaction.transaction_id, None, None) - .await?; - let nft_id = *accounts[0].sync(None).await?.nfts().first().unwrap(); - - // Send to account 1 - let transaction = accounts[0] - .send_nft( - [SendNftParams::new(accounts[1].first_address_bech32().await, nft_id)?], - None, - ) - .await - .unwrap(); - accounts[0] - .reissue_transaction_until_included(&transaction.transaction_id, None, None) - .await?; - - let balance = accounts[1].sync(None).await?; - assert_eq!(balance.nfts().len(), 1); - assert_eq!(*balance.nfts().first().unwrap(), nft_id); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn send_with_note() -> Result<()> { - let storage_path = "test-storage/send_with_note"; - setup(storage_path)?; - - let wallet = make_wallet(storage_path, None, None).await?; - - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - let amount = 1_000_000; - let tx = account_0 - .send_with_params( - [SendParams::new(amount, account_1.first_address_bech32().await)?], - Some(TransactionOptions { - note: Some(String::from("send_with_note")), - ..Default::default() - }), - ) - .await?; - - assert_eq!(tx.note, Some(String::from("send_with_note"))); - - tear_down(storage_path) -} - -#[ignore] -#[tokio::test] -async fn conflicting_transaction() -> Result<()> { - let storage_path_0 = "test-storage/conflicting_transaction_0"; - let storage_path_1 = "test-storage/conflicting_transaction_1"; - setup(storage_path_0)?; - setup(storage_path_1)?; - - let mnemonic = iota_sdk::client::utils::generate_mnemonic()?; - // Create two wallets with the same mnemonic - let wallet_0 = make_wallet(storage_path_0, Some(mnemonic.clone()), None).await?; - let wallet_0_account = &create_accounts_with_funds(&wallet_0, 1).await?[0]; - let wallet_1 = make_wallet(storage_path_1, Some(mnemonic), None).await?; - let wallet_1_account = wallet_1.create_account().finish().await?; - - // Balance should be equal - assert_eq!(wallet_0_account.sync(None).await?, wallet_1_account.sync(None).await?); - - // Send transaction with each account and without syncing again - let tx = wallet_0_account - .send_with_params( - [SendParams::new( - 1_000_000, - wallet_0_account.first_address_bech32().await, - )?], - None, - ) - .await?; - wallet_0_account - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await?; - // Second transaction will be conflicting - let tx = wallet_1_account - .send_with_params( - [SendParams::new( - // Something in the transaction must be different than in the first one, otherwise it will be the same - // one - 2_000_000, - wallet_0_account.first_address_bech32().await, - )?], - None, - ) - .await?; - // Should return an error since the tx is conflicting - match wallet_1_account - .reissue_transaction_until_included(&tx.transaction_id, None, None) - .await - .unwrap_err() - { - iota_sdk::wallet::Error::Client(client_error) => { - let iota_sdk::client::Error::TangleInclusion(_) = *client_error else { - panic!("Expected TangleInclusion error"); - }; - } - _ => panic!("Expected TangleInclusion error"), - } - - // After syncing the balance is still equal - assert_eq!(wallet_0_account.sync(None).await?, wallet_1_account.sync(None).await?); - - let conflicting_tx = wallet_1_account.get_transaction(&tx.transaction_id).await.unwrap(); - assert_eq!( - conflicting_tx.inclusion_state, - iota_sdk::wallet::account::types::InclusionState::Conflicting - ); - // The conflicting tx is also removed from the pending txs - assert!(wallet_1_account.pending_transactions().await.is_empty()); - - tear_down(storage_path_0).ok(); - tear_down(storage_path_1) -} - -#[tokio::test] -#[cfg(all(feature = "ledger_nano", feature = "events"))] -#[ignore = "requires ledger nano instance"] -async fn prepare_transaction_ledger() -> Result<()> { - use iota_sdk::wallet::events::{types::TransactionProgressEvent, WalletEvent, WalletEventType}; - - let storage_path = "test-storage/wallet_address_generation_ledger"; - setup(storage_path)?; - - let wallet = crate::wallet::common::make_ledger_nano_wallet(storage_path, None).await?; - - let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; - let account_1 = wallet.create_account().finish().await?; - - let amount = 1_000_000; - - let (sender, mut receiver) = tokio::sync::mpsc::channel(1); - - wallet - .listen([WalletEventType::TransactionProgress], move |event| { - if let WalletEvent::TransactionProgress(progress) = &event.event { - if let TransactionProgressEvent::PreparedTransaction(data) = progress { - sender - .try_send(data.as_ref().clone()) - .expect("too many PreparedTransaction events"); - } - } else { - panic!("expected TransactionProgress event") - } - }) - .await; - - let tx = account_0 - .send_with_params([SendParams::new(amount, account_1.first_address_bech32().await)?], None) - .await?; - - let data = receiver.recv().await.expect("never recieved event"); - // TODO put it back - // assert_eq!(data.transaction, tx.payload.transaction().into()); - for (sign, input) in data.inputs_data.iter().zip(tx.inputs) { - assert_eq!(sign.output, input.output); - assert_eq!(sign.output_metadata, input.metadata); - } - - tear_down(storage_path) -} +use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; + +// #[ignore] +// #[tokio::test] +// async fn send_amount() -> Result<()> { +// let storage_path_0 = "test-storage/send_amount_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/send_amount_1"; +// setup(storage_path_1)?; + +// let wallet_0 = make_wallet(storage_path_0, None, None).await?; +// request_funds(&wallet_0, 1).await?; + +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// let amount = 1_000_000; +// let tx = wallet_0 +// .send_with_params([SendParams::new(amount, wallet_1.address().await)?], None) +// .await?; + +// wallet_0 +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; + +// let balance = wallet_1.sync(None).await.unwrap(); +// assert_eq!(balance.base_coin().available(), amount); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// async fn send_amount_127_outputs() -> Result<()> { +// let storage_path_0 = "test-storage/send_amount_127_outputs_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/send_amount_127_outputs_1"; +// setup(storage_path_1)?; + +// let wallet_0 = make_wallet(storage_path_0, None, None).await?; +// request_funds(&wallet_0, 1).await?; + +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// let amount = 1_000_000; +// let tx = wallet_0 +// .send_with_params( +// vec![ +// SendParams::new( +// amount, +// wallet_1.address().await, +// )?; +// // Only 127, because we need one remainder +// 127 +// ], +// None, +// ) +// .await?; + +// wallet_0 +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; + +// let balance = wallet_1.sync(None).await.unwrap(); +// assert_eq!(balance.base_coin().available(), 127 * amount); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// async fn send_amount_custom_input() -> Result<()> { +// let storage_path_0 = "test-storage/send_amount_custom_input_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/send_amount_custom_input_1"; +// setup(storage_path_1)?; + +// let wallet_0 = make_wallet(storage_path_0, None, None).await?; +// request_funds(&wallet_0, 1).await?; + +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// // Send 10 outputs to wallet_1 +// let amount = 1_000_000; +// let tx = wallet_0 +// .send_with_params( +// vec![SendParams::new(amount, wallet_1.first_address_bech32().await)?; 10], +// None, +// ) +// .await?; + +// wallet_0 +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; + +// let balance = wallet_1.sync(None).await.unwrap(); +// assert_eq!(balance.base_coin().available(), 10 * amount); + +// // Send back with custom provided input +// let custom_input = &wallet_1.unspent_outputs(None).await?[5]; +// let tx = wallet_1 +// .send_with_params( +// [SendParams::new(amount, wallet_0.first_address_bech32().await)?], +// Some(TransactionOptions { +// custom_inputs: Some(vec![custom_input.output_id]), +// ..Default::default() +// }), +// ) +// .await?; + +// assert_eq!(tx.inputs.len(), 1); +// assert_eq!(tx.inputs.first().unwrap().metadata.output_id(), &custom_input.output_id); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// async fn send_nft() -> Result<()> { +// let storage_path_0 = "test-storage/send_nft_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/send_nft_1"; +// setup(storage_path_1)?; + +// let wallet_0 = make_wallet(storage_path_0, None, None).await?; +// request_funds(&wallet_0, 2).await?; + +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// let nft_options = [MintNftParams::new() +// .with_address(wallet_0.address().await) +// .with_metadata(b"some nft metadata".to_vec()) +// .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; + +// let transaction = wallet_0.mint_nfts(nft_options, None).await.unwrap(); +// wallet_0 +// .reissue_transaction_until_included(&transaction.transaction_id, None, None) +// .await?; +// let nft_id = *wallet_0.sync(None).await?.nfts().first().unwrap(); + +// // Send to wallet 1 +// let transaction = wallet_0 +// .send_nft( +// [SendNftParams::new(wallet_1.address().await, nft_id)?], +// None, +// ) +// .await +// .unwrap(); +// wallet_0 +// .reissue_transaction_until_included(&transaction.transaction_id, None, None) +// .await?; + +// let balance = wallet_1.sync(None).await?; +// assert_eq!(balance.nfts().len(), 1); +// assert_eq!(*balance.nfts().first().unwrap(), nft_id); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// async fn send_with_note() -> Result<()> { +// let storage_path_0 = "test-storage/send_with_note_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/send_with_note_1"; +// setup(storage_path_1)?; + +// let wallet_0 = make_wallet(storage_path_0, None, None).await?; +// request_funds(&wallet_0, 1).await?; + +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// let amount = 1_000_000; +// let tx = wallet_0 +// .send_with_params( +// [SendParams::new(amount, wallet_1.address().await)?], +// Some(TransactionOptions { +// note: Some(String::from("send_with_note")), +// ..Default::default() +// }), +// ) +// .await?; + +// assert_eq!(tx.note, Some(String::from("send_with_note"))); + +// tear_down(storage_path) +// } + +// #[ignore] +// #[tokio::test] +// async fn conflicting_transaction() -> Result<()> { +// let storage_path_0 = "test-storage/conflicting_transaction_0"; +// let storage_path_1 = "test-storage/conflicting_transaction_1"; +// setup(storage_path_0)?; +// setup(storage_path_1)?; + +// let mnemonic = iota_sdk::client::utils::generate_mnemonic()?; +// // Create two wallets with the same mnemonic +// let wallet_0 = make_wallet(storage_path_0, Some(mnemonic.clone()), None).await?; +// request_funds(&wallet_0, 1).await?; +// let wallet_1 = make_wallet(storage_path_1, Some(mnemonic), None).await?; + +// // Balance should be equal +// assert_eq!(wallet_0.sync(None).await?, wallet_1.sync(None).await?); + +// // Send transaction without syncing again +// let tx = wallet_0 +// .send_with_params( +// [SendParams::new( +// 1_000_000, +// wallet_0.address().await, +// )?], +// None, +// ) +// .await?; +// wallet_0 +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await?; +// // Second transaction will be conflicting +// let tx = wallet_1 +// .send_with_params( +// [SendParams::new( +// // Something in the transaction must be different than in the first one, otherwise it will be the +// same // one +// 2_000_000, +// wallet_0.address().await, +// )?], +// None, +// ) +// .await?; +// // Should return an error since the tx is conflicting +// match wallet_1 +// .reissue_transaction_until_included(&tx.transaction_id, None, None) +// .await +// .unwrap_err() +// { +// iota_sdk::wallet::Error::Client(client_error) => { +// let iota_sdk::client::Error::TangleInclusion(_) = *client_error else { +// panic!("Expected TangleInclusion error"); +// }; +// } +// _ => panic!("Expected TangleInclusion error"), +// } + +// // After syncing the balance is still equal +// assert_eq!(wallet_0.sync(None).await?, wallet_1.sync(None).await?); + +// let conflicting_tx = wallet_1.get_transaction(&tx.transaction_id).await.unwrap(); +// assert_eq!( +// conflicting_tx.inclusion_state, +// iota_sdk::wallet::types::InclusionState::Conflicting +// ); +// // The conflicting tx is also removed from the pending txs +// assert!(wallet_1.pending_transactions().await.is_empty()); + +// tear_down(storage_path_0).ok(); +// tear_down(storage_path_1) +// } + +// #[tokio::test] +// #[cfg(all(feature = "ledger_nano", feature = "events"))] +// #[ignore = "requires ledger nano instance"] +// async fn prepare_transaction_ledger() -> Result<()> { +// use iota_sdk::wallet::events::{types::TransactionProgressEvent, WalletEvent, WalletEventType}; + +// let storage_path_0 = "test-storage/wallet_address_generation_ledger_0"; +// setup(storage_path_0)?; +// let storage_path_1 = "test-storage/wallet_address_generation_ledger_1"; +// setup(storage_path_1)?; + +// let wallet_0 = crate::wallet::common::make_ledger_nano_wallet(storage_path_0, None).await?; +// request_funds(&wallet_0, 1).await?; + +// let wallet_1 = make_wallet(storage_path_1, None, None).await?; + +// let amount = 1_000_000; + +// let (sender, mut receiver) = tokio::sync::mpsc::channel(1); + +// wallet +// .listen([WalletEventType::TransactionProgress], move |event| { +// if let WalletEvent::TransactionProgress(progress) = &event.event { +// if let TransactionProgressEvent::PreparedTransaction(data) = progress { +// sender +// .try_send(data.as_ref().clone()) +// .expect("too many PreparedTransaction events"); +// } +// } else { +// panic!("expected TransactionProgress event") +// } +// }) +// .await; + +// let tx = wallet_0 +// .send_with_params([SendParams::new(amount, wallet_1.address().await)?], None) +// .await?; + +// let data = receiver.recv().await.expect("never recieved event"); +// // TODO put it back +// // assert_eq!(data.transaction, tx.payload.transaction().into()); +// for (sign, input) in data.inputs_data.iter().zip(tx.inputs) { +// assert_eq!(sign.output, input.output); +// assert_eq!(sign.output_metadata, input.metadata); +// } + +// tear_down(storage_path) +// }