From a4f90253febbfb761009d0dd70f80855828be3ba Mon Sep 17 00:00:00 2001 From: Derek Rein Date: Mon, 9 Mar 2026 13:02:53 +0700 Subject: [PATCH 1/4] feat: add CWP wallet support for approve set and CLOB operations Add send-transaction support to CwpSigner, enabling WalletConnect wallets to send on-chain approval transactions. Previously only local private key wallets could use `approve set`. - Add CWP send-transaction types and methods (cwp.rs) - Add send_and_confirm_cwp_tx helper with timeout and revert detection (auth.rs) - Branch approve set on PolySigner::Local vs PolySigner::Cwp - Add CWP wallet connect/disconnect commands (wallet.rs) - Add CWP config persistence (config.rs) Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 1173 +++++++++++++++++++++++++++++++++++++-- Cargo.toml | 5 +- src/auth.rs | 81 ++- src/commands/approve.rs | 144 +++-- src/commands/wallet.rs | 216 ++++++- src/config.rs | 54 +- src/cwp.rs | 493 ++++++++++++++++ src/main.rs | 1 + 8 files changed, 2025 insertions(+), 142 deletions(-) create mode 100644 src/cwp.rs diff --git a/Cargo.lock b/Cargo.lock index 26a3032..5def441 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.7.8" @@ -30,9 +36,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.7.3" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4973038846323e4e69a433916522195dce2947770076c03078fc21c80ea0f1c4" +checksum = "07dc44b606f29348ce7c127e7f872a6d2df3cfeff85b7d6bba62faca75112fdd" dependencies = [ "alloy-consensus", "alloy-contract", @@ -43,7 +49,11 @@ dependencies = [ "alloy-rpc-client", "alloy-serde", "alloy-signer", + "alloy-signer-aws", + "alloy-signer-gcp", + "alloy-signer-ledger", "alloy-signer-local", + "alloy-signer-turnkey", "alloy-transport", "alloy-transport-http", "alloy-trie", @@ -146,6 +156,7 @@ dependencies = [ "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", + "derive_more", "itoa", "serde", "serde_json", @@ -246,7 +257,7 @@ checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" dependencies = [ "alloy-primitives", "alloy-sol-types", - "http", + "http 1.4.0", "serde", "serde_json", "thiserror 2.0.18", @@ -347,7 +358,7 @@ dependencies = [ "futures-utils-wasm", "lru", "parking_lot", - "pin-project", + "pin-project 1.1.10", "reqwest 0.12.28", "serde", "serde_json", @@ -391,7 +402,7 @@ dependencies = [ "alloy-transport", "alloy-transport-http", "futures", - "pin-project", + "pin-project 1.1.10", "reqwest 0.12.28", "serde", "serde_json", @@ -452,7 +463,9 @@ version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" dependencies = [ + "alloy-dyn-abi", "alloy-primitives", + "alloy-sol-types", "async-trait", "auto_impl", "either", @@ -461,6 +474,63 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "alloy-signer-aws" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8857c6346edb898df606130a7d29b3dfc765746ccc7915b36466a1e16347b93" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "aws-config", + "aws-sdk-kms", + "k256", + "spki", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-signer-gcp" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "787a9d7e155ee613f4f4ad0514b8c721c069497a494cfe52dc8b1e8ba29bfe2f" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "gcloud-sdk", + "k256", + "spki", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-signer-ledger" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c85812b6ae80e3a5ba634cbf2c44835744a120e29768ced60ed81c54c8ef562" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "coins-ledger", + "futures-util", + "semver 1.0.27", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "alloy-signer-local" version = "1.7.3" @@ -477,6 +547,22 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "alloy-signer-turnkey" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd4f8c8ece153684392a054da16dc77d6c4754a3eb5004950f34c3ba5ea251" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "thiserror 2.0.18", + "tracing", + "turnkey_client", +] + [[package]] name = "alloy-sol-macro" version = "1.5.7" @@ -896,6 +982,18 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-compression" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -952,6 +1050,43 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c456581cb3c77fafcc8c67204a70680d40b61112d6da78c77bd31d945b65f1b5" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 1.4.0", + "time", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + [[package]] name = "aws-lc-rs" version = "1.16.0" @@ -974,6 +1109,291 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "aws-runtime" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c635c2dc792cb4a11ce1a4f392a925340d1bdf499289b5ec1ec6810954eb43f5" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 1.4.0", + "http-body 1.0.1", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa37c3e267142899d18cb1617258c93ed7a78873e9adff0221838b0e621188be" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6443ccadc777095d5ed13e21f5c364878c9f5bad4e35187a6cdbd863b0afcad" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa49f3c607b92daae0c078d48a4571f599f966dce3caee5f1ea55c4d9073f99" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52eec3db979d18cb807fc1070961cc51d87d069abe9ab57917769687368a8c6c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.63.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630e67f2a31094ffa51b210ae030855cb8f3b7ee1329bdd8d085aaf61e8b97fc" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.62.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb96aa208d62ee94104645f7b2ecaf77bf27edf161590b6224bfbac2832f979" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a46543fbc94621080b3cf553eb4cbbdc41dd9780a30c4756400f0139440a1d" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cebbddb6f3a5bd81553643e9c7daf3cc3dc5b0b5f398ac668630e8a84e6fff0" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3df87c14f0127a0d77eb261c3bc45d5b4833e2a1f63583ebfb728e4852134ee" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49952c52f7eebb72ce2a754d3866cc0f87b97d2a46146b79f80f3a93fb2b3716" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3a26048eeab0ddeba4b4f9d51654c79af8c3b32357dc5f336cee85ab331c33" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -986,6 +1406,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.8.3" @@ -1023,6 +1453,12 @@ dependencies = [ "hex-conservative", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.0" @@ -1176,6 +1612,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "c-kzg" version = "2.1.5" @@ -1221,11 +1667,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -1293,6 +1750,29 @@ dependencies = [ "cc", ] +[[package]] +name = "coins-ledger" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9bc0994d0aa0f4ade5f3a9baf4a8d936f250278c85a1124b401860454246ab" +dependencies = [ + "async-trait", + "byteorder", + "cfg-if", + "const-hex", + "getrandom 0.2.17", + "hidapi-rusb", + "js-sys", + "log", + "nix 0.26.4", + "once_cell", + "thiserror 1.0.69", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -1309,6 +1789,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "const-hex" version = "1.17.0" @@ -1316,7 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -1391,6 +1888,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -1406,6 +1912,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -1531,6 +2046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -1697,6 +2213,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -1798,7 +2315,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1829,6 +2346,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.10.0" @@ -1971,6 +2498,34 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" +[[package]] +name = "gcloud-sdk" +version = "0.27.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8458d2ad7741b6a16981b84e66b7e4d8026423096da721894769c6980d06ecc" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures", + "hyper", + "jsonwebtoken", + "once_cell", + "prost 0.13.5", + "prost-types 0.13.5", + "reqwest 0.12.28", + "secret-vault-value", + "serde", + "serde_json", + "tokio", + "tonic", + "tower", + "tower-layer", + "tower-util", + "tracing", + "url", +] + [[package]] name = "generic-array" version = "0.14.9" @@ -2018,6 +2573,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -2050,7 +2606,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap 2.13.0", "slab", "tokio", @@ -2128,6 +2684,18 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hidapi-rusb" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdc2ec354929a6e8f3c6b6923a4d97427ec2f764cfee8cd4bfe890946cdf08b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "rusb", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2146,6 +2714,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -2156,6 +2735,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -2163,7 +2753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -2174,8 +2764,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -2185,6 +2775,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -2196,9 +2792,10 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -2213,10 +2810,11 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.4.0", "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -2224,6 +2822,19 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -2234,14 +2845,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -2527,6 +3138,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "k256" version = "0.13.4" @@ -2547,7 +3173,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -2584,8 +3210,20 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags", + "bitflags 2.11.0", + "libc", +] + +[[package]] +name = "libusb1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" +dependencies = [ + "cc", "libc", + "pkg-config", + "vcpkg", ] [[package]] @@ -2641,18 +3279,53 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -2673,13 +3346,26 @@ dependencies = [ "smallvec", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -2795,6 +3481,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "papergrid" version = "0.13.0" @@ -2863,6 +3567,25 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2922,13 +3645,33 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" +dependencies = [ + "pin-project-internal 0.4.30", +] + [[package]] name = "pin-project" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ - "pin-project-internal", + "pin-project-internal 1.1.10", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -2964,6 +3707,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "polymarket-cli" version = "0.1.4" @@ -2971,6 +3720,7 @@ dependencies = [ "alloy", "anyhow", "assert_cmd", + "async-trait", "chrono", "clap", "dirs", @@ -2987,9 +3737,7 @@ dependencies = [ [[package]] name = "polymarket-client-sdk" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "827f8ed9308deb61c96e6ff6df6137742a038e3dd27aa62f78c06dea763951c3" +version = "0.4.3" dependencies = [ "alloy", "async-stream", @@ -3001,7 +3749,7 @@ dependencies = [ "futures", "hmac", "phf", - "rand 0.9.2", + "rand 0.10.0", "reqwest 0.13.2", "rust_decimal", "rust_decimal_macros", @@ -3012,7 +3760,7 @@ dependencies = [ "serde_repr", "serde_with", "sha2", - "strum_macros", + "strum_macros 0.28.0", "url", "uuid", ] @@ -3081,6 +3829,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -3140,7 +3897,7 @@ checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.11.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -3151,6 +3908,102 @@ dependencies = [ "unarray", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive 0.14.3", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost 0.13.5", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost 0.14.3", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -3190,7 +4043,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.2", "thiserror 2.0.18", "tokio", "tracing", @@ -3228,7 +4081,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] @@ -3287,6 +4140,17 @@ dependencies = [ "serde", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.1", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3326,6 +4190,12 @@ dependencies = [ "serde", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -3350,7 +4220,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -3407,6 +4277,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.9" @@ -3431,18 +4307,22 @@ dependencies = [ "base64", "bytes", "futures-core", - "http", - "http-body", + "futures-util", + "h2", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-rustls", "hyper-util", "js-sys", "log", + "mime_guess", "percent-encoding", "pin-project-lite", "quinn", "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -3450,12 +4330,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", ] @@ -3471,8 +4353,8 @@ dependencies = [ "encoding_rs", "futures-core", "h2", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-rustls", @@ -3598,6 +4480,16 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rusb" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" +dependencies = [ + "libc", + "libusb1-sys", +] + [[package]] name = "rust_decimal" version = "1.40.0" @@ -3660,7 +4552,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -3674,6 +4566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -3767,7 +4660,7 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cfg-if", "clipboard-win", "fd-lock", @@ -3775,7 +4668,7 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.29.0", "radix_trie", "unicode-segmentation", "unicode-width", @@ -3889,13 +4782,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secret-vault-value" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662c7f8e99d46c9d3a87561d771a970c29efaccbab4bbdc6ab65d099d2358077" +dependencies = [ + "prost 0.14.3", + "prost-types 0.14.3", + "serde", + "serde_json", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4063,7 +4969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -4103,12 +5009,30 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + [[package]] name = "siphasher" version = "1.0.2" @@ -4130,6 +5054,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.2" @@ -4174,7 +5108,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", ] [[package]] @@ -4189,6 +5123,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4255,7 +5201,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4433,7 +5379,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", - "socket2", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -4514,6 +5460,37 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project 1.1.10", + "prost 0.13.5", + "rustls-native-certs", + "socket2 0.5.10", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.3" @@ -4522,11 +5499,15 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", + "indexmap 2.13.0", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4535,13 +5516,18 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "async-compression", + "bitflags 2.11.0", "bytes", + "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", "iri-string", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -4559,6 +5545,18 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tower-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1093c19826d33807c72511e68f73b4a0469a3f22c2bd5f7d5212178b4b89674" +dependencies = [ + "futures-core", + "futures-util", + "pin-project 0.4.30", + "tower-service", +] + [[package]] name = "tracing" version = "0.1.44" @@ -4596,6 +5594,40 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "turnkey_api_key_stamper" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b72664037582371dfa96bfaa2e272446ea2551e269455e9fe3166445c76736" +dependencies = [ + "base64", + "hex", + "k256", + "p256", + "rand_core 0.6.4", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "turnkey_client" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf8cea094b6536ecc5cfad42cd45d2f9abf523cc7dacf7de23f132412d0ec3" +dependencies = [ + "mime", + "prost 0.12.6", + "prost-types 0.12.6", + "reqwest 0.12.28", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "turnkey_api_key_stamper", +] + [[package]] name = "typenum" version = "1.19.0" @@ -4626,6 +5658,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -4669,6 +5707,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4699,12 +5743,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -4838,13 +5894,26 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap 2.13.0", "semver 1.0.27", @@ -5279,7 +6348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.0", "indexmap 2.13.0", "log", "serde", @@ -5324,6 +6393,12 @@ dependencies = [ "tap", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index a01bf05..dd14d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,14 +14,15 @@ name = "polymarket" path = "src/main.rs" [dependencies] -polymarket-client-sdk = { version = "0.4", features = ["gamma", "data", "bridge", "clob", "ctf"] } -alloy = { version = "1.6.3", default-features = false, features = ["providers", "sol-types", "contract", "reqwest", "reqwest-rustls-tls", "signer-local", "signers"] } +polymarket-client-sdk = { path = "../polymarket-client-sdk", features = ["gamma", "data", "bridge", "clob", "ctf"] } +alloy = { version = "1.6.3", default-features = false, features = ["eip712", "providers", "sol-types", "contract", "reqwest", "reqwest-rustls-tls", "signer-local", "signers"] } clap = { version = "4", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } serde_json = "1" serde = { version = "1", features = ["derive"] } tabled = "0.17" rust_decimal = "1" +async-trait = "0.1" anyhow = "1" chrono = "0.4" dirs = "6" diff --git a/src/auth.rs b/src/auth.rs index a5cf34d..b3d5870 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,13 +1,16 @@ use std::str::FromStr; -use alloy::providers::ProviderBuilder; -use anyhow::{Context, Result}; +use alloy::primitives::B256; +use alloy::providers::{Provider, ProviderBuilder}; +use anyhow::{Context, Result, bail}; use polymarket_client_sdk::auth::state::Authenticated; use polymarket_client_sdk::auth::{LocalSigner, Normal, Signer as _}; use polymarket_client_sdk::clob::types::SignatureType; +use polymarket_client_sdk::types::Address; use polymarket_client_sdk::{POLYGON, clob}; -use crate::config; +use crate::config::{self, SignerType}; +use crate::cwp::{CwpSigner, PolySigner}; const DEFAULT_RPC_URL: &str = "https://polygon.drpc.org"; @@ -23,14 +26,33 @@ fn parse_signature_type(s: &str) -> SignatureType { } } -pub fn resolve_signer( - private_key: Option<&str>, -) -> Result { - let (key, _) = config::resolve_key(private_key)?; - let key = key.ok_or_else(|| anyhow::anyhow!("{}", config::NO_WALLET_MSG))?; - LocalSigner::from_str(&key) - .context("Invalid private key") - .map(|s| s.with_chain_id(Some(POLYGON))) +pub fn resolve_signer(private_key: Option<&str>) -> Result { + // CLI flag or env var always uses local signer + let (key, _source) = config::resolve_key(private_key)?; + if let Some(key) = key { + let signer = LocalSigner::from_str(&key) + .context("Invalid private key")? + .with_chain_id(Some(POLYGON)); + return Ok(PolySigner::Local(signer)); + } + + // Check config for CWP + if let Some(cfg) = config::load_config()? { + if cfg.signer_type == SignerType::Cwp { + let provider = cfg + .cwp_provider + .context("CWP provider not configured")?; + let address: alloy::primitives::Address = cfg + .cwp_address + .context("CWP address not configured")? + .parse() + .context("Invalid CWP address in config")?; + let signer = CwpSigner::new(&provider, address, Some(POLYGON)); + return Ok(PolySigner::Cwp(signer)); + } + } + + bail!("{}", config::NO_WALLET_MSG) } pub async fn authenticated_clob_client( @@ -66,7 +88,17 @@ pub async fn create_provider( private_key: Option<&str>, ) -> Result { let (key, _) = config::resolve_key(private_key)?; - let key = key.ok_or_else(|| anyhow::anyhow!("{}", config::NO_WALLET_MSG))?; + let key = key.ok_or_else(|| { + // Check if CWP wallet is configured — give a better error message + if let Some(cfg) = config::load_config().ok().flatten() { + if cfg.signer_type == SignerType::Cwp { + return anyhow::anyhow!( + "CTF operations require a local wallet. Use `polymarket wallet` to configure one." + ); + } + } + anyhow::anyhow!("{}", config::NO_WALLET_MSG) + })?; let signer = LocalSigner::from_str(&key) .context("Invalid private key")? .with_chain_id(Some(POLYGON)); @@ -77,6 +109,31 @@ pub async fn create_provider( .context("Failed to connect to Polygon RPC with wallet") } +pub async fn send_and_confirm_cwp_tx( + cwp_signer: &CwpSigner, + provider: &(impl Provider + Sync), + to: Address, + calldata: Vec, +) -> Result { + // Send via CWP wallet (wallet handles gas estimation) + let tx_hash = cwp_signer + .send_transaction(to, calldata, alloy::primitives::U256::ZERO, None) + .await + .context("CWP send-transaction failed")?; + + // Wait for on-chain confirmation (poll immediately, then every 2s, timeout at 2 min) + for _ in 0..60 { + if let Some(receipt) = provider.get_transaction_receipt(tx_hash).await? { + if !receipt.status() { + bail!("Transaction {tx_hash} reverted on-chain"); + } + return Ok(tx_hash); + } + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + } + bail!("Transaction {tx_hash} not confirmed after 2 minutes") +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/commands/approve.rs b/src/commands/approve.rs index b52dc0c..1861b67 100644 --- a/src/commands/approve.rs +++ b/src/commands/approve.rs @@ -3,12 +3,14 @@ use alloy::primitives::U256; use alloy::sol; +use alloy::sol_types::SolCall; use anyhow::{Context, Result}; use clap::{Args, Subcommand}; use polymarket_client_sdk::types::{Address, address}; use polymarket_client_sdk::{POLYGON, contract_config}; use crate::auth; +use crate::cwp::PolySigner; use crate::output::OutputFormat; use crate::output::approve::{ApprovalStatus, print_approval_status, print_tx_result}; @@ -135,13 +137,30 @@ async fn check( print_approval_status(&statuses, &output) } -async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> { - let provider = auth::create_provider(private_key).await?; - let config = contract_config(POLYGON, false).context("No contract config for Polygon")?; +struct TxStep<'a> { + step: usize, + total: usize, + label: &'a str, + token_type: &'static str, + contract_name: &'a str, + tx_hash: alloy::primitives::B256, +} - let usdc = IERC20::new(USDC_ADDRESS, provider.clone()); - let ctf = IERC1155::new(config.conditional_tokens, provider.clone()); +fn emit_result(output: &OutputFormat, results: &mut Vec, tx: &TxStep<'_>) { + match output { + OutputFormat::Table => print_tx_result(tx.step, tx.total, tx.label, tx.tx_hash), + OutputFormat::Json => results.push(serde_json::json!({ + "step": tx.step, + "type": tx.token_type, + "contract": tx.contract_name, + "tx_hash": format!("{}", tx.tx_hash), + })), + } +} +async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> { + let signer = auth::resolve_signer(private_key)?; + let config = contract_config(POLYGON, false).context("No contract config for Polygon")?; let targets = approval_targets()?; let total = targets.len() * 2; @@ -152,53 +171,76 @@ async fn set(private_key: Option<&str>, output: OutputFormat) -> Result<()> { let mut results: Vec = Vec::new(); let mut step = 0; - for target in &targets { - step += 1; - let label = format!("USDC \u{2192} {}", target.name); - let tx_hash = usdc - .approve(target.address, U256::MAX) - .send() - .await - .context(format!("Failed to send USDC approval for {}", target.name))? - .watch() - .await - .context(format!( - "Failed to confirm USDC approval for {}", - target.name - ))?; - - match output { - OutputFormat::Table => print_tx_result(step, total, &label, tx_hash), - OutputFormat::Json => results.push(serde_json::json!({ - "step": step, - "type": "erc20", - "contract": target.name, - "tx_hash": format!("{tx_hash}"), - })), + match &signer { + PolySigner::Local(_) => { + let provider = auth::create_provider(private_key).await?; + let usdc = IERC20::new(USDC_ADDRESS, provider.clone()); + let ctf = IERC1155::new(config.conditional_tokens, provider.clone()); + + for target in &targets { + step += 1; + let label = format!("USDC \u{2192} {}", target.name); + let tx_hash = usdc + .approve(target.address, U256::MAX) + .send() + .await + .context(format!("Failed to send USDC approval for {}", target.name))? + .watch() + .await + .context(format!( + "Failed to confirm USDC approval for {}", + target.name + ))?; + emit_result(&output, &mut results, &TxStep { step, total, label: &label, token_type: "erc20", contract_name: target.name, tx_hash }); + + step += 1; + let label = format!("CTF \u{2192} {}", target.name); + let tx_hash = ctf + .setApprovalForAll(target.address, true) + .send() + .await + .context(format!("Failed to send CTF approval for {}", target.name))? + .watch() + .await + .context(format!( + "Failed to confirm CTF approval for {}", + target.name + ))?; + emit_result(&output, &mut results, &TxStep { step, total, label: &label, token_type: "erc1155", contract_name: target.name, tx_hash }); + } } - - step += 1; - let label = format!("CTF \u{2192} {}", target.name); - let tx_hash = ctf - .setApprovalForAll(target.address, true) - .send() - .await - .context(format!("Failed to send CTF approval for {}", target.name))? - .watch() - .await - .context(format!( - "Failed to confirm CTF approval for {}", - target.name - ))?; - - match output { - OutputFormat::Table => print_tx_result(step, total, &label, tx_hash), - OutputFormat::Json => results.push(serde_json::json!({ - "step": step, - "type": "erc1155", - "contract": target.name, - "tx_hash": format!("{tx_hash}"), - })), + PolySigner::Cwp(cwp_signer) => { + let provider = auth::create_readonly_provider().await?; + for target in &targets { + step += 1; + let label = format!("USDC \u{2192} {}", target.name); + let calldata = IERC20::approveCall { + spender: target.address, + value: U256::MAX, + } + .abi_encode(); + let tx_hash = auth::send_and_confirm_cwp_tx(cwp_signer, &provider, USDC_ADDRESS, calldata) + .await + .context(format!("Failed USDC approval for {}", target.name))?; + emit_result(&output, &mut results, &TxStep { step, total, label: &label, token_type: "erc20", contract_name: target.name, tx_hash }); + + step += 1; + let label = format!("CTF \u{2192} {}", target.name); + let calldata = IERC1155::setApprovalForAllCall { + operator: target.address, + approved: true, + } + .abi_encode(); + let tx_hash = auth::send_and_confirm_cwp_tx( + cwp_signer, + &provider, + config.conditional_tokens, + calldata, + ) + .await + .context(format!("Failed CTF approval for {}", target.name))?; + emit_result(&output, &mut results, &TxStep { step, total, label: &label, token_type: "erc1155", contract_name: target.name, tx_hash }); + } } } diff --git a/src/commands/wallet.rs b/src/commands/wallet.rs index d54a63b..f994f73 100644 --- a/src/commands/wallet.rs +++ b/src/commands/wallet.rs @@ -6,7 +6,8 @@ use polymarket_client_sdk::auth::LocalSigner; use polymarket_client_sdk::auth::Signer as _; use polymarket_client_sdk::{POLYGON, derive_proxy_wallet}; -use crate::config; +use crate::config::{self, SignerType}; +use crate::cwp; use crate::output::OutputFormat; #[derive(Args)] @@ -41,6 +42,19 @@ pub enum WalletCommand { Address, /// Show wallet info (address, config path, key source) Show, + /// Connect an external wallet via CWP (CLI Wallet Protocol) + Connect { + /// CWP provider binary name (e.g. "walletconnect"); auto-discovers if omitted + provider: Option, + /// Overwrite existing wallet + #[arg(long)] + force: bool, + /// Signature type: eoa, proxy (default), or gnosis-safe + #[arg(long, default_value = "proxy")] + signature_type: String, + }, + /// Disconnect the CWP wallet + Disconnect, /// Delete all config and keys (fresh install) Reset { /// Skip confirmation prompt @@ -66,6 +80,12 @@ pub fn execute( } => cmd_import(&key, output, force, &signature_type), WalletCommand::Address => cmd_address(output, private_key_flag), WalletCommand::Show => cmd_show(output, private_key_flag), + WalletCommand::Connect { + provider, + force, + signature_type, + } => cmd_connect(output, provider.as_deref(), force, &signature_type), + WalletCommand::Disconnect => cmd_disconnect(output), WalletCommand::Reset { force } => cmd_reset(output, force), } } @@ -159,30 +179,64 @@ fn cmd_import(key: &str, output: OutputFormat, force: bool, signature_type: &str fn cmd_address(output: OutputFormat, private_key_flag: Option<&str>) -> Result<()> { let (key, _) = config::resolve_key(private_key_flag)?; - let key = key.ok_or_else(|| anyhow::anyhow!("{}", config::NO_WALLET_MSG))?; - let signer = LocalSigner::from_str(&key).context("Invalid private key")?; - let address = signer.address(); - - match output { - OutputFormat::Json => { - println!("{}", serde_json::json!({"address": address.to_string()})); + // Try local key first + if let Some(key) = key { + let signer = LocalSigner::from_str(&key).context("Invalid private key")?; + let address = signer.address(); + match output { + OutputFormat::Json => { + println!("{}", serde_json::json!({"address": address.to_string()})); + } + OutputFormat::Table => println!("{address}"), } - OutputFormat::Table => { - println!("{address}"); + return Ok(()); + } + + // Check CWP config + if let Some(cfg) = config::load_config()? { + if cfg.signer_type == SignerType::Cwp { + if let Some(addr) = &cfg.cwp_address { + match output { + OutputFormat::Json => { + println!("{}", serde_json::json!({"address": addr})); + } + OutputFormat::Table => println!("{addr}"), + } + return Ok(()); + } } } - Ok(()) + + bail!("{}", config::NO_WALLET_MSG) } fn cmd_show(output: OutputFormat, private_key_flag: Option<&str>) -> Result<()> { let (key, source) = config::resolve_key(private_key_flag)?; - let signer = key.as_deref().and_then(|k| LocalSigner::from_str(k).ok()); - let address = signer.as_ref().map(|s| s.address().to_string()); - let proxy_addr = signer + let cfg = config::load_config()?; + let is_cwp = cfg .as_ref() - .and_then(|s| derive_proxy_wallet(s.address(), POLYGON)) - .map(|a| a.to_string()); + .is_some_and(|c| c.signer_type == SignerType::Cwp); + + let (address, proxy_addr, signer_label) = if is_cwp { + let cfg = cfg.as_ref().unwrap(); + let addr = cfg.cwp_address.clone(); + let proxy = addr + .as_deref() + .and_then(|a| a.parse().ok()) + .and_then(|a| derive_proxy_wallet(a, POLYGON)) + .map(|a| a.to_string()); + let provider = cfg.cwp_provider.as_deref().unwrap_or("unknown"); + (addr, proxy, format!("cwp ({provider})")) + } else { + let signer = key.as_deref().and_then(|k| LocalSigner::from_str(k).ok()); + let addr = signer.as_ref().map(|s| s.address().to_string()); + let proxy = signer + .as_ref() + .and_then(|s| derive_proxy_wallet(s.address(), POLYGON)) + .map(|a| a.to_string()); + (addr, proxy, source.label().to_string()) + }; let sig_type = config::resolve_signature_type(None)?; let config_path = config::config_path()?; @@ -195,8 +249,9 @@ fn cmd_show(output: OutputFormat, private_key_flag: Option<&str>) -> Result<()> "address": address, "proxy_address": proxy_addr, "signature_type": sig_type, + "signer_type": if is_cwp { "cwp" } else { "local" }, "config_path": config_path.display().to_string(), - "source": source.label(), + "source": signer_label, "configured": address.is_some(), }) ); @@ -209,9 +264,134 @@ fn cmd_show(output: OutputFormat, private_key_flag: Option<&str>) -> Result<()> if let Some(proxy) = &proxy_addr { println!("Proxy wallet: {proxy}"); } + println!("Signer type: {}", if is_cwp { "cwp" } else { "local" }); println!("Signature type: {sig_type}"); println!("Config path: {}", config_path.display()); - println!("Key source: {}", source.label()); + println!("Key source: {signer_label}"); + } + } + Ok(()) +} + +fn cmd_connect( + output: OutputFormat, + provider: Option<&str>, + force: bool, + signature_type: &str, +) -> Result<()> { + guard_overwrite(force)?; + + let binary = match provider { + Some(p) => { + // If user gave a short name, prefix with "wallet-" + if p.starts_with("wallet-") { + p.to_string() + } else { + format!("wallet-{p}") + } + } + None => { + let providers = cwp::discover(); + match providers.len() { + 0 => bail!( + "No CWP wallet providers found on PATH.\n\ + Install a wallet-* binary (e.g. wallet-walletconnect) and try again." + ), + 1 => { + let p = &providers[0]; + eprintln!("Discovered CWP provider: {} ({})", p.name, p.binary); + p.binary.clone() + } + _ => { + eprintln!("Multiple CWP providers found:"); + for (i, p) in providers.iter().enumerate() { + eprintln!(" {}. {} ({})", i + 1, p.name, p.binary); + } + bail!( + "Multiple providers found. Specify one with: polymarket wallet connect " + ); + } + } + } + }; + + let signer = cwp::connect(&binary).context("Failed to connect via CWP")?; + let address = alloy::signers::Signer::address(&signer); + let proxy_addr = derive_proxy_wallet(address, POLYGON); + + config::save_cwp_wallet(&binary, &address.to_string(), POLYGON, signature_type)?; + let config_path = config::config_path()?; + + match output { + OutputFormat::Json => { + println!( + "{}", + serde_json::json!({ + "address": address.to_string(), + "proxy_address": proxy_addr.map(|a| a.to_string()), + "provider": binary, + "signature_type": signature_type, + "config_path": config_path.display().to_string(), + }) + ); + } + OutputFormat::Table => { + println!("Wallet connected via CWP!"); + println!("Address: {address}"); + if let Some(proxy) = proxy_addr { + println!("Proxy wallet: {proxy}"); + } + println!("Provider: {binary}"); + println!("Signature type: {signature_type}"); + println!("Config: {}", config_path.display()); + } + } + Ok(()) +} + +fn cmd_disconnect(output: OutputFormat) -> Result<()> { + if let Some(cfg) = config::load_config()? { + if cfg.signer_type != SignerType::Cwp { + match output { + OutputFormat::Table => println!("No CWP wallet connected. Nothing to disconnect."), + OutputFormat::Json => { + println!( + "{}", + serde_json::json!({"disconnected": false, "reason": "not a cwp wallet"}) + ); + } + } + return Ok(()); + } + } else { + match output { + OutputFormat::Table => println!("No wallet configured. Nothing to disconnect."), + OutputFormat::Json => { + println!( + "{}", + serde_json::json!({"disconnected": false, "reason": "no config"}) + ); + } + } + return Ok(()); + } + + let path = config::config_path()?; + config::delete_config()?; + + match output { + OutputFormat::Table => { + println!("CWP wallet disconnected."); + println!("Config deleted: {}", path.display()); + } + OutputFormat::Json => { + println!( + "{}", + serde_json::json!({ + "disconnected": true, + "deleted": path.display().to_string(), + }) + ); } } Ok(()) diff --git a/src/config.rs b/src/config.rs index 60c0179..116d5ef 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,14 +9,29 @@ const SIG_TYPE_ENV_VAR: &str = "POLYMARKET_SIGNATURE_TYPE"; pub(crate) const DEFAULT_SIGNATURE_TYPE: &str = "proxy"; pub(crate) const NO_WALLET_MSG: &str = - "No wallet configured. Run `polymarket wallet create` or `polymarket wallet import `"; + "No wallet configured. Run `polymarket wallet create`, `polymarket wallet import `, or `polymarket wallet connect`"; + +#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum SignerType { + #[default] + Local, + Cwp, +} #[derive(Serialize, Deserialize)] pub(crate) struct Config { - pub private_key: String, + #[serde(default)] + pub signer_type: SignerType, + #[serde(skip_serializing_if = "Option::is_none")] + pub private_key: Option, pub chain_id: u64, #[serde(default = "default_signature_type")] pub signature_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub cwp_provider: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cwp_address: Option, } fn default_signature_type() -> String { @@ -94,7 +109,7 @@ pub fn resolve_signature_type(cli_flag: Option<&str>) -> Result { Ok(DEFAULT_SIGNATURE_TYPE.to_string()) } -pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()> { +fn write_config(config: &Config) -> Result<()> { let dir = config_dir()?; fs::create_dir_all(&dir).context("Failed to create config directory")?; @@ -104,12 +119,7 @@ pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()> fs::set_permissions(&dir, fs::Permissions::from_mode(0o700))?; } - let config = Config { - private_key: key.to_string(), - chain_id, - signature_type: signature_type.to_string(), - }; - let json = serde_json::to_string_pretty(&config)?; + let json = serde_json::to_string_pretty(config)?; let path = config_path()?; #[cfg(unix)] @@ -135,6 +145,28 @@ pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()> Ok(()) } +pub fn save_wallet(key: &str, chain_id: u64, signature_type: &str) -> Result<()> { + write_config(&Config { + signer_type: SignerType::Local, + private_key: Some(key.to_string()), + chain_id, + signature_type: signature_type.to_string(), + cwp_provider: None, + cwp_address: None, + }) +} + +pub fn save_cwp_wallet(provider: &str, address: &str, chain_id: u64, signature_type: &str) -> Result<()> { + write_config(&Config { + signer_type: SignerType::Cwp, + private_key: None, + chain_id, + signature_type: signature_type.to_string(), + cwp_provider: Some(provider.to_string()), + cwp_address: Some(address.to_string()), + }) +} + /// Priority: CLI flag > env var > config file. pub fn resolve_key(cli_flag: Option<&str>) -> Result<(Option, KeySource)> { if let Some(key) = cli_flag { @@ -146,7 +178,9 @@ pub fn resolve_key(cli_flag: Option<&str>) -> Result<(Option, KeySource) return Ok((Some(key), KeySource::EnvVar)); } if let Some(config) = load_config()? { - return Ok((Some(config.private_key), KeySource::ConfigFile)); + if let Some(key) = config.private_key { + return Ok((Some(key), KeySource::ConfigFile)); + } } Ok((None, KeySource::None)) } diff --git a/src/cwp.rs b/src/cwp.rs new file mode 100644 index 0000000..69f49e9 --- /dev/null +++ b/src/cwp.rs @@ -0,0 +1,493 @@ +use std::collections::{HashMap, HashSet}; +use std::env; +use std::io::Write as _; +use std::process::{Command, Stdio}; + +use alloy::dyn_abi::eip712::TypedData; +use alloy::primitives::{Address, B256, ChainId, Signature, U256}; +use alloy::signers::local::PrivateKeySigner; +use anyhow::{Context, Result, bail}; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +// --- CWP Protocol Types --- + +#[derive(Debug, Deserialize)] +struct CwpInfo { + name: String, +} + +#[derive(Debug, Deserialize)] +struct AccountEntry { + address: String, +} + +#[derive(Debug, Deserialize)] +struct AccountsResponse { + accounts: Vec, +} + +#[derive(Debug, Serialize)] +struct SignHashInput { + account: String, + hash: String, +} + +#[derive(Debug, Deserialize)] +struct SignatureResponse { + signature: String, +} + +#[derive(Serialize)] +struct CwpTransaction { + to: String, + data: String, + value: String, + #[serde(skip_serializing_if = "Option::is_none")] + gas: Option, +} + +#[derive(Serialize)] +struct SendTransactionInput { + account: String, + chain: String, + transaction: CwpTransaction, +} + +#[derive(Deserialize)] +struct SendTransactionResponse { + #[serde(rename = "transactionHash")] + transaction_hash: String, +} + +// --- CWP Protocol Client --- + +fn cwp_exec(binary: &str, operation: &str, input: Option<&serde_json::Value>) -> Result { + let mut cmd = Command::new(binary); + cmd.arg(operation) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let mut child = cmd.spawn().with_context(|| format!("Failed to spawn {binary}"))?; + + if let Some(data) = input { + if let Some(mut stdin) = child.stdin.take() { + let json = serde_json::to_vec(data)?; + stdin.write_all(&json)?; + } + } else { + drop(child.stdin.take()); + } + + let output = child + .wait_with_output() + .context("Failed to read CWP output")?; + + if cfg!(debug_assertions) { + let stderr_str = String::from_utf8_lossy(&output.stderr); + if !stderr_str.is_empty() { + eprintln!("[debug] {binary} {operation} stderr: {stderr_str}"); + } + } + + match output.status.code() { + Some(0) => {} + Some(2) => bail!("CWP operation '{operation}' not supported by {binary}"), + Some(3) => bail!("User rejected the signing request"), + Some(4) => bail!("CWP operation timed out"), + Some(5) => bail!("Wallet not connected"), + Some(code) => bail!("CWP binary {binary} exited with code {code}"), + None => bail!("CWP binary {binary} was terminated by signal"), + } + + let stdout = String::from_utf8(output.stdout) + .context("CWP output is not valid UTF-8")?; + serde_json::from_str(stdout.trim()) + .context("CWP output is not valid JSON") +} + +// --- Discovery --- + +pub struct CwpProvider { + pub binary: String, + pub name: String, +} + +pub fn discover() -> Vec { + let path_var = match env::var("PATH") { + Ok(p) => p, + Err(_) => return vec![], + }; + + let mut seen = HashSet::new(); + let mut providers = Vec::new(); + + for dir in env::split_paths(&path_var) { + let entries = match std::fs::read_dir(&dir) { + Ok(e) => e, + Err(_) => continue, + }; + + for entry in entries.flatten() { + let name = entry.file_name(); + let name_str = name.to_string_lossy(); + + if !name_str.starts_with("wallet-") { + continue; + } + + if !seen.insert(name_str.to_string()) { + continue; + } + + let binary = name_str.to_string(); + + // Try to get info + let info = match Command::new(&binary) + .arg("info") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output() + { + Ok(output) if output.status.success() => { + let stdout = String::from_utf8_lossy(&output.stdout); + serde_json::from_str::(stdout.trim()).ok() + } + _ => None, + }; + + let display_name = info + .map(|i| i.name) + .unwrap_or_else(|| name_str.strip_prefix("wallet-").unwrap_or(&name_str).to_string()); + + providers.push(CwpProvider { + binary, + name: display_name, + }); + } + } + + providers +} + +// --- CwpSigner --- + +#[derive(Clone)] +pub struct CwpSigner { + binary: String, + address: Address, + chain_id: Option, +} + +impl CwpSigner { + pub fn new(binary: &str, address: Address, chain_id: Option) -> Self { + Self { + binary: binary.to_string(), + address, + chain_id, + } + } + + fn sign_hash_blocking(&self, hash: &B256) -> Result { + let input = serde_json::to_value(SignHashInput { + account: self.address.to_string(), + hash: format!("{hash}"), + })?; + + let output = cwp_exec(&self.binary, "sign-hash", Some(&input))?; + let resp: SignatureResponse = + serde_json::from_value(output).context("Invalid sign-hash response")?; + + parse_signature(&resp.signature) + } + + fn send_transaction_blocking( + &self, + to: Address, + data: Vec, + value: U256, + gas: Option, + ) -> Result { + let chain = format!( + "eip155:{}", + self.chain_id.unwrap_or(137) + ); + let input = serde_json::to_value(SendTransactionInput { + account: self.address.to_checksum(None), + chain, + transaction: CwpTransaction { + to: to.to_checksum(None), + data: format!("0x{}", alloy::primitives::hex::encode(&data)), + value: format!("{value}"), + gas: gas.map(|g| format!("{g}")), + }, + })?; + + let output = cwp_exec(&self.binary, "send-transaction", Some(&input))?; + let resp: SendTransactionResponse = + serde_json::from_value(output).context("Invalid send-transaction response")?; + + resp.transaction_hash + .parse() + .context("Invalid transaction hash from CWP") + } + + pub async fn send_transaction( + &self, + to: Address, + data: Vec, + value: U256, + gas: Option, + ) -> Result { + let signer = self.clone(); + tokio::task::spawn_blocking(move || { + signer.send_transaction_blocking(to, data, value, gas) + }) + .await + .context("send_transaction task panicked")? + } + + fn sign_typed_data_blocking(&self, typed_data: &TypedData) -> Result { + let mut typed_data_json = serde_json::to_value(typed_data)?; + normalize_typed_data_for_wallet(&mut typed_data_json); + let input = serde_json::json!({ + "account": self.address.to_string(), + "typedData": typed_data_json, + }); + + let output = cwp_exec(&self.binary, "sign-typed-data", Some(&input))?; + let resp: SignatureResponse = + serde_json::from_value(output).context("Invalid sign-typed-data response")?; + + parse_signature(&resp.signature) + } +} + +fn parse_signature(hex_sig: &str) -> Result { + let sig: alloy::primitives::Bytes = hex_sig.parse().context("Invalid signature hex")?; + if sig.len() != 65 { + bail!("Expected 65-byte signature, got {} bytes", sig.len()); + } + + Signature::from_raw(&sig).map_err(|e| anyhow::anyhow!("Invalid signature: {e}")) +} + +#[async_trait] +impl alloy::signers::Signer for CwpSigner { + fn address(&self) -> Address { + self.address + } + + fn chain_id(&self) -> Option { + self.chain_id + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.chain_id = chain_id; + } + + async fn sign_hash(&self, hash: &B256) -> alloy::signers::Result { + let hash = *hash; + let signer = self.clone(); + + tokio::task::spawn_blocking(move || signer.sign_hash_blocking(&hash)) + .await + .map_err(|e| alloy::signers::Error::Other(Box::new(e)))? + .map_err(cwp_err) + } + + async fn sign_dynamic_typed_data( + &self, + payload: &TypedData, + ) -> alloy::signers::Result { + let payload = payload.clone(); + let signer = self.clone(); + + tokio::task::spawn_blocking(move || signer.sign_typed_data_blocking(&payload)) + .await + .map_err(|e| alloy::signers::Error::Other(Box::new(e)))? + .map_err(cwp_err) + } +} + +fn cwp_err(e: anyhow::Error) -> alloy::signers::Error { + alloy::signers::Error::Other(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + e.to_string(), + ))) +} + +pub fn connect(binary: &str) -> Result { + let output = cwp_exec(binary, "accounts", None)?; + let resp: AccountsResponse = + serde_json::from_value(output).context("Invalid accounts response")?; + + let first = resp + .accounts + .first() + .context("No accounts returned by CWP provider")?; + + let address: Address = first + .address + .parse() + .context("Invalid address from CWP provider")?; + + Ok(CwpSigner::new(binary, address, None)) +} + +// --- Typed Data Normalization --- +// Alloy serializes uint256 as "0x..." hex and address as lowercase. +// Wallets (MetaMask, Zerion via WalletConnect) expect decimal strings for +// uint256 and checksummed addresses in eth_signTypedData_v4 payloads. + +fn normalize_typed_data_for_wallet(typed_data: &mut serde_json::Value) { + // Build a type lookup from the "types" field: { "ClobAuth": {"address": "address", "nonce": "uint256", ...} } + let type_map = build_type_map(typed_data); + + // Normalize domain values (chainId is uint256) + if let Some(domain) = typed_data.get_mut("domain") { + normalize_object(domain, &eip712_domain_types(), &type_map); + } + + // Normalize message values using the primaryType + let primary_type = typed_data + .get("primaryType") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + if let Some(message) = typed_data.get_mut("message") { + if let Some(field_types) = type_map.get(&primary_type) { + normalize_object(message, field_types, &type_map); + } + } +} + +fn eip712_domain_types() -> HashMap { + [ + ("name", "string"), + ("version", "string"), + ("chainId", "uint256"), + ("verifyingContract", "address"), + ("salt", "bytes32"), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect() +} + +fn build_type_map( + typed_data: &serde_json::Value, +) -> HashMap> { + let mut map = HashMap::new(); + if let Some(types) = typed_data.get("types").and_then(|t| t.as_object()) { + for (type_name, fields) in types { + let mut field_map = HashMap::new(); + if let Some(fields) = fields.as_array() { + for field in fields { + if let (Some(name), Some(ty)) = ( + field.get("name").and_then(|n| n.as_str()), + field.get("type").and_then(|t| t.as_str()), + ) { + field_map.insert(name.to_string(), ty.to_string()); + } + } + } + map.insert(type_name.clone(), field_map); + } + } + map +} + +fn normalize_object( + obj: &mut serde_json::Value, + field_types: &HashMap, + type_map: &HashMap>, +) { + if let Some(map) = obj.as_object_mut() { + for (field_name, value) in map.iter_mut() { + if let Some(sol_type) = field_types.get(field_name.as_str()) { + normalize_value(value, sol_type, type_map); + } + } + } +} + +fn normalize_value( + value: &mut serde_json::Value, + sol_type: &str, + type_map: &HashMap>, +) { + if sol_type.starts_with("uint") || sol_type.starts_with("int") { + // Convert hex "0x..." to decimal string + if let Some(s) = value.as_str() { + if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + if let Ok(n) = U256::from_str_radix(hex, 16) { + *value = serde_json::Value::String(n.to_string()); + } + } + } + } else if sol_type == "address" { + // Convert to checksummed address + if let Some(s) = value.as_str() { + if let Ok(addr) = s.parse::
() { + *value = serde_json::Value::String(addr.to_checksum(None)); + } + } + } else if let Some(nested_fields) = type_map.get(sol_type) { + // Recurse into nested struct types + normalize_object(value, nested_fields, type_map); + } +} + +// --- PolySigner --- +// Custom enum replacing Either. +// Unlike Either's Signer impl which only forwards sign_hash, +// this forwards ALL Signer methods including sign_dynamic_typed_data. + +pub enum PolySigner { + Local(PrivateKeySigner), + Cwp(CwpSigner), +} + +#[async_trait] +impl alloy::signers::Signer for PolySigner { + fn address(&self) -> Address { + match self { + Self::Local(s) => s.address(), + Self::Cwp(s) => s.address(), + } + } + + fn chain_id(&self) -> Option { + match self { + Self::Local(s) => s.chain_id(), + Self::Cwp(s) => s.chain_id(), + } + } + + fn set_chain_id(&mut self, chain_id: Option) { + match self { + Self::Local(s) => s.set_chain_id(chain_id), + Self::Cwp(s) => s.set_chain_id(chain_id), + } + } + + async fn sign_hash(&self, hash: &B256) -> alloy::signers::Result { + match self { + Self::Local(s) => s.sign_hash(hash).await, + Self::Cwp(s) => s.sign_hash(hash).await, + } + } + + async fn sign_dynamic_typed_data( + &self, + payload: &TypedData, + ) -> alloy::signers::Result { + match self { + Self::Local(s) => s.sign_dynamic_typed_data(payload).await, + Self::Cwp(s) => s.sign_dynamic_typed_data(payload).await, + } + } +} diff --git a/src/main.rs b/src/main.rs index 2abb55f..cafb46e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod auth; mod commands; mod config; +mod cwp; mod output; mod shell; From 2109f61c189c7763530ada1e97e416f088699ec7 Mon Sep 17 00:00:00 2001 From: Derek Rein Date: Mon, 9 Mar 2026 13:10:34 +0700 Subject: [PATCH 2/4] fix: use published polymarket-client-sdk instead of local path Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 2 ++ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5def441..328e78b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3738,6 +3738,8 @@ dependencies = [ [[package]] name = "polymarket-client-sdk" version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e3524434cec7736e9a6a973da143e5961b133164b98c83939c8ee1bebe4a82" dependencies = [ "alloy", "async-stream", diff --git a/Cargo.toml b/Cargo.toml index dd14d95..e3ce67e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ name = "polymarket" path = "src/main.rs" [dependencies] -polymarket-client-sdk = { path = "../polymarket-client-sdk", features = ["gamma", "data", "bridge", "clob", "ctf"] } +polymarket-client-sdk = { version = "0.4", features = ["gamma", "data", "bridge", "clob", "ctf"] } alloy = { version = "1.6.3", default-features = false, features = ["eip712", "providers", "sol-types", "contract", "reqwest", "reqwest-rustls-tls", "signer-local", "signers"] } clap = { version = "4", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } From 51744354febf0411e2b6ac3c5bc1682983f5f5b8 Mon Sep 17 00:00:00 2001 From: Derek Rein Date: Mon, 9 Mar 2026 17:28:18 +0700 Subject: [PATCH 3/4] fix: use checksummed addresses consistently across CWP operations sign_hash and sign_typed_data were using lowercase hex while send_transaction used EIP-55 checksummed format. Standardize on checksummed addresses for all CWP protocol account fields. Co-Authored-By: Claude Opus 4.6 --- src/cwp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cwp.rs b/src/cwp.rs index 69f49e9..43bb46f 100644 --- a/src/cwp.rs +++ b/src/cwp.rs @@ -192,7 +192,7 @@ impl CwpSigner { fn sign_hash_blocking(&self, hash: &B256) -> Result { let input = serde_json::to_value(SignHashInput { - account: self.address.to_string(), + account: self.address.to_checksum(None), hash: format!("{hash}"), })?; @@ -253,7 +253,7 @@ impl CwpSigner { let mut typed_data_json = serde_json::to_value(typed_data)?; normalize_typed_data_for_wallet(&mut typed_data_json); let input = serde_json::json!({ - "account": self.address.to_string(), + "account": self.address.to_checksum(None), "typedData": typed_data_json, }); From 304d2caaef5728ef3f450f109965e4909b0f0e52 Mon Sep 17 00:00:00 2001 From: Derek Rein Date: Mon, 9 Mar 2026 17:37:31 +0700 Subject: [PATCH 4/4] fix: address PR review feedback - Move sleep before receipt check to avoid unnecessary 2s delay on timeout - Add comment about signed int normalization scope (uint256 only) Co-Authored-By: Claude Opus 4.6 --- src/auth.rs | 8 +++++--- src/cwp.rs | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index b3d5870..4aa5765 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -121,15 +121,17 @@ pub async fn send_and_confirm_cwp_tx( .await .context("CWP send-transaction failed")?; - // Wait for on-chain confirmation (poll immediately, then every 2s, timeout at 2 min) - for _ in 0..60 { + // Wait for on-chain confirmation (poll every 2s, timeout at 2 min) + for i in 0..60 { + if i > 0 { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + } if let Some(receipt) = provider.get_transaction_receipt(tx_hash).await? { if !receipt.status() { bail!("Transaction {tx_hash} reverted on-chain"); } return Ok(tx_hash); } - tokio::time::sleep(std::time::Duration::from_secs(2)).await; } bail!("Transaction {tx_hash} not confirmed after 2 minutes") } diff --git a/src/cwp.rs b/src/cwp.rs index 43bb46f..f274963 100644 --- a/src/cwp.rs +++ b/src/cwp.rs @@ -420,7 +420,9 @@ fn normalize_value( type_map: &HashMap>, ) { if sol_type.starts_with("uint") || sol_type.starts_with("int") { - // Convert hex "0x..." to decimal string + // Convert hex "0x..." to decimal string. + // Note: uses U256 for both uint and int types. Polymarket only uses uint256; + // proper signed int support would need I256 if int types are ever used. if let Some(s) = value.as_str() { if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { if let Ok(n) = U256::from_str_radix(hex, 16) {