diff --git a/.gitignore b/.gitignore index ce26a92..9fbba68 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target *.swp -.vscode \ No newline at end of file +.vscode +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index e07156b..f8d8873 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,27 +4,27 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -56,19 +56,22 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-json-rpc", + "alloy-network", "alloy-provider", "alloy-rpc-client", "alloy-rpc-types", "alloy-serde", + "alloy-signer", + "alloy-signer-local", "alloy-transport", "alloy-transport-http", ] [[package]] name = "alloy-chains" -version = "0.1.49" +version = "0.1.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830045a4421ee38d3ab570d36d4d2b5152c066e72797139224da8de5d5981fd0" +checksum = "28e2652684758b0d9b389d248b209ed9fd9989ef489a550265fe4bb8454fe7eb" dependencies = [ "alloy-primitives", "num_enum", @@ -88,7 +91,7 @@ dependencies = [ "alloy-trie", "auto_impl", "c-kzg", - "derive_more", + "derive_more 1.0.0", "serde", ] @@ -108,9 +111,9 @@ dependencies = [ [[package]] name = "alloy-core" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c618bd382f0bc2ac26a7e4bfae01c9b015ca8f21b37ca40059ae35a7e62b3dc6" +checksum = "9d8bcce99ad10fe02640cfaec1c6bc809b837c783c1d52906aa5af66e2a196f6" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -121,9 +124,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" +checksum = "eb8e762aefd39a397ff485bc86df673465c4ad3ec8819cc60833a8a3ba5cdc87" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -155,7 +158,7 @@ checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ "alloy-primitives", "alloy-rlp", - "derive_more", + "derive_more 1.0.0", "serde", ] @@ -171,7 +174,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "c-kzg", - "derive_more", + "derive_more 1.0.0", "once_cell", "serde", "sha2", @@ -191,9 +194,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" +checksum = "fe6beff64ad0aa6ad1019a3db26fef565aefeb011736150ab73ed3366c3cfd1b" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -211,7 +214,7 @@ dependencies = [ "alloy-sol-types", "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.14", "tracing", ] @@ -237,7 +240,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.14", ] [[package]] @@ -255,25 +258,24 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" +checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", - "derive_more", + "derive_more 2.0.1", "foldhash", - "hashbrown 0.15.2", - "hex-literal", + "hashbrown 0.15.5", "indexmap", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand", + "rand 0.8.5", "ruint", "rustc-hash", "serde", @@ -311,7 +313,7 @@ dependencies = [ "schnellru", "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.14", "tokio", "tracing", "url", @@ -320,9 +322,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f542548a609dca89fcd72b3b9f355928cf844d4363c5eed9c5273a3dd225e097" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -331,13 +333,13 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.10" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a833d97bf8a5f0f878daf2c8451fff7de7f9de38baa5a45d936ec718d81255a" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -400,7 +402,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "derive_more", + "derive_more 1.0.0", "itertools 0.13.0", "serde", "serde_json", @@ -428,28 +430,44 @@ dependencies = [ "auto_impl", "elliptic-curve", "k256", - "thiserror 2.0.9", + "thiserror 2.0.14", +] + +[[package]] +name = "alloy-signer-local" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47fababf5a745133490cde927d48e50267f97d3d1209b9fc9f1d1d666964d172" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror 2.0.14", ] [[package]] name = "alloy-sol-macro" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" +checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" +checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -458,31 +476,32 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" +checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e" dependencies = [ "const-hex", "dunce", "heck", + "macro-string", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" +checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" dependencies = [ "serde", "winnow", @@ -490,9 +509,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" +checksum = "d43d5e60466a440230c07761aa67671d4719d46f43be8ea6e7ed334d8db4a9ab" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -513,7 +532,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.9", + "thiserror 2.0.14", "tokio", "tower", "tracing", @@ -538,14 +557,14 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e428104b2445a4f929030891b3dbf8c94433a8349ba6480946bf6af7975c2f6" +checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more", + "derive_more 1.0.0", "nybbles", "serde", "smallvec", @@ -554,9 +573,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "ark-ff" @@ -669,7 +688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -679,7 +698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -710,50 +729,50 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "auto_impl" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -770,9 +789,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bincode" @@ -785,24 +804,24 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitvec" @@ -827,9 +846,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -839,15 +858,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-slice-cast" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "byteorder" @@ -857,9 +876,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" dependencies = [ "serde", ] @@ -881,24 +900,24 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.5" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "color-eyre" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" dependencies = [ "backtrace", "color-spantrace", @@ -911,9 +930,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" dependencies = [ "once_cell", "owo-colors", @@ -923,9 +942,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" dependencies = [ "cfg-if", "cpufeatures", @@ -940,6 +959,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -958,9 +997,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -973,9 +1012,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -984,7 +1023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1015,9 +1054,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", @@ -1040,7 +1079,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -1051,7 +1099,19 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", + "unicode-xid", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", "unicode-xid", ] @@ -1084,7 +1144,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -1109,9 +1169,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -1126,7 +1186,7 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1134,18 +1194,18 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1188,11 +1248,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1203,7 +1263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand", + "rand 0.8.5", "rustc-hex", "static_assertions", ] @@ -1216,9 +1276,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -1306,7 +1366,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -1358,26 +1418,38 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "group" @@ -1386,7 +1458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1404,9 +1476,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -1422,9 +1494,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -1435,12 +1507,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - [[package]] name = "hmac" version = "0.12.1" @@ -1452,9 +1518,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -1473,12 +1539,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -1486,15 +1552,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -1527,16 +1593,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ + "base64", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1556,13 +1627,15 @@ dependencies = [ "base64", "bincode", "color-eyre", + "hex", "http", "mime_guess", - "rand", + "rand 0.8.5", "regex", "rmp-serde", "serde", "serde_json", + "sha3", "thiserror 1.0.69", "tracing", "tracing-error", @@ -1573,21 +1646,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1596,31 +1670,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1628,67 +1682,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.91", -] - [[package]] name = "id-arena" version = "2.2.1" @@ -1708,9 +1749,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1733,31 +1774,52 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "serde", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "itertools" @@ -1779,15 +1841,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -1839,33 +1901,33 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1873,9 +1935,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru" @@ -1883,7 +1945,18 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.5", +] + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -1897,9 +1970,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" @@ -1919,29 +1992,29 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -1995,9 +2068,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", @@ -2005,29 +2078,30 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "nybbles" -version = "0.3.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a62e678a89501192cc5ebf47dcbc656b608ae5e1c61c9251fe35230f119fe3" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" dependencies = [ "alloy-rlp", "const-hex", @@ -2038,24 +2112,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags", "cfg-if", @@ -2074,20 +2148,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -2103,41 +2177,43 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "3.5.0" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", + "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", + "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.104", ] [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2145,15 +2221,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2170,40 +2246,40 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.9", + "thiserror 2.0.14", "ucd-trie", ] [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2223,27 +2299,36 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -2259,9 +2344,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] @@ -2285,31 +2370,31 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", "bitflags", "lazy_static", "num-traits", - "rand", - "rand_chacha", + "rand 0.9.2", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -2325,13 +2410,19 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -2345,11 +2436,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "serde", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2357,7 +2458,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2366,23 +2477,32 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core", + "rand_core 0.9.3", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] @@ -2433,41 +2553,38 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", "futures-core", - "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-native-tls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", ] [[package]] @@ -2514,9 +2631,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" +checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -2530,7 +2647,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand", + "rand 0.8.5", + "rand 0.9.2", "rlp", "ruint-macro", "serde", @@ -2546,15 +2664,15 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-hex" @@ -2577,42 +2695,36 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.24", + "semver 1.0.26", ] [[package]] name = "rustix" -version = "0.38.42" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "rustls-pki-types", + "zeroize", ] -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" @@ -2628,9 +2740,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" @@ -2643,9 +2755,9 @@ dependencies = [ [[package]] name = "schnellru" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" dependencies = [ "ahash", "cfg-if", @@ -2687,9 +2799,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -2706,9 +2818,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "semver-parser" @@ -2721,29 +2833,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -2765,9 +2877,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -2816,35 +2928,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", ] [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2871,24 +2980,23 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strum" -version = "0.26.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -2910,9 +3018,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.91" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -2921,14 +3029,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.15" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" +checksum = "4560533fbd6914b94a8fb5cc803ed6801c3455668db3b810702c57612bac9412" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -2942,13 +3050,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -2959,12 +3067,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2981,11 +3089,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.14", ] [[package]] @@ -2996,28 +3104,27 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -3040,9 +3147,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -3050,29 +3157,31 @@ dependencies = [ [[package]] name = "tokio" -version = "1.42.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] @@ -3099,9 +3208,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -3112,15 +3221,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", @@ -3137,6 +3246,25 @@ dependencies = [ "futures-util", "pin-project-lite", "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", "tower-layer", "tower-service", ] @@ -3166,20 +3294,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -3245,9 +3373,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" @@ -3275,15 +3403,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-xid" @@ -3302,12 +3430,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3316,9 +3438,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -3334,9 +3456,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -3352,40 +3474,50 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt 0.39.0", +] [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -3396,9 +3528,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3406,22 +3538,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-encoder" @@ -3452,16 +3587,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808198a69b5a0535583370a51d459baa14261dfab04800c4864ee9e1a14346ed" dependencies = [ "bitflags", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "indexmap", - "semver 1.0.24", + "semver 1.0.26", ] [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -3473,9 +3608,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -3504,67 +3639,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] -name = "windows-strings" -version = "0.1.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-result", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.3", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows-targets", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -3573,53 +3701,101 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -3630,7 +3806,7 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa5b79cd8cb4b27a9be3619090c03cbb87fe7b1c6de254b4c9b4477188828af8" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen-rt 0.42.1", "wit-bindgen-rust-macro", ] @@ -3645,6 +3821,15 @@ dependencies = [ "wit-parser", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "wit-bindgen-rt" version = "0.42.1" @@ -3666,7 +3851,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.91", + "syn 2.0.104", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3682,7 +3867,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3716,7 +3901,7 @@ dependencies = [ "id-arena", "indexmap", "log", - "semver 1.0.24", + "semver 1.0.26", "serde", "serde_derive", "serde_json", @@ -3724,17 +3909,11 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" @@ -3747,9 +3926,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -3759,55 +3938,54 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", "synstructure", ] @@ -3828,14 +4006,25 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -3844,11 +4033,11 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.104", ] diff --git a/Cargo.toml b/Cargo.toml index b0c323e..25b390a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ license = "Apache-2.0" [features] logging = ["dep:color-eyre", "dep:tracing", "dep:tracing-error", "dep:tracing-subscriber"] +hyperwallet = [] simulation-mode = [] [dependencies] @@ -20,18 +21,24 @@ alloy = { version = "0.8.1", features = [ "json-rpc", "rpc-client", "rpc-types", + "signers", + "signer-local", + "consensus", + "network", ] } anyhow = "1.0" base64 = "0.22.1" bincode = "1.3.3" color-eyre = { version = "0.6", features = ["capture-spantrace"], optional = true } +hex = "0.4.3" http = "1.0.0" mime_guess = "2.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.120" rand = "0.8" regex = "1.11.1" rmp-serde = "1.1.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.120" +sha3 = "0.10.8" thiserror = "1.0" tracing = { version = "0.1", optional = true } tracing-error = { version = "0.2", optional = true } diff --git a/hyperware-wit/hyperwallet:sys-v0.wit b/hyperware-wit/hyperwallet:sys-v0.wit new file mode 100644 index 0000000..e5f5048 --- /dev/null +++ b/hyperware-wit/hyperwallet:sys-v0.wit @@ -0,0 +1,651 @@ +interface hyperwallet { + use standard.{address, process-id, node-id}; + + /// JSON is passed over Wasm boundary as a string + type json = string; + + /// Wallet address (Ethereum address) + type wallet-address = string; + + /// Chain ID for blockchain networks + type chain-id = u64; + + /// Session identifier + type session-id = string; + + /// User operation hash + type user-operation-hash = string; + + /// Cryptographic signature as bytes + type signature = list; + + /// Available operations in the Hyperwallet protocol + enum operation { + handshake, + unlock-wallet, + register-process, + update-spending-limits, + create-wallet, + import-wallet, + delete-wallet, + rename-wallet, + export-wallet, + encrypt-wallet, + decrypt-wallet, + get-wallet-info, + list-wallets, + set-wallet-limits, + send-eth, + send-token, + approve-token, + call-contract, + sign-transaction, + sign-message, + execute-via-tba, + check-tba-ownership, + setup-tba-delegation, + build-and-sign-user-operation-for-payment, + submit-user-operation, + build-user-operation, + sign-user-operation, + build-and-sign-user-operation, + estimate-user-operation-gas, + get-user-operation-receipt, + configure-paymaster, + resolve-identity, + create-note, + read-note, + setup-delegation, + verify-delegation, + mint-entry, + get-balance, + get-token-balance, + get-transaction-history, + estimate-gas, + get-gas-price, + get-transaction-receipt, + batch-operations, + schedule-operation, + cancel-operation, + } + + /// Categories of operations + enum operation-category { + system, + process-management, + wallet-management, + ethereum, + token-bound-account, + erc4337, + hypermap, + query, + advanced, + } + + /// Spending limits configuration + record spending-limits { + per-tx-eth: option, + daily-eth: option, + per-tx-usdc: option, + daily-usdc: option, + daily-reset-at: u64, + spent-today-eth: string, + spent-today-usdc: string, + } + + /// Process permissions + record process-permissions { + address: address, + allowed-operations: list, + spending-limits: option, + updatable-settings: list, + registered-at: u64, + } + + /// Settings that can be updated + enum updatable-setting { + spending-limits, + } + + /// Session information + record session-info { + server-version: string, + session-id: session-id, + registered-permissions: process-permissions, + initial-chain-id: chain-id, + } + + /// Handshake protocol steps + variant handshake-step { + client-hello(client-hello), + server-welcome(server-welcome), + register(register-request), + complete(complete-handshake), + } + + record client-hello { + client-version: string, + client-name: string, + } + + record server-welcome { + server-version: string, + supported-operations: list, + supported-chains: list, + features: list, + } + + record register-request { + required-operations: list, + spending-limits: option, + } + + record complete-handshake { + registered-permissions: process-permissions, + session-id: string, + } + + /// Main message structure + record hyperwallet-message { + request: hyperwallet-request, + session-id: session-id, + } + + /// All possible request types + variant hyperwallet-request { + // Session Management + handshake(handshake-step), + unlock-wallet(unlock-wallet-request), + + // Wallet Lifecycle Management + create-wallet(create-wallet-request), + import-wallet(import-wallet-request), + delete-wallet(delete-wallet-request), + rename-wallet(rename-wallet-request), + export-wallet(export-wallet-request), + list-wallets, + get-wallet-info(get-wallet-info-request), + set-wallet-limits(set-wallet-limits-request), + + // Ethereum Operations + send-eth(send-eth-request), + send-token(send-token-request), + approve-token(approve-token-request), + get-balance(get-balance-request), + get-token-balance(get-token-balance-request), + call-contract(call-contract-request), + sign-transaction(sign-transaction-request), + sign-message(sign-message-request), + get-transaction-history(get-transaction-history-request), + estimate-gas(estimate-gas-request), + get-gas-price, + get-transaction-receipt(get-transaction-receipt-request), + + // Token Bound Account Operations + execute-via-tba(execute-via-tba-request), + check-tba-ownership(check-tba-ownership-request), + setup-tba-delegation(setup-tba-delegation-request), + + // Account Abstraction (ERC-4337) + build-and-sign-user-operation-for-payment(build-and-sign-user-operation-for-payment-request), + submit-user-operation(submit-user-operation-request), + get-user-operation-receipt(get-user-operation-receipt-request), + build-user-operation(build-user-operation-request), + sign-user-operation(sign-user-operation-request), + build-and-sign-user-operation(build-and-sign-user-operation-request), + estimate-user-operation-gas(estimate-user-operation-gas-request), + configure-paymaster(configure-paymaster-request), + + // Hypermap Operations + resolve-identity(resolve-identity-request), + create-note(create-note-request), + read-note(read-note-request), + setup-delegation(setup-delegation-request), + verify-delegation(verify-delegation-request), + mint-entry(mint-entry-request), + + // Process Management + update-spending-limits(update-spending-limits-request), + } + + /// Response structure + record hyperwallet-response { + success: bool, + data: option, + error: option, + request-id: option, + } + + /// Error information + record operation-error { + code: error-code, + message: string, + details: option, + } + + /// Error codes + enum error-code { + permission-denied, + wallet-not-found, + insufficient-funds, + invalid-operation, + invalid-params, + spending-limit-exceeded, + chain-not-allowed, + blockchain-error, + internal-error, + authentication-failed, + wallet-locked, + operation-not-supported, + version-mismatch, + } + + /// Response data variants + variant hyperwallet-response-data { + // Session Management + handshake(handshake-step), + unlock-wallet(unlock-wallet-response), + + // Wallet Lifecycle + create-wallet(create-wallet-response), + import-wallet(import-wallet-response), + delete-wallet(delete-wallet-response), + export-wallet(export-wallet-response), + + // Wallet Queries + list-wallets(list-wallets-response), + get-wallet-info(get-wallet-info-response), + get-balance(get-balance-response), + get-token-balance(get-token-balance-response), + set-wallet-limits(set-wallet-limits-response), + + // Transactions + send-eth(send-eth-response), + send-token(send-token-response), + + // ERC4337 Account Abstraction + build-and-sign-user-operation-for-payment(build-and-sign-user-operation-response), + submit-user-operation(submit-user-operation-response), + get-user-operation-receipt(user-operation-receipt-response), + + // Hypermap + create-note(create-note-response), + + // Token Bound Accounts + execute-via-tba(execute-via-tba-response), + check-tba-ownership(check-tba-ownership-response), + } + + // Request types + + record unlock-wallet-request { + session-id: session-id, + wallet-id: string, + password: string, + } + + record create-wallet-request { + name: string, + password: option, + } + + record import-wallet-request { + name: string, + private-key: string, + password: option, + } + + record delete-wallet-request { + wallet-id: string, + } + + record rename-wallet-request { + wallet-id: string, + new-name: string, + } + + record export-wallet-request { + wallet-id: string, + password: option, + } + + record get-wallet-info-request { + wallet-id: string, + } + + /// Set wallet-level spending limits + record set-wallet-limits-request { + wallet-id: string, + limits: wallet-spending-limits, + } + + record get-balance-request { + wallet-id: string, + } + + record send-eth-request { + wallet-id: string, + to: string, + amount: string, + } + + record send-token-request { + wallet-id: string, + token-address: string, + to: string, + amount: string, + } + + record approve-token-request { + token-address: string, + spender: string, + amount: string, + } + + record get-token-balance-request { + wallet-id: string, + token-address: string, + } + + record call-contract-request { + to: string, + data: string, + value: option, + } + + record sign-transaction-request { + to: string, + value: string, + data: option, + gas-limit: option, + gas-price: option, + nonce: option, + } + + record sign-message-request { + message: string, + message-type: message-type, + } + + variant message-type { + plain-text, + eip191, + eip712(eip712-data), + } + + record eip712-data { + domain: json, + types: json, + } + + record get-transaction-history-request { + limit: option, + offset: option, + from-block: option, + to-block: option, + } + + record estimate-gas-request { + to: string, + data: option, + value: option, + } + + record get-transaction-receipt-request { + tx-hash: string, + } + + record execute-via-tba-request { + tba-address: string, + target: string, + call-data: string, + value: option, + } + + record check-tba-ownership-request { + tba-address: string, + signer-address: string, + } + + record setup-tba-delegation-request { + tba-address: string, + delegate-address: string, + permissions: list, + } + + record build-and-sign-user-operation-for-payment-request { + eoa-wallet-id: string, + tba-address: string, + target: string, + call-data: string, + use-paymaster: bool, + paymaster-config: option, + password: option, + } + + record paymaster-config { + is-circle-paymaster: bool, + paymaster-address: string, + paymaster-verification-gas: string, + paymaster-post-op-gas: string, + } + + record submit-user-operation-request { + signed-user-operation: json, + entry-point: string, + bundler-url: option, + } + + record get-user-operation-receipt-request { + user-op-hash: string, + } + + record build-user-operation-request { + target: string, + call-data: string, + value: option, + } + + record sign-user-operation-request { + unsigned-user-operation: json, + entry-point: string, + } + + record build-and-sign-user-operation-request { + target: string, + call-data: string, + value: option, + entry-point: string, + } + + record estimate-user-operation-gas-request { + user-operation: json, + entry-point: string, + } + + record configure-paymaster-request { + paymaster-address: string, + paymaster-data: option, + verification-gas-limit: string, + post-op-gas-limit: string, + } + + record resolve-identity-request { + entry-name: string, + } + + record create-note-request { + note-data: json, + metadata: option, + } + + record read-note-request { + note-id: string, + } + + record setup-delegation-request { + delegate-address: string, + permissions: list, + expiry: option, + } + + record verify-delegation-request { + delegate-address: string, + signature: string, + message: string, + } + + record mint-entry-request { + entry-name: string, + metadata: json, + } + + record update-spending-limits-request { + new-limits: spending-limits, + } + + // Response types + + record unlock-wallet-response { + success: bool, + wallet-id: string, + message: string, + } + + record create-wallet-response { + wallet-id: string, + address: string, + name: string, + } + + record import-wallet-response { + wallet-id: string, + address: string, + name: string, + } + + record delete-wallet-response { + success: bool, + wallet-id: string, + message: string, + } + + record export-wallet-response { + address: string, + private-key: string, + } + + record get-wallet-info-response { + wallet-id: string, + address: string, + name: string, + chain-id: chain-id, + is-locked: bool, + } + + record list-wallets-response { + process: string, + wallets: list, + total: u64, + } + + /// Response for setting wallet limits + record set-wallet-limits-response { + success: bool, + wallet-id: string, + message: string, + } + + record wallet { + address: wallet-address, + name: option, + chain-id: chain-id, + encrypted: bool, + created-at: option, + last-used: option, + spending-limits: option, + } + + record wallet-spending-limits { + max-per-call: option, + max-total: option, + currency: string, + total-spent: string, + set-at: option, + updated-at: option, + } + + record get-balance-response { + balance: balance, + wallet-id: string, + chain-id: chain-id, + } + + record balance { + formatted: string, + raw: string, + } + + record get-token-balance-response { + balance: string, + formatted: option, + decimals: option, + } + + record send-eth-response { + tx-hash: string, + from-address: string, + to-address: string, + amount: string, + chain-id: chain-id, + } + + record send-token-response { + tx-hash: string, + from-address: string, + to-address: string, + token-address: string, + amount: string, + chain-id: chain-id, + } + + record build-and-sign-user-operation-response { + signed-user-operation: json, + entry-point: string, + ready-to-submit: bool, + } + + record submit-user-operation-response { + user-op-hash: string, + } + + record user-operation-receipt-response { + receipt: option, + user-op-hash: string, + status: string, + } + + record create-note-response { + note-id: string, + content-hash: string, + created-at: u64, + } + + record execute-via-tba-response { + tx-hash: string, + tba-address: string, + target-address: string, + success: bool, + } + + record check-tba-ownership-response { + tba-address: string, + owner-address: string, + is-owned: bool, + } +} + +world hyperwallet-sys-v0 { + import hyperwallet; + include process-v1; +} diff --git a/hyperware-wit/process-lib.wit b/hyperware-wit/process-lib.wit index 834f96b..c85f693 100644 --- a/hyperware-wit/process-lib.wit +++ b/hyperware-wit/process-lib.wit @@ -1,5 +1,6 @@ world process-lib { import sign; import hypermap-cacher; + import hyperwallet; include lib; } diff --git a/src/eth.rs b/src/eth.rs index 19395fb..657c6a7 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -306,8 +306,8 @@ impl<'de> Deserialize<'de> for NodeOrRpcUrl { /// for that chain. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Provider { - chain_id: u64, - request_timeout: u64, + pub chain_id: u64, + pub request_timeout: u64, } impl Provider { @@ -761,21 +761,55 @@ impl Provider { print_verbosity_success: u8, print_verbosity_error: u8, ) { + let mut delay_secs = 5; // Initial delay + const MAX_DELAY_SECS: u64 = 60; // Maximum delay + loop { match self.subscribe(sub_id, filter.clone()) { - Ok(()) => break, - Err(_) => { + Ok(()) => break, // Success, exit loop + Err(e) => { + // Log the actual error crate::print_to_terminal( print_verbosity_error, - "failed to subscribe to chain! trying again in 5s...", + &format!( + "Failed to subscribe to chain (sub_id {}): {:?}. Retrying in {}s...", + sub_id, e, delay_secs + ), ); - std::thread::sleep(std::time::Duration::from_secs(5)); - continue; + std::thread::sleep(std::time::Duration::from_secs(delay_secs)); + // Increase delay for next attempt, capped at maximum + delay_secs = (delay_secs * 2).min(MAX_DELAY_SECS); + continue; // Retry } } } - crate::print_to_terminal(print_verbosity_success, "subscribed to logs successfully"); + crate::print_to_terminal( + print_verbosity_success, + &format!("Subscribed successfully (sub_id {})", sub_id), + ); } + //pub fn subscribe_loop( + // &self, + // sub_id: u64, + // filter: Filter, + // print_verbosity_success: u8, + // print_verbosity_error: u8, + //) { + // loop { + // match self.subscribe(sub_id, filter.clone()) { + // Ok(()) => break, + // Err(_) => { + // crate::print_to_terminal( + // print_verbosity_error, + // "failed to subscribe to chain! trying again in 5s...", + // ); + // std::thread::sleep(std::time::Duration::from_secs(5)); + // continue; + // } + // } + // } + // crate::print_to_terminal(print_verbosity_success, "subscribed to logs successfully"); + //} /// Unsubscribes from a previously created subscription. /// diff --git a/src/hyperwallet_client/api.rs b/src/hyperwallet_client/api.rs new file mode 100644 index 0000000..0496921 --- /dev/null +++ b/src/hyperwallet_client/api.rs @@ -0,0 +1,523 @@ +use super::types::{ + self, Balance, BuildAndSignUserOperationForPaymentRequest, BuildAndSignUserOperationResponse, + CreateWalletRequest, ExportWalletResponse, GetTokenBalanceResponse, HyperwalletMessage, + HyperwalletRequest, ImportWalletRequest, ListWalletsResponse, PaymasterConfig, + RenameWalletRequest, SendEthRequest, SendTokenRequest, SessionId, SetWalletLimitsRequest, + SetWalletLimitsResponse, TxReceipt, UnlockWalletRequest, UserOperationReceiptResponse, Wallet, +}; +use super::HyperwalletClientError; +use crate::wallet; +use alloy_primitives::{Address as EthAddress, U256}; + +pub fn create_wallet( + session_id: &SessionId, + name: Option<&str>, + password: Option<&str>, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::CreateWallet(CreateWalletRequest { + name: name.map(|s| s.to_string()).unwrap_or_default(), + password: password.map(|s| s.to_string()), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::CreateWallet(wallet_response)) => { + Ok(types::Wallet { + address: wallet_response.address, + name: Some(wallet_response.name), + chain_id: 8453, // Base mainnet - TODO: get from response + encrypted: password.is_some(), + created_at: None, + last_used: None, + spending_limits: None, + }) + } + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing or invalid wallet data in response"), + )), + } +} + +pub fn import_wallet( + session_id: &SessionId, + name: &str, + private_key: &str, + password: Option<&str>, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::ImportWallet(ImportWalletRequest { + name: name.to_string(), + private_key: private_key.to_string(), + password: password.map(|s| s.to_string()), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::ImportWallet(wallet_response)) => { + Ok(types::Wallet { + address: wallet_response.address, + name: Some(wallet_response.name), + chain_id: 8453, // Base mainnet - TODO: get from response + encrypted: password.is_some(), + created_at: None, + last_used: None, + spending_limits: None, + }) + } + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing or invalid wallet data in response"), + )), + } +} + +pub fn unlock_wallet( + session_id: &SessionId, + target_session_id: &str, + wallet_id: &str, + password: &str, +) -> Result<(), HyperwalletClientError> { + let message = build_message( + session_id, + HyperwalletRequest::UnlockWallet(UnlockWalletRequest { + session_id: target_session_id.to_string(), + wallet_id: wallet_id.to_string(), + password: password.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::UnlockWallet(_)) => Ok(()), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Failed to unlock wallet"), + )), + } +} + +pub fn list_wallets(session_id: &SessionId) -> Result { + let message = build_message(session_id, HyperwalletRequest::ListWallets); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::ListWallets(list_response)) => Ok(list_response), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing or invalid wallet list in response"), + )), + } +} + +pub fn get_wallet_info( + session_id: &SessionId, + wallet_id: &str, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::GetWalletInfo(types::GetWalletInfoRequest { + wallet_id: wallet_id.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::GetWalletInfo(info_response)) => Ok(types::Wallet { + address: info_response.address, + name: Some(info_response.name), + chain_id: info_response.chain_id, + encrypted: info_response.is_locked, + created_at: None, + last_used: None, + spending_limits: None, + }), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing or invalid wallet data in response"), + )), + } +} + +pub fn delete_wallet( + session_id: &SessionId, + wallet_id: &str, +) -> Result<(), HyperwalletClientError> { + let message = build_message( + session_id, + HyperwalletRequest::DeleteWallet(types::DeleteWalletRequest { + wallet_id: wallet_id.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::DeleteWallet(_)) => Ok(()), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Failed to delete wallet"), + )), + } +} + +pub fn export_wallet( + session_id: &SessionId, + wallet_id: &str, + password: Option<&str>, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::ExportWallet(types::ExportWalletRequest { + wallet_id: wallet_id.to_string(), + password: password.map(|s| s.to_string()), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::ExportWallet(export_response)) => Ok(export_response), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing export data in response"), + )), + } +} + +pub fn rename_wallet( + session_id: &SessionId, + wallet_id: &str, + new_name: &str, +) -> Result<(), HyperwalletClientError> { + let message = build_message( + session_id, + HyperwalletRequest::RenameWallet(RenameWalletRequest { + wallet_id: wallet_id.to_string(), + new_name: new_name.to_string(), + }), + ); + let response = super::send_message(message)?; + // RenameWallet doesn't have a response variant in the enum, so we just check for success + if response.error.is_none() { + Ok(()) + } else { + Err(HyperwalletClientError::ServerError( + response.error.unwrap_or_else(|| { + types::OperationError::internal_error("Failed to rename wallet") + }), + )) + } +} + +pub fn set_wallet_limits( + session_id: &SessionId, + wallet_id: &str, + limits: types::WalletSpendingLimits, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::SetWalletLimits(SetWalletLimitsRequest { + wallet_id: wallet_id.to_string(), + limits, + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::SetWalletLimits(resp)) => Ok(resp), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing SetWalletLimits response data"), + )), + } +} + +// === TRANSACTIONS === + +pub fn send_eth( + session_id: &SessionId, + wallet_id: &str, + to_address: &str, + amount_eth: &str, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::SendEth(SendEthRequest { + wallet_id: wallet_id.to_string(), + to: to_address.to_string(), + amount: amount_eth.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::SendEth(send_response)) => Ok(TxReceipt { + hash: send_response.tx_hash, + details: serde_json::json!({ + "from": send_response.from_address, + "to": send_response.to_address, + "amount": send_response.amount, + "chain_id": send_response.chain_id, + }), + }), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing transaction receipt in response"), + )), + } +} + +pub fn send_token( + session_id: &SessionId, + wallet_id: &str, + token_address: &str, + to_address: &str, + amount: &str, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::SendToken(SendTokenRequest { + wallet_id: wallet_id.to_string(), + token_address: token_address.to_string(), + to: to_address.to_string(), + amount: amount.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::SendToken(send_response)) => Ok(TxReceipt { + hash: send_response.tx_hash, + details: serde_json::json!({ + "from": send_response.from_address, + "to": send_response.to_address, + "token_address": send_response.token_address, + "amount": send_response.amount, + "chain_id": send_response.chain_id, + }), + }), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing transaction receipt in response"), + )), + } +} + +// === QUERIES === + +pub fn get_balance( + session_id: &SessionId, + wallet_id: &str, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::GetBalance(types::GetBalanceRequest { + wallet_id: wallet_id.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::GetBalance(balance_response)) => { + Ok(balance_response.balance) + } + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing balance data in response"), + )), + } +} + +pub fn get_token_balance( + session_id: &SessionId, + wallet_id: &str, + token_address: &str, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::GetTokenBalance(types::GetTokenBalanceRequest { + wallet_id: wallet_id.to_string(), + token_address: token_address.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::GetTokenBalance(balance_response)) => { + Ok(balance_response) + } + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing token balance data in response"), + )), + } +} + +// === ACCOUNT ABSTRACTION === + +pub fn build_and_sign_user_operation_for_payment( + session_id: &SessionId, + eoa_wallet_id: &str, + tba_address: &str, + target: &str, + call_data: &str, + use_paymaster: bool, + paymaster_config: Option, + password: Option<&str>, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::BuildAndSignUserOperationForPayment( + BuildAndSignUserOperationForPaymentRequest { + eoa_wallet_id: eoa_wallet_id.to_string(), + tba_address: tba_address.to_string(), + target: target.to_string(), + call_data: call_data.to_string(), + use_paymaster, + paymaster_config, + password: password.map(|s| s.to_string()), + }, + ), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::BuildAndSignUserOperationForPayment( + build_response, + )) => Ok(build_response), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing UserOperation build response data"), + )), + } +} + +pub fn submit_user_operation( + session_id: &SessionId, + signed_user_operation: serde_json::Value, + entry_point: &str, + bundler_url: Option<&str>, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::SubmitUserOperation(types::SubmitUserOperationRequest { + signed_user_operation: serde_json::to_string(&signed_user_operation) + .map_err(HyperwalletClientError::Serialization)?, + entry_point: entry_point.to_string(), + bundler_url: bundler_url.map(|s| s.to_string()), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::SubmitUserOperation(submit_response)) => { + Ok(submit_response.user_op_hash) + } + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing UserOperation response data"), + )), + } +} + +pub fn get_user_operation_receipt( + session_id: &SessionId, + user_op_hash: &str, +) -> Result { + let message = build_message( + session_id, + HyperwalletRequest::GetUserOperationReceipt(types::GetUserOperationReceiptRequest { + user_op_hash: user_op_hash.to_string(), + }), + ); + + let response = super::send_message(message)?; + match response.data { + Some(types::HyperwalletResponseData::GetUserOperationReceipt(receipt_response)) => { + Ok(receipt_response) + } + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error("Missing UserOperation receipt data in response"), + )), + } +} + +// === CONVENIENCE FUNCTIONS === + +pub fn execute_gasless_payment( + session_id: &SessionId, + signer_wallet_id: &str, + tba_address: &str, + recipient_address: &str, + amount_usdc: u128, +) -> Result { + let usdc_contract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // Base USDC + + let tba_calldata = create_tba_payment_calldata(usdc_contract, recipient_address, amount_usdc)?; + + let build_response = build_and_sign_user_operation_for_payment( + session_id, + signer_wallet_id, + tba_address, + tba_address, + &tba_calldata, + true, + Default::default(), + None, // password + )?; + + let user_op_hash = submit_user_operation( + session_id, + serde_json::from_str(&build_response.signed_user_operation) + .map_err(HyperwalletClientError::Deserialization)?, + &build_response.entry_point, + None, // bundler_url + )?; + + let receipt_response = + get_user_operation_receipt(session_id, &user_op_hash).unwrap_or_else(|_| { + UserOperationReceiptResponse { + receipt: None, + user_op_hash: user_op_hash.clone(), + status: "pending".to_string(), + } + }); + + let tx_hash = receipt_response + .receipt + .as_ref() + .and_then(|r| serde_json::from_str::(r).ok()) + .and_then(|v| v.get("transactionHash").cloned()) + .and_then(|h| h.as_str().map(|s| s.to_string())) + .unwrap_or_else(|| user_op_hash.clone()); + + Ok(tx_hash) +} + +// === HELPER FUNCTIONS === + +pub fn create_tba_payment_calldata( + usdc_contract: &str, + recipient_address: &str, + amount_usdc: u128, +) -> Result { + // Parse addresses + let usdc_addr = usdc_contract.parse::().map_err(|_| { + HyperwalletClientError::ServerError(types::OperationError::invalid_params( + "Invalid USDC contract address", + )) + })?; + + let recipient_addr = recipient_address.parse::().map_err(|_| { + HyperwalletClientError::ServerError(types::OperationError::invalid_params( + "Invalid recipient address", + )) + })?; + + let erc20_calldata = + wallet::create_erc20_transfer_calldata(recipient_addr, U256::from(amount_usdc)); + + // Create TBA execute calldata using wallet.rs + let tba_calldata = wallet::create_tba_userop_calldata(usdc_addr, U256::ZERO, erc20_calldata, 0); + + Ok(format!("0x{}", hex::encode(tba_calldata))) +} + +// === INTERNAL HELPERS === + +fn build_message(session_id: &SessionId, request: HyperwalletRequest) -> HyperwalletMessage { + HyperwalletMessage { + request, + session_id: session_id.clone(), + } +} diff --git a/src/hyperwallet_client/mod.rs b/src/hyperwallet_client/mod.rs new file mode 100644 index 0000000..adad227 --- /dev/null +++ b/src/hyperwallet_client/mod.rs @@ -0,0 +1,185 @@ +use crate::println as kiprintln; +use crate::Request; +use thiserror::Error; + +pub mod api; +pub mod serde_impls; +mod serde_response_impls; // Proper implementations replacing broken stubs +mod serde_variant_impls; +pub mod types; +pub use types::{ + Balance, BuildAndSignUserOperationForPaymentRequest, BuildAndSignUserOperationResponse, + ChainId, CheckTbaOwnershipResponse, CreateNoteResponse, CreateWalletRequest, + CreateWalletResponse, DeleteWalletRequest, DeleteWalletResponse, Eip712Data, ErrorCode, + ExecuteViaTbaResponse, ExportWalletRequest, ExportWalletResponse, GetBalanceRequest, + GetBalanceResponse, GetTokenBalanceRequest, GetTokenBalanceResponse, GetWalletInfoRequest, + GetWalletInfoResponse, HandshakeConfig, HandshakeStep, HyperwalletMessage, HyperwalletRequest, + HyperwalletResponse, HyperwalletResponseData, ImportWalletRequest, ImportWalletResponse, + ListWalletsResponse, MessageType, Operation, OperationCategory, OperationError, + PaymasterConfig, ProcessAddress, ProcessPermissions, RenameWalletRequest, SendEthRequest, + SendEthResponse, SendTokenRequest, SendTokenResponse, SessionId, SessionInfo, SpendingLimits, + SubmitUserOperationResponse, TxReceipt, UnlockWalletResponse, UpdatableSetting, + UserOperationHash, UserOperationReceiptResponse, WalletAddress, +}; + +pub use api::{ + build_and_sign_user_operation_for_payment, create_tba_payment_calldata, create_wallet, + delete_wallet, execute_gasless_payment, export_wallet, get_balance, get_token_balance, + get_user_operation_receipt, get_wallet_info, import_wallet, list_wallets, rename_wallet, + send_eth, send_token, submit_user_operation, unlock_wallet, +}; + +#[derive(Debug, Error)] +pub enum HyperwalletClientError { + #[error("Handshake Error: Version incompatibility - client {client} vs server {server}")] + VersionMismatch { client: String, server: String }, + #[error("Handshake Error: Required operation is not supported by the server: {operation:?}")] + OperationNotSupported { operation: Operation }, + #[error("Communication error while sending request to Hyperwallet: {0}")] + Communication(anyhow::Error), + #[error("Hyperwallet service returned a failure response: {0:?}")] + ServerError(OperationError), + #[error("Failed to serialize request: {0}")] + Serialization(serde_json::Error), + #[error("Failed to deserialize response: {0}")] + Deserialization(serde_json::Error), +} + +/// Performs the full handshake and registration protocol with the Hyperwallet service. +pub fn initialize(config: HandshakeConfig) -> Result { + const CLIENT_PROTOCOL_VERSION: &str = "0.1.0"; + let client_name = config.client_name.expect("Client name is required"); + + let hello_step = types::HandshakeStep::ClientHello(types::ClientHello { + client_version: CLIENT_PROTOCOL_VERSION.to_string(), + client_name, + }); + let hello_message = types::HyperwalletMessage { + request: types::HyperwalletRequest::Handshake(hello_step), + session_id: String::new(), + }; + + let welcome_response = send_message(hello_message)?; + + let welcome_step = match welcome_response.data { + Some(types::HyperwalletResponseData::Handshake(step)) => step, + _ => { + return Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error( + "Missing or invalid handshake step in ServerWelcome response", + ), + )) + } + }; + + let (server_version, supported_operations) = match welcome_step { + types::HandshakeStep::ServerWelcome(server_welcome) => ( + server_welcome.server_version, + server_welcome.supported_operations, + ), + _ => { + return Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error( + "Expected ServerWelcome handshake step, got different step", + ), + )) + } + }; + + // Basic protocol version negotiation: require matching major version + let client_major = CLIENT_PROTOCOL_VERSION + .split('.') + .next() + .unwrap_or(CLIENT_PROTOCOL_VERSION); + let server_major = server_version.split('.').next().unwrap_or(&server_version); + if client_major != server_major { + return Err(HyperwalletClientError::VersionMismatch { + client: CLIENT_PROTOCOL_VERSION.to_string(), + server: server_version, + }); + } + + for required_op in &config.required_operations { + if !supported_operations.contains(required_op) { + return Err(HyperwalletClientError::ServerError(types::OperationError { + code: types::ErrorCode::PermissionDenied, + message: format!( + "Required operation {:?} not supported by server", + required_op + ), + details: None, + })); + } + } + + let register_step = types::HandshakeStep::Register(types::RegisterRequest { + required_operations: config.required_operations.into_iter().collect(), + spending_limits: config.spending_limits, + }); + + let register_message = types::HyperwalletMessage { + request: types::HyperwalletRequest::Handshake(register_step), + session_id: String::new(), + }; + + let complete_response = send_message(register_message)?; + + let complete_step = match complete_response.data { + Some(types::HyperwalletResponseData::Handshake(step)) => step, + _ => { + return Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error( + "Complete response contained no data or invalid data type", + ), + )) + } + }; + + // Extract SessionInfo using pattern matching + match complete_step { + types::HandshakeStep::Complete(complete_handshake) => Ok(types::SessionInfo { + server_version, + session_id: complete_handshake.session_id, + registered_permissions: complete_handshake.registered_permissions, + initial_chain_id: config.initial_chain_id, + }), + _ => Err(HyperwalletClientError::ServerError( + types::OperationError::internal_error( + "Expected Complete handshake step, received different step", + ), + )), + } +} + +// === INTERNAL HELPERS === + +pub(crate) fn send_message( + message: types::HyperwalletMessage, +) -> Result { + // Use local address pattern like HTTP client - hyperwallet is always local + let response = Request::to(("our", "hyperwallet", "hyperwallet", "sys")) + .body(serde_json::to_vec(&message).map_err(HyperwalletClientError::Serialization)?) + .send_and_await_response(5) // 5s timeout + .map_err(|e| HyperwalletClientError::Communication(e.into()))? + .map_err(|e| HyperwalletClientError::Communication(e.into()))?; + + let hyperwallet_response: types::HyperwalletResponse = + serde_json::from_slice(response.body()).map_err(HyperwalletClientError::Deserialization)?; + + if !hyperwallet_response.success { + return Err(HyperwalletClientError::ServerError( + hyperwallet_response.error.unwrap_or_else(|| { + types::OperationError::internal_error("Operation failed with no error details") + }), + )); + } + + Ok(hyperwallet_response) +} + +pub fn current_timestamp() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() +} diff --git a/src/hyperwallet_client/serde_impls.rs b/src/hyperwallet_client/serde_impls.rs new file mode 100644 index 0000000..83b4111 --- /dev/null +++ b/src/hyperwallet_client/serde_impls.rs @@ -0,0 +1,1353 @@ +use crate::hyperware::process::hyperwallet as wit; +use serde::de::{self, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct}; +use serde::Deserialize; + +// ============================================================================ +// HyperwalletMessage +// ============================================================================ + +impl Serialize for wit::HyperwalletMessage { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("HyperwalletMessage", 2)?; + state.serialize_field("request", &self.request)?; + state.serialize_field("session_id", &self.session_id)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::HyperwalletMessage { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Request, + SessionId, + } + + struct HyperwalletMessageVisitor; + + impl<'de> Visitor<'de> for HyperwalletMessageVisitor { + type Value = wit::HyperwalletMessage; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct HyperwalletMessage") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut request = None; + let mut session_id = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Request => { + if request.is_some() { + return Err(de::Error::duplicate_field("request")); + } + request = Some(map.next_value()?); + } + Field::SessionId => { + if session_id.is_some() { + return Err(de::Error::duplicate_field("session_id")); + } + session_id = Some(map.next_value()?); + } + } + } + + let request = request.ok_or_else(|| de::Error::missing_field("request"))?; + let session_id = + session_id.ok_or_else(|| de::Error::missing_field("session_id"))?; + + Ok(wit::HyperwalletMessage { + request, + session_id, + }) + } + } + + const FIELDS: &[&str] = &["request", "session_id"]; + deserializer.deserialize_struct("HyperwalletMessage", FIELDS, HyperwalletMessageVisitor) + } +} + +// ============================================================================ +// HyperwalletResponse +// ============================================================================ + +impl Serialize for wit::HyperwalletResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("HyperwalletResponse", 4)?; + state.serialize_field("success", &self.success)?; + state.serialize_field("data", &self.data)?; + state.serialize_field("error", &self.error)?; + state.serialize_field("request_id", &self.request_id)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::HyperwalletResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Success, + Data, + Error, + RequestId, + } + + struct HyperwalletResponseVisitor; + + impl<'de> Visitor<'de> for HyperwalletResponseVisitor { + type Value = wit::HyperwalletResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct HyperwalletResponse") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut success = None; + let mut data = None; + let mut error = None; + let mut request_id = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Success => { + if success.is_some() { + return Err(de::Error::duplicate_field("success")); + } + success = Some(map.next_value()?); + } + Field::Data => { + if data.is_some() { + return Err(de::Error::duplicate_field("data")); + } + data = map.next_value()?; + } + Field::Error => { + if error.is_some() { + return Err(de::Error::duplicate_field("error")); + } + error = map.next_value()?; + } + Field::RequestId => { + if request_id.is_some() { + return Err(de::Error::duplicate_field("request_id")); + } + request_id = map.next_value()?; + } + } + } + + let success = success.ok_or_else(|| de::Error::missing_field("success"))?; + + Ok(wit::HyperwalletResponse { + success, + data, + error, + request_id, + }) + } + } + + const FIELDS: &[&str] = &["success", "data", "error", "request_id"]; + deserializer.deserialize_struct("HyperwalletResponse", FIELDS, HyperwalletResponseVisitor) + } +} + +// ============================================================================ +// OperationError +// ============================================================================ + +impl Serialize for wit::OperationError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("OperationError", 3)?; + state.serialize_field("code", &self.code)?; + state.serialize_field("message", &self.message)?; + state.serialize_field("details", &self.details)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::OperationError { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Code, + Message, + Details, + } + + struct OperationErrorVisitor; + + impl<'de> Visitor<'de> for OperationErrorVisitor { + type Value = wit::OperationError; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct OperationError") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut code = None; + let mut message = None; + let mut details = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Code => { + if code.is_some() { + return Err(de::Error::duplicate_field("code")); + } + code = Some(map.next_value()?); + } + Field::Message => { + if message.is_some() { + return Err(de::Error::duplicate_field("message")); + } + message = Some(map.next_value()?); + } + Field::Details => { + if details.is_some() { + return Err(de::Error::duplicate_field("details")); + } + // Accept Option: null -> None + details = map.next_value()?; + } + } + } + + let code = code.ok_or_else(|| de::Error::missing_field("code"))?; + let message = message.ok_or_else(|| de::Error::missing_field("message"))?; + + Ok(wit::OperationError { + code, + message, + details, + }) + } + } + + const FIELDS: &[&str] = &["code", "message", "details"]; + deserializer.deserialize_struct("OperationError", FIELDS, OperationErrorVisitor) + } +} + +// ============================================================================ +// ErrorCode enum +// ============================================================================ + +impl Serialize for wit::ErrorCode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(match self { + wit::ErrorCode::InternalError => "InternalError", + wit::ErrorCode::InvalidParams => "InvalidParams", + wit::ErrorCode::InvalidOperation => "InvalidOperation", + wit::ErrorCode::PermissionDenied => "PermissionDenied", + wit::ErrorCode::WalletNotFound => "WalletNotFound", + wit::ErrorCode::WalletLocked => "WalletLocked", + wit::ErrorCode::AuthenticationFailed => "AuthenticationFailed", + wit::ErrorCode::InsufficientFunds => "InsufficientFunds", + wit::ErrorCode::SpendingLimitExceeded => "SpendingLimitExceeded", + wit::ErrorCode::BlockchainError => "BlockchainError", + wit::ErrorCode::ChainNotAllowed => "ChainNotAllowed", + wit::ErrorCode::OperationNotSupported => "OperationNotSupported", + wit::ErrorCode::VersionMismatch => "VersionMismatch", + }) + } +} + +impl<'a> Deserialize<'a> for wit::ErrorCode { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "InternalError" => Ok(wit::ErrorCode::InternalError), + "InvalidParams" => Ok(wit::ErrorCode::InvalidParams), + "InvalidOperation" => Ok(wit::ErrorCode::InvalidOperation), + "PermissionDenied" => Ok(wit::ErrorCode::PermissionDenied), + "WalletNotFound" => Ok(wit::ErrorCode::WalletNotFound), + "WalletLocked" => Ok(wit::ErrorCode::WalletLocked), + "AuthenticationFailed" => Ok(wit::ErrorCode::AuthenticationFailed), + "InsufficientFunds" => Ok(wit::ErrorCode::InsufficientFunds), + "SpendingLimitExceeded" => Ok(wit::ErrorCode::SpendingLimitExceeded), + "BlockchainError" => Ok(wit::ErrorCode::BlockchainError), + "ChainNotAllowed" => Ok(wit::ErrorCode::ChainNotAllowed), + "OperationNotSupported" => Ok(wit::ErrorCode::OperationNotSupported), + "VersionMismatch" => Ok(wit::ErrorCode::VersionMismatch), + _ => Err(de::Error::unknown_variant( + &s, + &[ + "InternalError", + "InvalidParams", + "InvalidOperation", + "PermissionDenied", + "WalletNotFound", + "WalletLocked", + "AuthenticationFailed", + "InsufficientFunds", + "SpendingLimitExceeded", + "BlockchainError", + "ChainNotAllowed", + "OperationNotSupported", + "VersionMismatch", + ], + )), + } + } +} // ============================================================================ + // Request Types (Records) + // ============================================================================ + +// CreateWalletRequest +impl Serialize for wit::CreateWalletRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + // Only include password when present to avoid sending null + let field_count = if self.password.is_some() { 2 } else { 1 }; + let mut state = serializer.serialize_struct("CreateWalletRequest", field_count)?; + state.serialize_field("name", &self.name)?; + if let Some(ref pwd) = self.password { + state.serialize_field("password", pwd)?; + } + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::CreateWalletRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Name, + Password, + } + + struct CreateWalletRequestVisitor; + + impl<'de> Visitor<'de> for CreateWalletRequestVisitor { + type Value = wit::CreateWalletRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct CreateWalletRequest") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut name = None; + let mut password = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Name => { + if name.is_some() { + return Err(de::Error::duplicate_field("name")); + } + name = Some(map.next_value()?); + } + Field::Password => { + if password.is_some() { + return Err(de::Error::duplicate_field("password")); + } + password = Some(map.next_value()?); + } + } + } + + let name = name.ok_or_else(|| de::Error::missing_field("name"))?; + + Ok(wit::CreateWalletRequest { name, password }) + } + } + + const FIELDS: &[&str] = &["name", "password"]; + deserializer.deserialize_struct("CreateWalletRequest", FIELDS, CreateWalletRequestVisitor) + } +} + +// UnlockWalletRequest +impl Serialize for wit::UnlockWalletRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("UnlockWalletRequest", 3)?; + state.serialize_field("session_id", &self.session_id)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("password", &self.password)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::UnlockWalletRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + SessionId, + WalletId, + Password, + } + + struct UnlockWalletRequestVisitor; + + impl<'de> Visitor<'de> for UnlockWalletRequestVisitor { + type Value = wit::UnlockWalletRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct UnlockWalletRequest") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut session_id = None; + let mut wallet_id = None; + let mut password = None; + + while let Some(key) = map.next_key()? { + match key { + Field::SessionId => { + if session_id.is_some() { + return Err(de::Error::duplicate_field("session_id")); + } + session_id = Some(map.next_value()?); + } + Field::WalletId => { + if wallet_id.is_some() { + return Err(de::Error::duplicate_field("wallet_id")); + } + wallet_id = Some(map.next_value()?); + } + Field::Password => { + if password.is_some() { + return Err(de::Error::duplicate_field("password")); + } + password = Some(map.next_value()?); + } + } + } + + let session_id = + session_id.ok_or_else(|| de::Error::missing_field("session_id"))?; + let wallet_id = wallet_id.ok_or_else(|| de::Error::missing_field("wallet_id"))?; + let password = password.ok_or_else(|| de::Error::missing_field("password"))?; + + Ok(wit::UnlockWalletRequest { + session_id, + wallet_id, + password, + }) + } + } + + const FIELDS: &[&str] = &["session_id", "wallet_id", "password"]; + deserializer.deserialize_struct("UnlockWalletRequest", FIELDS, UnlockWalletRequestVisitor) + } +} + +// ============================================================================ +// Operation enum +// ============================================================================ + +impl Serialize for wit::Operation { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use wit::Operation::*; + serializer.serialize_str(match self { + Handshake => "Handshake", + UnlockWallet => "UnlockWallet", + RegisterProcess => "RegisterProcess", + UpdateSpendingLimits => "UpdateSpendingLimits", + CreateWallet => "CreateWallet", + ImportWallet => "ImportWallet", + DeleteWallet => "DeleteWallet", + RenameWallet => "RenameWallet", + ExportWallet => "ExportWallet", + EncryptWallet => "EncryptWallet", + DecryptWallet => "DecryptWallet", + GetWalletInfo => "GetWalletInfo", + ListWallets => "ListWallets", + SetWalletLimits => "SetWalletLimits", + SendEth => "SendEth", + SendToken => "SendToken", + ApproveToken => "ApproveToken", + CallContract => "CallContract", + SignTransaction => "SignTransaction", + SignMessage => "SignMessage", + ExecuteViaTba => "ExecuteViaTba", + CheckTbaOwnership => "CheckTbaOwnership", + SetupTbaDelegation => "SetupTbaDelegation", + BuildAndSignUserOperationForPayment => "BuildAndSignUserOperationForPayment", + SubmitUserOperation => "SubmitUserOperation", + BuildUserOperation => "BuildUserOperation", + SignUserOperation => "SignUserOperation", + BuildAndSignUserOperation => "BuildAndSignUserOperation", + EstimateUserOperationGas => "EstimateUserOperationGas", + GetUserOperationReceipt => "GetUserOperationReceipt", + ConfigurePaymaster => "ConfigurePaymaster", + ResolveIdentity => "ResolveIdentity", + CreateNote => "CreateNote", + ReadNote => "ReadNote", + SetupDelegation => "SetupDelegation", + VerifyDelegation => "VerifyDelegation", + MintEntry => "MintEntry", + GetBalance => "GetBalance", + GetTokenBalance => "GetTokenBalance", + GetTransactionHistory => "GetTransactionHistory", + EstimateGas => "EstimateGas", + GetGasPrice => "GetGasPrice", + GetTransactionReceipt => "GetTransactionReceipt", + BatchOperations => "BatchOperations", + ScheduleOperation => "ScheduleOperation", + CancelOperation => "CancelOperation", + }) + } +} + +impl<'a> Deserialize<'a> for wit::Operation { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + let s = String::deserialize(deserializer)?; + use wit::Operation::*; + match s.as_str() { + "Handshake" => Ok(Handshake), + "UnlockWallet" => Ok(UnlockWallet), + "RegisterProcess" => Ok(RegisterProcess), + "UpdateSpendingLimits" => Ok(UpdateSpendingLimits), + "CreateWallet" => Ok(CreateWallet), + "ImportWallet" => Ok(ImportWallet), + "DeleteWallet" => Ok(DeleteWallet), + "RenameWallet" => Ok(RenameWallet), + "ExportWallet" => Ok(ExportWallet), + "EncryptWallet" => Ok(EncryptWallet), + "DecryptWallet" => Ok(DecryptWallet), + "GetWalletInfo" => Ok(GetWalletInfo), + "ListWallets" => Ok(ListWallets), + "SetWalletLimits" => Ok(SetWalletLimits), + "SendEth" => Ok(SendEth), + "SendToken" => Ok(SendToken), + "ApproveToken" => Ok(ApproveToken), + "CallContract" => Ok(CallContract), + "SignTransaction" => Ok(SignTransaction), + "SignMessage" => Ok(SignMessage), + "ExecuteViaTba" => Ok(ExecuteViaTba), + "CheckTbaOwnership" => Ok(CheckTbaOwnership), + "SetupTbaDelegation" => Ok(SetupTbaDelegation), + "BuildAndSignUserOperationForPayment" => Ok(BuildAndSignUserOperationForPayment), + "SubmitUserOperation" => Ok(SubmitUserOperation), + "BuildUserOperation" => Ok(BuildUserOperation), + "SignUserOperation" => Ok(SignUserOperation), + "BuildAndSignUserOperation" => Ok(BuildAndSignUserOperation), + "EstimateUserOperationGas" => Ok(EstimateUserOperationGas), + "GetUserOperationReceipt" => Ok(GetUserOperationReceipt), + "ConfigurePaymaster" => Ok(ConfigurePaymaster), + "ResolveIdentity" => Ok(ResolveIdentity), + "CreateNote" => Ok(CreateNote), + "ReadNote" => Ok(ReadNote), + "SetupDelegation" => Ok(SetupDelegation), + "VerifyDelegation" => Ok(VerifyDelegation), + "MintEntry" => Ok(MintEntry), + "GetBalance" => Ok(GetBalance), + "GetTokenBalance" => Ok(GetTokenBalance), + "GetTransactionHistory" => Ok(GetTransactionHistory), + "EstimateGas" => Ok(EstimateGas), + "GetGasPrice" => Ok(GetGasPrice), + "GetTransactionReceipt" => Ok(GetTransactionReceipt), + "BatchOperations" => Ok(BatchOperations), + "ScheduleOperation" => Ok(ScheduleOperation), + "CancelOperation" => Ok(CancelOperation), + _ => Err(de::Error::unknown_variant(&s, &[])), + } + } +} + +// ============================================================================ +// ProcessPermissions +// ============================================================================ + +impl Serialize for wit::ProcessPermissions { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("ProcessPermissions", 5)?; + state.serialize_field("address", &self.address)?; + state.serialize_field("allowed_operations", &self.allowed_operations)?; + state.serialize_field("spending_limits", &self.spending_limits)?; + state.serialize_field("updatable_settings", &self.updatable_settings)?; + state.serialize_field("registered_at", &self.registered_at)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::ProcessPermissions { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Address, + AllowedOperations, + SpendingLimits, + UpdatableSettings, + RegisteredAt, + } + + struct ProcessPermissionsVisitor; + + impl<'de> Visitor<'de> for ProcessPermissionsVisitor { + type Value = wit::ProcessPermissions; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct ProcessPermissions") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut address = None; + let mut allowed_operations = None; + let mut spending_limits = None; + let mut updatable_settings = None; + let mut registered_at = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Address => { + if address.is_some() { + return Err(de::Error::duplicate_field("address")); + } + address = Some(map.next_value()?); + } + Field::AllowedOperations => { + if allowed_operations.is_some() { + return Err(de::Error::duplicate_field("allowed_operations")); + } + allowed_operations = Some(map.next_value()?); + } + Field::SpendingLimits => { + if spending_limits.is_some() { + return Err(de::Error::duplicate_field("spending_limits")); + } + spending_limits = Some(map.next_value()?); + } + Field::UpdatableSettings => { + if updatable_settings.is_some() { + return Err(de::Error::duplicate_field("updatable_settings")); + } + updatable_settings = Some(map.next_value()?); + } + Field::RegisteredAt => { + if registered_at.is_some() { + return Err(de::Error::duplicate_field("registered_at")); + } + registered_at = Some(map.next_value()?); + } + } + } + + let address = address.ok_or_else(|| de::Error::missing_field("address"))?; + let allowed_operations = allowed_operations + .ok_or_else(|| de::Error::missing_field("allowed_operations"))?; + let registered_at = + registered_at.ok_or_else(|| de::Error::missing_field("registered_at"))?; + + Ok(wit::ProcessPermissions { + address, + allowed_operations, + spending_limits, + updatable_settings: updatable_settings.unwrap_or_default(), + registered_at, + }) + } + } + + const FIELDS: &[&str] = &[ + "address", + "allowed_operations", + "spending_limits", + "updatable_settings", + "registered_at", + ]; + deserializer.deserialize_struct("ProcessPermissions", FIELDS, ProcessPermissionsVisitor) + } +} + +// ============================================================================ +// SpendingLimits +// ============================================================================ + +impl Serialize for wit::SpendingLimits { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("SpendingLimits", 7)?; + state.serialize_field("per_tx_eth", &self.per_tx_eth)?; + state.serialize_field("daily_eth", &self.daily_eth)?; + state.serialize_field("per_tx_usdc", &self.per_tx_usdc)?; + state.serialize_field("daily_usdc", &self.daily_usdc)?; + state.serialize_field("daily_reset_at", &self.daily_reset_at)?; + state.serialize_field("spent_today_eth", &self.spent_today_eth)?; + state.serialize_field("spent_today_usdc", &self.spent_today_usdc)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::SpendingLimits { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + PerTxEth, + DailyEth, + PerTxUsdc, + DailyUsdc, + DailyResetAt, + SpentTodayEth, + SpentTodayUsdc, + } + + struct SpendingLimitsVisitor; + + impl<'de> Visitor<'de> for SpendingLimitsVisitor { + type Value = wit::SpendingLimits; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct SpendingLimits") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut per_tx_eth = None; + let mut daily_eth = None; + let mut per_tx_usdc = None; + let mut daily_usdc = None; + let mut daily_reset_at = None; + let mut spent_today_eth = None; + let mut spent_today_usdc = None; + + while let Some(key) = map.next_key()? { + match key { + Field::PerTxEth => { + if per_tx_eth.is_some() { + return Err(de::Error::duplicate_field("per_tx_eth")); + } + per_tx_eth = Some(map.next_value()?); + } + Field::DailyEth => { + if daily_eth.is_some() { + return Err(de::Error::duplicate_field("daily_eth")); + } + daily_eth = Some(map.next_value()?); + } + Field::PerTxUsdc => { + if per_tx_usdc.is_some() { + return Err(de::Error::duplicate_field("per_tx_usdc")); + } + per_tx_usdc = Some(map.next_value()?); + } + Field::DailyUsdc => { + if daily_usdc.is_some() { + return Err(de::Error::duplicate_field("daily_usdc")); + } + daily_usdc = Some(map.next_value()?); + } + Field::DailyResetAt => { + if daily_reset_at.is_some() { + return Err(de::Error::duplicate_field("daily_reset_at")); + } + daily_reset_at = Some(map.next_value()?); + } + Field::SpentTodayEth => { + if spent_today_eth.is_some() { + return Err(de::Error::duplicate_field("spent_today_eth")); + } + spent_today_eth = Some(map.next_value()?); + } + Field::SpentTodayUsdc => { + if spent_today_usdc.is_some() { + return Err(de::Error::duplicate_field("spent_today_usdc")); + } + spent_today_usdc = Some(map.next_value()?); + } + } + } + + let daily_reset_at = + daily_reset_at.ok_or_else(|| de::Error::missing_field("daily_reset_at"))?; + let spent_today_eth = + spent_today_eth.ok_or_else(|| de::Error::missing_field("spent_today_eth"))?; + let spent_today_usdc = + spent_today_usdc.ok_or_else(|| de::Error::missing_field("spent_today_usdc"))?; + + Ok(wit::SpendingLimits { + per_tx_eth, + daily_eth, + per_tx_usdc, + daily_usdc, + daily_reset_at, + spent_today_eth, + spent_today_usdc, + }) + } + } + + const FIELDS: &[&str] = &[ + "per_tx_eth", + "daily_eth", + "per_tx_usdc", + "daily_usdc", + "daily_reset_at", + "spent_today_eth", + "spent_today_usdc", + ]; + deserializer.deserialize_struct("SpendingLimits", FIELDS, SpendingLimitsVisitor) + } +} + +// ============================================================================ +// UpdatableSetting enum +// ============================================================================ + +impl Serialize for wit::UpdatableSetting { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use wit::UpdatableSetting::*; + serializer.serialize_str(match self { + SpendingLimits => "SpendingLimits", + }) + } +} + +impl<'a> Deserialize<'a> for wit::UpdatableSetting { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + let s = String::deserialize(deserializer)?; + use wit::UpdatableSetting::*; + match s.as_str() { + "SpendingLimits" => Ok(SpendingLimits), + _ => Err(de::Error::unknown_variant(&s, &["SpendingLimits"])), + } + } +} + +// ============================================================================ +// HandshakeStep variant type +// ============================================================================ + +impl Serialize for wit::HandshakeStep { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use wit::HandshakeStep::*; + + match self { + ClientHello(data) => { + let mut state = serializer.serialize_struct("HandshakeStep", 2)?; + state.serialize_field("type", "ClientHello")?; + state.serialize_field("data", data)?; + state.end() + } + ServerWelcome(data) => { + let mut state = serializer.serialize_struct("HandshakeStep", 2)?; + state.serialize_field("type", "ServerWelcome")?; + state.serialize_field("data", data)?; + state.end() + } + Register(data) => { + let mut state = serializer.serialize_struct("HandshakeStep", 2)?; + state.serialize_field("type", "Register")?; + state.serialize_field("data", data)?; + state.end() + } + Complete(data) => { + let mut state = serializer.serialize_struct("HandshakeStep", 2)?; + state.serialize_field("type", "Complete")?; + state.serialize_field("data", data)?; + state.end() + } + } + } +} + +impl<'a> Deserialize<'a> for wit::HandshakeStep { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + struct HandshakeStepVisitor; + + impl<'de> Visitor<'de> for HandshakeStepVisitor { + type Value = wit::HandshakeStep; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a HandshakeStep variant") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut variant_type = None; + let mut data = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "type" => { + if variant_type.is_some() { + return Err(de::Error::duplicate_field("type")); + } + variant_type = Some(map.next_value::()?); + } + "data" => { + if data.is_some() { + return Err(de::Error::duplicate_field("data")); + } + data = Some(map.next_value::()?); + } + _ => { + let _ = map.next_value::()?; + } + } + } + + let variant_type = variant_type.ok_or_else(|| de::Error::missing_field("type"))?; + + use wit::HandshakeStep::*; + match variant_type.as_str() { + "ClientHello" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let hello = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!("Failed to deserialize ClientHello: {}", e)) + })?; + Ok(ClientHello(hello)) + } + "ServerWelcome" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let welcome = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!("Failed to deserialize ServerWelcome: {}", e)) + })?; + Ok(ServerWelcome(welcome)) + } + "Register" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let register = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize RegisterRequest: {}", + e + )) + })?; + Ok(Register(register)) + } + "Complete" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let complete = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CompleteHandshake: {}", + e + )) + })?; + Ok(Complete(complete)) + } + _ => Err(de::Error::unknown_variant( + &variant_type, + &["ClientHello", "ServerWelcome", "Register", "Complete"], + )), + } + } + } + + const FIELDS: &[&str] = &["type", "data"]; + deserializer.deserialize_struct("HandshakeStep", FIELDS, HandshakeStepVisitor) + } +} + +// ClientHello +impl Serialize for wit::ClientHello { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("ClientHello", 2)?; + state.serialize_field("client_version", &self.client_version)?; + state.serialize_field("client_name", &self.client_name)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::ClientHello { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ClientVersion, + ClientName, + } + + struct ClientHelloVisitor; + + impl<'de> Visitor<'de> for ClientHelloVisitor { + type Value = wit::ClientHello; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct ClientHello") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut client_version = None; + let mut client_name = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ClientVersion => { + if client_version.is_some() { + return Err(de::Error::duplicate_field("client_version")); + } + client_version = Some(map.next_value()?); + } + Field::ClientName => { + if client_name.is_some() { + return Err(de::Error::duplicate_field("client_name")); + } + client_name = Some(map.next_value()?); + } + } + } + + let client_version = + client_version.ok_or_else(|| de::Error::missing_field("client_version"))?; + let client_name = + client_name.ok_or_else(|| de::Error::missing_field("client_name"))?; + + Ok(wit::ClientHello { + client_version, + client_name, + }) + } + } + + const FIELDS: &[&str] = &["client_version", "client_name"]; + deserializer.deserialize_struct("ClientHello", FIELDS, ClientHelloVisitor) + } +} + +// ServerWelcome +impl Serialize for wit::ServerWelcome { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("ServerWelcome", 4)?; + state.serialize_field("server_version", &self.server_version)?; + state.serialize_field("supported_operations", &self.supported_operations)?; + state.serialize_field("supported_chains", &self.supported_chains)?; + state.serialize_field("features", &self.features)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::ServerWelcome { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ServerVersion, + SupportedOperations, + SupportedChains, + Features, + } + + struct ServerWelcomeVisitor; + + impl<'de> Visitor<'de> for ServerWelcomeVisitor { + type Value = wit::ServerWelcome; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct ServerWelcome") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut server_version = None; + let mut supported_operations = None; + let mut supported_chains = None; + let mut features = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ServerVersion => { + if server_version.is_some() { + return Err(de::Error::duplicate_field("server_version")); + } + server_version = Some(map.next_value()?); + } + Field::SupportedOperations => { + if supported_operations.is_some() { + return Err(de::Error::duplicate_field("supported_operations")); + } + supported_operations = Some(map.next_value()?); + } + Field::SupportedChains => { + if supported_chains.is_some() { + return Err(de::Error::duplicate_field("supported_chains")); + } + supported_chains = Some(map.next_value()?); + } + Field::Features => { + if features.is_some() { + return Err(de::Error::duplicate_field("features")); + } + features = Some(map.next_value()?); + } + } + } + + let server_version = + server_version.ok_or_else(|| de::Error::missing_field("server_version"))?; + let supported_operations = supported_operations + .ok_or_else(|| de::Error::missing_field("supported_operations"))?; + let supported_chains = + supported_chains.ok_or_else(|| de::Error::missing_field("supported_chains"))?; + let features = features.ok_or_else(|| de::Error::missing_field("features"))?; + + Ok(wit::ServerWelcome { + server_version, + supported_operations, + supported_chains, + features, + }) + } + } + + const FIELDS: &[&str] = &[ + "server_version", + "supported_operations", + "supported_chains", + "features", + ]; + deserializer.deserialize_struct("ServerWelcome", FIELDS, ServerWelcomeVisitor) + } +} + +// RegisterRequest +impl Serialize for wit::RegisterRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("RegisterRequest", 2)?; + state.serialize_field("required_operations", &self.required_operations)?; + state.serialize_field("spending_limits", &self.spending_limits)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::RegisterRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + RequiredOperations, + SpendingLimits, + } + + struct RegisterRequestVisitor; + + impl<'de> Visitor<'de> for RegisterRequestVisitor { + type Value = wit::RegisterRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct RegisterRequest") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut required_operations = None; + let mut spending_limits = None; + + while let Some(key) = map.next_key()? { + match key { + Field::RequiredOperations => { + if required_operations.is_some() { + return Err(de::Error::duplicate_field("required_operations")); + } + required_operations = Some(map.next_value()?); + } + Field::SpendingLimits => { + if spending_limits.is_some() { + return Err(de::Error::duplicate_field("spending_limits")); + } + spending_limits = Some(map.next_value()?); + } + } + } + + let required_operations = required_operations + .ok_or_else(|| de::Error::missing_field("required_operations"))?; + + Ok(wit::RegisterRequest { + required_operations, + spending_limits, + }) + } + } + + const FIELDS: &[&str] = &["required_operations", "spending_limits"]; + deserializer.deserialize_struct("RegisterRequest", FIELDS, RegisterRequestVisitor) + } +} + +// CompleteHandshake +impl Serialize for wit::CompleteHandshake { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let mut state = serializer.serialize_struct("CompleteHandshake", 2)?; + state.serialize_field("session_id", &self.session_id)?; + state.serialize_field("registered_permissions", &self.registered_permissions)?; + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::CompleteHandshake { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + SessionId, + RegisteredPermissions, + } + + struct CompleteHandshakeVisitor; + + impl<'de> Visitor<'de> for CompleteHandshakeVisitor { + type Value = wit::CompleteHandshake; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct CompleteHandshake") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut session_id = None; + let mut registered_permissions = None; + + while let Some(key) = map.next_key()? { + match key { + Field::SessionId => { + if session_id.is_some() { + return Err(de::Error::duplicate_field("session_id")); + } + session_id = Some(map.next_value()?); + } + Field::RegisteredPermissions => { + if registered_permissions.is_some() { + return Err(de::Error::duplicate_field("registered_permissions")); + } + registered_permissions = Some(map.next_value()?); + } + } + } + + let session_id = + session_id.ok_or_else(|| de::Error::missing_field("session_id"))?; + let registered_permissions = registered_permissions + .ok_or_else(|| de::Error::missing_field("registered_permissions"))?; + + Ok(wit::CompleteHandshake { + session_id, + registered_permissions, + }) + } + } + + const FIELDS: &[&str] = &["session_id", "registered_permissions"]; + deserializer.deserialize_struct("CompleteHandshake", FIELDS, CompleteHandshakeVisitor) + } +} diff --git a/src/hyperwallet_client/serde_request_response_impls.rs b/src/hyperwallet_client/serde_request_response_impls.rs new file mode 100644 index 0000000..88bc9d4 --- /dev/null +++ b/src/hyperwallet_client/serde_request_response_impls.rs @@ -0,0 +1,127 @@ +use crate::hyperware::process::hyperwallet as wit; +use serde::de::{self}; +use serde::ser::Serialize; +use serde::Deserialize; + +// Create a macro to generate stub implementations for request/response types +macro_rules! impl_stub_serde { + ($type:ty, $name:literal) => { + impl Serialize for $type { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + // For now, serialize as debug representation + let json_val = serde_json::json!({ + "type": $name, + "data": format!("{:?}", self) + }); + json_val.serialize(serializer) + } + } + + impl<'a> Deserialize<'a> for $type { + fn deserialize(deserializer: D) -> Result<$type, D::Error> + where + D: serde::de::Deserializer<'a>, + { + // The hyperwallet service is sending the response wrapped in a debug format + // We need to deserialize the actual JSON payload + let val = serde_json::Value::deserialize(deserializer)?; + + // Try normal JSON deserialization first + match serde_json::from_value(val.clone()) { + Ok(result) => Ok(result), + Err(_) => { + // If that fails, it might be wrapped in the debug format + // For now, we can't parse debug format strings back to structs + // This is a fundamental issue with the hyperwallet service + Err(de::Error::custom(format!( + "{} cannot be deserialized from debug format. The hyperwallet service needs to be fixed to return proper JSON.", + $name + ))) + } + } + } + } + }; +} + +// Request types +impl_stub_serde!( + wit::UpdateSpendingLimitsRequest, + "UpdateSpendingLimitsRequest" +); +impl_stub_serde!(wit::ImportWalletRequest, "ImportWalletRequest"); +impl_stub_serde!(wit::DeleteWalletRequest, "DeleteWalletRequest"); +impl_stub_serde!(wit::RenameWalletRequest, "RenameWalletRequest"); +impl_stub_serde!(wit::ExportWalletRequest, "ExportWalletRequest"); +impl_stub_serde!(wit::GetWalletInfoRequest, "GetWalletInfoRequest"); +impl_stub_serde!(wit::SendEthRequest, "SendEthRequest"); +impl_stub_serde!(wit::SendTokenRequest, "SendTokenRequest"); +impl_stub_serde!(wit::ApproveTokenRequest, "ApproveTokenRequest"); +impl_stub_serde!(wit::GetBalanceRequest, "GetBalanceRequest"); +impl_stub_serde!(wit::GetTokenBalanceRequest, "GetTokenBalanceRequest"); +impl_stub_serde!(wit::CallContractRequest, "CallContractRequest"); +impl_stub_serde!(wit::SignTransactionRequest, "SignTransactionRequest"); +impl_stub_serde!(wit::SignMessageRequest, "SignMessageRequest"); +impl_stub_serde!( + wit::BuildAndSignUserOperationForPaymentRequest, + "BuildAndSignUserOperationForPaymentRequest" +); +impl_stub_serde!( + wit::SubmitUserOperationRequest, + "SubmitUserOperationRequest" +); +impl_stub_serde!( + wit::GetUserOperationReceiptRequest, + "GetUserOperationReceiptRequest" +); +impl_stub_serde!( + wit::GetTransactionHistoryRequest, + "GetTransactionHistoryRequest" +); +impl_stub_serde!(wit::EstimateGasRequest, "EstimateGasRequest"); + +// Response types +impl_stub_serde!(wit::UnlockWalletResponse, "UnlockWalletResponse"); +impl_stub_serde!(wit::CreateWalletResponse, "CreateWalletResponse"); +impl_stub_serde!(wit::ImportWalletResponse, "ImportWalletResponse"); +impl_stub_serde!(wit::DeleteWalletResponse, "DeleteWalletResponse"); +impl_stub_serde!(wit::ExportWalletResponse, "ExportWalletResponse"); +impl_stub_serde!(wit::ListWalletsResponse, "ListWalletsResponse"); +impl_stub_serde!(wit::GetWalletInfoResponse, "GetWalletInfoResponse"); +impl_stub_serde!(wit::GetBalanceResponse, "GetBalanceResponse"); +impl_stub_serde!(wit::GetTokenBalanceResponse, "GetTokenBalanceResponse"); +impl_stub_serde!(wit::SendEthResponse, "SendEthResponse"); +impl_stub_serde!(wit::SendTokenResponse, "SendTokenResponse"); +impl_stub_serde!(wit::BuildAndSignUserOperationResponse,"BuildAndSignUserOperationResponse"); +impl_stub_serde!(wit::SubmitUserOperationResponse,"SubmitUserOperationResponse"); +impl_stub_serde!(wit::UserOperationReceiptResponse,"UserOperationReceiptResponse"); + +// Other request types +impl_stub_serde!(wit::GetTransactionReceiptRequest,"GetTransactionReceiptRequest"); +impl_stub_serde!(wit::BuildUserOperationRequest, "BuildUserOperationRequest"); +impl_stub_serde!(wit::SignUserOperationRequest, "SignUserOperationRequest"); +impl_stub_serde!(wit::BuildAndSignUserOperationRequest,"BuildAndSignUserOperationRequest"); +impl_stub_serde!(wit::EstimateUserOperationGasRequest,"EstimateUserOperationGasRequest"); +impl_stub_serde!(wit::ConfigurePaymasterRequest, "ConfigurePaymasterRequest"); +impl_stub_serde!(wit::ExecuteViaTbaRequest, "ExecuteViaTbaRequest"); +impl_stub_serde!(wit::CheckTbaOwnershipRequest, "CheckTbaOwnershipRequest"); +impl_stub_serde!(wit::SetupTbaDelegationRequest, "SetupTbaDelegationRequest"); +impl_stub_serde!(wit::CreateNoteRequest, "CreateNoteRequest"); +impl_stub_serde!(wit::ReadNoteRequest, "ReadNoteRequest"); +impl_stub_serde!(wit::ResolveIdentityRequest, "ResolveIdentityRequest"); +impl_stub_serde!(wit::SetupDelegationRequest, "SetupDelegationRequest"); +impl_stub_serde!(wit::VerifyDelegationRequest, "VerifyDelegationRequest"); +impl_stub_serde!(wit::MintEntryRequest, "MintEntryRequest"); + +// Other response types +impl_stub_serde!(wit::CreateNoteResponse, "CreateNoteResponse"); +impl_stub_serde!(wit::ExecuteViaTbaResponse, "ExecuteViaTbaResponse"); +impl_stub_serde!(wit::CheckTbaOwnershipResponse, "CheckTbaOwnershipResponse"); + +// Other types that may need serde +impl_stub_serde!(wit::Balance, "Balance"); +impl_stub_serde!(wit::Wallet, "Wallet"); +impl_stub_serde!(wit::WalletSpendingLimits, "WalletSpendingLimits"); diff --git a/src/hyperwallet_client/serde_response_impls.rs b/src/hyperwallet_client/serde_response_impls.rs new file mode 100644 index 0000000..2d4e764 --- /dev/null +++ b/src/hyperwallet_client/serde_response_impls.rs @@ -0,0 +1,2028 @@ +// Proper serde implementations for request/response types + +use crate::hyperware::process::hyperwallet as wit; +// no direct use of serde::de; rely on derives and serde_json where necessary +use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize}; + +// ============== REQUEST TYPES ============== +// SetWalletLimitsRequest +impl Serialize for wit::SetWalletLimitsRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SetWalletLimitsRequest", 2)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("limits", &self.limits)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SetWalletLimitsRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + limits: wit::WalletSpendingLimits, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SetWalletLimitsRequest { + wallet_id: h.wallet_id, + limits: h.limits, + }) + } +} + +// ImportWalletRequest +impl Serialize for wit::ImportWalletRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let field_count = if self.password.is_some() { 3 } else { 2 }; + let mut state = serializer.serialize_struct("ImportWalletRequest", field_count)?; + state.serialize_field("name", &self.name)?; + state.serialize_field("private_key", &self.private_key)?; + if let Some(ref pwd) = self.password { + state.serialize_field("password", pwd)?; + } + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ImportWalletRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + name: String, + private_key: String, + password: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ImportWalletRequest { + name: h.name, + private_key: h.private_key, + password: h.password, + }) + } +} + +// DeleteWalletRequest +impl Serialize for wit::DeleteWalletRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("DeleteWalletRequest", 1)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::DeleteWalletRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::DeleteWalletRequest { + wallet_id: h.wallet_id, + }) + } +} + +// RenameWalletRequest +impl Serialize for wit::RenameWalletRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("RenameWalletRequest", 2)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("new_name", &self.new_name)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::RenameWalletRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + new_name: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::RenameWalletRequest { + wallet_id: h.wallet_id, + new_name: h.new_name, + }) + } +} + +// ExportWalletRequest +impl Serialize for wit::ExportWalletRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let field_count = if self.password.is_some() { 2 } else { 1 }; + let mut state = serializer.serialize_struct("ExportWalletRequest", field_count)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + if let Some(ref pwd) = self.password { + state.serialize_field("password", pwd)?; + } + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ExportWalletRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + password: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ExportWalletRequest { + wallet_id: h.wallet_id, + password: h.password, + }) + } +} + +// GetWalletInfoRequest +impl Serialize for wit::GetWalletInfoRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetWalletInfoRequest", 1)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetWalletInfoRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetWalletInfoRequest { + wallet_id: h.wallet_id, + }) + } +} + +// SendEthRequest +impl Serialize for wit::SendEthRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SendEthRequest", 3)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("to", &self.to)?; + state.serialize_field("amount", &self.amount)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SendEthRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + to: String, + amount: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SendEthRequest { + wallet_id: h.wallet_id, + to: h.to, + amount: h.amount, + }) + } +} + +// SendTokenRequest +impl Serialize for wit::SendTokenRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SendTokenRequest", 4)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("token_address", &self.token_address)?; + state.serialize_field("to", &self.to)?; + state.serialize_field("amount", &self.amount)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SendTokenRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + token_address: String, + to: String, + amount: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SendTokenRequest { + wallet_id: h.wallet_id, + token_address: h.token_address, + to: h.to, + amount: h.amount, + }) + } +} + +// GetBalanceRequest +impl Serialize for wit::GetBalanceRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetBalanceRequest", 1)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetBalanceRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetBalanceRequest { + wallet_id: h.wallet_id, + }) + } +} + +// GetTokenBalanceRequest +impl Serialize for wit::GetTokenBalanceRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetTokenBalanceRequest", 2)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("token_address", &self.token_address)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetTokenBalanceRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + token_address: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetTokenBalanceRequest { + wallet_id: h.wallet_id, + token_address: h.token_address, + }) + } +} + +// BuildAndSignUserOperationForPaymentRequest +impl Serialize for wit::BuildAndSignUserOperationForPaymentRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = + serializer.serialize_struct("BuildAndSignUserOperationForPaymentRequest", 7)?; + state.serialize_field("eoa_wallet_id", &self.eoa_wallet_id)?; + state.serialize_field("tba_address", &self.tba_address)?; + state.serialize_field("target", &self.target)?; + state.serialize_field("call_data", &self.call_data)?; + state.serialize_field("use_paymaster", &self.use_paymaster)?; + state.serialize_field("paymaster_config", &self.paymaster_config)?; + state.serialize_field("password", &self.password)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::BuildAndSignUserOperationForPaymentRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + eoa_wallet_id: String, + tba_address: String, + target: String, + call_data: String, + use_paymaster: bool, + paymaster_config: Option, + password: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::BuildAndSignUserOperationForPaymentRequest { + eoa_wallet_id: h.eoa_wallet_id, + tba_address: h.tba_address, + target: h.target, + call_data: h.call_data, + use_paymaster: h.use_paymaster, + paymaster_config: h.paymaster_config, + password: h.password, + }) + } +} + +// SubmitUserOperationRequest +impl Serialize for wit::SubmitUserOperationRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SubmitUserOperationRequest", 3)?; + state.serialize_field("signed_user_operation", &self.signed_user_operation)?; + state.serialize_field("entry_point", &self.entry_point)?; + state.serialize_field("bundler_url", &self.bundler_url)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SubmitUserOperationRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + signed_user_operation: String, + entry_point: String, + bundler_url: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SubmitUserOperationRequest { + signed_user_operation: h.signed_user_operation, + entry_point: h.entry_point, + bundler_url: h.bundler_url, + }) + } +} + +// GetUserOperationReceiptRequest +impl Serialize for wit::GetUserOperationReceiptRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetUserOperationReceiptRequest", 1)?; + state.serialize_field("user_op_hash", &self.user_op_hash)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetUserOperationReceiptRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + user_op_hash: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetUserOperationReceiptRequest { + user_op_hash: h.user_op_hash, + }) + } +} + +// ============== RESPONSE TYPES ============== +// SetWalletLimitsResponse +impl Serialize for wit::SetWalletLimitsResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SetWalletLimitsResponse", 3)?; + state.serialize_field("success", &self.success)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("message", &self.message)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SetWalletLimitsResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + success: bool, + wallet_id: String, + message: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SetWalletLimitsResponse { + success: h.success, + wallet_id: h.wallet_id, + message: h.message, + }) + } +} + +// CreateWalletResponse +impl Serialize for wit::CreateWalletResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CreateWalletResponse", 3)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("address", &self.address)?; + state.serialize_field("name", &self.name)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::CreateWalletResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + address: String, + name: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::CreateWalletResponse { + wallet_id: h.wallet_id, + address: h.address, + name: h.name, + }) + } +} + +// ImportWalletResponse +impl Serialize for wit::ImportWalletResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ImportWalletResponse", 3)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("address", &self.address)?; + state.serialize_field("name", &self.name)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ImportWalletResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + address: String, + name: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ImportWalletResponse { + wallet_id: h.wallet_id, + address: h.address, + name: h.name, + }) + } +} + +// DeleteWalletResponse +impl Serialize for wit::DeleteWalletResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("DeleteWalletResponse", 3)?; + state.serialize_field("success", &self.success)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("message", &self.message)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::DeleteWalletResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + success: bool, + wallet_id: String, + message: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::DeleteWalletResponse { + success: h.success, + wallet_id: h.wallet_id, + message: h.message, + }) + } +} + +// ExportWalletResponse +impl Serialize for wit::ExportWalletResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ExportWalletResponse", 2)?; + state.serialize_field("address", &self.address)?; + state.serialize_field("private_key", &self.private_key)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ExportWalletResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + address: String, + private_key: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ExportWalletResponse { + address: h.address, + private_key: h.private_key, + }) + } +} + +// ListWalletsResponse +impl Serialize for wit::ListWalletsResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ListWalletsResponse", 3)?; + state.serialize_field("process", &self.process)?; + state.serialize_field("wallets", &self.wallets)?; + state.serialize_field("total", &self.total)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ListWalletsResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + process: String, + wallets: Vec, + total: u64, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ListWalletsResponse { + process: h.process, + wallets: h.wallets, + total: h.total, + }) + } +} + +// GetWalletInfoResponse +impl Serialize for wit::GetWalletInfoResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetWalletInfoResponse", 5)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("address", &self.address)?; + state.serialize_field("name", &self.name)?; + state.serialize_field("chain_id", &self.chain_id)?; + state.serialize_field("is_locked", &self.is_locked)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetWalletInfoResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + wallet_id: String, + address: String, + name: String, + chain_id: u64, + is_locked: bool, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetWalletInfoResponse { + wallet_id: h.wallet_id, + address: h.address, + name: h.name, + chain_id: h.chain_id, + is_locked: h.is_locked, + }) + } +} + +// GetBalanceResponse +impl Serialize for wit::GetBalanceResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetBalanceResponse", 3)?; + state.serialize_field("balance", &self.balance)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("chain_id", &self.chain_id)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetBalanceResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + balance: wit::Balance, + wallet_id: String, + chain_id: u64, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetBalanceResponse { + balance: h.balance, + wallet_id: h.wallet_id, + chain_id: h.chain_id, + }) + } +} + +// GetTokenBalanceResponse +impl Serialize for wit::GetTokenBalanceResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetTokenBalanceResponse", 3)?; + state.serialize_field("balance", &self.balance)?; + state.serialize_field("formatted", &self.formatted)?; + state.serialize_field("decimals", &self.decimals)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetTokenBalanceResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + balance: String, + formatted: Option, + decimals: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetTokenBalanceResponse { + balance: h.balance, + formatted: h.formatted, + decimals: h.decimals, + }) + } +} + +// SendEthResponse +impl Serialize for wit::SendEthResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SendEthResponse", 5)?; + state.serialize_field("tx_hash", &self.tx_hash)?; + state.serialize_field("from_address", &self.from_address)?; + state.serialize_field("to_address", &self.to_address)?; + state.serialize_field("amount", &self.amount)?; + state.serialize_field("chain_id", &self.chain_id)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SendEthResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tx_hash: String, + from_address: String, + to_address: String, + amount: String, + chain_id: u64, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SendEthResponse { + tx_hash: h.tx_hash, + from_address: h.from_address, + to_address: h.to_address, + amount: h.amount, + chain_id: h.chain_id, + }) + } +} + +// SendTokenResponse +impl Serialize for wit::SendTokenResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SendTokenResponse", 6)?; + state.serialize_field("tx_hash", &self.tx_hash)?; + state.serialize_field("from_address", &self.from_address)?; + state.serialize_field("to_address", &self.to_address)?; + state.serialize_field("token_address", &self.token_address)?; + state.serialize_field("amount", &self.amount)?; + state.serialize_field("chain_id", &self.chain_id)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SendTokenResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tx_hash: String, + from_address: String, + to_address: String, + token_address: String, + amount: String, + chain_id: u64, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SendTokenResponse { + tx_hash: h.tx_hash, + from_address: h.from_address, + to_address: h.to_address, + token_address: h.token_address, + amount: h.amount, + chain_id: h.chain_id, + }) + } +} + +// UnlockWalletResponse +impl Serialize for wit::UnlockWalletResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("UnlockWalletResponse", 3)?; + state.serialize_field("success", &self.success)?; + state.serialize_field("wallet_id", &self.wallet_id)?; + state.serialize_field("message", &self.message)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::UnlockWalletResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + success: bool, + wallet_id: String, + message: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::UnlockWalletResponse { + success: h.success, + wallet_id: h.wallet_id, + message: h.message, + }) + } +} + +// BuildAndSignUserOperationResponse +impl Serialize for wit::BuildAndSignUserOperationResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("BuildAndSignUserOperationResponse", 3)?; + state.serialize_field("signed_user_operation", &self.signed_user_operation)?; + state.serialize_field("entry_point", &self.entry_point)?; + state.serialize_field("ready_to_submit", &self.ready_to_submit)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::BuildAndSignUserOperationResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + signed_user_operation: String, + entry_point: String, + ready_to_submit: bool, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::BuildAndSignUserOperationResponse { + signed_user_operation: h.signed_user_operation, + entry_point: h.entry_point, + ready_to_submit: h.ready_to_submit, + }) + } +} + +// SubmitUserOperationResponse +impl Serialize for wit::SubmitUserOperationResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SubmitUserOperationResponse", 1)?; + state.serialize_field("user_op_hash", &self.user_op_hash)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SubmitUserOperationResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + user_op_hash: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SubmitUserOperationResponse { + user_op_hash: h.user_op_hash, + }) + } +} + +// UserOperationReceiptResponse +impl Serialize for wit::UserOperationReceiptResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("UserOperationReceiptResponse", 3)?; + state.serialize_field("receipt", &self.receipt)?; + state.serialize_field("user_op_hash", &self.user_op_hash)?; + state.serialize_field("status", &self.status)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::UserOperationReceiptResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + receipt: Option, + user_op_hash: String, + status: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::UserOperationReceiptResponse { + receipt: h.receipt, + user_op_hash: h.user_op_hash, + status: h.status, + }) + } +} + +// ============== SUPPORTING TYPES ============== + +// Balance +impl Serialize for wit::Balance { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Balance", 2)?; + state.serialize_field("formatted", &self.formatted)?; + state.serialize_field("raw", &self.raw)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::Balance { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + formatted: String, + raw: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::Balance { + formatted: h.formatted, + raw: h.raw, + }) + } +} + +// Wallet +impl Serialize for wit::Wallet { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Wallet", 7)?; + state.serialize_field("address", &self.address)?; + state.serialize_field("name", &self.name)?; + state.serialize_field("chain_id", &self.chain_id)?; + state.serialize_field("encrypted", &self.encrypted)?; + state.serialize_field("created_at", &self.created_at)?; + state.serialize_field("last_used", &self.last_used)?; + state.serialize_field("spending_limits", &self.spending_limits)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::Wallet { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + address: String, + name: Option, + chain_id: u64, + encrypted: bool, + created_at: Option, + last_used: Option, + spending_limits: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::Wallet { + address: h.address, + name: h.name, + chain_id: h.chain_id, + encrypted: h.encrypted, + created_at: h.created_at, + last_used: h.last_used, + spending_limits: h.spending_limits, + }) + } +} + +// WalletSpendingLimits +impl Serialize for wit::WalletSpendingLimits { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("WalletSpendingLimits", 6)?; + state.serialize_field("max_per_call", &self.max_per_call)?; + state.serialize_field("max_total", &self.max_total)?; + state.serialize_field("currency", &self.currency)?; + state.serialize_field("total_spent", &self.total_spent)?; + state.serialize_field("set_at", &self.set_at)?; + state.serialize_field("updated_at", &self.updated_at)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::WalletSpendingLimits { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + max_per_call: Option, + max_total: Option, + currency: String, + total_spent: String, + set_at: Option, + updated_at: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::WalletSpendingLimits { + max_per_call: h.max_per_call, + max_total: h.max_total, + currency: h.currency, + total_spent: h.total_spent, + set_at: h.set_at, + updated_at: h.updated_at, + }) + } +} + +// ============== MISSING REQUEST TYPES ============== + +// UpdateSpendingLimitsRequest +impl Serialize for wit::UpdateSpendingLimitsRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("UpdateSpendingLimitsRequest", 1)?; + state.serialize_field("new_limits", &self.new_limits)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::UpdateSpendingLimitsRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + new_limits: wit::SpendingLimits, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::UpdateSpendingLimitsRequest { + new_limits: h.new_limits, + }) + } +} + +// ApproveTokenRequest +impl Serialize for wit::ApproveTokenRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ApproveTokenRequest", 3)?; + state.serialize_field("token_address", &self.token_address)?; + state.serialize_field("spender", &self.spender)?; + state.serialize_field("amount", &self.amount)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ApproveTokenRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + token_address: String, + spender: String, + amount: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ApproveTokenRequest { + token_address: h.token_address, + spender: h.spender, + amount: h.amount, + }) + } +} + +// CallContractRequest +impl Serialize for wit::CallContractRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CallContractRequest", 3)?; + state.serialize_field("to", &self.to)?; + state.serialize_field("data", &self.data)?; + state.serialize_field("value", &self.value)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::CallContractRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + to: String, + data: String, + value: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::CallContractRequest { + to: h.to, + data: h.data, + value: h.value, + }) + } +} + +// SignTransactionRequest +impl Serialize for wit::SignTransactionRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SignTransactionRequest", 6)?; + state.serialize_field("to", &self.to)?; + state.serialize_field("value", &self.value)?; + state.serialize_field("data", &self.data)?; + state.serialize_field("gas_limit", &self.gas_limit)?; + state.serialize_field("gas_price", &self.gas_price)?; + state.serialize_field("nonce", &self.nonce)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SignTransactionRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + to: String, + value: String, + data: Option, + gas_limit: Option, + gas_price: Option, + nonce: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SignTransactionRequest { + to: h.to, + value: h.value, + data: h.data, + gas_limit: h.gas_limit, + gas_price: h.gas_price, + nonce: h.nonce, + }) + } +} + +// MessageType enum (needed for SignMessageRequest) +impl Serialize for wit::MessageType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + wit::MessageType::PlainText => { + serializer.serialize_unit_variant("MessageType", 0, "plain_text") + } + wit::MessageType::Eip191 => { + serializer.serialize_unit_variant("MessageType", 1, "eip191") + } + wit::MessageType::Eip712(data) => { + use serde::ser::SerializeStructVariant; + let mut state = + serializer.serialize_struct_variant("MessageType", 2, "eip712", 1)?; + state.serialize_field("data", data)?; + state.end() + } + } + } +} + +impl<'de> Deserialize<'de> for wit::MessageType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + enum MessageTypeHelper { + PlainText, + Eip191, + Eip712 { data: wit::Eip712Data }, + } + + match MessageTypeHelper::deserialize(deserializer)? { + MessageTypeHelper::PlainText => Ok(wit::MessageType::PlainText), + MessageTypeHelper::Eip191 => Ok(wit::MessageType::Eip191), + MessageTypeHelper::Eip712 { data } => Ok(wit::MessageType::Eip712(data)), + } + } +} + +// Eip712Data struct (needed for MessageType::Eip712) +impl Serialize for wit::Eip712Data { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Eip712Data", 2)?; + state.serialize_field("domain", &self.domain)?; + state.serialize_field("types", &self.types)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::Eip712Data { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + domain: String, + types: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::Eip712Data { + domain: h.domain, + types: h.types, + }) + } +} + +// SignMessageRequest +impl Serialize for wit::SignMessageRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SignMessageRequest", 2)?; + state.serialize_field("message", &self.message)?; + state.serialize_field("message_type", &self.message_type)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SignMessageRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + message: String, + message_type: wit::MessageType, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SignMessageRequest { + message: h.message, + message_type: h.message_type, + }) + } +} + +// GetTransactionHistoryRequest +impl Serialize for wit::GetTransactionHistoryRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetTransactionHistoryRequest", 4)?; + state.serialize_field("limit", &self.limit)?; + state.serialize_field("offset", &self.offset)?; + state.serialize_field("from_block", &self.from_block)?; + state.serialize_field("to_block", &self.to_block)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetTransactionHistoryRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + limit: Option, + offset: Option, + from_block: Option, + to_block: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetTransactionHistoryRequest { + limit: h.limit, + offset: h.offset, + from_block: h.from_block, + to_block: h.to_block, + }) + } +} + +// EstimateGasRequest +impl Serialize for wit::EstimateGasRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("EstimateGasRequest", 3)?; + state.serialize_field("to", &self.to)?; + state.serialize_field("data", &self.data)?; + state.serialize_field("value", &self.value)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::EstimateGasRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + to: String, + data: Option, + value: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::EstimateGasRequest { + to: h.to, + data: h.data, + value: h.value, + }) + } +} + +// GetTransactionReceiptRequest +impl Serialize for wit::GetTransactionReceiptRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("GetTransactionReceiptRequest", 1)?; + state.serialize_field("tx_hash", &self.tx_hash)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::GetTransactionReceiptRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tx_hash: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::GetTransactionReceiptRequest { tx_hash: h.tx_hash }) + } +} + +// BuildUserOperationRequest +impl Serialize for wit::BuildUserOperationRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("BuildUserOperationRequest", 3)?; + state.serialize_field("target", &self.target)?; + state.serialize_field("call_data", &self.call_data)?; + state.serialize_field("value", &self.value)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::BuildUserOperationRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + target: String, + call_data: String, + value: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::BuildUserOperationRequest { + target: h.target, + call_data: h.call_data, + value: h.value, + }) + } +} + +// SignUserOperationRequest +impl Serialize for wit::SignUserOperationRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SignUserOperationRequest", 2)?; + state.serialize_field("unsigned_user_operation", &self.unsigned_user_operation)?; + state.serialize_field("entry_point", &self.entry_point)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SignUserOperationRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + unsigned_user_operation: String, + entry_point: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SignUserOperationRequest { + unsigned_user_operation: h.unsigned_user_operation, + entry_point: h.entry_point, + }) + } +} + +// BuildAndSignUserOperationRequest +impl Serialize for wit::BuildAndSignUserOperationRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("BuildAndSignUserOperationRequest", 4)?; + state.serialize_field("target", &self.target)?; + state.serialize_field("call_data", &self.call_data)?; + state.serialize_field("value", &self.value)?; + state.serialize_field("entry_point", &self.entry_point)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::BuildAndSignUserOperationRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + target: String, + call_data: String, + value: Option, + entry_point: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::BuildAndSignUserOperationRequest { + target: h.target, + call_data: h.call_data, + value: h.value, + entry_point: h.entry_point, + }) + } +} + +// EstimateUserOperationGasRequest +impl Serialize for wit::EstimateUserOperationGasRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("EstimateUserOperationGasRequest", 2)?; + state.serialize_field("user_operation", &self.user_operation)?; + state.serialize_field("entry_point", &self.entry_point)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::EstimateUserOperationGasRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + user_operation: String, + entry_point: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::EstimateUserOperationGasRequest { + user_operation: h.user_operation, + entry_point: h.entry_point, + }) + } +} + +// ConfigurePaymasterRequest +impl Serialize for wit::ConfigurePaymasterRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ConfigurePaymasterRequest", 4)?; + state.serialize_field("paymaster_address", &self.paymaster_address)?; + state.serialize_field("paymaster_data", &self.paymaster_data)?; + state.serialize_field("verification_gas_limit", &self.verification_gas_limit)?; + state.serialize_field("post_op_gas_limit", &self.post_op_gas_limit)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ConfigurePaymasterRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + paymaster_address: String, + paymaster_data: Option, + verification_gas_limit: String, + post_op_gas_limit: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ConfigurePaymasterRequest { + paymaster_address: h.paymaster_address, + paymaster_data: h.paymaster_data, + verification_gas_limit: h.verification_gas_limit, + post_op_gas_limit: h.post_op_gas_limit, + }) + } +} + +// ExecuteViaTbaRequest +impl Serialize for wit::ExecuteViaTbaRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ExecuteViaTbaRequest", 4)?; + state.serialize_field("tba_address", &self.tba_address)?; + state.serialize_field("target", &self.target)?; + state.serialize_field("call_data", &self.call_data)?; + state.serialize_field("value", &self.value)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ExecuteViaTbaRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tba_address: String, + target: String, + call_data: String, + value: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ExecuteViaTbaRequest { + tba_address: h.tba_address, + target: h.target, + call_data: h.call_data, + value: h.value, + }) + } +} + +// CheckTbaOwnershipRequest +impl Serialize for wit::CheckTbaOwnershipRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CheckTbaOwnershipRequest", 2)?; + state.serialize_field("tba_address", &self.tba_address)?; + state.serialize_field("signer_address", &self.signer_address)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::CheckTbaOwnershipRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tba_address: String, + signer_address: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::CheckTbaOwnershipRequest { + tba_address: h.tba_address, + signer_address: h.signer_address, + }) + } +} + +// SetupTbaDelegationRequest +impl Serialize for wit::SetupTbaDelegationRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SetupTbaDelegationRequest", 3)?; + state.serialize_field("tba_address", &self.tba_address)?; + state.serialize_field("delegate_address", &self.delegate_address)?; + state.serialize_field("permissions", &self.permissions)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SetupTbaDelegationRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tba_address: String, + delegate_address: String, + permissions: Vec, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SetupTbaDelegationRequest { + tba_address: h.tba_address, + delegate_address: h.delegate_address, + permissions: h.permissions, + }) + } +} + +// CreateNoteRequest +impl Serialize for wit::CreateNoteRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CreateNoteRequest", 2)?; + state.serialize_field("note_data", &self.note_data)?; + state.serialize_field("metadata", &self.metadata)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::CreateNoteRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + note_data: String, + metadata: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::CreateNoteRequest { + note_data: h.note_data, + metadata: h.metadata, + }) + } +} + +// ReadNoteRequest +impl Serialize for wit::ReadNoteRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ReadNoteRequest", 1)?; + state.serialize_field("note_id", &self.note_id)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ReadNoteRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + note_id: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ReadNoteRequest { note_id: h.note_id }) + } +} + +// ResolveIdentityRequest +impl Serialize for wit::ResolveIdentityRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ResolveIdentityRequest", 1)?; + state.serialize_field("entry_name", &self.entry_name)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ResolveIdentityRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + entry_name: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ResolveIdentityRequest { + entry_name: h.entry_name, + }) + } +} + +// SetupDelegationRequest +impl Serialize for wit::SetupDelegationRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("SetupDelegationRequest", 3)?; + state.serialize_field("delegate_address", &self.delegate_address)?; + state.serialize_field("permissions", &self.permissions)?; + state.serialize_field("expiry", &self.expiry)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::SetupDelegationRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + delegate_address: String, + permissions: Vec, + expiry: Option, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::SetupDelegationRequest { + delegate_address: h.delegate_address, + permissions: h.permissions, + expiry: h.expiry, + }) + } +} + +// VerifyDelegationRequest +impl Serialize for wit::VerifyDelegationRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("VerifyDelegationRequest", 3)?; + state.serialize_field("delegate_address", &self.delegate_address)?; + state.serialize_field("signature", &self.signature)?; + state.serialize_field("message", &self.message)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::VerifyDelegationRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + delegate_address: String, + signature: String, + message: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::VerifyDelegationRequest { + delegate_address: h.delegate_address, + signature: h.signature, + message: h.message, + }) + } +} + +// MintEntryRequest +impl Serialize for wit::MintEntryRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("MintEntryRequest", 2)?; + state.serialize_field("entry_name", &self.entry_name)?; + state.serialize_field("metadata", &self.metadata)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::MintEntryRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + entry_name: String, + metadata: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::MintEntryRequest { + entry_name: h.entry_name, + metadata: h.metadata, + }) + } +} + +// ============== MISSING RESPONSE TYPES ============== + +// CreateNoteResponse +impl Serialize for wit::CreateNoteResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CreateNoteResponse", 3)?; + state.serialize_field("note_id", &self.note_id)?; + state.serialize_field("content_hash", &self.content_hash)?; + state.serialize_field("created_at", &self.created_at)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::CreateNoteResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + note_id: String, + content_hash: String, + created_at: u64, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::CreateNoteResponse { + note_id: h.note_id, + content_hash: h.content_hash, + created_at: h.created_at, + }) + } +} + +// ExecuteViaTbaResponse +impl Serialize for wit::ExecuteViaTbaResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ExecuteViaTbaResponse", 4)?; + state.serialize_field("tx_hash", &self.tx_hash)?; + state.serialize_field("tba_address", &self.tba_address)?; + state.serialize_field("target_address", &self.target_address)?; + state.serialize_field("success", &self.success)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::ExecuteViaTbaResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tx_hash: String, + tba_address: String, + target_address: String, + success: bool, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::ExecuteViaTbaResponse { + tx_hash: h.tx_hash, + tba_address: h.tba_address, + target_address: h.target_address, + success: h.success, + }) + } +} + +// CheckTbaOwnershipResponse +impl Serialize for wit::CheckTbaOwnershipResponse { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CheckTbaOwnershipResponse", 3)?; + state.serialize_field("tba_address", &self.tba_address)?; + state.serialize_field("owner_address", &self.owner_address)?; + state.serialize_field("is_owned", &self.is_owned)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::CheckTbaOwnershipResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + tba_address: String, + owner_address: String, + is_owned: bool, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::CheckTbaOwnershipResponse { + tba_address: h.tba_address, + owner_address: h.owner_address, + is_owned: h.is_owned, + }) + } +} + +// PaymasterConfig +impl Serialize for wit::PaymasterConfig { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("PaymasterConfig", 4)?; + state.serialize_field("is_circle_paymaster", &self.is_circle_paymaster)?; + state.serialize_field("paymaster_address", &self.paymaster_address)?; + state.serialize_field( + "paymaster_verification_gas", + &self.paymaster_verification_gas, + )?; + state.serialize_field("paymaster_post_op_gas", &self.paymaster_post_op_gas)?; + state.end() + } +} + +impl<'de> Deserialize<'de> for wit::PaymasterConfig { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + is_circle_paymaster: bool, + paymaster_address: String, + paymaster_verification_gas: String, + paymaster_post_op_gas: String, + } + let h = Helper::deserialize(deserializer)?; + Ok(wit::PaymasterConfig { + is_circle_paymaster: h.is_circle_paymaster, + paymaster_address: h.paymaster_address, + paymaster_verification_gas: h.paymaster_verification_gas, + paymaster_post_op_gas: h.paymaster_post_op_gas, + }) + } +} diff --git a/src/hyperwallet_client/serde_variant_impls.rs b/src/hyperwallet_client/serde_variant_impls.rs new file mode 100644 index 0000000..cca4380 --- /dev/null +++ b/src/hyperwallet_client/serde_variant_impls.rs @@ -0,0 +1,1020 @@ +use crate::hyperware::process::hyperwallet as wit; +use serde::de::{self, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct}; +use serde::Deserialize; + +// ============================================================================ +// HyperwalletRequest variant type +// ============================================================================ + +impl Serialize for wit::HyperwalletRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use wit::HyperwalletRequest::*; + + let mut state = serializer.serialize_struct("HyperwalletRequest", 2)?; + + match self { + Handshake(data) => { + state.serialize_field("type", "Handshake")?; + state.serialize_field("data", data)?; + } + UnlockWallet(data) => { + state.serialize_field("type", "UnlockWallet")?; + state.serialize_field("data", data)?; + } + UpdateSpendingLimits(data) => { + state.serialize_field("type", "UpdateSpendingLimits")?; + state.serialize_field("data", data)?; + } + CreateWallet(data) => { + state.serialize_field("type", "CreateWallet")?; + state.serialize_field("data", data)?; + } + ImportWallet(data) => { + state.serialize_field("type", "ImportWallet")?; + state.serialize_field("data", data)?; + } + DeleteWallet(data) => { + state.serialize_field("type", "DeleteWallet")?; + state.serialize_field("data", data)?; + } + RenameWallet(data) => { + state.serialize_field("type", "RenameWallet")?; + state.serialize_field("data", data)?; + } + ExportWallet(data) => { + state.serialize_field("type", "ExportWallet")?; + state.serialize_field("data", data)?; + } + ListWallets => { + state.serialize_field("type", "ListWallets")?; + state.serialize_field("data", &serde_json::Value::Null)?; + } + GetWalletInfo(data) => { + state.serialize_field("type", "GetWalletInfo")?; + state.serialize_field("data", data)?; + } + SendEth(data) => { + state.serialize_field("type", "SendEth")?; + state.serialize_field("data", data)?; + } + SendToken(data) => { + state.serialize_field("type", "SendToken")?; + state.serialize_field("data", data)?; + } + ApproveToken(data) => { + state.serialize_field("type", "ApproveToken")?; + state.serialize_field("data", data)?; + } + GetBalance(data) => { + state.serialize_field("type", "GetBalance")?; + state.serialize_field("data", data)?; + } + GetTokenBalance(data) => { + state.serialize_field("type", "GetTokenBalance")?; + state.serialize_field("data", data)?; + } + CallContract(data) => { + state.serialize_field("type", "CallContract")?; + state.serialize_field("data", data)?; + } + SignTransaction(data) => { + state.serialize_field("type", "SignTransaction")?; + state.serialize_field("data", data)?; + } + SignMessage(data) => { + state.serialize_field("type", "SignMessage")?; + state.serialize_field("data", data)?; + } + BuildAndSignUserOperationForPayment(data) => { + state.serialize_field("type", "BuildAndSignUserOperationForPayment")?; + state.serialize_field("data", data)?; + } + SubmitUserOperation(data) => { + state.serialize_field("type", "SubmitUserOperation")?; + state.serialize_field("data", data)?; + } + GetUserOperationReceipt(data) => { + state.serialize_field("type", "GetUserOperationReceipt")?; + state.serialize_field("data", data)?; + } + GetTransactionHistory(data) => { + state.serialize_field("type", "GetTransactionHistory")?; + state.serialize_field("data", data)?; + } + EstimateGas(data) => { + state.serialize_field("type", "EstimateGas")?; + state.serialize_field("data", data)?; + } + GetGasPrice => { + state.serialize_field("type", "GetGasPrice")?; + state.serialize_field("data", &serde_json::Value::Null)?; + } + GetTransactionReceipt(data) => { + state.serialize_field("type", "GetTransactionReceipt")?; + state.serialize_field("data", data)?; + } + BuildUserOperation(data) => { + state.serialize_field("type", "BuildUserOperation")?; + state.serialize_field("data", data)?; + } + SignUserOperation(data) => { + state.serialize_field("type", "SignUserOperation")?; + state.serialize_field("data", data)?; + } + BuildAndSignUserOperation(data) => { + state.serialize_field("type", "BuildAndSignUserOperation")?; + state.serialize_field("data", data)?; + } + EstimateUserOperationGas(data) => { + state.serialize_field("type", "EstimateUserOperationGas")?; + state.serialize_field("data", data)?; + } + ConfigurePaymaster(data) => { + state.serialize_field("type", "ConfigurePaymaster")?; + state.serialize_field("data", data)?; + } + ExecuteViaTba(data) => { + state.serialize_field("type", "ExecuteViaTba")?; + state.serialize_field("data", data)?; + } + CheckTbaOwnership(data) => { + state.serialize_field("type", "CheckTbaOwnership")?; + state.serialize_field("data", data)?; + } + SetupTbaDelegation(data) => { + state.serialize_field("type", "SetupTbaDelegation")?; + state.serialize_field("data", data)?; + } + CreateNote(data) => { + state.serialize_field("type", "CreateNote")?; + state.serialize_field("data", data)?; + } + ReadNote(data) => { + state.serialize_field("type", "ReadNote")?; + state.serialize_field("data", data)?; + } + ResolveIdentity(data) => { + state.serialize_field("type", "ResolveIdentity")?; + state.serialize_field("data", data)?; + } + SetupDelegation(data) => { + state.serialize_field("type", "SetupDelegation")?; + state.serialize_field("data", data)?; + } + VerifyDelegation(data) => { + state.serialize_field("type", "VerifyDelegation")?; + state.serialize_field("data", data)?; + } + MintEntry(data) => { + state.serialize_field("type", "MintEntry")?; + state.serialize_field("data", data)?; + } + SetWalletLimits(data) => { + state.serialize_field("type", "SetWalletLimits")?; + state.serialize_field("data", data)?; + } + } + + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::HyperwalletRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + struct HyperwalletRequestVisitor; + + impl<'de> Visitor<'de> for HyperwalletRequestVisitor { + type Value = wit::HyperwalletRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a HyperwalletRequest variant") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut variant_type = None; + let mut data = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "type" => { + if variant_type.is_some() { + return Err(de::Error::duplicate_field("type")); + } + variant_type = Some(map.next_value::()?); + } + "data" => { + if data.is_some() { + return Err(de::Error::duplicate_field("data")); + } + data = Some(map.next_value::()?); + } + _ => { + let _ = map.next_value::()?; + } + } + } + + let variant_type = variant_type.ok_or_else(|| de::Error::missing_field("type"))?; + + use wit::HyperwalletRequest::*; + match variant_type.as_str() { + "Handshake" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let step = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!("Failed to deserialize HandshakeStep: {}", e)) + })?; + Ok(Handshake(step)) + } + "ImportWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ImportWalletRequest: {}", + e + )) + })?; + Ok(ImportWallet(req)) + } + "DeleteWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize DeleteWalletRequest: {}", + e + )) + })?; + Ok(DeleteWallet(req)) + } + "RenameWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize RenameWalletRequest: {}", + e + )) + })?; + Ok(RenameWallet(req)) + } + "ExportWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ExportWalletRequest: {}", + e + )) + })?; + Ok(ExportWallet(req)) + } + "UnlockWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize UnlockWalletRequest: {}", + e + )) + })?; + Ok(UnlockWallet(req)) + } + "CreateWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CreateWalletRequest: {}", + e + )) + })?; + Ok(CreateWallet(req)) + } + "ListWallets" => Ok(ListWallets), + "GetWalletInfo" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetWalletInfoRequest: {}", + e + )) + })?; + Ok(GetWalletInfo(req)) + } + "SendEth" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SendEthRequest: {}", + e + )) + })?; + Ok(SendEth(req)) + } + "SendToken" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SendTokenRequest: {}", + e + )) + })?; + Ok(SendToken(req)) + } + "ApproveToken" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ApproveTokenRequest: {}", + e + )) + })?; + Ok(ApproveToken(req)) + } + "GetBalance" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetBalanceRequest: {}", + e + )) + })?; + Ok(GetBalance(req)) + } + "GetTokenBalance" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetTokenBalanceRequest: {}", + e + )) + })?; + Ok(GetTokenBalance(req)) + } + "CallContract" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CallContractRequest: {}", + e + )) + })?; + Ok(CallContract(req)) + } + "SignTransaction" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SignTransactionRequest: {}", + e + )) + })?; + Ok(SignTransaction(req)) + } + "SignMessage" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SignMessageRequest: {}", + e + )) + })?; + Ok(SignMessage(req)) + } + "GetTransactionHistory" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetTransactionHistoryRequest: {}", + e + )) + })?; + Ok(GetTransactionHistory(req)) + } + "EstimateGas" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize EstimateGasRequest: {}", + e + )) + })?; + Ok(EstimateGas(req)) + } + "GetGasPrice" => Ok(GetGasPrice), + "GetTransactionReceipt" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetTransactionReceiptRequest: {}", + e + )) + })?; + Ok(GetTransactionReceipt(req)) + } + "BuildAndSignUserOperationForPayment" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize BuildAndSignUserOperationForPaymentRequest: {}", + e + )) + })?; + Ok(BuildAndSignUserOperationForPayment(req)) + } + "SubmitUserOperation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SubmitUserOperationRequest: {}", + e + )) + })?; + Ok(SubmitUserOperation(req)) + } + "GetUserOperationReceipt" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetUserOperationReceiptRequest: {}", + e + )) + })?; + Ok(GetUserOperationReceipt(req)) + } + "SetWalletLimits" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SetWalletLimitsRequest: {}", + e + )) + })?; + Ok(SetWalletLimits(req)) + } + "BuildUserOperation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize BuildUserOperationRequest: {}", + e + )) + })?; + Ok(BuildUserOperation(req)) + } + "SignUserOperation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SignUserOperationRequest: {}", + e + )) + })?; + Ok(SignUserOperation(req)) + } + "BuildAndSignUserOperation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize BuildAndSignUserOperationRequest: {}", + e + )) + })?; + Ok(BuildAndSignUserOperation(req)) + } + "EstimateUserOperationGas" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize EstimateUserOperationGasRequest: {}", + e + )) + })?; + Ok(EstimateUserOperationGas(req)) + } + "ConfigurePaymaster" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ConfigurePaymasterRequest: {}", + e + )) + })?; + Ok(ConfigurePaymaster(req)) + } + "ExecuteViaTba" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ExecuteViaTbaRequest: {}", + e + )) + })?; + Ok(ExecuteViaTba(req)) + } + "CheckTbaOwnership" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CheckTbaOwnershipRequest: {}", + e + )) + })?; + Ok(CheckTbaOwnership(req)) + } + "SetupTbaDelegation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SetupTbaDelegationRequest: {}", + e + )) + })?; + Ok(SetupTbaDelegation(req)) + } + "CreateNote" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CreateNoteRequest: {}", + e + )) + })?; + Ok(CreateNote(req)) + } + "ReadNote" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ReadNoteRequest: {}", + e + )) + })?; + Ok(ReadNote(req)) + } + "ResolveIdentity" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ResolveIdentityRequest: {}", + e + )) + })?; + Ok(ResolveIdentity(req)) + } + "SetupDelegation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SetupDelegationRequest: {}", + e + )) + })?; + Ok(SetupDelegation(req)) + } + "VerifyDelegation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize VerifyDelegationRequest: {}", + e + )) + })?; + Ok(VerifyDelegation(req)) + } + "MintEntry" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let req = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize MintEntryRequest: {}", + e + )) + })?; + Ok(MintEntry(req)) + } + _ => { + // For unimplemented variants, return an error with helpful message + Err(de::Error::unknown_variant( + &variant_type, + &[ + "Handshake", + "ImportWallet", + "DeleteWallet", + "RenameWallet", + "ExportWallet", + "UnlockWallet", + "CreateWallet", + "ListWallets", + "GetWalletInfo", + "SendEth", + "SendToken", + "ApproveToken", + "GetBalance", + "GetTokenBalance", + "CallContract", + "SignTransaction", + "SignMessage", + "GetTransactionHistory", + "EstimateGas", + "GetGasPrice", + "GetTransactionReceipt", + "BuildAndSignUserOperationForPayment", + "SubmitUserOperation", + "GetUserOperationReceipt", + "BuildUserOperation", + "SignUserOperation", + "BuildAndSignUserOperation", + "EstimateUserOperationGas", + "ConfigurePaymaster", + "ExecuteViaTba", + "CheckTbaOwnership", + "SetupTbaDelegation", + "CreateNote", + "ReadNote", + "ResolveIdentity", + "SetupDelegation", + "VerifyDelegation", + "MintEntry", + ], + )) + } + } + } + } + + const FIELDS: &[&str] = &["type", "data"]; + deserializer.deserialize_struct("HyperwalletRequest", FIELDS, HyperwalletRequestVisitor) + } +} + +// ============================================================================ +// HyperwalletResponseData variant type +// ============================================================================ + +impl Serialize for wit::HyperwalletResponseData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use wit::HyperwalletResponseData::*; + + let mut state = serializer.serialize_struct("HyperwalletResponseData", 2)?; + + match self { + Handshake(data) => { + state.serialize_field("type", "Handshake")?; + state.serialize_field("data", data)?; + } + UnlockWallet(data) => { + state.serialize_field("type", "UnlockWallet")?; + state.serialize_field("data", data)?; + } + CreateWallet(data) => { + state.serialize_field("type", "CreateWallet")?; + state.serialize_field("data", data)?; + } + ImportWallet(data) => { + state.serialize_field("type", "ImportWallet")?; + state.serialize_field("data", data)?; + } + DeleteWallet(data) => { + state.serialize_field("type", "DeleteWallet")?; + state.serialize_field("data", data)?; + } + ExportWallet(data) => { + state.serialize_field("type", "ExportWallet")?; + state.serialize_field("data", data)?; + } + ListWallets(data) => { + state.serialize_field("type", "ListWallets")?; + state.serialize_field("data", data)?; + } + GetWalletInfo(data) => { + state.serialize_field("type", "GetWalletInfo")?; + state.serialize_field("data", data)?; + } + GetBalance(data) => { + state.serialize_field("type", "GetBalance")?; + state.serialize_field("data", data)?; + } + GetTokenBalance(data) => { + state.serialize_field("type", "GetTokenBalance")?; + state.serialize_field("data", data)?; + } + SendEth(data) => { + state.serialize_field("type", "SendEth")?; + state.serialize_field("data", data)?; + } + SendToken(data) => { + state.serialize_field("type", "SendToken")?; + state.serialize_field("data", data)?; + } + BuildAndSignUserOperationForPayment(data) => { + state.serialize_field("type", "BuildAndSignUserOperationForPayment")?; + state.serialize_field("data", data)?; + } + SubmitUserOperation(data) => { + state.serialize_field("type", "SubmitUserOperation")?; + state.serialize_field("data", data)?; + } + GetUserOperationReceipt(data) => { + state.serialize_field("type", "GetUserOperationReceipt")?; + state.serialize_field("data", data)?; + } + CreateNote(data) => { + state.serialize_field("type", "CreateNote")?; + state.serialize_field("data", data)?; + } + ExecuteViaTba(data) => { + state.serialize_field("type", "ExecuteViaTba")?; + state.serialize_field("data", data)?; + } + CheckTbaOwnership(data) => { + state.serialize_field("type", "CheckTbaOwnership")?; + state.serialize_field("data", data)?; + } + SetWalletLimits(data) => { + state.serialize_field("type", "SetWalletLimits")?; + state.serialize_field("data", data)?; + } + } + + state.end() + } +} + +impl<'a> Deserialize<'a> for wit::HyperwalletResponseData { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'a>, + { + struct HyperwalletResponseDataVisitor; + + impl<'de> Visitor<'de> for HyperwalletResponseDataVisitor { + type Value = wit::HyperwalletResponseData; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a HyperwalletResponseData variant") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut variant_type = None; + let mut data = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "type" => { + if variant_type.is_some() { + return Err(de::Error::duplicate_field("type")); + } + variant_type = Some(map.next_value::()?); + } + "data" => { + if data.is_some() { + return Err(de::Error::duplicate_field("data")); + } + data = Some(map.next_value::()?); + } + _ => { + let _ = map.next_value::()?; + } + } + } + + let variant_type = variant_type.ok_or_else(|| de::Error::missing_field("type"))?; + + use wit::HyperwalletResponseData::*; + match variant_type.as_str() { + "Handshake" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let step = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!("Failed to deserialize HandshakeStep: {}", e)) + })?; + Ok(Handshake(step)) + } + "ImportWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ImportWalletResponse: {}", + e + )) + })?; + Ok(ImportWallet(response)) + } + "DeleteWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize DeleteWalletResponse: {}", + e + )) + })?; + Ok(DeleteWallet(response)) + } + "ExportWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ExportWalletResponse: {}", + e + )) + })?; + Ok(ExportWallet(response)) + } + "ListWallets" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ListWalletsResponse: {}", + e + )) + })?; + Ok(ListWallets(response)) + } + "GetWalletInfo" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetWalletInfoResponse: {}", + e + )) + })?; + Ok(GetWalletInfo(response)) + } + "CreateWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CreateWalletResponse: {}", + e + )) + })?; + Ok(CreateWallet(response)) + } + "UnlockWallet" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize UnlockWalletResponse: {}", + e + )) + })?; + Ok(UnlockWallet(response)) + } + "SendEth" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SendEthResponse: {}", + e + )) + })?; + Ok(SendEth(response)) + } + "SendToken" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SendTokenResponse: {}", + e + )) + })?; + Ok(SendToken(response)) + } + "BuildAndSignUserOperationForPayment" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize BuildAndSignUserOperationResponse: {}", + e + )) + })?; + Ok(BuildAndSignUserOperationForPayment(response)) + } + "SubmitUserOperation" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SubmitUserOperationResponse: {}", + e + )) + })?; + Ok(SubmitUserOperation(response)) + } + "GetUserOperationReceipt" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize UserOperationReceiptResponse: {}", + e + )) + })?; + Ok(GetUserOperationReceipt(response)) + } + "GetBalance" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetBalanceResponse: {}", + e + )) + })?; + Ok(GetBalance(response)) + } + "GetTokenBalance" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize GetTokenBalanceResponse: {}", + e + )) + })?; + Ok(GetTokenBalance(response)) + } + "SetWalletLimits" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize SetWalletLimitsResponse: {}", + e + )) + })?; + Ok(SetWalletLimits(response)) + } + "CreateNote" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CreateNoteResponse: {}", + e + )) + })?; + Ok(CreateNote(response)) + } + "ExecuteViaTba" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize ExecuteViaTbaResponse: {}", + e + )) + })?; + Ok(ExecuteViaTba(response)) + } + "CheckTbaOwnership" => { + let data = data.ok_or_else(|| de::Error::missing_field("data"))?; + let response = serde_json::from_value(data).map_err(|e| { + de::Error::custom(format!( + "Failed to deserialize CheckTbaOwnershipResponse: {}", + e + )) + })?; + Ok(CheckTbaOwnership(response)) + } + _ => { + // For unimplemented variants, return an error with helpful message + Err(de::Error::unknown_variant( + &variant_type, + &[ + "Handshake", + "ImportWallet", + "DeleteWallet", + "ExportWallet", + "ListWallets", + "GetWalletInfo", + "CreateWallet", + "UnlockWallet", + "SendEth", + "SendToken", + "BuildAndSignUserOperationForPayment", + "SubmitUserOperation", + "GetUserOperationReceipt", + "GetBalance", + "GetTokenBalance", + "CreateNote", + "ExecuteViaTba", + "CheckTbaOwnership", + ], + )) + } + } + } + } + + const FIELDS: &[&str] = &["type", "data"]; + deserializer.deserialize_struct( + "HyperwalletResponseData", + FIELDS, + HyperwalletResponseDataVisitor, + ) + } +} diff --git a/src/hyperwallet_client/types.rs b/src/hyperwallet_client/types.rs new file mode 100644 index 0000000..b2f4119 --- /dev/null +++ b/src/hyperwallet_client/types.rs @@ -0,0 +1,471 @@ +//! Hyperwallet types exposed via WIT interface +//! This module re-exports the WIT-generated types and provides conversion utilities + +use crate::hyperware::process::hyperwallet as wit; + +// Re-export WIT types with original names for backwards compatibility +pub use wit::{ + Balance, ChainId, ClientHello, CompleteHandshake, CreateNoteResponse, CreateWalletRequest, + CreateWalletResponse, DeleteWalletRequest, DeleteWalletResponse, ErrorCode, + ExecuteViaTbaResponse, ExportWalletRequest, ExportWalletResponse, GetBalanceRequest, + GetBalanceResponse, GetTokenBalanceRequest, GetTokenBalanceResponse, GetWalletInfoRequest, + GetWalletInfoResponse, ImportWalletRequest, ImportWalletResponse, ListWalletsResponse, + OperationError, PaymasterConfig, RegisterRequest, RenameWalletRequest, SendEthRequest, + SendEthResponse, SendTokenRequest, SendTokenResponse, ServerWelcome, SessionId, Signature, + SpendingLimits, UnlockWalletRequest, UnlockWalletResponse, UpdatableSetting, UserOperationHash, + UserOperationReceiptResponse, Wallet, WalletAddress, WalletSpendingLimits, +}; + +// Re-export enum variants +pub use wit::{ + HandshakeStep, HyperwalletRequest, HyperwalletResponse, HyperwalletResponseData, MessageType, + Operation, OperationCategory, +}; + +// Additional WIT types needed by clients +pub use wit::Eip712Data; + +// Implement Default for PaymasterConfig +impl Default for wit::PaymasterConfig { + fn default() -> Self { + Self { + is_circle_paymaster: true, + paymaster_address: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec".to_string(), + paymaster_verification_gas: "0x7a120".to_string(), + paymaster_post_op_gas: "0x493e0".to_string(), + } + } +} + +// Re-export request types +pub use wit::{ + ApproveTokenRequest, BuildAndSignUserOperationForPaymentRequest, + BuildAndSignUserOperationRequest, BuildUserOperationRequest, CallContractRequest, + CheckTbaOwnershipRequest, ConfigurePaymasterRequest, CreateNoteRequest, EstimateGasRequest, + EstimateUserOperationGasRequest, ExecuteViaTbaRequest, GetTransactionHistoryRequest, + GetTransactionReceiptRequest, GetUserOperationReceiptRequest, MintEntryRequest, + ReadNoteRequest, ResolveIdentityRequest, SetWalletLimitsRequest, SetupDelegationRequest, + SetupTbaDelegationRequest, SignMessageRequest, SignTransactionRequest, + SignUserOperationRequest, SubmitUserOperationRequest, UpdateSpendingLimitsRequest, + VerifyDelegationRequest, +}; + +// SetWalletLimits request/response types (wallet-level limits) +// Not available in current WIT; requires WIT update to add request/response and variants. + +// Re-export response types +pub use wit::{ + BuildAndSignUserOperationResponse, CheckTbaOwnershipResponse, SetWalletLimitsResponse, + SubmitUserOperationResponse, +}; + +// Type aliases for compatibility +pub type ProcessAddress = crate::Address; + +// Additional types that need conversion or special handling +pub use wit::HyperwalletMessage; +pub use wit::ProcessPermissions; + +/// Session information combining handshake completion with metadata +#[derive(Debug, Clone)] +pub struct SessionInfo { + pub server_version: String, + pub session_id: SessionId, + pub registered_permissions: ProcessPermissions, + pub initial_chain_id: ChainId, +} + +impl SessionInfo { + /// Returns true when the server registered this operation for the session + pub fn supports(&self, operation: &Operation) -> bool { + self.registered_permissions + .allowed_operations + .iter() + .any(|op| op == operation) + } + + /// Returns the list of operations the server registered for the session + pub fn allowed_operations(&self) -> &Vec { + &self.registered_permissions.allowed_operations + } +} + +/// Configuration for the handshake process +#[derive(Debug)] +pub struct HandshakeConfig { + pub(crate) required_operations: Vec, + pub(crate) spending_limits: Option, + pub(crate) client_name: Option, + pub(crate) initial_chain_id: ChainId, +} + +impl Default for HandshakeConfig { + fn default() -> Self { + Self { + required_operations: Vec::new(), + spending_limits: None, + client_name: None, + initial_chain_id: 8453, // Default to Base mainnet + } + } +} + +impl HandshakeConfig { + pub fn new() -> Self { + Default::default() + } + + pub fn with_operations(mut self, operations: &[Operation]) -> Self { + for op in operations { + if !self.required_operations.iter().any(|o| o == op) { + self.required_operations.push(op.clone()); + } + } + self + } + + pub fn require_category(mut self, category: OperationCategory) -> Self { + // Convert operations matching the category + let all_ops = all_operations(); + for op in all_ops { + if operation_category(&op) == category + && !self.required_operations.iter().any(|o| o == &op) + { + self.required_operations.push(op); + } + } + self + } + + pub fn with_spending_limits(mut self, limits: SpendingLimits) -> Self { + self.spending_limits = Some(limits); + self + } + + pub fn with_name(mut self, name: impl Into) -> Self { + self.client_name = Some(name.into()); + self + } + + pub fn with_initial_chain(mut self, chain_id: ChainId) -> Self { + self.initial_chain_id = chain_id; + self + } +} + +/// Get all available operations +pub fn all_operations() -> Vec { + vec![ + Operation::Handshake, + Operation::UnlockWallet, + Operation::RegisterProcess, + Operation::UpdateSpendingLimits, + Operation::CreateWallet, + Operation::ImportWallet, + Operation::DeleteWallet, + Operation::RenameWallet, + Operation::ExportWallet, + Operation::EncryptWallet, + Operation::DecryptWallet, + Operation::GetWalletInfo, + Operation::ListWallets, + Operation::SetWalletLimits, + Operation::SendEth, + Operation::SendToken, + Operation::ApproveToken, + Operation::CallContract, + Operation::SignTransaction, + Operation::SignMessage, + Operation::ExecuteViaTba, + Operation::CheckTbaOwnership, + Operation::SetupTbaDelegation, + Operation::BuildAndSignUserOperationForPayment, + Operation::SubmitUserOperation, + Operation::BuildUserOperation, + Operation::SignUserOperation, + Operation::BuildAndSignUserOperation, + Operation::EstimateUserOperationGas, + Operation::GetUserOperationReceipt, + Operation::ConfigurePaymaster, + Operation::ResolveIdentity, + Operation::CreateNote, + Operation::ReadNote, + Operation::SetupDelegation, + Operation::VerifyDelegation, + Operation::MintEntry, + Operation::GetBalance, + Operation::GetTokenBalance, + Operation::GetTransactionHistory, + Operation::EstimateGas, + Operation::GetGasPrice, + Operation::GetTransactionReceipt, + Operation::BatchOperations, + Operation::ScheduleOperation, + Operation::CancelOperation, + ] +} + +/// Get the category for an operation +pub fn operation_category(op: &Operation) -> OperationCategory { + match op { + Operation::Handshake | Operation::UnlockWallet => OperationCategory::System, + + Operation::RegisterProcess | Operation::UpdateSpendingLimits => { + OperationCategory::ProcessManagement + } + + Operation::CreateWallet + | Operation::ImportWallet + | Operation::DeleteWallet + | Operation::RenameWallet + | Operation::ExportWallet + | Operation::EncryptWallet + | Operation::DecryptWallet + | Operation::GetWalletInfo + | Operation::ListWallets + | Operation::SetWalletLimits => OperationCategory::WalletManagement, + + Operation::SendEth + | Operation::SendToken + | Operation::ApproveToken + | Operation::CallContract + | Operation::SignTransaction + | Operation::SignMessage + | Operation::GetBalance + | Operation::GetTokenBalance + | Operation::GetTransactionHistory + | Operation::EstimateGas + | Operation::GetGasPrice + | Operation::GetTransactionReceipt => OperationCategory::Ethereum, + + Operation::ExecuteViaTba | Operation::CheckTbaOwnership | Operation::SetupTbaDelegation => { + OperationCategory::TokenBoundAccount + } + + Operation::BuildAndSignUserOperationForPayment + | Operation::SubmitUserOperation + | Operation::BuildUserOperation + | Operation::SignUserOperation + | Operation::BuildAndSignUserOperation + | Operation::EstimateUserOperationGas + | Operation::GetUserOperationReceipt + | Operation::ConfigurePaymaster => OperationCategory::Erc4337, + + Operation::ResolveIdentity + | Operation::CreateNote + | Operation::ReadNote + | Operation::SetupDelegation + | Operation::VerifyDelegation + | Operation::MintEntry => OperationCategory::Hypermap, + + Operation::BatchOperations | Operation::ScheduleOperation | Operation::CancelOperation => { + OperationCategory::Advanced + } + } +} + +/// Get the operation type for a HyperwalletRequest +pub fn operation_type(request: &HyperwalletRequest) -> Operation { + match request { + // Session Management + HyperwalletRequest::Handshake(_) => Operation::Handshake, + HyperwalletRequest::UnlockWallet(_) => Operation::UnlockWallet, + + // Wallet Lifecycle Management + HyperwalletRequest::CreateWallet(_) => Operation::CreateWallet, + HyperwalletRequest::ImportWallet(_) => Operation::ImportWallet, + HyperwalletRequest::DeleteWallet(_) => Operation::DeleteWallet, + HyperwalletRequest::RenameWallet(_) => Operation::RenameWallet, + HyperwalletRequest::ExportWallet(_) => Operation::ExportWallet, + HyperwalletRequest::ListWallets => Operation::ListWallets, + HyperwalletRequest::GetWalletInfo(_) => Operation::GetWalletInfo, + + // Ethereum Operations + HyperwalletRequest::SendEth(_) => Operation::SendEth, + HyperwalletRequest::SendToken(_) => Operation::SendToken, + HyperwalletRequest::ApproveToken(_) => Operation::ApproveToken, + HyperwalletRequest::GetBalance(_) => Operation::GetBalance, + HyperwalletRequest::GetTokenBalance(_) => Operation::GetTokenBalance, + HyperwalletRequest::CallContract(_) => Operation::CallContract, + HyperwalletRequest::SignTransaction(_) => Operation::SignTransaction, + HyperwalletRequest::SignMessage(_) => Operation::SignMessage, + HyperwalletRequest::GetTransactionHistory(_) => Operation::GetTransactionHistory, + HyperwalletRequest::EstimateGas(_) => Operation::EstimateGas, + HyperwalletRequest::GetGasPrice => Operation::GetGasPrice, + HyperwalletRequest::GetTransactionReceipt(_) => Operation::GetTransactionReceipt, + + // Token Bound Account Operations + HyperwalletRequest::ExecuteViaTba(_) => Operation::ExecuteViaTba, + HyperwalletRequest::CheckTbaOwnership(_) => Operation::CheckTbaOwnership, + HyperwalletRequest::SetupTbaDelegation(_) => Operation::SetupTbaDelegation, + + // Account Abstraction (ERC-4337) + HyperwalletRequest::BuildAndSignUserOperationForPayment(_) => { + Operation::BuildAndSignUserOperationForPayment + } + HyperwalletRequest::SubmitUserOperation(_) => Operation::SubmitUserOperation, + HyperwalletRequest::GetUserOperationReceipt(_) => Operation::GetUserOperationReceipt, + HyperwalletRequest::BuildUserOperation(_) => Operation::BuildUserOperation, + HyperwalletRequest::SignUserOperation(_) => Operation::SignUserOperation, + HyperwalletRequest::BuildAndSignUserOperation(_) => Operation::BuildAndSignUserOperation, + HyperwalletRequest::EstimateUserOperationGas(_) => Operation::EstimateUserOperationGas, + HyperwalletRequest::ConfigurePaymaster(_) => Operation::ConfigurePaymaster, + HyperwalletRequest::SetWalletLimits(_) => Operation::SetWalletLimits, + + // Hypermap Operations + HyperwalletRequest::ResolveIdentity(_) => Operation::ResolveIdentity, + HyperwalletRequest::CreateNote(_) => Operation::CreateNote, + HyperwalletRequest::ReadNote(_) => Operation::ReadNote, + HyperwalletRequest::SetupDelegation(_) => Operation::SetupDelegation, + HyperwalletRequest::VerifyDelegation(_) => Operation::VerifyDelegation, + HyperwalletRequest::MintEntry(_) => Operation::MintEntry, + + // Process Management (Legacy) + HyperwalletRequest::UpdateSpendingLimits(_) => Operation::UpdateSpendingLimits, + } +} + +// Helper functions for HyperwalletResponse +impl wit::HyperwalletResponse { + pub fn success(data: HyperwalletResponseData) -> Self { + Self { + success: true, + data: Some(data), + error: None, + request_id: None, + } + } + + pub fn error(error: OperationError) -> Self { + Self { + success: false, + data: None, + error: Some(error), + request_id: None, + } + } +} + +// Helper functions for OperationError +impl wit::OperationError { + pub fn internal_error(message: &str) -> Self { + Self { + code: ErrorCode::InternalError, + message: message.to_string(), + details: None, + } + } + + pub fn invalid_params(message: &str) -> Self { + Self { + code: ErrorCode::InvalidParams, + message: message.to_string(), + details: None, + } + } + + pub fn wallet_not_found(wallet_id: &str) -> Self { + Self { + code: ErrorCode::WalletNotFound, + message: format!("Wallet '{}' not found or not accessible", wallet_id), + details: None, + } + } + + pub fn chain_not_allowed(chain_id: u64) -> Self { + Self { + code: ErrorCode::ChainNotAllowed, + message: format!("Chain ID {} is not allowed for this process", chain_id), + details: None, + } + } + + pub fn blockchain_error(message: &str) -> Self { + Self { + code: ErrorCode::BlockchainError, + message: format!("Blockchain error: {}", message), + details: None, + } + } + + pub fn insufficient_funds(details: &str) -> Self { + Self { + code: ErrorCode::InsufficientFunds, + message: format!("Insufficient funds: {}", details), + details: None, + } + } + + pub fn spending_limit_exceeded(details: &str) -> Self { + Self { + code: ErrorCode::SpendingLimitExceeded, + message: format!("Spending limit exceeded: {}", details), + details: None, + } + } + + pub fn authentication_failed(reason: &str) -> Self { + Self { + code: ErrorCode::AuthenticationFailed, + message: format!("Authentication failed: {}", reason), + details: None, + } + } + + pub fn wallet_locked(wallet_id: &str) -> Self { + Self { + code: ErrorCode::WalletLocked, + message: format!( + "Wallet '{}' is locked. Unlock it first to perform operations", + wallet_id + ), + details: None, + } + } + + pub fn operation_not_supported(operation: &str) -> Self { + Self { + code: ErrorCode::OperationNotSupported, + message: format!("Operation '{}' is not supported or not enabled", operation), + details: None, + } + } + + pub fn permission_denied(message: &str) -> Self { + Self { + code: ErrorCode::PermissionDenied, + message: format!("Permission denied: {}", message), + details: None, + } + } +} + +// Add operation_type method to HyperwalletMessage +impl wit::HyperwalletMessage { + pub fn operation_type(&self) -> Operation { + operation_type(&self.request) + } +} + +// Conversion function for ProcessPermissions +impl wit::ProcessPermissions { + pub fn new(address: crate::Address, required_operations: Vec) -> Self { + Self { + address, + allowed_operations: required_operations, + spending_limits: None, + updatable_settings: vec![], + registered_at: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + } + } +} + +// Legacy type that doesn't exist in WIT - kept for compatibility +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TxReceipt { + pub hash: String, + pub details: serde_json::Value, +} diff --git a/src/lib.rs b/src/lib.rs index 4134715..6cd68d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,8 +32,6 @@ wit_bindgen::generate!({ /// Interact with the eth provider module. pub mod eth; -/// Interact with the system homepage. -/// /// Your process must have the [`Capability`] to message /// `homepage:homepage:sys` to use this module. pub mod homepage; @@ -49,6 +47,8 @@ pub mod hypermap; /// be incompatible with WIT types in some cases, leading to annoying errors. /// Use only to interact with the kernel or runtime in certain ways. pub mod kernel_types; +/// Tools for exploring and working with Token-Bound Accounts (TBAs) in Hypermap +//pub mod tba_explorer; /// Interact with the key_value module /// /// Your process must have the [`Capability`] to message and receive messages from @@ -63,6 +63,9 @@ pub mod logging; /// `net:distro:sys` to use this module. pub mod net; pub mod sign; +/// Low-level Ethereum signing operations and key management. +#[cfg(feature = "hyperwallet")] +pub mod signer; /// Interact with the sqlite module /// /// Your process must have the [`Capability] to message and receive messages from @@ -77,10 +80,16 @@ pub mod timer; /// Your process must have the [`Capability`] to message and receive messages from /// `vfs:distro:sys` to use this module. pub mod vfs; +/// Ethereum wallet management with transaction preparation and submission. +#[cfg(feature = "hyperwallet")] +pub mod wallet; /// A set of types and macros for writing "script" processes. pub mod scripting; +#[cfg(feature = "hyperwallet")] +pub mod hyperwallet_client; + mod types; pub use types::{ address::{Address, AddressParseError}, diff --git a/src/signer.rs b/src/signer.rs new file mode 100644 index 0000000..21895d5 --- /dev/null +++ b/src/signer.rs @@ -0,0 +1,505 @@ +//! Ethereum signer functionality for Hyperware. +//! +//! This module provides low-level cryptographic signing operations for Ethereum, +//! including private key management, message signing, and transaction signing. +//! It separates the cryptographic concerns from the higher-level wallet functionality. + +use crate::eth::EthError; +use hex; +use rand::{thread_rng, RngCore}; +use serde::{Deserialize, Serialize}; +use sha3::{Digest, Sha3_256}; +use thiserror::Error; + +use alloy::{ + consensus::{SignableTransaction, TxEip1559, TxEnvelope}, + network::eip2718::Encodable2718, + network::TxSignerSync, + primitives::TxKind, + signers::{local::PrivateKeySigner, SignerSync}, +}; +use alloy_primitives::{Address as EthAddress, B256, U256}; +use std::str::FromStr; + +// For encryption/decryption +const SALT_SIZE: usize = 16; +const NONCE_SIZE: usize = 12; +const KEY_SIZE: usize = 32; +const TAG_SIZE: usize = 16; + +/// Transaction data structure used for signing transactions +#[derive(Debug, Clone)] +pub struct TransactionData { + /// The recipient address + pub to: EthAddress, + /// The amount to send in wei + pub value: U256, + /// Optional transaction data (for contract interactions) + pub data: Option>, + /// The transaction nonce + pub nonce: u64, + /// The maximum gas for the transaction + pub gas_limit: u64, + /// The gas price in wei + pub gas_price: u128, + /// Optional max priority fee (for EIP-1559 transactions) + pub max_priority_fee: Option, + /// The chain ID for the transaction + pub chain_id: u64, +} + +/// Errors that can occur during signing operations +#[derive(Debug, Error)] +pub enum SignerError { + #[error("failed to generate random bytes: {0}")] + RandomGenerationError(String), + + #[error("invalid private key format: {0}")] + InvalidPrivateKey(String), + + #[error("chain ID mismatch: expected {expected}, got {actual}")] + ChainIdMismatch { expected: u64, actual: u64 }, + + #[error("failed to sign transaction or message: {0}")] + SigningError(String), + + #[error("ethereum error: {0}")] + EthError(#[from] EthError), + + #[error("encryption error: {0}")] + EncryptionError(String), + + #[error("decryption error: {0}")] + DecryptionError(String), +} + +/// The storage format for encrypted signers +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EncryptedSignerData { + /// The encrypted private key data + pub encrypted_data: Vec, + /// The Ethereum address (for quick reference without decryption) + pub address: String, + /// The chain ID this signer is for + pub chain_id: u64, +} + +/// The Signer trait defines the interface for all signing implementations +pub trait Signer { + /// Get the Ethereum address associated with this signer + fn address(&self) -> EthAddress; + + /// Get the chain ID this signer is configured for + fn chain_id(&self) -> u64; + + /// Sign a transaction with the private key + fn sign_transaction(&self, tx_data: &TransactionData) -> Result, SignerError>; + + /// Sign a message following Ethereum's personal_sign format + fn sign_message(&self, message: &[u8]) -> Result, SignerError>; + + /// Sign a raw hash without any prefix (for EIP-712 and similar) + fn sign_hash(&self, hash: &[u8]) -> Result, SignerError>; +} + +/// Local signer implementation using a private key stored in memory +#[derive(Debug, Clone)] +pub struct LocalSigner { + /// The underlying private key signer from alloy + pub inner: PrivateKeySigner, + + /// The Ethereum address derived from the private key + pub address: EthAddress, + + /// The chain ID this signer is configured for + pub chain_id: u64, + + /// The private key as a hex string + pub private_key_hex: String, +} + +// Manual implementation of Serialize for LocalSigner +impl Serialize for LocalSigner { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + // Serialize only the fields we need + let mut state = serializer.serialize_struct("LocalSigner", 3)?; + state.serialize_field("address", &self.address)?; + state.serialize_field("chain_id", &self.chain_id)?; + state.serialize_field("private_key_hex", &self.private_key_hex)?; + state.end() + } +} + +// Manual implementation of Deserialize for LocalSigner +impl<'de> Deserialize<'de> for LocalSigner { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct LocalSignerData { + #[allow(dead_code)] + address: EthAddress, + chain_id: u64, + private_key_hex: String, + } + + let data = LocalSignerData::deserialize(deserializer)?; + + // Reconstruct the LocalSigner from the private key + match LocalSigner::from_private_key(&data.private_key_hex, data.chain_id) { + Ok(signer) => Ok(signer), + Err(e) => Err(serde::de::Error::custom(format!( + "Failed to reconstruct signer: {}", + e + ))), + } + } +} + +impl LocalSigner { + /// Create a new signer with a randomly generated private key + pub fn new_random(chain_id: u64) -> Result { + // Generate a secure random private key directly + let mut rng = thread_rng(); + let mut private_key_bytes = [0u8; 32]; + rng.fill_bytes(&mut private_key_bytes); + + // Make sure the private key is valid (less than curve order) + // TODO: This is a simplification + let max_scalar = + hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140") + .map_err(|_| { + SignerError::RandomGenerationError("Failed to decode max scalar".to_string()) + })?; + + // Simple check: if our random bytes are >= max_scalar, regenerate + // This is a simplified approach - production code would use more sophisticated comparison + if private_key_bytes.as_slice().cmp(max_scalar.as_slice()) != std::cmp::Ordering::Less { + // Try again with a new random value + rng.fill_bytes(&mut private_key_bytes); + } + + // Convert to B256 for the PrivateKeySigner + let key = B256::from_slice(&private_key_bytes); + + // Store the private key hex string for later use + let private_key_hex = format!("0x{}", hex::encode(private_key_bytes)); + + // Create the PrivateKeySigner + let inner = match PrivateKeySigner::from_bytes(&key) { + Ok(signer) => signer, + Err(e) => return Err(SignerError::InvalidPrivateKey(e.to_string())), + }; + + let address = inner.address(); + + Ok(Self { + inner, + address, + chain_id, + private_key_hex, + }) + } + + /// Create a signer from a private key in hexadecimal string format + pub fn from_private_key(private_key: &str, chain_id: u64) -> Result { + // Remove 0x prefix if present + let clean_key = private_key.trim_start_matches("0x"); + + // Parse hex string into bytes + if clean_key.len() != 64 { + return Err(SignerError::InvalidPrivateKey( + "Private key must be 32 bytes (64 hex characters)".to_string(), + )); + } + + let key_bytes = + hex::decode(clean_key).map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?; + + Self::from_bytes(&key_bytes, chain_id, format!("0x{}", clean_key)) + } + + /// Create a signer from raw private key bytes + fn from_bytes( + bytes: &[u8], + chain_id: u64, + private_key_hex: String, + ) -> Result { + if bytes.len() != 32 { + return Err(SignerError::InvalidPrivateKey( + "Private key must be exactly 32 bytes".to_string(), + )); + } + + // Convert to B256 (fixed bytes) + let key = B256::from_slice(bytes); + + // Create the PrivateKeySigner + let inner = match PrivateKeySigner::from_bytes(&key) { + Ok(wallet) => wallet, + Err(e) => return Err(SignerError::InvalidPrivateKey(e.to_string())), + }; + + let address = inner.address(); + + Ok(Self { + inner, + address, + chain_id, + private_key_hex, + }) + } + + /// Encrypt this signer using a password + pub fn encrypt(&self, password: &str) -> Result { + // Extract the private key hex (without 0x prefix) + let clean_key = self.private_key_hex.trim_start_matches("0x"); + + // Convert to bytes + let key_bytes = + hex::decode(clean_key).map_err(|e| SignerError::EncryptionError(e.to_string()))?; + + // Encrypt the private key + let encrypted_data = + encrypt_data(&key_bytes, password).map_err(|e| SignerError::EncryptionError(e))?; + + // Create encrypted data structure + Ok(EncryptedSignerData { + encrypted_data, + address: self.address.to_string(), + chain_id: self.chain_id, + }) + } + + /// Decrypt an encrypted signer + pub fn decrypt(encrypted: &EncryptedSignerData, password: &str) -> Result { + let decrypted_bytes = decrypt_data(&encrypted.encrypted_data, password) + .map_err(|e| SignerError::DecryptionError(e))?; + + // Convert bytes back to hex string + let private_key_hex = format!("0x{}", hex::encode(&decrypted_bytes)); + + // Create a new signer with the specified chain ID + Self::from_bytes(&decrypted_bytes, encrypted.chain_id, private_key_hex) + } + + /// Export the private key as a hexadecimal string + pub fn export_private_key(&self) -> String { + self.private_key_hex.clone() + } +} + +impl Signer for LocalSigner { + fn address(&self) -> EthAddress { + self.address + } + + fn chain_id(&self) -> u64 { + self.chain_id + } + + fn sign_transaction(&self, tx_data: &TransactionData) -> Result, SignerError> { + // Verify chain ID matches the signer's chain ID + if tx_data.chain_id != self.chain_id { + return Err(SignerError::ChainIdMismatch { + expected: self.chain_id, + actual: tx_data.chain_id, + }); + } + + // Convert hyperware types to alloy types + let to_str = tx_data.to.to_string(); + let to = alloy_primitives::Address::from_str(&to_str) + .map_err(|e| SignerError::SigningError(format!("Invalid contract address: {}", e)))?; + + // Create transaction based on chain type + // Both Ethereum mainnet and Base use EIP-1559 transactions + let mut tx = TxEip1559 { + chain_id: tx_data.chain_id, + nonce: tx_data.nonce, + to: TxKind::Call(to), + gas_limit: tx_data.gas_limit, + max_fee_per_gas: tx_data.gas_price, + // Use provided priority fee or calculate a reasonable default based on the chain + max_priority_fee_per_gas: tx_data.max_priority_fee.unwrap_or_else(|| { + match tx_data.chain_id { + // Ethereum mainnet (1) + 1 => tx_data.gas_price / 10, + // Base (8453) - typically accepts lower priority fees + 8453 => tx_data.gas_price / 5, + // Default fallback for other networks + _ => tx_data.gas_price / 10, + } + }), + input: tx_data.data.clone().unwrap_or_default().into(), + value: tx_data.value, + ..Default::default() + }; + + // Sign the transaction with the wallet + let sig = match self.inner.sign_transaction_sync(&mut tx) { + Ok(sig) => sig, + Err(e) => return Err(SignerError::SigningError(e.to_string())), + }; + + // Create signed transaction envelope + let signed = TxEnvelope::from(tx.into_signed(sig)); + + // Encode the transaction + let mut buf = vec![]; + signed.encode_2718(&mut buf); + + Ok(buf) + } + + fn sign_message(&self, message: &[u8]) -> Result, SignerError> { + // Create the Ethereum signed message prefixed hash + let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len()); + let prefixed_message = [prefix.as_bytes(), message].concat(); + + // Hash the message + let hash = sha3::Keccak256::digest(&prefixed_message); + let hash_bytes = B256::from_slice(hash.as_slice()); + + // Sign the hash + match self.inner.sign_hash_sync(&hash_bytes) { + Ok(signature) => Ok(signature.as_bytes().to_vec()), + Err(e) => Err(SignerError::SigningError(e.to_string())), + } + } + + fn sign_hash(&self, hash: &[u8]) -> Result, SignerError> { + // Sign a raw hash without any prefix + if hash.len() != 32 { + return Err(SignerError::SigningError( + "Hash must be exactly 32 bytes".to_string(), + )); + } + + let hash_bytes = B256::from_slice(hash); + + // Sign the hash directly + match self.inner.sign_hash_sync(&hash_bytes) { + Ok(signature) => Ok(signature.as_bytes().to_vec()), + Err(e) => Err(SignerError::SigningError(e.to_string())), + } + } +} + +/// Encrypt a private key using a password +pub fn encrypt_data(data: &[u8], password: &str) -> Result, String> { + let mut rng = thread_rng(); + + // Generate salt + let mut salt = [0u8; SALT_SIZE]; + rng.fill_bytes(&mut salt); + + // Generate nonce + let mut nonce = [0u8; NONCE_SIZE]; + rng.fill_bytes(&mut nonce); + + // Derive key using SHA3 + let key = derive_key(password.as_bytes(), &salt); + + // Encrypt data using XOR + let encrypted_data = encrypt_with_key(data, &key, &nonce); + + // Generate authentication tag + let tag = compute_tag(&salt, &nonce, &encrypted_data, &key); + + // Combine all components + Ok([ + salt.as_ref(), + nonce.as_ref(), + encrypted_data.as_ref(), + tag.as_ref(), + ] + .concat()) +} + +/// Decrypt an encrypted private key +pub fn decrypt_data(encrypted_data: &[u8], password: &str) -> Result, String> { + // Check if data is long enough to contain all components + if encrypted_data.len() < SALT_SIZE + NONCE_SIZE + TAG_SIZE { + return Err("Encrypted data is too short".into()); + } + + // Extract components + let salt = &encrypted_data[..SALT_SIZE]; + let nonce = &encrypted_data[SALT_SIZE..SALT_SIZE + NONCE_SIZE]; + let tag = &encrypted_data[encrypted_data.len() - TAG_SIZE..]; + let ciphertext = &encrypted_data[SALT_SIZE + NONCE_SIZE..encrypted_data.len() - TAG_SIZE]; + + // Derive key + let key = derive_key(password.as_bytes(), salt); + + // Verify the authentication tag + let expected_tag = compute_tag(salt, nonce, ciphertext, &key); + if tag != expected_tag { + return Err("Decryption failed: Authentication tag mismatch".into()); + } + + // Decrypt data + let plaintext = decrypt_with_key(ciphertext, &key, nonce); + + Ok(plaintext) +} + +/// Derive a key from a password and salt using SHA3 +fn derive_key(password: &[u8], salt: &[u8]) -> [u8; KEY_SIZE] { + // Initial hash + let mut hasher = Sha3_256::new(); + hasher.update(salt); + hasher.update(password); + let mut key = hasher.finalize().into(); + + // Multiple iterations for stronger key derivation + for _ in 0..10000 { + let mut hasher = Sha3_256::new(); + hasher.update(key); + hasher.update(salt); + key = hasher.finalize().into(); + } + + key +} + +/// Encrypt data with a key and nonce using XOR +fn encrypt_with_key(data: &[u8], key: &[u8; KEY_SIZE], nonce: &[u8]) -> Vec { + let mut result = Vec::with_capacity(data.len()); + + for (i, &byte) in data.iter().enumerate() { + // Create a unique keystream byte for each position + let key_byte = key[i % key.len()]; + let nonce_byte = nonce[i % nonce.len()]; + let keystream = key_byte ^ nonce_byte ^ (i as u8); + + // XOR with data + result.push(byte ^ keystream); + } + + result +} + +/// Decrypt data (same as encrypt since XOR is symmetric) +fn decrypt_with_key(data: &[u8], key: &[u8; KEY_SIZE], nonce: &[u8]) -> Vec { + encrypt_with_key(data, key, nonce) +} + +/// Compute an authentication tag using SHA3 +fn compute_tag(salt: &[u8], nonce: &[u8], data: &[u8], key: &[u8]) -> Vec { + let mut hasher = Sha3_256::new(); + hasher.update(salt); + hasher.update(nonce); + hasher.update(data); + hasher.update(key); + + let hash = hasher.finalize(); + hash[..TAG_SIZE].to_vec() +} diff --git a/src/tba_explorer.rs b/src/tba_explorer.rs new file mode 100644 index 0000000..3eac6ce --- /dev/null +++ b/src/tba_explorer.rs @@ -0,0 +1,391 @@ +/// not ready for thought + + +use crate::{ + eth::{ + self, + Bytes, + Provider, + Address as EthAddress + }, + hypermap::{ + self, + Hypermap + }, + println as kiprintln, +}; +use alloy_primitives::{ + B256, + FixedBytes +}; +use alloy::rpc_types::request::{ + TransactionInput, + TransactionRequest +}; +use alloy_sol_types::SolValue; +use std::str::FromStr; +use hex; +use serde::{ + Serialize, + Deserialize +}; + +/// ERC-6551 Account Interface for interacting with Token-Bound Accounts +pub struct Erc6551Account { + address: EthAddress, + provider: Provider, +} + +impl Erc6551Account { + pub fn new(address: EthAddress, provider: Provider) -> Self { + Self { address, provider } + } + + /// Get token information from the TBA + pub fn token(&self) -> Result<(u64, EthAddress, u64)> { + // Function selector for token() + let selector = [0x45, 0xc3, 0x11, 0x87]; // token() + + let tx_req = TransactionRequest::default() + .input(TransactionInput::new(Bytes::from(selector.to_vec()))) + .to(self.address); + + let res_bytes = self.provider.call(tx_req, None)?; + + // Parse the response (chainId, tokenContract, tokenId) + if res_bytes.len() < 96 { + return Err(anyhow!("Invalid response length for token() call")); + } + + // Extract chainId (first 32 bytes) + let chain_id = u64::from_be_bytes([ + res_bytes[24], res_bytes[25], res_bytes[26], res_bytes[27], + res_bytes[28], res_bytes[29], res_bytes[30], res_bytes[31], + ]); + + // Extract tokenContract (next 32 bytes, but we only need 20 bytes) + let token_contract = EthAddress::from_slice(&res_bytes[44..64]); + + // Extract tokenId (last 32 bytes) + let token_id = u64::from_be_bytes([ + res_bytes[88], res_bytes[89], res_bytes[90], res_bytes[91], + res_bytes[92], res_bytes[93], res_bytes[94], res_bytes[95], + ]); + + Ok((chain_id, token_contract, token_id)) + } + + /// Check if a signer is valid for this account + pub fn is_valid_signer(&self, signer: &EthAddress) -> Result { + // Function selector for isValidSigner(address,bytes) + let selector = [0x52, 0x65, 0x78, 0x4c, 0x3c]; + + // Encode the signer address (padded to 32 bytes) + let mut call_data = Vec::with_capacity(68); + call_data.extend_from_slice(&selector); + + // Add the address parameter (padded to 32 bytes) + call_data.extend_from_slice(&[0; 12]); // 12 bytes padding + call_data.extend_from_slice(signer.as_slice()); + + // Add offset to empty bytes array (32) and length (0) + call_data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32]); + call_data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + let tx_req = TransactionRequest::default() + .input(TransactionInput::new(Bytes::from(call_data))) + .to(self.address); + + match self.provider.call(tx_req, None) { + Ok(res) => { + // Expect a boolean response (32 bytes with last byte being 0 or 1) + if res.len() >= 32 && (res[31] == 1) { + Ok(true) + } else { + Ok(false) + } + }, + Err(e) => { + kiprintln!("isValidSigner call failed: {:?}", e); + Ok(false) + } + } + } + + /// Get the implementation address of this TBA + pub fn get_implementation(&self) -> Result { + // EIP-1967 implementation slot + let impl_slot = B256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")?; + + match self.provider.get_storage_at(self.address, impl_slot, None) { + Ok(value) => { + // Extract address from storage value (last 20 bytes) + let bytes = value.as_ref(); + if bytes.len() >= 20 { + let impl_addr = EthAddress::from_slice(&bytes[bytes.len()-20..]); + Ok(impl_addr) + } else { + Err(anyhow!("Invalid implementation address format")) + } + }, + Err(e) => Err(anyhow!("Failed to get implementation: {:?}", e)), + } + } + + /// Check if an auth-key is set on the TBA + pub fn has_auth_key(&self) -> Result { + // Storage slot for auth keys (depends on implementation) + // This would need to match the exact implementation's storage layout + let auth_key_slot = B256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001")?; + + match self.provider.get_storage_at(self.address, auth_key_slot, None) { + Ok(value) => { + // Check if the slot has a non-zero value + let bytes = value.as_ref(); + Ok(!bytes.iter().all(|&b| b == 0)) + }, + Err(e) => { + kiprintln!("Failed to check auth key: {:?}", e); + Ok(false) + } + } + } +} + +/// Complete TBA information structure +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TbaInfo { + pub node_name: String, + pub tba_address: EthAddress, + pub owner_address: EthAddress, + pub implementation: EthAddress, + pub chain_id: u64, + pub token_contract: EthAddress, + pub token_id: u64, + pub auth_signers: Vec, +} + +/// Extended Hypermap functionality specifically for TBA exploration +pub struct TbaExplorer { + hypermap: Hypermap, +} + +impl TbaExplorer { + pub fn new(timeout: u64) -> Self { + Self { + hypermap: Hypermap::default(timeout), + } + } + + /// Format a node name to ensure it has proper extension + pub fn format_node_name(&self, node_name: &str) -> String { + if !node_name.contains('.') { + format!("{}.hypr", node_name) + } else { + node_name.to_string() + } + } + + /// Get information about a TBA by node name + pub fn get_tba_info(&self, node_name: &str) -> Result { + // Format node name properly + let name = self.format_node_name(node_name); + + // Get TBA and owner from Hypermap + let (tba_address, owner_address, _) = self.hypermap.get(&name)?; + + // Create ERC-6551 account wrapper + let account = Erc6551Account::new(tba_address, self.hypermap.provider.clone()); + + // Get token info + let (chain_id, token_contract, token_id) = match account.token() { + Ok(info) => info, + Err(e) => { + kiprintln!("Failed to get token info: {:?}", e); + // Provide defaults + (0, EthAddress::default(), 0) + } + }; + + // Get implementation + let implementation = match account.get_implementation() { + Ok(impl_addr) => impl_addr, + Err(e) => { + kiprintln!("Failed to get implementation: {:?}", e); + EthAddress::default() + } + }; + + // Get custom auth signers from ~auth-signers note if it exists + let auth_signers = self.get_auth_signers(&name)?; + + Ok(TbaInfo { + node_name: name, + tba_address, + owner_address, + implementation, + chain_id, + token_contract, + token_id, + auth_signers, + }) + } + + /// Get authorized signers from a node's notes + fn get_auth_signers(&self, node_name: &str) -> Result> { + let mut auth_signers = Vec::new(); + + // Get the namehash + let namehash = hypermap::namehash(node_name); + + // Create filter for ~auth-signers note + let note_filter = self.hypermap.notes_filter(&["~auth-signers"]) + .topic1(vec![FixedBytes::<32>::from_str(&namehash)?]); + + // Get logs + let logs = self.hypermap.provider.get_logs(¬e_filter)?; + + if logs.is_empty() { + return Ok(Vec::new()); + } + + // Process the latest version of the note + if let Some(latest_log) = logs.last() { + if let Ok(note) = hypermap::decode_note_log(latest_log) { + // Try to parse as JSON list of addresses + if let Ok(json) = serde_json::from_slice::(¬e.data) { + if let Some(addresses) = json.as_array() { + for addr in addresses { + if let Some(addr_str) = addr.as_str() { + if let Ok(address) = EthAddress::from_str(addr_str) { + auth_signers.push(address); + } + } + } + } + } else { + // If not JSON, try to parse as comma-separated list + let data_str = String::from_utf8_lossy(¬e.data); + for addr_str in data_str.split(',') { + let trimmed = addr_str.trim(); + if let Ok(address) = EthAddress::from_str(trimmed) { + auth_signers.push(address); + } + } + } + } + } + + Ok(auth_signers) + } + + /// Check if an address is authorized to sign for a node's TBA + pub fn is_authorized_signer(&self, node_name: &str, signer: &EthAddress) -> Result { + // Get TBA info + let tba_info = self.get_tba_info(node_name)?; + + // Check if signer is owner (always authorized) + if &tba_info.owner_address == signer { + return Ok(true); + } + + // Check if signer is in auth_signers list from note + if tba_info.auth_signers.contains(signer) { + return Ok(true); + } + + // Check using isValidSigner directly + let account = Erc6551Account::new(tba_info.tba_address, self.hypermap.provider.clone()); + account.is_valid_signer(signer) + } + + /// Get all ~net-key and any custom signing keys from a node + pub fn get_signing_keys(&self, node_name: &str) -> Result>> { + let mut keys = Vec::new(); + + // Format node name + let name = self.format_node_name(node_name); + + // Get the namehash + let namehash = hypermap::namehash(&name); + + // Create filter for ~net-key and ~signing-key notes + let keys_filter = self.hypermap.notes_filter(&["~net-key", "~signing-key"]) + .topic1(vec![FixedBytes::<32>::from_str(&namehash)?]); + + // Get logs + let logs = self.hypermap.provider.get_logs(&keys_filter)?; + + for log in logs { + if let Ok(note) = hypermap::decode_note_log(&log) { + // Add the key data + keys.push(note.data.to_vec()); + } + } + + Ok(keys) + } + + /// Check if a TBA supports the custom auth signer mechanism + pub fn supports_auth_signers(&self, node_name: &str) -> Result { + // Get TBA info + let tba_info = self.get_tba_info(node_name)?; + + // Check if the implementation is the customized TBA + // This would require knowing the specific implementation address + // For now, we'll just check if the implementation isn't the default + if tba_info.implementation == EthAddress::default() { + return Ok(false); + } + + // We could also check if there's already an auth_signers note + if !tba_info.auth_signers.is_empty() { + return Ok(true); + } + + // Try calling isValidSigner with a test address to see if it works + let account = Erc6551Account::new(tba_info.tba_address, self.hypermap.provider.clone()); + + // Check if auth key slot is used + account.has_auth_key() + } + + /// Format key data for storage in Hypermap + pub fn format_auth_signers(&self, signers: &[EthAddress]) -> Result { + // Format as JSON array of address strings + let signer_strings: Vec = signers.iter() + .map(|addr| addr.to_string()) + .collect(); + + let json = serde_json::to_string(&signer_strings)?; + + Ok(Bytes::copy_from_slice(json.as_bytes())) + } +} + +/// Helper function to convert a hex string to an Address +pub fn hex_to_address(hex_str: &str) -> Result { + let cleaned = hex_str.trim_start_matches("0x"); + if cleaned.len() != 40 { + return Err(anyhow!("Invalid address length")); + } + + let bytes = hex::decode(cleaned)?; + Ok(EthAddress::from_slice(&bytes)) +} + +/// Helper function to convert bytes to a human-readable format +pub fn format_bytes(bytes: &[u8]) -> String { + if bytes.len() <= 64 { + // For small data, show full hex + format!("0x{}", hex::encode(bytes)) + } else { + // For larger data, truncate + format!("0x{}...{} ({} bytes)", + hex::encode(&bytes[..8]), + hex::encode(&bytes[bytes.len() - 8..]), + bytes.len()) + } +} \ No newline at end of file diff --git a/src/wallet.rs b/src/wallet.rs new file mode 100644 index 0000000..0f7e6ad --- /dev/null +++ b/src/wallet.rs @@ -0,0 +1,2889 @@ +//! ## (unfinished, unpolished and not fully tested) EVM wallet functionality for Hyperware. +//! +//! This module provides high-level wallet functionality for Ethereum, +//! including transaction signing, contract interaction, and account management. +//! It provides a simple interface for sending ETH and interacting with ERC20, +//! ERC721, and ERC1155 tokens. +//! +//! ERC6551 + the hypermap is not supported yet. +//! + +use crate::eth::{BlockNumberOrTag, EthError, Provider}; +use crate::hypermap; +use crate::hypermap::{namehash, valid_fact, valid_name, valid_note}; +use crate::println as kiprintln; +use crate::signer::{EncryptedSignerData, LocalSigner, Signer, SignerError, TransactionData}; + +use alloy::rpc::types::{ + request::TransactionRequest, Filter, FilterBlockOption, FilterSet, TransactionReceipt, +}; +use alloy_primitives::TxKind; +use alloy_primitives::{Address as EthAddress, Bytes, TxHash, B256, U256}; +use alloy_sol_types::{sol, SolCall}; +use hex; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use thiserror::Error; + +// Define UserOperation struct for ERC-4337 +sol! { + #[derive(Debug, Default, PartialEq, Eq)] + struct UserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + uint256 callGasLimit; + uint256 verificationGasLimit; + uint256 preVerificationGas; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + bytes paymasterAndData; + bytes signature; + } + + // v0.8 UserOperation struct with packed fields + #[derive(Debug, Default, PartialEq, Eq)] + struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; // packed verificationGasLimit (16 bytes) and callGasLimit (16 bytes) + uint256 preVerificationGas; + bytes32 gasFees; // packed maxPriorityFeePerGas (16 bytes) and maxFeePerGas (16 bytes) + bytes paymasterAndData; + bytes signature; + } +} + +// Define contract interfaces +pub mod contracts { + use alloy_sol_types::sol; + + sol! { + interface IERC20 { + function balanceOf(address who) external view returns (uint256); + function decimals() external view returns (uint8); + function symbol() external view returns (string); + function name() external view returns (string); + function totalSupply() external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + } + + interface IERC721 { + function balanceOf(address owner) external view returns (uint256); + function ownerOf(uint256 tokenId) external view returns (address); + function isApprovedForAll(address owner, address operator) external view returns (bool); + function safeTransferFrom(address from, address to, uint256 tokenId) external; + function setApprovalForAll(address operator, bool approved) external; + } + + interface IERC1155 { + function balanceOf(address account, uint256 id) external view returns (uint256); + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory); + function isApprovedForAll(address account, address operator) external view returns (bool); + function setApprovalForAll(address operator, bool approved) external; + function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; + function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external; + } + + interface IERC6551Account { + function execute(address to, uint256 value, bytes calldata data, uint8 operation) external payable returns (bytes memory); + function execute(address to, uint256 value, bytes calldata data, uint8 operation, uint256 txGas) external payable returns (bytes memory); + function isValidSigner(address signer, bytes calldata data) external view returns (bytes4 magicValue); + function token() external view returns (uint256 chainId, address tokenContract, uint256 tokenId); + function setSignerDataKey(bytes32 signerDataKey) external; + } + + // ERC-4337 EntryPoint Interface + interface IEntryPoint { + function handleOps(bytes calldata packedOps, address payable beneficiary) external; + function getUserOpHash(bytes calldata packedUserOp) external view returns (bytes32); + function getNonce(address sender, uint192 key) external view returns (uint256); + } + + // ERC-4337 Account Interface + interface IAccount { + function validateUserOp( + bytes calldata packedUserOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); + } + + // ERC-4337 Paymaster Interface + interface IPaymaster { + enum PostOpMode { + opSucceeded, + opReverted, + postOpReverted + } + + function validatePaymasterUserOp( + bytes calldata packedUserOp, + bytes32 userOpHash, + uint256 maxCost + ) external returns (bytes memory context, uint256 validationData); + + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost + ) external; + } + } +} + +#[derive(Debug, Error)] +pub enum WalletError { + #[error("signing error: {0}")] + SignerError(#[from] SignerError), + + #[error("ethereum error: {0}")] + EthError(#[from] EthError), + + #[error("name resolution error: {0}")] + NameResolutionError(String), + + #[error("invalid amount: {0}")] + InvalidAmount(String), + + #[error("transaction error: {0}")] + TransactionError(String), + + #[error("gas estimation error: {0}")] + GasEstimationError(String), + + #[error("insufficient funds: {0}")] + InsufficientFunds(String), + + #[error("network congestion: {0}")] + NetworkCongestion(String), + + #[error("transaction underpriced")] + TransactionUnderpriced, + + #[error("transaction nonce too low")] + TransactionNonceTooLow, + + #[error("permission denied: {0}")] + PermissionDenied(String), +} + +/// Represents the storage state of a wallet's private key +#[derive(Debug, Clone)] +pub enum KeyStorage { + /// An unencrypted wallet with a signer + Decrypted(LocalSigner), + Encrypted(EncryptedSignerData), +} + +// Manual implementation of Serialize for KeyStorage +impl Serialize for KeyStorage { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + match self { + KeyStorage::Decrypted(signer) => { + let mut state = serializer.serialize_struct("KeyStorage", 2)?; + state.serialize_field("type", "Decrypted")?; + state.serialize_field("signer", signer)?; + state.end() + } + KeyStorage::Encrypted(data) => { + let mut state = serializer.serialize_struct("KeyStorage", 2)?; + state.serialize_field("type", "Encrypted")?; + state.serialize_field("data", data)?; + state.end() + } + } + } +} + +// Manual implementation of Deserialize for KeyStorage +impl<'de> Deserialize<'de> for KeyStorage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(tag = "type")] + enum KeyStorageData { + #[serde(rename = "Decrypted")] + Decrypted { signer: LocalSigner }, + + #[serde(rename = "Encrypted")] + Encrypted { data: EncryptedSignerData }, + } + + let data = KeyStorageData::deserialize(deserializer)?; + + match data { + KeyStorageData::Decrypted { signer } => Ok(KeyStorage::Decrypted(signer)), + KeyStorageData::Encrypted { data } => Ok(KeyStorage::Encrypted(data)), + } + } +} + +impl KeyStorage { + /// Get the encrypted data if this is an encrypted key storage + pub fn get_encrypted_data(&self) -> Option> { + match self { + KeyStorage::Encrypted(data) => Some(data.encrypted_data.clone()), + KeyStorage::Decrypted(_) => None, + } + } + + /// Get the address associated with this wallet + pub fn get_address(&self) -> String { + match self { + KeyStorage::Decrypted(signer) => signer.address().to_string(), + KeyStorage::Encrypted(data) => data.address.clone(), + } + } + + /// Get the chain ID associated with this wallet + pub fn get_chain_id(&self) -> u64 { + match self { + KeyStorage::Decrypted(signer) => signer.chain_id(), + KeyStorage::Encrypted(data) => data.chain_id, + } + } +} + +/// Represents an amount of ETH with proper formatting +#[derive(Debug, Clone)] +pub struct EthAmount { + /// Value in wei + wei_value: U256, +} + +impl EthAmount { + /// Create a new amount from ETH value + pub fn from_eth(eth_value: f64) -> Self { + // Convert ETH to wei (1 ETH = 10^18 wei) + let wei = (eth_value * 1_000_000_000_000_000_000.0) as u128; + Self { + wei_value: U256::from(wei), + } + } + + /// Create from a string like "0.1 ETH" or "10 wei" + pub fn from_string(amount_str: &str) -> Result { + let parts: Vec<&str> = amount_str.trim().split_whitespace().collect(); + + if parts.is_empty() { + return Err(WalletError::InvalidAmount( + "Empty amount string".to_string(), + )); + } + + let value_str = parts[0]; + let unit = parts + .get(1) + .map(|s| s.to_lowercase()) + .unwrap_or_else(|| "eth".to_string()); + + let value = value_str.parse::().map_err(|_| { + WalletError::InvalidAmount(format!("Invalid numeric value: {}", value_str)) + })?; + + match unit.as_str() { + "eth" => Ok(Self::from_eth(value)), + "wei" => Ok(Self { + wei_value: U256::from(value as u128), + }), + _ => Err(WalletError::InvalidAmount(format!( + "Unknown unit: {}", + unit + ))), + } + } + + /// Get the value in wei + pub fn as_wei(&self) -> U256 { + self.wei_value + } + + /// Get a human-readable string representation + pub fn to_string(&self) -> String { + // Just return the numerical value without denomination + if self.wei_value >= U256::from(100_000_000_000_000u128) { + // Convert to u128 first (safe since ETH total supply fits in u128) then to f64 + let wei_u128 = self.wei_value.to::(); + let eth_value = wei_u128 as f64 / 1_000_000_000_000_000_000.0; + format!("{:.6}", eth_value) + } else { + format!("{}", self.wei_value) + } + } + + /// Get a formatted string with denomination (for display purposes) + pub fn to_display_string(&self) -> String { + // For values over 0.0001 ETH, show in ETH, otherwise in wei + if self.wei_value >= U256::from(100_000_000_000_000u128) { + // Convert to u128 first (safe since ETH total supply fits in u128) then to f64 + let wei_u128 = self.wei_value.to::(); + let eth_value = wei_u128 as f64 / 1_000_000_000_000_000_000.0; + format!("{:.6} ETH", eth_value) + } else { + format!("{} wei", self.wei_value) + } + } +} + +/// Transaction receipt returned after sending +#[derive(Debug, Clone)] +pub struct TxReceipt { + /// Transaction hash + pub hash: TxHash, + /// Transaction details + pub details: String, +} + +/// Result type for Hypermap transactions +#[derive(Debug, Clone)] +pub struct HypermapTxReceipt { + /// Transaction hash + pub hash: TxHash, + /// Description of the operation + pub description: String, +} + +// +// HELPER FUNCTIONS +// + +/// Helper for making contract view function calls +fn call_view_function( + contract: EthAddress, + call: T, + provider: &Provider, +) -> Result { + let call_data = call.abi_encode(); + let tx = TransactionRequest { + to: Some(TxKind::Call(contract)), + input: call_data.into(), + ..Default::default() + }; + + let result = provider.call(tx, None)?; + + if result.is_empty() { + return Err(WalletError::TransactionError( + "Empty result from call".into(), + )); + } + + match T::abi_decode_returns(&result, true) { + Ok(decoded) => Ok(decoded), + Err(e) => Err(WalletError::TransactionError(format!( + "Failed to decode result: {}", + e + ))), + } +} + +/// Calculate gas parameters based on network type +fn calculate_gas_params(provider: &Provider, chain_id: u64) -> Result<(u128, u128), WalletError> { + match chain_id { + 1 => { + // Mainnet: 50% buffer and 1.5 gwei priority fee + let latest_block = provider + .get_block_by_number(BlockNumberOrTag::Latest, false)? + .ok_or_else(|| { + WalletError::TransactionError("Failed to get latest block".into()) + })?; + + let base_fee = latest_block + .header + .inner + .base_fee_per_gas + .ok_or_else(|| WalletError::TransactionError("No base fee in block".into()))? + as u128; + + Ok((base_fee + (base_fee / 2), 1_500_000_000u128)) + } + 8453 => { + // Base + let latest_block = provider + .get_block_by_number(BlockNumberOrTag::Latest, false)? + .ok_or_else(|| { + WalletError::TransactionError("Failed to get latest block".into()) + })?; + + let base_fee = latest_block + .header + .inner + .base_fee_per_gas + .ok_or_else(|| WalletError::TransactionError("No base fee in block".into()))? + as u128; + + let max_fee = base_fee + (base_fee / 3); + + let min_priority_fee = 100_000u128; + + let max_priority_fee = max_fee / 2; + + let priority_fee = std::cmp::max( + min_priority_fee, + std::cmp::min(base_fee / 10, max_priority_fee), + ); + + Ok((max_fee, priority_fee)) + } + 10 => { + // Optimism: 25% buffer and 0.3 gwei priority fee + let latest_block = provider + .get_block_by_number(BlockNumberOrTag::Latest, false)? + .ok_or_else(|| { + WalletError::TransactionError("Failed to get latest block".into()) + })?; + + let base_fee = latest_block + .header + .inner + .base_fee_per_gas + .ok_or_else(|| WalletError::TransactionError("No base fee in block".into()))? + as u128; + + Ok((base_fee + (base_fee / 4), 300_000_000u128)) + } + 31337 | 1337 => { + // Test networks + Ok((2_000_000_000, 100_000_000)) + } + _ => { + // Default: 30% buffer + let base_fee = provider.get_gas_price()?.to::(); + let adjusted_fee = (base_fee * 130) / 100; + Ok((adjusted_fee, adjusted_fee / 10)) + } + } +} + +/// Prepare and send a transaction with common parameters +fn prepare_and_send_tx( + to: EthAddress, + call_data: Vec, + value: U256, + provider: &Provider, + signer: &S, + gas_limit: Option, + format_receipt: F, +) -> Result +where + F: FnOnce(TxHash) -> String, +{ + // Get the current nonce for the signer's address + let signer_address = signer.address(); + let nonce = provider + .get_transaction_count(signer_address, None)? + .to::(); + + // Calculate gas parameters based on chain ID + let (gas_price, priority_fee) = calculate_gas_params(provider, signer.chain_id())?; + + // Use provided gas limit or estimate it with 20% buffer + let gas_limit = match gas_limit { + Some(limit) => limit, + None => { + let tx_req = TransactionRequest { + from: Some(signer_address), + to: Some(TxKind::Call(to)), + input: call_data.clone().into(), + ..Default::default() + }; + + match provider.estimate_gas(tx_req, None) { + Ok(gas) => { + let limit = (gas.to::() * 120) / 100; // Add 20% buffer + limit + } + Err(_) => { + 100_000 // Default value if estimation fails + } + } + } + }; + + // Prepare transaction data + let tx_data = TransactionData { + to, + value, + data: Some(call_data), + nonce, + gas_limit, + gas_price, + max_priority_fee: Some(priority_fee), + chain_id: signer.chain_id(), + }; + + // Sign and send transaction + let signed_tx = signer.sign_transaction(&tx_data)?; + + let tx_hash = provider.send_raw_transaction(signed_tx.into())?; + + kiprintln!("PL:: Transaction sent with hash: {}", tx_hash); + + // Return the receipt with formatted details + Ok(TxReceipt { + hash: tx_hash, + details: format_receipt(tx_hash), + }) +} + +/// Helper for creating Hypermap transaction operations +fn create_hypermap_tx( + parent_entry: &str, + hypermap_call_data: Bytes, + description_fn: F, + provider: Provider, + signer: &S, +) -> Result +where + F: FnOnce() -> String, +{ + // Get the parent TBA address and verify ownership + let hypermap = provider.hypermap(); + let parent_hash_str = namehash(parent_entry); + let (tba, owner, _) = hypermap.get_hash(&parent_hash_str)?; + + // Check that the signer is the owner of the parent entry + let signer_address = signer.address(); + if signer_address != owner { + return Err(WalletError::PermissionDenied(format!( + "Signer address {} does not own the entry {}", + signer_address, parent_entry + ))); + } + + // Create the ERC-6551 execute call + let execute_call = contracts::IERC6551Account::execute_0Call { + to: *hypermap.address(), + value: U256::ZERO, + data: hypermap_call_data, + operation: 0, // CALL operation + }; + + // Format receipt message + let description = description_fn(); + let format_receipt = move |_| description.clone(); + + // For ERC-6551 operations we need a higher gas limit + let gas_limit = Some(600_000); + + // Send the transaction + let receipt = prepare_and_send_tx( + tba, + execute_call.abi_encode(), + U256::ZERO, + &provider, + signer, + gas_limit, + format_receipt, + )?; + + // Convert to Hypermap receipt + Ok(HypermapTxReceipt { + hash: receipt.hash, + description: receipt.details, + }) +} + +// +// NAME RESOLUTION +// + +// Resolve a .hypr/.os/future tlzs names to an Ethereum address using Hypermap +pub fn resolve_name(name: &str, chain_id: u64) -> Result { + // If it's already an address, just parse it + if name.starts_with("0x") && name.len() == 42 { + return EthAddress::from_str(name).map_err(|_| { + WalletError::NameResolutionError(format!("Invalid address format: {}", name)) + }); + } + + // hardcoded to .hypr for now + let formatted_name = if !name.contains('.') { + format!("{}.hypr", name) + } else { + name.to_string() + }; + + // Use hypermap resolution + let hypermap = hypermap::Hypermap::default(chain_id); + match hypermap.get(&formatted_name) { + Ok((_tba, owner, _)) => Ok(owner), + Err(e) => Err(WalletError::NameResolutionError(format!( + "Failed to resolve name '{}': {}", + name, e + ))), + } +} + +/// Resolve a token symbol to its contract address on the specified chain +pub fn resolve_token_symbol(token: &str, chain_id: u64) -> Result { + // If it's already an address, just parse it + if token.starts_with("0x") && token.len() == 42 { + return EthAddress::from_str(token).map_err(|_| { + WalletError::NameResolutionError(format!("Invalid address format: {}", token)) + }); + } + + // Convert to uppercase for case-insensitive comparison + let token_upper = token.to_uppercase(); + + // Map of known token addresses by chain ID and symbol + match chain_id { + 1 => { // Ethereum Mainnet + match token_upper.as_str() { + "USDC" => EthAddress::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") + .map_err(|_| WalletError::NameResolutionError("Invalid USDC address format".to_string())), + "USDT" => EthAddress::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7") + .map_err(|_| WalletError::NameResolutionError("Invalid USDT address format".to_string())), + "DAI" => EthAddress::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F") + .map_err(|_| WalletError::NameResolutionError("Invalid DAI address format".to_string())), + "WETH" => EthAddress::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + .map_err(|_| WalletError::NameResolutionError("Invalid WETH address format".to_string())), + "WBTC" => EthAddress::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599") + .map_err(|_| WalletError::NameResolutionError("Invalid WBTC address format".to_string())), + _ => Err(WalletError::NameResolutionError( + format!("Token '{}' not recognized on chain ID {}", token, chain_id) + )), + } + }, + 8453 => { // Base + match token_upper.as_str() { + "USDC" => EthAddress::from_str("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913") + .map_err(|_| WalletError::NameResolutionError("Invalid USDC address format".to_string())), + "DAI" => EthAddress::from_str("0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb") + .map_err(|_| WalletError::NameResolutionError("Invalid DAI address format".to_string())), + "WETH" => EthAddress::from_str("0x4200000000000000000000000000000000000006") + .map_err(|_| WalletError::NameResolutionError("Invalid WETH address format".to_string())), + _ => Err(WalletError::NameResolutionError( + format!("Token '{}' not recognized on chain ID {}", token, chain_id) + )), + } + }, + 10 => { // Optimism + match token_upper.as_str() { + "USDC" => EthAddress::from_str("0x7F5c764cBc14f9669B88837ca1490cCa17c31607") + .map_err(|_| WalletError::NameResolutionError("Invalid USDC address format".to_string())), + "DAI" => EthAddress::from_str("0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1") + .map_err(|_| WalletError::NameResolutionError("Invalid DAI address format".to_string())), + "WETH" => EthAddress::from_str("0x4200000000000000000000000000000000000006") + .map_err(|_| WalletError::NameResolutionError("Invalid WETH address format".to_string())), + _ => Err(WalletError::NameResolutionError( + format!("Token '{}' not recognized on chain ID {}", token, chain_id) + )), + } + }, + 137 => { // Polygon + match token_upper.as_str() { + "USDC" => EthAddress::from_str("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174") + .map_err(|_| WalletError::NameResolutionError("Invalid USDC address format".to_string())), + "USDT" => EthAddress::from_str("0xc2132D05D31c914a87C6611C10748AEb04B58e8F") + .map_err(|_| WalletError::NameResolutionError("Invalid USDT address format".to_string())), + "DAI" => EthAddress::from_str("0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063") + .map_err(|_| WalletError::NameResolutionError("Invalid DAI address format".to_string())), + "WETH" => EthAddress::from_str("0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619") + .map_err(|_| WalletError::NameResolutionError("Invalid WETH address format".to_string())), + "WMATIC" => EthAddress::from_str("0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270") + .map_err(|_| WalletError::NameResolutionError("Invalid WMATIC address format".to_string())), + _ => Err(WalletError::NameResolutionError( + format!("Token '{}' not recognized on chain ID {}", token, chain_id) + )), + } + }, + 42161 => { // Arbitrum + match token_upper.as_str() { + "USDC" => EthAddress::from_str("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8") + .map_err(|_| WalletError::NameResolutionError("Invalid USDC address format".to_string())), + "USDT" => EthAddress::from_str("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9") + .map_err(|_| WalletError::NameResolutionError("Invalid USDT address format".to_string())), + "DAI" => EthAddress::from_str("0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1") + .map_err(|_| WalletError::NameResolutionError("Invalid DAI address format".to_string())), + "WETH" => EthAddress::from_str("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") + .map_err(|_| WalletError::NameResolutionError("Invalid WETH address format".to_string())), + _ => Err(WalletError::NameResolutionError( + format!("Token '{}' not recognized on chain ID {}", token, chain_id) + )), + } + }, + 11155111 => { // Sepolia Testnet + match token_upper.as_str() { + // Common tokens on Sepolia testnet + "USDC" => EthAddress::from_str("0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238") + .map_err(|_| WalletError::NameResolutionError("Invalid USDC address format".to_string())), + "USDT" => EthAddress::from_str("0x8267cF9254734C6Eb452a7bb9AAF97B392258b21") + .map_err(|_| WalletError::NameResolutionError("Invalid USDT address format".to_string())), + "DAI" => EthAddress::from_str("0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6") + .map_err(|_| WalletError::NameResolutionError("Invalid DAI address format".to_string())), + "WETH" => EthAddress::from_str("0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9") + .map_err(|_| WalletError::NameResolutionError("Invalid WETH address format".to_string())), + _ => Err(WalletError::NameResolutionError( + format!("Token '{}' not recognized on Sepolia testnet. Please use a contract address.", token) + )), + } + }, + // Test networks + 31337 | 1337 => { + return Err(WalletError::NameResolutionError( + format!("Token symbol resolution not supported on test networks. Please use the full contract address.") + )); + }, + _ => { + return Err(WalletError::NameResolutionError( + format!("Token symbol resolution not supported for chain ID {}", chain_id) + )); + } + } + .map_err(|e| match e { + WalletError::NameResolutionError(msg) => WalletError::NameResolutionError(msg), + _ => WalletError::NameResolutionError( + format!("Invalid address format for token '{}' on chain ID {}", token, chain_id) + ), + }) +} + +// +// ETHEREUM FUNCTIONS +// + +/// Send ETH to an address or name +pub fn send_eth( + to: &str, + amount: EthAmount, + provider: Provider, + signer: &S, +) -> Result { + // Resolve the name to an address + let to_address = resolve_name(to, signer.chain_id())?; + + // Standard gas limit for ETH transfer is always 21000 + let gas_limit = Some(21000); + + // Format receipt message + let amount_str = amount.to_string(); + let format_receipt = move |_tx_hash| format!("Sent {} to {}", amount_str, to); + + // For ETH transfers, we have no call data + let empty_call_data = vec![]; + + // Use the helper function to prepare and send the transaction + prepare_and_send_tx( + to_address, + empty_call_data, + amount.as_wei(), + &provider, + signer, + gas_limit, + format_receipt, + ) +} + +/// Get the ETH balance for an address or name +pub fn get_eth_balance( + address_or_name: &str, + chain_id: u64, + provider: Provider, +) -> Result { + // Resolve name to address + let address = resolve_name(address_or_name, chain_id)?; + + // Query balance + let balance = provider.get_balance(address, None)?; + + // Return formatted amount + Ok(EthAmount { wei_value: balance }) +} + +/// Wait for a transaction to be confirmed +pub fn wait_for_transaction( + tx_hash: TxHash, + provider: Provider, + confirmations: u64, + timeout_secs: u64, +) -> Result { + let start_time = std::time::Instant::now(); + let timeout = std::time::Duration::from_secs(timeout_secs); + + loop { + // Check if we've exceeded the timeout + if start_time.elapsed() > timeout { + return Err(WalletError::TransactionError(format!( + "Transaction confirmation timeout after {} seconds", + timeout_secs + ))); + } + + // Try to get the receipt + if let Ok(Some(receipt)) = provider.get_transaction_receipt(tx_hash) { + // Check if we have enough confirmations + let latest_block = provider.get_block_number()?; + let receipt_block = receipt.block_number.unwrap_or(0) as u64; + + if latest_block >= receipt_block + confirmations { + return Ok(receipt); + } + } + + // Wait a bit before checking again + std::thread::sleep(std::time::Duration::from_secs(2)); + } +} + +// +// ERC-20 TOKEN FUNCTIONS +// + +/// Get the ERC20 token balance of an address or name +/// Returns the balance in token units (adjusted for decimals) +pub fn erc20_balance_of( + token_address: &str, + owner_address: &str, + provider: &Provider, +) -> Result { + // First try to resolve the token as a symbol + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, // Fall back to regular name resolution + }; + + let owner = resolve_name(owner_address, provider.chain_id)?; + + let call = contracts::IERC20::balanceOfCall { who: owner }; + let balance = call_view_function(token, call, provider)?; + + let decimals = erc20_decimals(token_address, provider)?; + let balance_float = balance._0.to::() as f64 / 10f64.powi(decimals as i32); + + Ok(balance_float) +} + +/// Get the number of decimals for an ERC20 token +pub fn erc20_decimals(token_address: &str, provider: &Provider) -> Result { + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, + }; + + let call = contracts::IERC20::decimalsCall {}; + let decimals = call_view_function(token, call, provider)?; + Ok(decimals._0) +} + +/// Get the token symbol for an ERC20 token +pub fn erc20_symbol(token_address: &str, provider: &Provider) -> Result { + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, + }; + + let call = contracts::IERC20::symbolCall {}; + let symbol = call_view_function(token, call, provider)?; + Ok(symbol._0) +} + +/// Get the token name for an ERC20 token +pub fn erc20_name(token_address: &str, provider: &Provider) -> Result { + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, + }; + + let call = contracts::IERC20::nameCall {}; + let name = call_view_function(token, call, provider)?; + Ok(name._0) +} + +/// Get the total supply of an ERC20 token +pub fn erc20_total_supply(token_address: &str, provider: &Provider) -> Result { + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, + }; + + let call = contracts::IERC20::totalSupplyCall {}; + let total_supply = call_view_function(token, call, provider)?; + Ok(total_supply._0) +} + +/// Get the allowance for an ERC20 token +pub fn erc20_allowance( + token_address: &str, + owner_address: &str, + spender_address: &str, + provider: &Provider, +) -> Result { + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, + }; + + let owner = resolve_name(owner_address, provider.chain_id)?; + let spender = resolve_name(spender_address, provider.chain_id)?; + + let call = contracts::IERC20::allowanceCall { owner, spender }; + let allowance = call_view_function(token, call, provider)?; + Ok(allowance._0) +} + +/// Transfer ERC20 tokens to an address or name +pub fn erc20_transfer( + token_address: &str, + to_address: &str, + amount: U256, + provider: &Provider, + signer: &S, +) -> Result { + kiprintln!( + "PL:: Transferring {} tokens to {} on {}", + amount, + to_address, + provider.chain_id + ); + + // Resolve token address (could be a symbol like "USDC") + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, + }; + + kiprintln!("PL:: Resolved token address: {}", token); + + // Resolve recipient address + let to = resolve_name(to_address, provider.chain_id)?; + kiprintln!("PL:: Resolved recipient address: {}", to); + + // Create the call + let call = contracts::IERC20::transferCall { to, value: amount }; + let call_data = call.abi_encode(); + + // Get token details for receipt formatting + let token_symbol = + erc20_symbol(token_address, provider).unwrap_or_else(|_| "tokens".to_string()); + let token_decimals = erc20_decimals(token_address, provider).unwrap_or(18); + + // Format receipt message + let format_receipt = move |_| { + let amount_float = amount.to::() as f64 / 10f64.powi(token_decimals as i32); + format!( + "Transferred {:.6} {} to {}", + amount_float, token_symbol, to_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + Some(100_000), // Default gas limit for ERC20 transfers + format_receipt, + ) +} + +/// Approve an address to spend ERC20 tokens +pub fn erc20_approve( + token_address: &str, + spender_address: &str, + amount: U256, + provider: &Provider, + signer: &S, +) -> Result { + // Resolve addresses + let token = resolve_name(token_address, provider.chain_id)?; + let spender = resolve_name(spender_address, provider.chain_id)?; + + // Create the call + let call = contracts::IERC20::approveCall { + spender, + value: amount, + }; + let call_data = call.abi_encode(); + + // Get token details for receipt formatting + let token_symbol = + erc20_symbol(token_address, provider).unwrap_or_else(|_| "tokens".to_string()); + let token_decimals = erc20_decimals(token_address, provider).unwrap_or(18); + + // Format receipt message + let format_receipt = move |_| { + let amount_float = amount.to::() as f64 / 10f64.powi(token_decimals as i32); + format!( + "Approved {:.6} {} to be spent by {}", + amount_float, token_symbol, spender_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + Some(60_000), // Default gas limit for approvals + format_receipt, + ) +} + +/// Transfer ERC20 tokens from one address to another (requires approval) +pub fn erc20_transfer_from( + token_address: &str, + from_address: &str, + to_address: &str, + amount: U256, + provider: &Provider, + signer: &S, +) -> Result { + // Resolve addresses + let token = resolve_name(token_address, provider.chain_id)?; + let from = resolve_name(from_address, provider.chain_id)?; + let to = resolve_name(to_address, provider.chain_id)?; + + // Create the call + let call = contracts::IERC20::transferFromCall { + from, + to, + value: amount, + }; + let call_data = call.abi_encode(); + + // Get token details for receipt formatting + let token_symbol = + erc20_symbol(token_address, provider).unwrap_or_else(|_| "tokens".to_string()); + let token_decimals = erc20_decimals(token_address, provider).unwrap_or(18); + + // Format receipt message + let format_receipt = move |_| { + let amount_float = amount.to::() as f64 / 10f64.powi(token_decimals as i32); + format!( + "Transferred {:.6} {} from {} to {}", + amount_float, token_symbol, from_address, to_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + Some(100_000), // Default gas limit + format_receipt, + ) +} + +// +// ERC-721 NFT FUNCTIONS +// + +/// Get the NFT balance of an address +pub fn erc721_balance_of( + token_address: &str, + owner_address: &str, + provider: &Provider, +) -> Result { + let token = resolve_name(token_address, provider.chain_id)?; + let owner = resolve_name(owner_address, provider.chain_id)?; + + let call = contracts::IERC721::balanceOfCall { owner }; + let balance = call_view_function(token, call, provider)?; + Ok(balance._0) +} + +/// Get the owner of an NFT token +pub fn erc721_owner_of( + token_address: &str, + token_id: U256, + provider: &Provider, +) -> Result { + let token = resolve_name(token_address, provider.chain_id)?; + let call = contracts::IERC721::ownerOfCall { tokenId: token_id }; + let owner = call_view_function(token, call, provider)?; + Ok(owner._0) +} + +/// Check if an operator is approved for all NFTs of an owner +pub fn erc721_is_approved_for_all( + token_address: &str, + owner_address: &str, + operator_address: &str, + provider: &Provider, +) -> Result { + let token = resolve_name(token_address, provider.chain_id)?; + let owner = resolve_name(owner_address, provider.chain_id)?; + let operator = resolve_name(operator_address, provider.chain_id)?; + + let call = contracts::IERC721::isApprovedForAllCall { owner, operator }; + let is_approved = call_view_function(token, call, provider)?; + Ok(is_approved._0) +} + +/// Safely transfer an NFT +pub fn erc721_safe_transfer_from( + token_address: &str, + from_address: &str, + to_address: &str, + token_id: U256, + provider: &Provider, + signer: &S, +) -> Result { + // Resolve addresses + let token = resolve_name(token_address, provider.chain_id)?; + let from = resolve_name(from_address, provider.chain_id)?; + let to = resolve_name(to_address, provider.chain_id)?; + + // Create the call + let call = contracts::IERC721::safeTransferFromCall { + from, + to, + tokenId: token_id, + }; + let call_data = call.abi_encode(); + + // Format receipt message + let format_receipt = move |_| { + format!( + "Safely transferred NFT ID {} from {} to {}", + token_id, from_address, to_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + Some(200_000), // Higher gas limit for NFT transfers + format_receipt, + ) +} + +/// Set approval for all tokens to an operator +pub fn erc721_set_approval_for_all( + token_address: &str, + operator_address: &str, + approved: bool, + provider: &Provider, + signer: &S, +) -> Result { + // Resolve addresses + let token = resolve_name(token_address, provider.chain_id)?; + let operator = resolve_name(operator_address, provider.chain_id)?; + + // Create the call + let call = contracts::IERC721::setApprovalForAllCall { operator, approved }; + let call_data = call.abi_encode(); + + // Format receipt message + let format_receipt = move |_| { + format!( + "{} operator {} to manage all of your NFTs in contract {}", + if approved { + "Approved" + } else { + "Revoked approval for" + }, + operator_address, + token_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + Some(60_000), // Default gas limit for approvals + format_receipt, + ) +} + +// +// ERC-1155 MULTI-TOKEN FUNCTIONS +// + +/// Get the balance of a specific token ID for an account +pub fn erc1155_balance_of( + token_address: &str, + account_address: &str, + token_id: U256, + provider: &Provider, +) -> Result { + let token = resolve_name(token_address, provider.chain_id)?; + let account = resolve_name(account_address, provider.chain_id)?; + + let call = contracts::IERC1155::balanceOfCall { + account, + id: token_id, + }; + let balance = call_view_function(token, call, provider)?; + Ok(balance._0) +} + +/// Get balances for multiple accounts and token IDs +pub fn erc1155_balance_of_batch( + token_address: &str, + account_addresses: Vec<&str>, + token_ids: Vec, + provider: &Provider, +) -> Result, WalletError> { + // Check that arrays are same length + if account_addresses.len() != token_ids.len() { + return Err(WalletError::TransactionError( + "Arrays of accounts and token IDs must have the same length".into(), + )); + } + + // Resolve token address + let token = resolve_name(token_address, provider.chain_id)?; + + // Resolve all account addresses + let mut accounts = Vec::with_capacity(account_addresses.len()); + for addr in account_addresses { + accounts.push(resolve_name(addr, provider.chain_id)?); + } + + let call = contracts::IERC1155::balanceOfBatchCall { + accounts, + ids: token_ids, + }; + let balances = call_view_function(token, call, provider)?; + Ok(balances._0) +} + +/// Check if an operator is approved for all tokens of an account +pub fn erc1155_is_approved_for_all( + token_address: &str, + account_address: &str, + operator_address: &str, + provider: &Provider, +) -> Result { + let token = resolve_name(token_address, provider.chain_id)?; + let account = resolve_name(account_address, provider.chain_id)?; + let operator = resolve_name(operator_address, provider.chain_id)?; + + let call = contracts::IERC1155::isApprovedForAllCall { account, operator }; + let is_approved = call_view_function(token, call, provider)?; + Ok(is_approved._0) +} + +/// Set approval for all tokens to an operator +pub fn erc1155_set_approval_for_all( + token_address: &str, + operator_address: &str, + approved: bool, + provider: &Provider, + signer: &S, +) -> Result { + // Resolve addresses + let token = resolve_name(token_address, provider.chain_id)?; + let operator = resolve_name(operator_address, provider.chain_id)?; + + // Create the call + let call = contracts::IERC1155::setApprovalForAllCall { operator, approved }; + let call_data = call.abi_encode(); + + // Format receipt message + let format_receipt = move |_| { + format!( + "{} operator {} to manage all of your ERC1155 tokens in contract {}", + if approved { + "Approved" + } else { + "Revoked approval for" + }, + operator_address, + token_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + Some(60_000), // Default gas limit for approvals + format_receipt, + ) +} + +/// Transfer a single ERC1155 token +pub fn erc1155_safe_transfer_from( + token_address: &str, + from_address: &str, + to_address: &str, + token_id: U256, + amount: U256, + data: Vec, + provider: &Provider, + signer: &S, +) -> Result { + // Resolve addresses + let token = resolve_name(token_address, provider.chain_id)?; + let from = resolve_name(from_address, provider.chain_id)?; + let to = resolve_name(to_address, provider.chain_id)?; + + // Create the call + let call = contracts::IERC1155::safeTransferFromCall { + from, + to, + id: token_id, + amount, + data: Bytes::from(data), + }; + let call_data = call.abi_encode(); + + // Format receipt message + let format_receipt = move |_| { + format!( + "Transferred {} of token ID {} from {} to {}", + amount, token_id, from_address, to_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + Some(150_000), // Default gas limit for ERC1155 transfers + format_receipt, + ) +} + +/// Batch transfer multiple ERC1155 tokens +pub fn erc1155_safe_batch_transfer_from( + token_address: &str, + from_address: &str, + to_address: &str, + token_ids: Vec, + amounts: Vec, + data: Vec, + provider: &Provider, + signer: &S, +) -> Result { + // Check that arrays are same length + if token_ids.len() != amounts.len() { + return Err(WalletError::TransactionError( + "Arrays of token IDs and amounts must have the same length".into(), + )); + } + + // Resolve addresses + let token = resolve_name(token_address, provider.chain_id)?; + let from = resolve_name(from_address, provider.chain_id)?; + let to = resolve_name(to_address, provider.chain_id)?; + + // Create the call + let call = contracts::IERC1155::safeBatchTransferFromCall { + from, + to, + ids: token_ids.clone(), + amounts: amounts.clone(), + data: Bytes::from(data), + }; + let call_data = call.abi_encode(); + + // For batch transfers, gas estimation is tricky - use a formula that scales with token count + let token_count = token_ids.len(); + // Base gas (200,000) + extra per token (50,000 each) + let default_gas = Some(200_000 + (token_count as u64 * 50_000)); + + // Format receipt message + let format_receipt = move |_| { + format!( + "Batch transferred {} token types from {} to {}", + token_count, from_address, to_address + ) + }; + + // Prepare and send transaction + prepare_and_send_tx( + token, + call_data, + U256::ZERO, + provider, + signer, + default_gas, + format_receipt, + ) +} + +// +// HYPERMAP FUNCTIONS +// + +/// Create a note (mutable data) on a Hypermap namespace entry +pub fn create_note( + parent_entry: &str, + note_key: &str, + data: Vec, + provider: Provider, + signer: &S, +) -> Result { + // Verify the note key is valid + if !valid_note(note_key) { + return Err(WalletError::NameResolutionError( + format!("Invalid note key '{}'. Must start with '~' and contain only lowercase letters, numbers, and hyphens", note_key) + )); + } + + // Create the note function call data + let note_function = hypermap::contract::noteCall { + note: Bytes::from(note_key.as_bytes().to_vec()), + data: Bytes::from(data), + }; + + // Create the hypermap transaction + create_hypermap_tx( + parent_entry, + Bytes::from(note_function.abi_encode()), + || format!("Created note '{}' on '{}'", note_key, parent_entry), + provider, + signer, + ) +} + +/// Create a fact (immutable data) on a Hypermap namespace entry +pub fn create_fact( + parent_entry: &str, + fact_key: &str, + data: Vec, + provider: Provider, + signer: &S, +) -> Result { + // Verify the fact key is valid + if !valid_fact(fact_key) { + return Err(WalletError::NameResolutionError( + format!("Invalid fact key '{}'. Must start with '!' and contain only lowercase letters, numbers, and hyphens", fact_key) + )); + } + + // Create the fact function call data + let fact_function = hypermap::contract::factCall { + fact: Bytes::from(fact_key.as_bytes().to_vec()), + data: Bytes::from(data), + }; + + // Create the hypermap transaction + create_hypermap_tx( + parent_entry, + Bytes::from(fact_function.abi_encode()), + || format!("Created fact '{}' on '{}'", fact_key, parent_entry), + provider, + signer, + ) +} + +/// Mint a new namespace entry under a parent entry +pub fn mint_entry( + parent_entry: &str, + label: &str, + recipient: &str, + implementation: &str, + provider: Provider, + signer: &S, +) -> Result { + // Verify the label is valid + if !valid_name(label) { + return Err(WalletError::NameResolutionError(format!( + "Invalid label '{}'. Must contain only lowercase letters, numbers, and hyphens", + label + ))); + } + + // Resolve addresses + let recipient_address = resolve_name(recipient, provider.chain_id)?; + let implementation_address = resolve_name(implementation, provider.chain_id)?; + + // Create the mint function call data + let mint_function = hypermap::contract::mintCall { + who: recipient_address, + label: Bytes::from(label.as_bytes().to_vec()), + initialization: Bytes::default(), // No initialization data + erc721Data: Bytes::default(), // No ERC721 data + implementation: implementation_address, + }; + + // Create the hypermap transaction + create_hypermap_tx( + parent_entry, + Bytes::from(mint_function.abi_encode()), + || format!("Minted new entry '{}' under '{}'", label, parent_entry), + provider, + signer, + ) +} + +/// Set the gene for a namespace entry +pub fn set_gene( + entry: &str, + gene_implementation: &str, + provider: Provider, + signer: &S, +) -> Result { + // Resolve gene implementation address + let gene_address = resolve_name(gene_implementation, provider.chain_id)?; + + // Create the gene function call data + let gene_function = hypermap::contract::geneCall { + _gene: gene_address, + }; + + // Create the hypermap transaction + create_hypermap_tx( + entry, + Bytes::from(gene_function.abi_encode()), + || format!("Set gene for '{}' to '{}'", entry, gene_implementation), + provider, + signer, + ) +} + +// +// TOKEN BOUND ACCOUNT (ERC-6551) FUNCTIONS +// + +/// Executes a call through a Token Bound Account (TBA) using a specific signer. +/// This function is designed for scenarios where the signer might be an authorized delegate +/// (e.g., via Hypermap notes like ~access-list) rather than the direct owner of the underlying NFT. +/// The TBA's own `execute` implementation is responsible for verifying the signer's authorization. +pub fn execute_via_tba_with_signer( + tba_address_or_name: &str, + hot_wallet_signer: &S, + target_address_or_name: &str, + call_data: Vec, + value: U256, + provider: &Provider, + operation: Option, +) -> Result { + // Resolve addresses + let tba = resolve_name(tba_address_or_name, provider.chain_id)?; + let target = resolve_name(target_address_or_name, provider.chain_id)?; + let op = operation.unwrap_or(0); // Default to CALL operation + + kiprintln!( + "PL:: Executing via TBA {} (signer: {}) -> Target {} (Op: {}, Value: {})", + tba, + hot_wallet_signer.address(), + target, + op, + value + ); + + // Create the outer execute call (with txGas) directed at the TBA + // Use the _1 suffix for the second defined execute function (5 args) + let internal_gas_limit = U256::from(500_000); // Explicitly set gas for the internal call + let execute_call = contracts::IERC6551Account::execute_1Call { + // <-- Using _1 suffix now + to: target, + value, // This value is sent from the TBA to the target + data: Bytes::from(call_data), + operation: op, + txGas: internal_gas_limit, // Provide gas for the internal call + }; + let execute_call_data = execute_call.abi_encode(); + + // Format receipt message + let format_receipt = move |_| { + format!( + "Execute via TBA {} to target {} (Signer: {}, Internal Gas: {})", // Updated log + tba_address_or_name, + target_address_or_name, + hot_wallet_signer.address(), + internal_gas_limit // Log internal gas + ) + }; + + // Prepare and send the transaction *to* the TBA, signed by the hot_wallet_signer. + // The `value` field in the *outer* transaction is U256::ZERO because the ETH transfer + // happens *inside* the TBA's execution context, funded by the TBA itself. + // prepare_and_send_tx will handle gas estimation for the outer transaction. + prepare_and_send_tx( + tba, // Transaction is sent TO the TBA address + execute_call_data, // Data is the ABI-encoded `execute` call with internal gas limit + U256::ZERO, // Outer transaction sends no ETH directly to the TBA + provider, + hot_wallet_signer, // Signed by the provided (potentially delegated) signer + None, // Let prepare_and_send_tx estimate outer gas limit + format_receipt, + ) +} + +/// Executes a call through a Token Bound Account (TBA) +/// Assumes the provided signer is the owner/controller authorized by the standard ERC6551 implementation. +pub fn tba_execute( + tba_address: &str, + target_address: &str, + call_data: Vec, + value: U256, + operation: u8, // 0 for CALL, 1 for DELEGATECALL, etc. + provider: &Provider, + signer: &S, +) -> Result { + // Resolve addresses + let tba = resolve_name(tba_address, provider.chain_id)?; + let target = resolve_name(target_address, provider.chain_id)?; + + // Create the execute call + let execute_call = contracts::IERC6551Account::execute_0Call { + to: target, + value, + data: Bytes::from(call_data), + operation, + }; + + // Format receipt message + let format_receipt = move |_| { + format!( + "Executed call via TBA {} to target {}", + tba_address, target_address + ) + }; + + // Prepare and send the transaction - Use a higher default gas limit for TBA executions + prepare_and_send_tx( + tba, + execute_call.abi_encode(), + value, // Pass the value intended for the target call + provider, + signer, + Some(500_000), // Higher gas limit for potential complex TBA logic + target call + format_receipt, + ) +} + +/// Checks if an address is a valid signer for a given TBA. +/// This checks against the standard ERC-6551 `isValidSigner` which returns a magic value. +pub fn tba_is_valid_signer( + tba_address: &str, + signer_address: &str, + provider: &Provider, +) -> Result { + // Resolve addresses + let tba = resolve_name(tba_address, provider.chain_id)?; + let signer_addr = resolve_name(signer_address, provider.chain_id)?; + + // Create the isValidSigner call + let call = contracts::IERC6551Account::isValidSignerCall { + signer: signer_addr, + data: Bytes::default(), // No extra data needed for standard check + }; + let call_data = call.abi_encode(); + + // Perform the raw eth_call + let tx = TransactionRequest { + to: Some(TxKind::Call(tba)), + input: call_data.into(), + ..Default::default() + }; + let result_bytes = provider.call(tx, None)?; + + // Expected magic value for valid signer (IERC6551Account.isValidSigner.selector) + const ERC6551_IS_VALID_SIGNER: [u8; 4] = [0x52, 0x3e, 0x32, 0x60]; + + // Check if the returned bytes match the magic value + Ok(result_bytes.len() >= 4 && result_bytes[..4] == ERC6551_IS_VALID_SIGNER) +} + +/// Retrieves the token information (chainId, contract address, tokenId) associated with a TBA. +pub fn tba_get_token_info( + tba_address: &str, + provider: &Provider, +) -> Result<(u64, EthAddress, U256), WalletError> { + // Resolve TBA address + let tba = resolve_name(tba_address, provider.chain_id)?; + + // Create the token() call + let call = contracts::IERC6551Account::tokenCall {}; + + // Use the view function helper + let result = call_view_function(tba, call, provider)?; + + // Use named fields from the generated struct + let chain_id_u256 = result.chainId; + let token_contract = result.tokenContract; + let token_id = result.tokenId; + + // Convert chainId U256 to u64 (potential truncation, but unlikely for real chain IDs) + let chain_id = chain_id_u256.to::(); + + Ok((chain_id, token_contract, token_id)) +} + +/// Sets the signer data key for a HypermapSignerAccount TBA. +/// This links the TBA's alternative signer validation mechanism to a specific Hypermap note hash. +pub fn tba_set_signer_data_key( + tba_address: &str, + data_key_hash: B256, // Use B256 for bytes32 + provider: &Provider, + signer: &S, // Must be called by the current owner/controller of the TBA +) -> Result { + // Resolve TBA address + let tba = resolve_name(tba_address, provider.chain_id)?; + + // Create the setSignerDataKey call + let set_key_call = contracts::IERC6551Account::setSignerDataKeyCall { + signerDataKey: data_key_hash, + }; + + // Format receipt message + let format_receipt = move |_| { + format!( + "Set signer data key for TBA {} to hash {}", + tba_address, data_key_hash + ) + }; + + // Prepare and send the transaction + prepare_and_send_tx( + tba, + set_key_call.abi_encode(), + U256::ZERO, + provider, + signer, + Some(100_000), // Gas limit for a simple storage set + format_receipt, + ) +} + +// +// HYPERMAP + TBA HELPER FUNCTIONS +// + +/// Creates a note (~allowed-signer) on a Hypermap entry containing an alternative signer's address, +/// and then configures the entry's TBA to use this note for alternative signature validation. +/// Requires the signature of the *owner* of the Hypermap entry. +pub fn setup_alternative_signer( + entry_name: &str, // e.g., "username.hypr" + alt_signer_address: EthAddress, // The address allowed to sign alternatively + provider: &Provider, + owner_signer: &S, // Signer holding the key that owns 'entry_name' +) -> Result { + // 1. Calculate the Hypermap entry hash + let entry_hash = hypermap::namehash(entry_name); + + // 2. Get the TBA associated with this entry and verify ownership + let hypermap_contract = provider.hypermap(); + let (tba, owner, _) = hypermap_contract.get_hash(&entry_hash)?; + + // 3. Check if the provided signer is the owner + if owner_signer.address() != owner { + return Err(WalletError::PermissionDenied(format!( + "Signer {} does not own the entry {}", + owner_signer.address(), + entry_name + ))); + } + + // 4. Define the note key for the allowed signer + let note_key = "~allowed-signer"; + + // 5. Create the note in Hypermap storing the alternative signer's address + // This requires a transaction signed by the owner_signer, executed via the parent TBA (owner's TBA) + kiprintln!( + "PL:: Creating note '{}' on '{}' with data {}", + note_key, + entry_name, + alt_signer_address + ); + let create_note_receipt = create_note( + entry_name, + note_key, + alt_signer_address.to_vec(), // Store address as bytes + provider.clone(), + owner_signer, + )?; + kiprintln!( + "PL:: Create note transaction sent: {}", + create_note_receipt.hash + ); + // Note: We might want to wait for this tx to confirm before proceeding + + // 6. Calculate the namehash of the specific note + // Combine note key and entry name, then hash + let note_full_name = format!("{}.{}", note_key, entry_name); + let note_hash_str = hypermap::namehash(¬e_full_name); + // Convert the String hash to B256 + let note_hash = B256::from_str(¬e_hash_str.trim_start_matches("0x")) + .map_err(|_| WalletError::TransactionError("Failed to parse note hash string".into()))?; + + kiprintln!( + "PL:: Calculated note hash for '{}': {}", + note_full_name, + note_hash + ); + + // 7. Set this note hash as the signer data key in the TBA + // This also requires a transaction signed by the owner_signer, sent directly to the TBA + kiprintln!( + "PL:: Setting signer data key on TBA {} to note hash {}", + tba, + note_hash + ); + let set_key_receipt = tba_set_signer_data_key( + &tba.to_string(), // Use the TBA address resolved earlier + note_hash, + provider, + owner_signer, // Owner signer makes this call directly to the TBA + )?; + kiprintln!( + "PL:: Set signer data key transaction sent: {}", + set_key_receipt.hash + ); + + // Return the receipt of the *second* transaction (setting the key) + // Ideally, we'd return both or confirm the first one succeeded. + Ok(TxReceipt { + hash: set_key_receipt.hash, + details: format!( + "Set up alternative signer {} for entry {} (Note Tx: {}, SetKey Tx: {})", + alt_signer_address, entry_name, create_note_receipt.hash, set_key_receipt.hash + ), + }) +} + +/// Retrieves the alternative signer address stored in the '~allowed-signer' note +/// associated with a given Hypermap entry. +pub fn get_alternative_signer( + entry_name: &str, + provider: &Provider, +) -> Result, WalletError> { + // 1. Calculate the Hypermap entry hash + let _entry_hash = hypermap::namehash(entry_name); + + // 2. Define the note key + let note_key = "~allowed-signer"; + + // 3. Calculate the specific note hash + let hypermap_contract = provider.hypermap(); + let note_full_name = format!("{}.{}", note_key, entry_name); + let note_hash = hypermap::namehash(¬e_full_name); + + // 4. Get the data stored at this specific note hash from Hypermap + match hypermap_contract.get_hash(¬e_hash) { + Ok((_tba, _owner, data_option)) => { + // Handle the Option + match data_option { + Some(data_bytes) => { + // 5. If data is present and exactly 20 bytes long, parse it as an address + if data_bytes.len() == 20 { + Ok(Some(EthAddress::from_slice(data_bytes.as_ref()))) + } else { + // Data exists but is the wrong length - use TransactionError + Err(WalletError::TransactionError(format!( + "Data at note hash {} for entry {} has incorrect length ({}) for an address", + note_hash, + entry_name, + data_bytes.len() + ))) + } + } + None => { + // Note exists (or get_hash succeeded) but has no data + Ok(None) + } + } + } + Err(EthError::RpcError(msg)) => { + // Convert potential JSON error message to string for checking + let msg_str = msg.to_string(); + if msg_str.contains("Execution reverted") || msg_str.contains("invalid opcode") { + // This likely means the note hash doesn't exist or isn't registered + Ok(None) + } else { + // Propagate other RPC errors + Err(WalletError::EthError(EthError::RpcError(msg))) + } + } + Err(e) => Err(WalletError::EthError(e)), // Propagate other errors + } +} + +// TODO: TEST +// ... existing test section ... + +/// Transaction details in a more user-friendly format +#[derive(Debug, Clone)] +pub struct TransactionDetails { + pub hash: TxHash, + pub from: EthAddress, + pub to: Option, + pub value: EthAmount, + pub block_number: Option, + pub timestamp: Option, + pub gas_used: Option, + pub gas_price: Option, + pub success: Option, + pub direction: TransactionDirection, +} + +/// Direction of the transaction relative to the address +#[derive(Debug, Clone, PartialEq)] +pub enum TransactionDirection { + Incoming, + Outgoing, + SelfTransfer, +} + +/// Get transactions for an address - simplified version that works with Alloy +pub fn get_address_transactions( + address_or_name: &str, + provider: &Provider, + max_blocks_back: Option, +) -> Result, WalletError> { + let target_address = resolve_name(address_or_name, provider.chain_id)?; + + // Get block range + let latest_block = provider.get_block_number()?; + let blocks_back = max_blocks_back.unwrap_or(1000); + let start_block = if latest_block > blocks_back { + latest_block - blocks_back + } else { + 0 + }; + + // Create filter to find logs involving our address + let filter = Filter { + block_option: FilterBlockOption::Range { + from_block: Some(start_block.into()), + to_block: Some(latest_block.into()), + }, + address: FilterSet::from(vec![target_address]), + topics: Default::default(), + }; + + // Get logs matching our filter + let logs = provider.get_logs(&filter)?; + kiprintln!( + "Found {} logs involving address {}", + logs.len(), + target_address + ); + + // Extract unique transaction hashes + let mut tx_hashes = Vec::new(); + for log in logs { + if let Some(hash) = log.transaction_hash { + if !tx_hashes.contains(&hash) { + tx_hashes.push(hash); + } + } + } + + // Create transaction details objects for each tx hash + let mut transactions = Vec::new(); + + for tx_hash in tx_hashes { + // For each transaction, create a basic TransactionDetails object + // with just the transaction hash and basic direction + let mut tx_detail = TransactionDetails { + hash: tx_hash, + from: EthAddress::default(), // We'll update this if we can get the transaction + to: None, + value: EthAmount { + wei_value: U256::ZERO, + }, + block_number: None, + timestamp: None, + gas_used: None, + gas_price: None, + success: None, + direction: TransactionDirection::Incoming, // Default, will update if needed + }; + + // Try to get transaction receipt for more details + if let Ok(Some(receipt)) = provider.get_transaction_receipt(tx_hash) { + // Update from receipt fields that we know exist + tx_detail.block_number = receipt.block_number; + + // Get transaction success status if available + let status = receipt.status(); + if status { + tx_detail.success = Some(true); + } else { + tx_detail.success = Some(false); + } + + // Try to get original transaction for more details + if let Ok(Some(tx)) = provider.get_transaction_by_hash(tx_hash) { + // Update from transaction fields + tx_detail.from = tx.from; + + // Set direction based on compared addresses + if tx.from == target_address { + tx_detail.direction = TransactionDirection::Outgoing; + } else { + tx_detail.direction = TransactionDirection::Incoming; + } + + // Try to get block timestamp if we have block number + if let Some(block_num) = tx_detail.block_number { + if let Ok(Some(block)) = + provider.get_block_by_number(BlockNumberOrTag::Number(block_num), false) + { + // Block header timestamp is a u64, not an Option + tx_detail.timestamp = Some(block.header.timestamp); + } + } + } + } + + transactions.push(tx_detail); + } + + // Sort by block number (descending) + transactions.sort_by(|a, b| match (b.block_number, a.block_number) { + (Some(b_num), Some(a_num)) => b_num.cmp(&a_num), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => std::cmp::Ordering::Equal, + }); + + Ok(transactions) +} + +/// Format transaction details for display +pub fn format_transaction_details(tx: &TransactionDetails) -> String { + // Symbol to represent transaction direction + let direction_symbol = match tx.direction { + TransactionDirection::Incoming => "←", + TransactionDirection::Outgoing => "→", + TransactionDirection::SelfTransfer => "↻", + }; + + // Transaction status + let status = match tx.success { + Some(true) => "Succeeded", + Some(false) => "Failed", + None => "Unknown", + }; + + // Format value + let value = tx.value.to_string(); + + // Format timestamp without external dependencies + let timestamp = match tx.timestamp { + Some(ts) => format_timestamp(ts), + None => "Pending".to_string(), + }; + + // Format to and from addresses + let from_addr = format!( + "{:.8}...{}", + tx.from.to_string()[0..10].to_string(), + tx.from.to_string()[34..].to_string() + ); + + let to_addr = tx.to.map_or("Contract Creation".to_string(), |addr| { + format!( + "{:.8}...{}", + addr.to_string()[0..10].to_string(), + addr.to_string()[34..].to_string() + ) + }); + + // Format final output + format!( + "TX: {} [{}]\n {} {} {}\n Block: {}, Status: {}, Value: {}, Time: {}", + tx.hash, + status, + from_addr, + direction_symbol, + to_addr, + tx.block_number + .map_or("Pending".to_string(), |b| b.to_string()), + status, + value, + timestamp + ) +} +/// Format a Unix timestamp without external dependencies +fn format_timestamp(timestamp: u64) -> String { + // Simple timestamp formatting + // We'll use a very basic approach that doesn't rely on date/time libraries + + // Convert to seconds, minutes, hours, days since epoch + let secs = timestamp % 60; + let mins = (timestamp / 60) % 60; + let hours = (timestamp / 3600) % 24; + let days_since_epoch = timestamp / 86400; + + // Very rough estimation - doesn't account for leap years properly + let years_since_epoch = days_since_epoch / 365; + let days_this_year = days_since_epoch % 365; + + // Rough month calculation + let month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let mut month = 0; + let mut day = days_this_year as u32; + + // Adjust for leap years in a very rough way + let is_leap_year = (1970 + years_since_epoch as u32) % 4 == 0; + let month_days = if is_leap_year { + let mut md = month_days.to_vec(); + md[1] = 29; // February has 29 days in leap years + md + } else { + month_days.to_vec() + }; + + // Find month and day + for (i, &days_in_month) in month_days.iter().enumerate() { + if day < days_in_month { + month = i; + break; + } + day -= days_in_month; + } + + // Adjust to 1-based + day += 1; + month += 1; + + // Format the date + format!( + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + 1970 + years_since_epoch, + month, + day, + hours, + mins, + secs + ) +} + +// New structured type to hold all ERC20 token information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TokenDetails { + pub address: String, + pub symbol: String, + pub name: String, + pub decimals: u8, + pub total_supply: String, + pub balance: String, + pub formatted_balance: String, +} + +/// Get all relevant details for an ERC20 token in one call +/// This consolidates multiple calls into a single function for frontend use +pub fn get_token_details( + token_address: &str, + wallet_address: &str, + provider: &Provider, +) -> Result { + // First resolve the token address (could be a symbol or address) + let token = match resolve_token_symbol(token_address, provider.chain_id) { + Ok(addr) => addr, + Err(_) => resolve_name(token_address, provider.chain_id)?, + }; + + // Get basic token information + let token_str = token.to_string(); + let symbol = erc20_symbol(token_address, provider)?; + let name = erc20_name(token_address, provider)?; + let decimals = erc20_decimals(token_address, provider)?; + + // Get total supply + let total_supply = erc20_total_supply(token_address, provider)?; + let total_supply_float = total_supply.to::() as f64 / 10f64.powi(decimals as i32); + let formatted_total_supply = format!("{:.2}", total_supply_float); + + // Get balance if wallet address is provided + let (balance, formatted_balance) = if !wallet_address.is_empty() { + let balance = erc20_balance_of(token_address, wallet_address, provider)?; + (balance.to_string(), format!("{:.6}", balance)) + } else { + ("0".to_string(), "0.000000".to_string()) + }; + + Ok(TokenDetails { + address: token_str, + symbol, + name, + decimals, + total_supply: formatted_total_supply, + balance, + formatted_balance, + }) +} + +// +// CALLDATA CREATION HELPERS +// + +/// Creates the ABI-encoded calldata for an ERC20 `transfer` call. +/// +/// # Arguments +/// * `recipient` - The address to transfer tokens to. +/// * `amount` - The amount of tokens to transfer (in the token's smallest unit, e.g., wei for ETH-like). +/// +/// # Returns +/// A `Vec` containing the ABI-encoded calldata. +pub fn create_erc20_transfer_calldata(recipient: EthAddress, amount: U256) -> Vec { + let call = contracts::IERC20::transferCall { + to: recipient, + value: amount, + }; + call.abi_encode() +} + +/// Creates the ABI-encoded calldata for a Hypermap `note` call. +/// Performs validation on the note key format. +/// +/// # Arguments +/// * `note_key` - The note key (e.g., "~my-note"). Must start with '~'. +/// * `data` - The byte data to store in the note. +/// +/// # Returns +/// A `Result, WalletError>` containing the ABI-encoded calldata on success, +/// or a `WalletError::NameResolutionError` if the note key format is invalid. +pub fn create_hypermap_note_calldata( + note_key: &str, + data: Vec, +) -> Result, WalletError> { + // Validate the note key format + if !hypermap::valid_note(note_key) { + return Err(WalletError::NameResolutionError(format!( + "Invalid note key format: '{}'. Must start with '~' and follow naming rules.", + note_key + ))); + } + + let call = hypermap::contract::noteCall { + note: Bytes::from(note_key.as_bytes().to_vec()), + data: Bytes::from(data), + }; + Ok(call.abi_encode()) +} + +/// UserOperation builder for ERC-4337 +#[derive(Debug, Clone)] +pub struct UserOperationBuilder { + pub sender: EthAddress, + pub nonce: U256, + pub init_code: Vec, + pub call_data: Vec, + pub call_gas_limit: U256, + pub verification_gas_limit: U256, + pub pre_verification_gas: U256, + pub max_fee_per_gas: U256, + pub max_priority_fee_per_gas: U256, + pub paymaster_and_data: Vec, + pub chain_id: u64, +} + +impl UserOperationBuilder { + /// Create a new UserOperationBuilder with defaults + pub fn new(sender: EthAddress, chain_id: u64) -> Self { + Self { + sender, + nonce: U256::ZERO, + init_code: Vec::new(), + call_data: Vec::new(), + call_gas_limit: U256::from(80_000), // Reduced from 100k + verification_gas_limit: U256::from(100_000), // Reduced from 150k + pre_verification_gas: U256::from(50_000), // Increased from 21k for L2 + // Set reasonable gas prices for Base chain + max_fee_per_gas: U256::from(1_000_000_000), // 1 gwei + max_priority_fee_per_gas: U256::from(1_000_000_000), // 1 gwei + paymaster_and_data: Vec::new(), + chain_id, + } + } + + /// Build and sign the UserOperation + pub fn build_and_sign( + self, + entry_point: EthAddress, + signer: &S, + ) -> Result { + // Create the v0.8 PackedUserOperation struct + let mut packed_op = build_packed_user_operation(&self); + + // Get the UserOp hash for signing + let user_op_hash = self.get_user_op_hash_v08(&packed_op, entry_point, self.chain_id); + + // Log the hash before signing + kiprintln!( + "PL:: UserOperation hash to sign: 0x{}", + hex::encode(&user_op_hash) + ); + kiprintln!("PL:: Entry point: {}", entry_point); + kiprintln!("PL:: Chain ID: {}", self.chain_id); + + // Sign the hash + let signature = signer.sign_message(&user_op_hash)?; + + // Set the signature + packed_op.signature = Bytes::from(signature); + + Ok(packed_op) + } + + /// Calculate the UserOp hash according to ERC-4337 spec + fn _get_user_op_hash( + &self, + user_op: &UserOperation, + entry_point: EthAddress, + chain_id: u64, + ) -> Vec { + use sha3::{Digest, Keccak256}; + + // Pack the UserOp for hashing (without signature) + let packed = self._pack_user_op_for_hash(user_op); + let user_op_hash = Keccak256::digest(&packed); + + // Create the final hash with entry point and chain ID + let mut hasher = Keccak256::new(); + hasher.update(user_op_hash); + hasher.update(entry_point.as_slice()); + hasher.update(&chain_id.to_be_bytes()); + + hasher.finalize().to_vec() + } + + /// Calculate the UserOp hash for v0.8 according to ERC-4337 spec + fn get_user_op_hash_v08( + &self, + user_op: &PackedUserOperation, + entry_point: EthAddress, + chain_id: u64, + ) -> Vec { + use sha3::{Digest, Keccak256}; + + // Pack the UserOp for hashing (without signature) + let packed = self.pack_user_op_for_hash_v08(user_op); + let user_op_hash = Keccak256::digest(&packed); + + // Create the final hash with entry point and chain ID + let mut hasher = Keccak256::new(); + hasher.update(user_op_hash); + hasher.update(entry_point.as_slice()); + hasher.update(&chain_id.to_be_bytes()); + + hasher.finalize().to_vec() + } + + /// Pack UserOp fields for hashing (ERC-4337 specification) + fn _pack_user_op_for_hash(&self, user_op: &UserOperation) -> Vec { + use sha3::{Digest, Keccak256}; + + let mut packed = Vec::new(); + + // Pack all fields except signature + packed.extend_from_slice(user_op.sender.as_slice()); + packed.extend_from_slice(&user_op.nonce.to_be_bytes::<32>()); + + // For initCode and paymasterAndData, we hash them if non-empty + if !user_op.initCode.is_empty() { + let hash = Keccak256::digest(&user_op.initCode); + packed.extend_from_slice(&hash); + } else { + packed.extend_from_slice(&[0u8; 32]); + } + + if !user_op.callData.is_empty() { + let hash = Keccak256::digest(&user_op.callData); + packed.extend_from_slice(&hash); + } else { + packed.extend_from_slice(&[0u8; 32]); + } + + packed.extend_from_slice(&user_op.callGasLimit.to_be_bytes::<32>()); + packed.extend_from_slice(&user_op.verificationGasLimit.to_be_bytes::<32>()); + packed.extend_from_slice(&user_op.preVerificationGas.to_be_bytes::<32>()); + packed.extend_from_slice(&user_op.maxFeePerGas.to_be_bytes::<32>()); + packed.extend_from_slice(&user_op.maxPriorityFeePerGas.to_be_bytes::<32>()); + + if !user_op.paymasterAndData.is_empty() { + let hash = Keccak256::digest(&user_op.paymasterAndData); + packed.extend_from_slice(&hash); + } else { + packed.extend_from_slice(&[0u8; 32]); + } + + packed + } + + /// Pack UserOp fields for hashing v0.8 (ERC-4337 specification) + fn pack_user_op_for_hash_v08(&self, user_op: &PackedUserOperation) -> Vec { + use sha3::{Digest, Keccak256}; + + let mut packed = Vec::new(); + + // Pack all fields except signature + packed.extend_from_slice(user_op.sender.as_slice()); + packed.extend_from_slice(&user_op.nonce.to_be_bytes::<32>()); + + // For initCode and paymasterAndData, we hash them if non-empty + if !user_op.initCode.is_empty() { + let hash = Keccak256::digest(&user_op.initCode); + packed.extend_from_slice(&hash); + } else { + packed.extend_from_slice(&[0u8; 32]); + } + + if !user_op.callData.is_empty() { + let hash = Keccak256::digest(&user_op.callData); + packed.extend_from_slice(&hash); + } else { + packed.extend_from_slice(&[0u8; 32]); + } + + // Pack the packed fields directly (accountGasLimits and gasFees) + packed.extend_from_slice(&user_op.accountGasLimits.0); + packed.extend_from_slice(&user_op.preVerificationGas.to_be_bytes::<32>()); + packed.extend_from_slice(&user_op.gasFees.0); + + if !user_op.paymasterAndData.is_empty() { + let hash = Keccak256::digest(&user_op.paymasterAndData); + packed.extend_from_slice(&hash); + } else { + packed.extend_from_slice(&[0u8; 32]); + } + + packed + } + + /// Set paymaster and paymaster data with EIP-2612 permit signature + pub fn paymaster_with_permit( + &mut self, + paymaster: EthAddress, + _token_address: EthAddress, + _max_cost: U256, + _tba_address: EthAddress, + _signer: &S, + _provider: &Provider, + ) -> Result<(), WalletError> { + // Use simple Circle format - no permit signature needed + // The TBA has already approved the paymaster to spend USDC + let paymaster_data = encode_circle_paymaster_data( + paymaster, 500_000, // Default verification gas limit + 300_000, // Default call gas limit + ); + + // Set the combined paymaster and data + self.paymaster_and_data = paymaster_data; + + Ok(()) + } +} + +/// Helper to create calldata for TBA execute through UserOp +pub fn create_tba_userop_calldata( + target: EthAddress, + value: U256, + data: Vec, + operation: u8, +) -> Vec { + // Use existing IERC6551Account interface + let call = contracts::IERC6551Account::execute_0Call { + to: target, + value, + data: Bytes::from(data), + operation, + }; + call.abi_encode() +} + +/// Pack two 16-byte values into a single bytes32 for v0.8 UserOperation +fn pack_gas_values(high: U256, low: U256) -> B256 { + let mut packed = [0u8; 32]; + // Take the lower 16 bytes of each value + let high_bytes = high.to_be_bytes::<32>(); + let low_bytes = low.to_be_bytes::<32>(); + + // Pack high value in first 16 bytes + packed[0..16].copy_from_slice(&high_bytes[16..32]); + // Pack low value in last 16 bytes + packed[16..32].copy_from_slice(&low_bytes[16..32]); + + B256::from(packed) +} + +/// Build a v0.8 PackedUserOperation from the builder values +pub fn build_packed_user_operation(builder: &UserOperationBuilder) -> PackedUserOperation { + // Pack gas limits: verificationGasLimit (high) and callGasLimit (low) + let account_gas_limits = + pack_gas_values(builder.verification_gas_limit, builder.call_gas_limit); + + // Pack gas fees: maxPriorityFeePerGas (high) and maxFeePerGas (low) + let gas_fees = pack_gas_values(builder.max_priority_fee_per_gas, builder.max_fee_per_gas); + + PackedUserOperation { + sender: builder.sender, + nonce: builder.nonce, + initCode: Bytes::from(builder.init_code.clone()), + callData: Bytes::from(builder.call_data.clone()), + accountGasLimits: account_gas_limits, + preVerificationGas: builder.pre_verification_gas, + gasFees: gas_fees, + paymasterAndData: Bytes::from(builder.paymaster_and_data.clone()), + signature: Bytes::default(), + } +} + +/// Get the ERC-4337 EntryPoint address for a given chain +pub fn get_entry_point_address(chain_id: u64) -> Option { + match chain_id { + // v0.8.0 EntryPoint is deployed at this address on Base and other chains + 8453 => { + // Base - use v0.8 EntryPoint + EthAddress::from_str("0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108").ok() + } + // v0.6.0 EntryPoint for other chains (keeping for compatibility) + 1 | 10 | 137 | 42161 => { + EthAddress::from_str("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789").ok() + } + // Sepolia testnet + 11155111 => EthAddress::from_str("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789").ok(), + _ => None, + } +} + +/// Known paymaster addresses by chain +pub fn get_known_paymaster(chain_id: u64) -> Option { + match chain_id { + 8453 => { + // Base + // Circle's USDC paymaster on Base + EthAddress::from_str("0x0578cFB241215b77442a541325d6A4E6dFE700Ec").ok() + } + _ => None, + } +} + +/// Structure for EIP-2612 permit data +#[derive(Debug, Clone)] +pub struct PermitData { + pub owner: EthAddress, + pub spender: EthAddress, + pub value: U256, + pub nonce: U256, + pub deadline: U256, +} + +/// Generate EIP-2612 permit signature for USDC +pub fn generate_eip2612_permit_signature( + permit_data: &PermitData, + token_address: EthAddress, + chain_id: u64, + signer: &S, +) -> Result, WalletError> { + use sha3::{Digest, Keccak256}; + + // EIP-712 Domain Separator + // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") + let domain_type_hash = Keccak256::digest( + b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", + ); + + // USDC uses "USD Coin" and version "2" + let name_hash = Keccak256::digest(b"USD Coin"); + let version_hash = Keccak256::digest(b"2"); + + // Build domain separator + let mut domain_data = Vec::new(); + domain_data.extend_from_slice(&domain_type_hash); + domain_data.extend_from_slice(&name_hash); + domain_data.extend_from_slice(&version_hash); + domain_data.extend_from_slice(&U256::from(chain_id).to_be_bytes::<32>()); + domain_data.extend_from_slice(token_address.as_slice()); + let domain_separator = Keccak256::digest(&domain_data); + + // Permit type hash + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + let permit_type_hash = Keccak256::digest( + b"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)", + ); + + // Build permit struct hash + let mut permit_data_encoded = Vec::new(); + permit_data_encoded.extend_from_slice(&permit_type_hash); + permit_data_encoded.extend_from_slice(permit_data.owner.as_slice()); + permit_data_encoded.extend_from_slice(permit_data.spender.as_slice()); + permit_data_encoded.extend_from_slice(&permit_data.value.to_be_bytes::<32>()); + permit_data_encoded.extend_from_slice(&permit_data.nonce.to_be_bytes::<32>()); + permit_data_encoded.extend_from_slice(&permit_data.deadline.to_be_bytes::<32>()); + let permit_struct_hash = Keccak256::digest(&permit_data_encoded); + + // Build final message hash for EIP-712 + let mut message = Vec::new(); + message.push(0x19); + message.push(0x01); + message.extend_from_slice(&domain_separator); + message.extend_from_slice(&permit_struct_hash); + let message_hash = Keccak256::digest(&message); + + // Sign the hash (raw signature without prefix) + // Use sign_hash for EIP-712, not sign_message which adds prefix + let signature = signer.sign_hash(&message_hash)?; + + Ok(signature) +} + +/// Get the current permit nonce for an address from USDC contract +pub fn get_usdc_permit_nonce( + token_address: &str, + owner: EthAddress, + provider: &Provider, +) -> Result { + // USDC has a nonces(address) function + // Function selector: 0x7ecebe00 + let mut call_data = Vec::new(); + call_data.extend_from_slice(&hex::decode("7ecebe00").unwrap()); + call_data.extend_from_slice(&[0u8; 12]); // Pad address to 32 bytes + call_data.extend_from_slice(owner.as_slice()); + + let token = resolve_name(token_address, provider.chain_id)?; + + // Create a transaction request for the call + let tx = TransactionRequest { + to: Some(TxKind::Call(token)), + input: call_data.into(), + ..Default::default() + }; + + // Make the call + let result = provider.call(tx, None)?; + + // Parse the result as U256 + if result.len() >= 32 { + Ok(U256::from_be_slice(&result[..32])) + } else { + Err(WalletError::TransactionError( + "Invalid nonce response".to_string(), + )) + } +} + +/// Encode paymaster data for USDC payment with EIP-2612 permit signature +/// DEPRECATED - We don't use permit signatures anymore, only simple format +/* +pub fn encode_usdc_paymaster_data_with_signer( + paymaster: EthAddress, + token_address: EthAddress, + max_cost: U256, + tba_address: EthAddress, + signer: &S, + provider: &Provider, +) -> Result, WalletError> { + // Start with paymaster address (20 bytes) + let mut data = Vec::new(); + data.extend_from_slice(paymaster.as_slice()); + + // Add paymaster-specific data for Circle's TokenPaymaster v0.8 + // Format: encodePacked([uint8, address, uint256, bytes]) + // - uint8: mode (0 for permit mode) + // - address: USDC token address + // - uint256: permit amount + // - bytes: permit signature + + // Mode byte (0 for permit mode) + data.push(0u8); + + // Token address (USDC) + data.extend_from_slice(token_address.as_slice()); + + // Permit amount - use max_cost which should cover gas + data.extend_from_slice(&max_cost.to_be_bytes::<32>()); + + // Get current nonce for the TBA from USDC contract + let nonce = get_usdc_permit_nonce(&token_address.to_string(), tba_address, provider)?; + + // Generate permit data + let deadline = U256::from(u64::MAX); // Max deadline + let permit_data = PermitData { + owner: tba_address, + spender: paymaster, + value: max_cost, + nonce, + deadline, + }; + + // Generate the actual permit signature + let chain_id = provider.chain_id; + let permit_signature = + generate_eip2612_permit_signature(&permit_data, token_address, chain_id, signer)?; + + // Add the real permit signature + data.extend_from_slice(&permit_signature); + + Ok(data) +} +*/ + +/// Encode paymaster data for USDC payment (keeping old function for compatibility) +/// This version uses a dummy signature and will fail with AA33 +pub fn encode_usdc_paymaster_data( + paymaster: EthAddress, + _token_address: EthAddress, + _max_cost: U256, +) -> Vec { + // Use the new Circle format with default gas limits + encode_circle_paymaster_data(paymaster, 500_000, 300_000) +} + +/// Encode paymaster data for Circle's USDC paymaster +/// Format: abi.encode(address paymaster, uint256 verificationGasLimit, uint256 callGasLimit) +/// Returns the complete ABI encoding that the paymaster expects to receive +pub fn encode_circle_paymaster_data( + paymaster: EthAddress, + verification_gas_limit: u128, + call_gas_limit: u128, +) -> Vec { + let mut data = Vec::new(); + + // ABI encoding includes the paymaster address as the first parameter + // This matches the developer's example: + // encode(["address", "uint256", "uint256"], ['0x0578cFB241215b77442a541325d6A4E6dFE700Ec', 500000, 300000]) + + // First parameter: paymaster address as uint256 (padded to 32 bytes) + let mut padded_address = vec![0u8; 12]; // 12 zero bytes for padding + padded_address.extend_from_slice(paymaster.as_slice()); // 20 bytes of address + data.extend_from_slice(&padded_address); + + // Second parameter: verification gas limit as uint256 (32 bytes) + let verification_gas_u256 = U256::from(verification_gas_limit); + data.extend_from_slice(&verification_gas_u256.to_be_bytes::<32>()); + + // Third parameter: call gas limit as uint256 (32 bytes) + let call_gas_u256 = U256::from(call_gas_limit); + data.extend_from_slice(&call_gas_u256.to_be_bytes::<32>()); + + // Total: 96 bytes (32 + 32 + 32) + // This is the complete ABI encoding the paymaster expects + data +} + +/// Creates the ABI-encoded calldata for an ERC20 `permit` call. +/// This is used when the TBA needs to call permit on-chain (since TBAs can't sign off-chain) +/// +/// # Arguments +/// * `owner` - The address that owns the tokens (the TBA in this case) +/// * `spender` - The address to grant allowance to (e.g., the paymaster) +/// * `value` - The amount of tokens to approve +/// * `deadline` - The deadline timestamp for the permit +/// +/// # Returns +/// A `Vec` containing the ABI-encoded calldata for the permit function +pub fn create_erc20_permit_calldata( + owner: EthAddress, + spender: EthAddress, + value: U256, + deadline: U256, +) -> Vec { + // For on-chain permit calls by the TBA, we don't need nonce, v, r, s + // The TBA will call permit() directly as the owner + // This creates calldata for: permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + // But for TBA on-chain calls, v=0, r=0, s=0 works because the TBA IS the owner + + use alloy_sol_types::SolCall; + + // Define the permit function call + // Note: Using the standard ERC20Permit interface + sol! { + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s); + } + + let call = permitCall { + owner, + spender, + value, + deadline, + v: 0, // Dummy values for on-chain call + r: B256::ZERO, + s: B256::ZERO, + }; + + call.abi_encode() +} + +/// Creates a multicall calldata that combines permit + another operation +/// This is useful for TBAs to approve and use tokens in a single transaction +pub fn create_multicall_permit_and_execute( + _token_address: EthAddress, + permit_spender: EthAddress, + permit_amount: U256, + permit_deadline: U256, + _execute_target: EthAddress, + _execute_calldata: Vec, + _execute_value: U256, +) -> Vec { + // Create permit calldata + let permit_calldata = create_erc20_permit_calldata( + EthAddress::ZERO, // Will be replaced by TBA address when executed + permit_spender, + permit_amount, + permit_deadline, + ); + + // For TBA execute, we need to create two execute calls: + // 1. Execute permit on token contract + // 2. Execute the actual operation + + // This would need to be wrapped in a multicall or batch execute + // The exact implementation depends on the TBA's interface + + // For now, return just the permit calldata + // In practice, this would be combined with the execute calldata + permit_calldata +} + +// Encode paymaster data for USDC payment with on-chain permit (for TBAs) +// This version doesn't include a permit signature since TBAs will call permit on-chain +// DEPRECATED - We only use the simple Circle format now +/* +pub fn encode_usdc_paymaster_data_for_tba( + paymaster: EthAddress, + token_address: EthAddress, + max_cost: U256, +) -> Vec { + // Start with paymaster address (20 bytes) + let mut data = Vec::new(); + data.extend_from_slice(paymaster.as_slice()); + + // Add paymaster-specific data for Circle's TokenPaymaster v0.8 + // For TBAs, we might need a different mode or the paymaster needs to support on-chain permits + + // Mode byte (1 for on-chain approval mode, if supported) + // Note: This depends on Circle's paymaster implementation + // Mode 0 = permit signature mode (for EOAs) + // Mode 1 = assume pre-existing approval (for smart contracts) + data.push(1u8); + + // Token address (USDC) + data.extend_from_slice(token_address.as_slice()); + + // Max cost amount + data.extend_from_slice(&max_cost.to_be_bytes::<32>()); + + // No signature needed for on-chain approval mode + + data +} +*/