diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2400424b75..fd86322e34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -593,6 +593,9 @@ importers: ox: specifier: 0.9.6 version: 0.9.6(typescript@5.9.3)(zod@3.25.76) + tor-hazae41: + specifier: 0.2.5 + version: 0.2.5 ws: specifier: 8.18.3 version: 8.18.3 @@ -904,6 +907,21 @@ packages: '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + '@brumewallet/wallet.wasm@1.0.2': + resolution: {integrity: sha512-NRgxE2xAHZOHnkrTyOQ6QDMU/GncOzA3uwp4VLfDPfZhDjnQ9o133tiBNGLjAYLEAr2rt37MA6huy958wu75Ig==} + peerDependencies: + '@hazae41/base16.wasm': ^1.0.5 + '@hazae41/base58.wasm': ^1.0.6 + '@hazae41/base64.wasm': ^1.0.9 + '@hazae41/chacha20poly1305.wasm': ^1.0.1 + '@hazae41/ed25519.wasm': ^1.0.11 + '@hazae41/network.wasm': ^1.0.2 + '@hazae41/ripemd.wasm': ^1.0.6 + '@hazae41/secp256k1.wasm': ^1.0.10 + '@hazae41/sha1.wasm': ^1.0.5 + '@hazae41/sha3.wasm': ^1.0.4 + '@hazae41/x25519.wasm': ^1.0.8 + '@changesets/apply-release-plan@7.0.13': resolution: {integrity: sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg==} @@ -1211,6 +1229,177 @@ packages: engines: {node: '>=6'} hasBin: true + '@hazae41/aes.wasm@1.0.3': + resolution: {integrity: sha512-hWFukqQ7XBBtuuNm2HwbSx1wW5b20QJ9zhWmUIe8MjLBGU8RwkhIYmB/EeJFPdKxS+c9WbtGg6f145g3azPaKA==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/asn1@1.3.31': + resolution: {integrity: sha512-vuErGzgaZDxyEjmhkKHWciwwHvR87lLVqIhs+FLVamBSHZLGOwewgqUlgOK9bNfK6WFB6N7grLX/MJSe3us+PQ==} + + '@hazae41/base16.wasm@1.0.10': + resolution: {integrity: sha512-L+0z4JfWoUrMgAVB5iGGCWdmXdOtAzOX9cyyAToIU/zLJ3SAM8NB2IkZt8hYrsKWrajoBNHNhpWB8LGpODfWjw==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/base16@1.0.18': + resolution: {integrity: sha512-N4bcBzrXnbJeM4o0lFXd+PlSL3Z7UIOtVN4+602nmE+2cHbBzT2BsPTX4oH5+YBhX3rEMFE/pJ60nT57amRxhw==} + + '@hazae41/base58.wasm@1.0.8': + resolution: {integrity: sha512-ekTUN39bRPi9I1U13ohbmpJMFSZRirlNBxoJFY3KBimzmmiFybF7/MZK95UnE35g+W083gagTPxdnr4Y1KMGhA==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/base64.wasm@1.0.12': + resolution: {integrity: sha512-UPApu4C5J3BQVuDyrfYvhC3qQr9GCZkoTjzO/WAcg1LsBQPw+G7iPhwDjmdu8CS1j3P+HastoujenVqfghMbSw==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/base64@1.0.15': + resolution: {integrity: sha512-SX97fH6s5wxu454q0JohKQEPUEqQDTREWoKhvs+523VFz0Ur+DsPaC60jl7eamfbb3QwDcycu3Wf0GmqDQ+Ypw==} + + '@hazae41/base64url@1.0.17': + resolution: {integrity: sha512-1X73r8GJHN6KGUu4f+6jY7s5Sqz8Y/HpNsdD+7IGhrK7EZ96CbKD/0sEh2np+UJ4vxwMmmtMZsWdyKnJdbDiug==} + + '@hazae41/binary@1.3.5': + resolution: {integrity: sha512-o2unN0+W8+QDJkrUEYpTgJafZrPcjjpszYLEy8WwDR0BOvzT/cFNm1lA8vJEsv2jSGfGmqCSid4ndgkHWO8UAg==} + + '@hazae41/bitset@1.0.1': + resolution: {integrity: sha512-XZAGbTJjq5vY4Z/RzVLH2hfAOE7jjz+wTUTk9GuWv5GIofPTNyO/3ETwP9NdTFJpcQPvTLdjiBjoWZ1+e/J8eA==} + + '@hazae41/bitwise.wasm@1.0.9': + resolution: {integrity: sha512-zzDtJyFkWJbDCeOj85OTvQaLJ/+W9MJpGFtRTBRjtcigWkS/ZXTBolMdGUkEtppboUDCf7Jxi8Vya918gTWMNA==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/box@2.0.1': + resolution: {integrity: sha512-eX9AeEfeZkRSgEeoZMZn1oAEE1lDpmBFSuYcpJ2NZ2btxh+VeiuegHCazzqMWqyR79OLJQxK+o1iVA5j6hi16A==} + + '@hazae41/bytes@1.2.11': + resolution: {integrity: sha512-cmuy7gwYGUZjCu2tEMfvCYUDOuC31g23B5q4dQ8IBBH7SxFOW3tSdm1UHKDm49bFFtOZ/uO86B6jSTj2Ci5XCg==} + + '@hazae41/cadenas@0.4.2': + resolution: {integrity: sha512-yGdGZjAtlG6x7ira74bmJY4xQ3+bQehATP1QfNRqhOt3EiSC4LIbq0158/X3p3/48jHY2vpVO1B8h0h9HDCymw==} + + '@hazae41/cascade@2.2.2': + resolution: {integrity: sha512-9Y0MVMPca3AY2W63ckyULrgw2d8V9+ROOSZs+5PPbzm0zS+ihlD015Kpkpf8J1jtvIWIRhflcFa5Rbk/Ip5DKQ==} + + '@hazae41/chacha20poly1305.wasm@1.0.8': + resolution: {integrity: sha512-Mu9+BdvNypjGOcbnuvLfXFARHpHy++87caCnFqW+9ce57qQFgPKmWkAV2SKqIvC6mhy2jr95yR9Uej3D7BRWLw==} + deprecated: Use @hazae41/chacha20poly1305-wasm instead + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/chacha20poly1305@1.0.8': + resolution: {integrity: sha512-wp3dPjGweWLA7t/7LMoAj9nt25c5wpufnthBrFHKJi+qS2bhxEWvlOtWWnT3rD1DyoS2caKWRN4CbgIlg0ymkQ==} + + '@hazae41/cursor@1.2.4': + resolution: {integrity: sha512-/pE3kOcZnPtLdw6MTv3ALEcuUV3Ab3lWNAafGQSMAzN7vUIJJUIa3mReScwsCLNnfYWy34Y46RIuRb/2kOnEcg==} + + '@hazae41/disposer@2.0.20': + resolution: {integrity: sha512-O+/vlXgyJtd9yGEtkjJ2GszqiEhCDdusaPhnUMwjyrY1En5X2Jd3CTHahJxdsrfBVuXuULy3xcaC67qse/b86g==} + + '@hazae41/ed25519.wasm@1.0.13': + resolution: {integrity: sha512-ccTp8C5FglxI/d1zhSxJlectnLAjwp/cEyBLPb4M99vM4CZfLt1hlnxIfB38D2uyAGjzMY3BWf9ISYvsY0BD1w==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/ed25519@2.1.22': + resolution: {integrity: sha512-7LAfNguTwihtqWyUwM5Vt7ERJTywV2jEfvE1cWdZVTsNyvhQNExWYezlpwTpRz5F8dCvqHQmiop0izcdstkLQQ==} + peerDependencies: + '@hazae41/base64url': ^1.0.17 + + '@hazae41/future@1.0.3': + resolution: {integrity: sha512-tc6xV7buZI1P8d66OTH7noD5L3fKCZpTOfyvlVMIcjqvkY/gRcBinFzegc6DmXx08ZlwfAlnebU+ZX5NO+9+iw==} + + '@hazae41/kcp@1.1.3': + resolution: {integrity: sha512-Q/l1vZ1NYeElTHrgIxjDUsI5U2Bfkj5lCFyRyEXiya66bCxKTXYXDguGh+qi/bS0onPX6/TSipLHeyYXXHGAIA==} + + '@hazae41/keccak256@1.0.10': + resolution: {integrity: sha512-HdlqaAOFOX8OIBGe6giMhYMExLwxBL5uLr/2btiilD3rCh3HpMppzyORjhsUxAATDscLMqmMYMBbsZfREOV8Nw==} + + '@hazae41/memory-wasm@1.0.46': + resolution: {integrity: sha512-ifPyk7lVx9VpPayNOl8Z84z6eUf/9jXG4c397BJUFrmaS6iuCk5KRNrf3kQzvGmMLpfykXqI6cIqxYayUUCopw==} + + '@hazae41/memory.wasm@1.0.37': + resolution: {integrity: sha512-WP+SRckje//RkeHRJZ3OWSPqPUPwzDb+5XH76/1WzSC3kfhwt0CQsf4pX/2Z86WtDAlgJg1d0VOxQfs2uI2m9Q==} + deprecated: Please use @hazae41/memory-wasm (with a hyphen instead of a dot) + + '@hazae41/mutex@2.1.0': + resolution: {integrity: sha512-69PEhdpLNuuOshb14pvK2gmOAvtJApz2vIzEgRtvVNffeGrUxXO2GyPKXqjjZ80Bvy4WA9Ov0BDIdeeqOyqwMQ==} + + '@hazae41/network.wasm@1.0.10': + resolution: {integrity: sha512-AypIZuCuY7D1JYzsZaCIc5NPYmyOVeA/O7QhwnfGcGCGMm9xYTsW5aRVEaMkvJdBrgiEFOl3jnFW88q0ElRNcA==} + peerDependencies: + '@hazae41/base16.wasm': ^1.0.9 + '@hazae41/memory.wasm': ^1.0.27 + '@hazae41/sha3.wasm': ^1.0.7 + + '@hazae41/option@1.1.4': + resolution: {integrity: sha512-ZGAVkOJJw2YIeihG8PaWs4R3KDloCgWGNDPqbATvT3kTtoyU+dJmya/UCM2P+ZwCfWYpE7nGiYJbBTlpp929Qg==} + deprecated: Please use @hazae41/result-and-option now + + '@hazae41/plume@3.0.5': + resolution: {integrity: sha512-Kx6dP09Fj/34RTLHXJ3nlTgPv9TLPsY9O0h1MjbKW8/DB4S3RhmFWLcbLeoJ870do4b0zuRQWNPDAPvSkK6AAA==} + peerDependencies: + '@hazae41/option': ^1.1.4 + + '@hazae41/result@1.3.2': + resolution: {integrity: sha512-X2RdjCC4DypMZFN3aZ7P5Zh8kEMN6SEqcMARRa/CraEU7Es/hj0yBh1Pt5eAYrRkpnWiE9+ohKSU4s7nL5L1gg==} + deprecated: Please use @hazae41/result-and-option now + + '@hazae41/ripemd.wasm@1.0.8': + resolution: {integrity: sha512-fVvXo5zl1Db9R57CpzyOkgdxNSVrUIWMaaSlUFeO9QJTgFf1uQq5lR3su3mourScfDSz+i0fA6W6An8Kn6m3QA==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/ripemd160@1.0.3': + resolution: {integrity: sha512-Zom/lqSc6hHjpokwjoGBaEnGJIW/vrseCQqnx3K+t70rxfB39ZuIveREyOx+cuchx6b1xpsGNyq+4Wbw2Igx+Q==} + + '@hazae41/rsa.wasm@1.0.14': + resolution: {integrity: sha512-EoimiN7oc8HEhT/plofp6e0fNjDiA04ZDFn886oArOzsSPuNAvePGNfmhQ1QpKssOCy7cpcmsqxjSiswfGlz3A==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/secp256k1.wasm@1.0.21': + resolution: {integrity: sha512-u2GTQU+GmtwNTIvQScFtYx9W6Sy2jiAUAout70FVYwFRzeWCoOQUOGlfPbMsTrMaCZSWjSIdNsaAjMRu2sZy7g==} + deprecated: Use @hazae41/secp256k1-wasm + peerDependencies: + '@hazae41/memory-wasm': ^1.0.37 + + '@hazae41/sha1.wasm@1.0.7': + resolution: {integrity: sha512-BQUPHbIMfgG7K2mJoX0FYaZtTYojn0KQCFRh5bJNXV5eyM2oEdfMGNv0UbllX4R/ookseTNOODdmnC9McPOarA==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/sha1@1.1.14': + resolution: {integrity: sha512-I5f2SDHVVw6TZGt06ov/dxRglozxEt0WCU0u2rr6Q7PktAYcLUNAV/CGy/TyQBgS+oMb8ucS6U/inzb8FlhEzA==} + + '@hazae41/sha3.wasm@1.0.7': + resolution: {integrity: sha512-GvgR5EZaYyGRQ84ayz3XaGODF4sg732e5rLg4y9881+EC42XQmgvIUWdnDuCUZyEvg11SgLURj3l8U2j4+Njgw==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/signals@1.0.2': + resolution: {integrity: sha512-vgcjmjyMtN5l61D8a+GWqQfhvTA5aEcgEPK5RKDPe66dTgj10M/xOgQV/McJuwgDRf0gvEvpUMStMs+OfVEIxA==} + + '@hazae41/smux@1.1.3': + resolution: {integrity: sha512-2xe71M//WSGaZ6O06TF0E/4pQBG9p56Ufx/hEoxY970Sn4spWEd45iXvNbMr3muFt3j9QUITt8DBWmlTSRfFdw==} + + '@hazae41/symbol-dispose-polyfill@1.0.2': + resolution: {integrity: sha512-Jjlfw/K3A4Abl2QijyaAq4Xx+FN8Xu6132vqGUnNj3pFzPg+bGq6pytalvTEzU0o/VEOn7/hDnJ5v1NlCjUXkQ==} + + '@hazae41/x25519.wasm@1.0.11': + resolution: {integrity: sha512-kNZ6PZxIakxKBbYb0w4pMjwpMNqsco6ZVf+Sfza7JkZZ+K5dMF7Lhwap6/tyaa0cEitj1YijIfXcBCksWmDO9w==} + peerDependencies: + '@hazae41/memory.wasm': ^1.0.27 + + '@hazae41/x25519@2.2.9': + resolution: {integrity: sha512-OuDHsdNLzgDLlaSFqLUdWM52isFs0PpDImDLbMviWsHTndcRum2jpQGnQHyP0EyA/GbdjQaVAMBteje1SIPjuA==} + + '@hazae41/x509@1.2.10': + resolution: {integrity: sha512-hfi4WQWRwQHgPZKhD/nLty2XlPJyLehaoHib/1UpffYbKcauWGhIy/GtI8TaCA6kBvpFYylUcuWmY8+kV1pquQ==} + '@hono/node-server@1.19.5': resolution: {integrity: sha512-iBuhh+uaaggeAuf+TftcjZyWh2GEgZcVGXkNtskLVoWaXhnJtC5HLHrU8W1KHDoucqO1MswwglmkWLFyiDn4WQ==} engines: {node: '>=18.14.1'} @@ -4622,6 +4811,22 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hazae-echalote-with-fleche-pr1-fix@0.4.19: + resolution: {integrity: sha512-sRCi6NLnKonD9Y+yKYjS9/hal1yXmbi5djkL9tUKA1tNuDKGqgAbKrwvdya1q2/SjdlY1lLIrOGXtW4Ucj04Kw==} + peerDependencies: + '@hazae41/aes.wasm': ^1.0.0 + '@hazae41/base16': ^1.0.18 + '@hazae41/base64': ^1.0.15 + '@hazae41/ed25519': ^2.1.21 + '@hazae41/rsa.wasm': ^1.0.10 + '@hazae41/sha1': ^1.1.14 + '@hazae41/x25519': ^2.2.9 + + hazae-fleche-pr1-fix@1.4.6: + resolution: {integrity: sha512-Jp4qt65UV7azK6a6AmPrPBTN74bPpcFBnWMGyoKlMPQrjuaSGVAjgfGTo78gQTwIw0S7POIwSHdLy7ftPgBsvA==} + peerDependencies: + '@hazae41/bitwise.wasm': ^1 + help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} @@ -6539,6 +6744,9 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + tor-hazae41@0.2.5: + resolution: {integrity: sha512-e0LfEyXthvl2XQWgVu0n2OpNtt3ldCmYXzARQK5VZqvbaC8H5YwaHM26pOwfyii/YirCXXcGnhlTz4p2hAkUFQ==} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -7408,6 +7616,20 @@ snapshots: '@braintree/sanitize-url@7.1.1': {} + '@brumewallet/wallet.wasm@1.0.2(@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37))(@hazae41/base58.wasm@1.0.8(@hazae41/memory.wasm@1.0.37))(@hazae41/base64.wasm@1.0.12(@hazae41/memory.wasm@1.0.37))(@hazae41/chacha20poly1305.wasm@1.0.8(@hazae41/memory.wasm@1.0.37))(@hazae41/ed25519.wasm@1.0.13(@hazae41/memory.wasm@1.0.37))(@hazae41/network.wasm@1.0.10(@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37))(@hazae41/memory.wasm@1.0.37)(@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37)))(@hazae41/ripemd.wasm@1.0.8(@hazae41/memory.wasm@1.0.37))(@hazae41/secp256k1.wasm@1.0.21(@hazae41/memory-wasm@1.0.46))(@hazae41/sha1.wasm@1.0.7(@hazae41/memory.wasm@1.0.37))(@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37))(@hazae41/x25519.wasm@1.0.11(@hazae41/memory.wasm@1.0.37))': + dependencies: + '@hazae41/base16.wasm': 1.0.10(@hazae41/memory.wasm@1.0.37) + '@hazae41/base58.wasm': 1.0.8(@hazae41/memory.wasm@1.0.37) + '@hazae41/base64.wasm': 1.0.12(@hazae41/memory.wasm@1.0.37) + '@hazae41/chacha20poly1305.wasm': 1.0.8(@hazae41/memory.wasm@1.0.37) + '@hazae41/ed25519.wasm': 1.0.13(@hazae41/memory.wasm@1.0.37) + '@hazae41/network.wasm': 1.0.10(@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37))(@hazae41/memory.wasm@1.0.37)(@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37)) + '@hazae41/ripemd.wasm': 1.0.8(@hazae41/memory.wasm@1.0.37) + '@hazae41/secp256k1.wasm': 1.0.21(@hazae41/memory-wasm@1.0.46) + '@hazae41/sha1.wasm': 1.0.7(@hazae41/memory.wasm@1.0.37) + '@hazae41/sha3.wasm': 1.0.7(@hazae41/memory.wasm@1.0.37) + '@hazae41/x25519.wasm': 1.0.11(@hazae41/memory.wasm@1.0.37) + '@changesets/apply-release-plan@7.0.13': dependencies: '@changesets/config': 3.1.1 @@ -7759,6 +7981,206 @@ snapshots: protobufjs: 7.4.0 yargs: 17.7.2 + '@hazae41/aes.wasm@1.0.3(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/asn1@1.3.31': + dependencies: + '@hazae41/base16': 1.0.18 + '@hazae41/binary': 1.3.5 + '@hazae41/bytes': 1.2.11 + '@hazae41/cursor': 1.2.4 + + '@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/base16@1.0.18': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + + '@hazae41/base58.wasm@1.0.8(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/base64.wasm@1.0.12(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/base64@1.0.15': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + + '@hazae41/base64url@1.0.17': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + + '@hazae41/binary@1.3.5': + dependencies: + '@hazae41/cursor': 1.2.4 + + '@hazae41/bitset@1.0.1': {} + + '@hazae41/bitwise.wasm@1.0.9(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/box@2.0.1': {} + + '@hazae41/bytes@1.2.11': {} + + '@hazae41/cadenas@0.4.2(@hazae41/base64url@1.0.17)': + dependencies: + '@hazae41/asn1': 1.3.31 + '@hazae41/base16': 1.0.18 + '@hazae41/binary': 1.3.5 + '@hazae41/bytes': 1.2.11 + '@hazae41/cascade': 2.2.2 + '@hazae41/cursor': 1.2.4 + '@hazae41/ed25519': 2.1.22(@hazae41/base64url@1.0.17) + '@hazae41/future': 1.0.3 + '@hazae41/option': 1.1.4 + '@hazae41/x509': 1.2.10 + transitivePeerDependencies: + - '@hazae41/base64url' + + '@hazae41/cascade@2.2.2': {} + + '@hazae41/chacha20poly1305.wasm@1.0.8(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/chacha20poly1305@1.0.8': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + + '@hazae41/cursor@1.2.4': {} + + '@hazae41/disposer@2.0.20': {} + + '@hazae41/ed25519.wasm@1.0.13(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/ed25519@2.1.22(@hazae41/base64url@1.0.17)': + dependencies: + '@hazae41/base64url': 1.0.17 + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + '@hazae41/result': 1.3.2 + + '@hazae41/future@1.0.3': {} + + '@hazae41/kcp@1.1.3': + dependencies: + '@hazae41/binary': 1.3.5 + '@hazae41/bytes': 1.2.11 + '@hazae41/cascade': 2.2.2 + '@hazae41/cursor': 1.2.4 + '@hazae41/future': 1.0.3 + + '@hazae41/keccak256@1.0.10': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + + '@hazae41/memory-wasm@1.0.46': {} + + '@hazae41/memory.wasm@1.0.37': {} + + '@hazae41/mutex@2.1.0': + dependencies: + '@hazae41/future': 1.0.3 + + '@hazae41/network.wasm@1.0.10(@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37))(@hazae41/memory.wasm@1.0.37)(@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37))': + dependencies: + '@hazae41/base16.wasm': 1.0.10(@hazae41/memory.wasm@1.0.37) + '@hazae41/memory.wasm': 1.0.37 + '@hazae41/sha3.wasm': 1.0.7(@hazae41/memory.wasm@1.0.37) + + '@hazae41/option@1.1.4': + dependencies: + '@hazae41/result': 1.3.2 + + '@hazae41/plume@3.0.5(@hazae41/option@1.1.4)': + dependencies: + '@hazae41/disposer': 2.0.20 + '@hazae41/future': 1.0.3 + '@hazae41/option': 1.1.4 + '@hazae41/signals': 1.0.2 + + '@hazae41/result@1.3.2': + dependencies: + '@hazae41/option': 1.1.4 + + '@hazae41/ripemd.wasm@1.0.8(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/ripemd160@1.0.3': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + '@hazae41/result': 1.3.2 + + '@hazae41/rsa.wasm@1.0.14(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/secp256k1.wasm@1.0.21(@hazae41/memory-wasm@1.0.46)': + dependencies: + '@hazae41/memory-wasm': 1.0.46 + + '@hazae41/sha1.wasm@1.0.7(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/sha1@1.1.14': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + + '@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/signals@1.0.2': + dependencies: + '@hazae41/disposer': 2.0.20 + '@hazae41/future': 1.0.3 + + '@hazae41/smux@1.1.3': + dependencies: + '@hazae41/binary': 1.3.5 + '@hazae41/bytes': 1.2.11 + '@hazae41/cascade': 2.2.2 + '@hazae41/cursor': 1.2.4 + '@hazae41/future': 1.0.3 + + '@hazae41/symbol-dispose-polyfill@1.0.2': {} + + '@hazae41/x25519.wasm@1.0.11(@hazae41/memory.wasm@1.0.37)': + dependencies: + '@hazae41/memory.wasm': 1.0.37 + + '@hazae41/x25519@2.2.9': + dependencies: + '@hazae41/box': 2.0.1 + '@hazae41/option': 1.1.4 + '@hazae41/result': 1.3.2 + + '@hazae41/x509@1.2.10': + dependencies: + '@hazae41/asn1': 1.3.31 + '@hazae41/base16': 1.0.18 + '@hazae41/base64': 1.0.15 + '@hazae41/binary': 1.3.5 + '@hono/node-server@1.19.5(hono@4.10.3)': dependencies: hono: 4.10.3 @@ -11490,6 +11912,46 @@ snapshots: property-information: 7.0.0 space-separated-tokens: 2.0.2 + hazae-echalote-with-fleche-pr1-fix@0.4.19(@hazae41/aes.wasm@1.0.3(@hazae41/memory.wasm@1.0.37))(@hazae41/base16@1.0.18)(@hazae41/base64@1.0.15)(@hazae41/base64url@1.0.17)(@hazae41/bitwise.wasm@1.0.9(@hazae41/memory.wasm@1.0.37))(@hazae41/ed25519@2.1.22(@hazae41/base64url@1.0.17))(@hazae41/rsa.wasm@1.0.14(@hazae41/memory.wasm@1.0.37))(@hazae41/sha1@1.1.14)(@hazae41/x25519@2.2.9): + dependencies: + '@hazae41/aes.wasm': 1.0.3(@hazae41/memory.wasm@1.0.37) + '@hazae41/asn1': 1.3.31 + '@hazae41/base16': 1.0.18 + '@hazae41/base64': 1.0.15 + '@hazae41/binary': 1.3.5 + '@hazae41/bitset': 1.0.1 + '@hazae41/bytes': 1.2.11 + '@hazae41/cadenas': 0.4.2(@hazae41/base64url@1.0.17) + '@hazae41/cascade': 2.2.2 + '@hazae41/cursor': 1.2.4 + '@hazae41/ed25519': 2.1.22(@hazae41/base64url@1.0.17) + '@hazae41/fleche': hazae-fleche-pr1-fix@1.4.6(@hazae41/bitwise.wasm@1.0.9(@hazae41/memory.wasm@1.0.37)) + '@hazae41/future': 1.0.3 + '@hazae41/kcp': 1.1.3 + '@hazae41/mutex': 2.1.0 + '@hazae41/option': 1.1.4 + '@hazae41/plume': 3.0.5(@hazae41/option@1.1.4) + '@hazae41/rsa.wasm': 1.0.14(@hazae41/memory.wasm@1.0.37) + '@hazae41/sha1': 1.1.14 + '@hazae41/smux': 1.1.3 + '@hazae41/x25519': 2.2.9 + '@hazae41/x509': 1.2.10 + transitivePeerDependencies: + - '@hazae41/base64url' + - '@hazae41/bitwise.wasm' + + hazae-fleche-pr1-fix@1.4.6(@hazae41/bitwise.wasm@1.0.9(@hazae41/memory.wasm@1.0.37)): + dependencies: + '@hazae41/base64': 1.0.15 + '@hazae41/binary': 1.3.5 + '@hazae41/bitwise.wasm': 1.0.9(@hazae41/memory.wasm@1.0.37) + '@hazae41/bytes': 1.2.11 + '@hazae41/cascade': 2.2.2 + '@hazae41/cursor': 1.2.4 + '@hazae41/disposer': 2.0.20 + '@hazae41/future': 1.0.3 + '@hazae41/signals': 1.0.2 + help-me@5.0.0: {} highlight.js@10.7.3: {} @@ -13908,6 +14370,55 @@ snapshots: toml@3.0.0: {} + tor-hazae41@0.2.5: + dependencies: + '@brumewallet/wallet.wasm': 1.0.2(@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37))(@hazae41/base58.wasm@1.0.8(@hazae41/memory.wasm@1.0.37))(@hazae41/base64.wasm@1.0.12(@hazae41/memory.wasm@1.0.37))(@hazae41/chacha20poly1305.wasm@1.0.8(@hazae41/memory.wasm@1.0.37))(@hazae41/ed25519.wasm@1.0.13(@hazae41/memory.wasm@1.0.37))(@hazae41/network.wasm@1.0.10(@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37))(@hazae41/memory.wasm@1.0.37)(@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37)))(@hazae41/ripemd.wasm@1.0.8(@hazae41/memory.wasm@1.0.37))(@hazae41/secp256k1.wasm@1.0.21(@hazae41/memory-wasm@1.0.46))(@hazae41/sha1.wasm@1.0.7(@hazae41/memory.wasm@1.0.37))(@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37))(@hazae41/x25519.wasm@1.0.11(@hazae41/memory.wasm@1.0.37)) + '@hazae41/aes.wasm': 1.0.3(@hazae41/memory.wasm@1.0.37) + '@hazae41/asn1': 1.3.31 + '@hazae41/base16': 1.0.18 + '@hazae41/base16.wasm': 1.0.10(@hazae41/memory.wasm@1.0.37) + '@hazae41/base58.wasm': 1.0.8(@hazae41/memory.wasm@1.0.37) + '@hazae41/base64': 1.0.15 + '@hazae41/base64.wasm': 1.0.12(@hazae41/memory.wasm@1.0.37) + '@hazae41/base64url': 1.0.17 + '@hazae41/binary': 1.3.5 + '@hazae41/bitwise.wasm': 1.0.9(@hazae41/memory.wasm@1.0.37) + '@hazae41/box': 2.0.1 + '@hazae41/bytes': 1.2.11 + '@hazae41/cadenas': 0.4.2(@hazae41/base64url@1.0.17) + '@hazae41/cascade': 2.2.2 + '@hazae41/chacha20poly1305': 1.0.8 + '@hazae41/chacha20poly1305.wasm': 1.0.8(@hazae41/memory.wasm@1.0.37) + '@hazae41/cursor': 1.2.4 + '@hazae41/disposer': 2.0.20 + '@hazae41/echalote': hazae-echalote-with-fleche-pr1-fix@0.4.19(@hazae41/aes.wasm@1.0.3(@hazae41/memory.wasm@1.0.37))(@hazae41/base16@1.0.18)(@hazae41/base64@1.0.15)(@hazae41/base64url@1.0.17)(@hazae41/bitwise.wasm@1.0.9(@hazae41/memory.wasm@1.0.37))(@hazae41/ed25519@2.1.22(@hazae41/base64url@1.0.17))(@hazae41/rsa.wasm@1.0.14(@hazae41/memory.wasm@1.0.37))(@hazae41/sha1@1.1.14)(@hazae41/x25519@2.2.9) + '@hazae41/ed25519': 2.1.22(@hazae41/base64url@1.0.17) + '@hazae41/ed25519.wasm': 1.0.13(@hazae41/memory.wasm@1.0.37) + '@hazae41/fleche': hazae-fleche-pr1-fix@1.4.6(@hazae41/bitwise.wasm@1.0.9(@hazae41/memory.wasm@1.0.37)) + '@hazae41/future': 1.0.3 + '@hazae41/kcp': 1.1.3 + '@hazae41/keccak256': 1.0.10 + '@hazae41/memory-wasm': 1.0.46 + '@hazae41/memory.wasm': 1.0.37 + '@hazae41/mutex': 2.1.0 + '@hazae41/network.wasm': 1.0.10(@hazae41/base16.wasm@1.0.10(@hazae41/memory.wasm@1.0.37))(@hazae41/memory.wasm@1.0.37)(@hazae41/sha3.wasm@1.0.7(@hazae41/memory.wasm@1.0.37)) + '@hazae41/option': 1.1.4 + '@hazae41/plume': 3.0.5(@hazae41/option@1.1.4) + '@hazae41/result': 1.3.2 + '@hazae41/ripemd.wasm': 1.0.8(@hazae41/memory.wasm@1.0.37) + '@hazae41/ripemd160': 1.0.3 + '@hazae41/rsa.wasm': 1.0.14(@hazae41/memory.wasm@1.0.37) + '@hazae41/secp256k1.wasm': 1.0.21(@hazae41/memory-wasm@1.0.46) + '@hazae41/sha1': 1.1.14 + '@hazae41/sha1.wasm': 1.0.7(@hazae41/memory.wasm@1.0.37) + '@hazae41/sha3.wasm': 1.0.7(@hazae41/memory.wasm@1.0.37) + '@hazae41/signals': 1.0.2 + '@hazae41/smux': 1.1.3 + '@hazae41/symbol-dispose-polyfill': 1.0.2 + '@hazae41/x25519': 2.2.9 + '@hazae41/x25519.wasm': 1.0.11(@hazae41/memory.wasm@1.0.37) + '@hazae41/x509': 1.2.10 + totalist@3.0.1: {} tr46@0.0.3: {} @@ -14136,6 +14647,7 @@ snapshots: abitype: 1.1.0(typescript@5.6.2)(zod@3.25.76) isows: 1.0.7(ws@8.18.3) ox: 0.9.6(typescript@5.6.2)(zod@3.25.76) + tor-hazae41: 0.2.5 ws: 8.18.3 optionalDependencies: typescript: 5.6.2 diff --git a/site/pages/docs/clients/transports/http.md b/site/pages/docs/clients/transports/http.md index dbf2427078..1727b6bd5f 100644 --- a/site/pages/docs/clients/transports/http.md +++ b/site/pages/docs/clients/transports/http.md @@ -59,6 +59,27 @@ const [blockNumber, balance, ensName] = await Promise.all([ ]) ``` +## Tor Support + +The `http` Transport supports routing requests through the Tor network for enhanced privacy. This is useful when you want to anonymize your RPC requests. + +```ts twoslash +import { createPublicClient, http } from 'viem' +import { mainnet } from 'viem/chains' + +const client = createPublicClient({ + chain: mainnet, + transport: http({ + tor: { // [!code focus:4] + snowflakeUrl: 'wss://snowflake.pse.dev/', + filter: ['eth_sendRawTransaction'], + }, + }), +}) +``` + +You can configure which RPC methods should be routed through Tor using the `filter` option. In the example above, only `eth_sendRawTransaction` calls will go through Tor, while other methods will use the regular HTTP connection. + ## Parameters ### url (optional) @@ -264,3 +285,53 @@ const transport = http('https://1.rpc.thirdweb.com/...', { }) ``` +### tor (optional) + +- **Type:** `TorClientOptions & { sharedClient?: TorClient, filter: string[] | ((input: RequestInfo | URL, init?: RequestInit) => boolean) }` + +Configuration for routing requests through the Tor network. Requires `snowflakeUrl`, `filter`. Additional TorClientOptions are documented at [tor-hazae41](https://www.npmjs.com/package/tor-hazae41). + +```ts twoslash +import { http } from 'viem' +// ---cut--- +const transport = http('https://1.rpc.thirdweb.com/...', { + tor: { // [!code focus:4] + snowflakeUrl: 'wss://snowflake.pse.dev/', + filter: ['eth_sendRawTransaction'], + }, +}) +``` + +### tor.snowflakeUrl + +- **Type:** `string` + +The URL of the Snowflake proxy server for Tor connectivity. + +### tor.filter + +- **Type:** `string[] | ((input: RequestInfo | URL, init?: RequestInit) => boolean)` + +Determines which requests should be routed through Tor: +- If an array of strings: Only RPC methods matching these names will use Tor +- If a function: Called for each request to determine if it should use Tor + +### tor.url (optional) + +- **Type:** `string` +- **Default:** Same URL as non-Tor requests + +The RPC URL to use when routing requests through Tor. + +### tor.sharedClient (optional) + +- **Type:** `TorClient` + +A shared TorClient instance to reuse across multiple transports. If not provided, a new TorClient will be created using the other TorClientOptions. + +### tor.timeout (optional) + +- **Type:** `number` +- **Default:** `max(60_000, config.timeout)` + +The timeout (in ms) for the HTTP request over Tor. diff --git a/src/clients/transports/http.test.ts b/src/clients/transports/http.test.ts index b74ae25604..06ae28e0d2 100644 --- a/src/clients/transports/http.test.ts +++ b/src/clients/transports/http.test.ts @@ -600,6 +600,241 @@ describe('request', () => { }) }) +describe('tor support', () => { + test('tor config with string array filter', async () => { + const mockTorFetch = new MockFetch('0xtor') + const mockRegularFetch = new MockFetch('0xregular') + + const transport = http('https://mockapi.com/rpc', { + fetchFn: mockRegularFetch.fetch, + tor: { + snowflakeUrl: 'wss://snowflake.example.com/', + filter: ['eth_getBalance', 'eth_sendTransaction'], // Only these methods use Tor + sharedClient: mockTorFetch as any, + }, + })({ chain: localhost }) + + // Reset counters + mockTorFetch.reset() + mockRegularFetch.reset() + + // Request that should NOT use Tor + const blockNumber = await transport.request({ method: 'eth_blockNumber' }) + expect(blockNumber).toBe('0xregular') + expect(mockRegularFetch.wasCalled()).toBe(true) + expect(mockTorFetch.callCount).toBe(0) + + // Request that SHOULD use Tor + const balance = await transport.request({ + method: 'eth_getBalance', + params: ['0x123'], + }) + expect(balance).toBe('0xtor') + expect(mockRegularFetch.callCount).toBe(1) // unchanged from previous call + expect(mockTorFetch.callCount).toBe(1) + expect(mockTorFetch.wasMethodUsed('eth_getBalance')).toBe(true) + expect(mockTorFetch.wasLastRequestBatch()).toBe(false) + }) + + test('tor config with function filter', async () => { + const mockTorFetch = new MockFetch('0xtor') + const mockRegularFetch = new MockFetch('0xregular') + + const transport = http('https://mockapi.com/rpc', { + fetchFn: mockRegularFetch.fetch, + tor: { + snowflakeUrl: 'wss://snowflake.example.com/', + filter: (body) => body.some((req) => req.method.includes('Balance')), // Function-based filter + sharedClient: mockTorFetch as any, + }, + })({ chain: localhost }) + + // Reset counters + mockTorFetch.reset() + mockRegularFetch.reset() + + // Request that should NOT use Tor + await transport.request({ method: 'eth_blockNumber' }) + expect(mockRegularFetch.callCount).toBe(1) + expect(mockTorFetch.callCount).toBe(0) + + // Request that SHOULD use Tor (contains 'Balance') + await transport.request({ method: 'eth_getBalance', params: ['0x123'] }) + expect(mockRegularFetch.callCount).toBe(1) // unchanged + expect(mockTorFetch.callCount).toBe(1) + }) + + test('tor config with batch requests', async () => { + const mockTorFetch = new MockFetch('0xtor') + const mockRegularFetch = new MockFetch('0xregular') + + const transport = http('https://mockapi.com/rpc', { + batch: true, + fetchFn: mockRegularFetch.fetch, + tor: { + snowflakeUrl: 'wss://snowflake.example.com/', + filter: ['eth_getBalance'], // Only balance calls use Tor + sharedClient: mockTorFetch as any, + }, + })({ chain: localhost }) + + // Reset counters + mockTorFetch.reset() + mockRegularFetch.reset() + + // Batch with mixed requests - one should trigger Tor for the entire batch + const promises = [ + transport.request({ method: 'eth_blockNumber' }), + transport.request({ method: 'eth_getBalance', params: ['0x123'] }), // This triggers Tor + ] + + const results = await Promise.all(promises) + + // Since one request in the batch uses Tor, the entire batch should go through Tor + expect(mockTorFetch.callCount).toBe(1) + expect(mockRegularFetch.callCount).toBe(0) + expect(results).toEqual(['0xtor1', '0xtor2']) + + // Verify the batch contained both methods using helper methods + expect(mockTorFetch.wasLastRequestBatch()).toBe(true) + expect(mockTorFetch.getLastRequestBody()).toHaveLength(2) + expect(mockTorFetch.wasMethodUsed('eth_blockNumber')).toBe(true) + expect(mockTorFetch.wasMethodUsed('eth_getBalance')).toBe(true) + }) + + test('tor error handling', async () => { + const mockTorFetch = new MockFetch('0xtor') + mockTorFetch.setErrorResponse({ + jsonrpc: '2.0', + id: 1, + error: { code: -32601, message: 'Method not found via Tor' }, + }) + + const transport = http('https://mockapi.com/rpc', { + tor: { + snowflakeUrl: 'wss://snowflake.example.com/', + filter: ['eth_getBalance'], + sharedClient: mockTorFetch as any, + }, + })({ chain: localhost }) + + await expect(() => + transport.request({ method: 'eth_getBalance', params: ['0x123'] }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [MethodNotFoundRpcError: The method "eth_getBalance" does not exist / is not available. + + URL: http://localhost + Request body: {"method":"eth_getBalance","params":["0x123"]} + + Details: Method not found via Tor + Version: viem@x.y.z] + `) + }) + + class MockFetch { + public callCount = 0 + public usedMethods: string[] = [] + public requestBodies: any[] = [] + private responseOverride: Response | undefined + private errorResponse: any + + constructor(private defaultResult = '0xmock') {} + + setResponse(response: Response) { + this.responseOverride = response + return this + } + + setErrorResponse(error: any) { + this.errorResponse = error + return this + } + + reset() { + this.callCount = 0 + this.usedMethods = [] + this.requestBodies = [] + this.responseOverride = undefined + this.errorResponse = undefined + return this + } + + // Helper methods for testing + wasCalled(): boolean { + return this.callCount > 0 + } + + wasMethodUsed(method: string): boolean { + return this.usedMethods.includes(method) + } + + getLastRequestBody() { + return this.requestBodies[this.requestBodies.length - 1] + } + + wasLastRequestBatch(): boolean { + const lastBody = this.getLastRequestBody() + return Array.isArray(lastBody) + } + + // Works as both fetch function and TorClient.fetch method + fetch = async (_input: RequestInfo | URL | string, init?: RequestInit) => { + this.callCount++ + + // Track request details + if (init?.body && typeof init.body === 'string') { + try { + const body = JSON.parse(init.body) + this.requestBodies.push(body) + + if (Array.isArray(body)) { + this.usedMethods.push(...body.map((req: any) => req.method)) + } else { + this.usedMethods.push(body.method) + } + } catch { + // ignore parse errors + } + } + + // Return custom response if set + if (this.responseOverride) { + return this.responseOverride + } + + // Return error response if set + if (this.errorResponse) { + return new Response(JSON.stringify(this.errorResponse), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + } + + // Determine if this is a batch request + const isBatch = + this.requestBodies[this.requestBodies.length - 1] && + Array.isArray(this.requestBodies[this.requestBodies.length - 1]) + + if (isBatch) { + const batchSize = + this.requestBodies[this.requestBodies.length - 1].length + const results = Array.from({ length: batchSize }, (_, i) => ({ + result: `${this.defaultResult}${i + 1}`, + })) + return new Response(JSON.stringify(results), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + } + + return new Response(JSON.stringify({ result: this.defaultResult }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + } + } +}) + test('no url', () => { expect(() => http()({})).toThrowErrorMatchingInlineSnapshot( ` diff --git a/src/clients/transports/http.ts b/src/clients/transports/http.ts index e6710ea9be..2bbbde5f35 100644 --- a/src/clients/transports/http.ts +++ b/src/clients/transports/http.ts @@ -1,3 +1,4 @@ +import { TorClient, type TorClientOptions } from 'tor-hazae41' import { RpcRequestError } from '../../errors/request.js' import { UrlRequiredError, @@ -5,10 +6,11 @@ import { } from '../../errors/transport.js' import type { ErrorType } from '../../errors/utils.js' import type { EIP1193RequestFn, RpcSchema } from '../../types/eip1193.js' -import type { RpcRequest } from '../../types/rpc.js' +import type { RpcRequest, RpcResponse } from '../../types/rpc.js' import { createBatchScheduler } from '../../utils/promise/createBatchScheduler.js' import { getHttpRpcClient, + type HttpRpcClient, type HttpRpcClientOptions, } from '../../utils/rpc/http.js' @@ -62,8 +64,38 @@ export type HttpTransportConfig< rpcSchema?: rpcSchema | RpcSchema | undefined /** The timeout (in ms) for the HTTP request. Default: 10_000 */ timeout?: TransportConfig['timeout'] | undefined + /** + * Configuration for routing requests through the Tor network. + * Requires `snowflakeUrl`, `filter`. + * Other TorClientOptions are documented at https://www.npmjs.com/package/tor-hazae41 + * @link https://viem.sh/docs/clients/transports/http#tor-support + */ + tor?: TorClientOptions & { + /** + * Determines which requests should be routed through Tor. + * - If an array of strings: Only RPC methods matching these names will use Tor + * - If a function: Called for each request to determine if it should use Tor + */ + filter: string[] | RpcRequestFilter + /** + * The RPC url to use when routing through tor. + * Default: same url as non-tor requests. + */ + url?: string + /** + * A shared TorClient instance to reuse across multiple transports. + * If not provided, a new TorClient will be created using the other TorClientOptions. + */ + sharedClient?: TorClient + /** + * The timeout (in ms) for the HTTP request over Tor. Default: max(60_000, config.timeout). + */ + timeout?: number + } } +export type RpcRequestFilter = (body: RpcRequest[]) => boolean + export type HttpTransport< rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false, @@ -112,13 +144,19 @@ export function http< const url_ = url || chain?.rpcUrls.default.http[0] if (!url_) throw new UrlRequiredError() - const rpcClient = getHttpRpcClient(url_, { + const rpcClientOptions = { fetchFn, fetchOptions, onRequest: onFetchRequest, onResponse: onFetchResponse, timeout, - }) + } + + const rpcClient = getHttpRpcClient(url_, rpcClientOptions) + + const torTools = config.tor + ? makeTorTools(config.tor, url_, rpcClientOptions, timeout) + : undefined return createTransport( { @@ -134,23 +172,26 @@ export function http< shouldSplitBatch(requests) { return requests.length > batchSize }, - fn: (body: RpcRequest[]) => - rpcClient.request({ - body, - }), + fn: (body: RpcRequest[]) => { + if (torTools?.filter(body)) { + return torTools.rpcClient.request({ body }) + } + return rpcClient.request({ body }) + }, sort: (a, b) => a.id - b.id, }) - const fn = async (body: RpcRequest) => - batch - ? schedule(body) - : [ - await rpcClient.request({ - body, - }), - ] + let responseData: Awaited> | RpcResponse[] + + if (batch) { + responseData = await schedule(body) + } else if (torTools?.filter([body])) { + responseData = [await torTools.rpcClient.request({ body })] + } else { + responseData = [await rpcClient.request({ body })] + } - const [{ error, result }] = await fn(body) + const [{ error, result }] = responseData if (raw) return { error, result } if (error) @@ -173,3 +214,30 @@ export function http< ) } } + +function makeTorTools( + torConfig: NonNullable, + url: string, + rpcClientOptions: HttpRpcClientOptions, + timeout: number, +): { rpcClient: HttpRpcClient; filter: (body: RpcRequest[]) => boolean } { + const tor = torConfig.sharedClient ?? new TorClient(torConfig) + + let filter: RpcRequestFilter + + if (typeof torConfig.filter === 'function') { + filter = torConfig.filter + } else { + const filterMethods = torConfig.filter + filter = (body) => body.some((req) => filterMethods.includes(req.method)) + } + + return { + rpcClient: getHttpRpcClient(torConfig.url ?? url, { + ...rpcClientOptions, + fetchFn: (input, init) => tor.fetch(input.toString(), init), + timeout: torConfig.timeout ?? Math.max(timeout, 60_000), + }), + filter, + } +} diff --git a/src/manualTorTest.ts b/src/manualTorTest.ts new file mode 100644 index 0000000000..66f07ea5c1 --- /dev/null +++ b/src/manualTorTest.ts @@ -0,0 +1,189 @@ +/** + * Manual Tor Integration Test + * + * To run: + * npx tsx src/manualTorTest.ts + * + * This is a standalone Node.js program that tests Tor integration + * without any testing frameworks. + */ + +import { TorClient } from 'tor-hazae41' +import { createPublicClient } from './clients/createPublicClient.js' +import { http } from './clients/transports/http.js' + +const RPC_URL = 'https://eth.llamarpc.com/' +const SNOWFLAKE_URL = 'wss://snowflake.pse.dev/' + +// Only run if this file is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + if (process.argv.includes('--minimal')) { + // Minimal version demonstrates small code + runMinimalExample() + } else { + // This version is a bit verbose because we're actually doing some nice-ish + // testing. + runTest() + } +} + +async function runMinimalExample() { + const client = createPublicClient({ + transport: http(RPC_URL, { + tor: { + filter: ['eth_getBalance'], + snowflakeUrl: SNOWFLAKE_URL, + onLog: (message, type) => log(`[tor] [${type}] ${message}`), + }, + }), + }) + + const balance = await client.getBalance({ + address: '0x0000000000000000000000000000000000000000', + }) + + console.log(`${balance / 10n ** 18n} ETH burned at 0x0`) + process.exit(0) +} + +// Main execution +async function runTest() { + try { + log('Manual Tor Integration Test Starting...') + + // Check RPC connection first + const rpcConnected = await checkRpcConnection() + if (!rpcConnected) { + process.exit(1) + } + + // Run the test + await testTorIntegration() + + log('\nāœ… All tests completed successfully!') + process.exit(0) + } catch (error) { + log('\nšŸ’„ Test execution failed:', error) + process.exit(1) + } +} + +async function testTorIntegration() { + log('Starting Tor Integration Test...') + + // Track usage flags + let torUsed = false + let regularUsed = false + const torUsedMethods: string[] = [] + + // Mock regular fetch that tracks usage + const regularFetch = async (input: RequestInfo | URL, init?: RequestInit) => { + log('>>> Regular fetch called for:', init?.body || 'no body') + regularUsed = true + + // Use actual fetch for the request + return fetch(input, init) + } + + const tor = new TorClient({ + snowflakeUrl: SNOWFLAKE_URL, + onLog: (message, type) => log(`[tor] [${type}] ${message}`), + }) + + // Wrapped TorClient that tracks usage + const wrappedTorClient = { + fetch: async (_input: string, init?: RequestInit) => { + log('>>> Tor fetch called for:', init?.body || 'no body') + torUsed = true + + // Parse the request to see which methods used Tor + if (init?.body && typeof init.body === 'string') { + try { + const body = JSON.parse(init.body) + const requests = Array.isArray(body) ? body : [body] + const methods = requests.map((req: any) => req.method) + torUsedMethods.push(...methods) + log('>>> Tor routing methods:', methods) + } catch (e) { + log('>>> Failed to parse Tor request body:', e) + } + } + + // Forward to the real RPC endpoint over tor + return tor.fetch(RPC_URL, init) + }, + } + + try { + const client = createPublicClient({ + transport: http(RPC_URL, { + fetchFn: regularFetch, + tor: { + filter: ['eth_getBalance'], // Array of method names + sharedClient: wrappedTorClient as any, + snowflakeUrl: SNOWFLAKE_URL, + }, + }), + }) + + const balance = await client.getBalance({ + address: '0x0000000000000000000000000000000000000000', + }) + + assert(typeof balance === 'bigint', 'Balance should be a bigint') + assert(torUsed, 'Tor should have been used with array filter') + assert( + !regularUsed, + 'Regular fetch should NOT have been used with array filter', + ) + log(`Balance with array filter: ${balance} (burned at 0x0)`) + + log('\nšŸŽ‰ All tests passed! Tor integration is working correctly.') + } catch (error) { + log('\nāŒ Test failed:', error) + throw error + } +} + +async function log(message: string, ...args: any[]) { + console.log(`[${new Date().toISOString()}] ${message}`, ...args) +} + +async function assert(condition: boolean, message: string) { + if (!condition) { + throw new Error(`Assertion failed: ${message}`) + } + log(`āœ“ ${message}`) +} + +// Check if we can connect to the RPC endpoint +async function checkRpcConnection() { + try { + const response = await fetch(RPC_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_blockNumber', + params: [], + id: 1, + }), + }) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + + const result = await response.json() + if (result.error) { + throw new Error(`RPC Error: ${result.error.message}`) + } + + log(`āœ“ RPC connection successful. Latest block: ${result.result}`) + return true + } catch (error) { + log(`āŒ Failed to connect to RPC at ${RPC_URL}:`, error) + log(`Please make sure rpc is running at ${RPC_URL}`) + return false + } +} diff --git a/src/package.json b/src/package.json index 0a41f6b4db..79f7e2587f 100644 --- a/src/package.json +++ b/src/package.json @@ -203,6 +203,7 @@ "abitype": "1.1.0", "isows": "1.0.7", "ox": "0.9.6", + "tor-hazae41": "0.2.5", "ws": "8.18.3" }, "license": "MIT",