From 359080b4aee4571810bc992bc8db5da0224d24d5 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Wed, 19 Mar 2025 13:45:44 +0530 Subject: [PATCH 01/18] feat(#72): atlas off-chain code --- atlas/CHANGELOG.md | 5 + atlas/LICENSE | 20 ++++ atlas/app/Main.hs | 7 ++ atlas/cabal.project | 101 ++++++++++++++++ .../data/compiled-scripts/smart-wallet.plutus | 5 + atlas/src/ZkFold/Cardano/SmartWallet/Api.hs | 32 ++++++ .../ZkFold/Cardano/SmartWallet/Constants.hs | 19 +++ atlas/src/ZkFold/Cardano/SmartWallet/Types.hs | 38 ++++++ .../Cardano/SmartWallet/Types/Validator.hs | 108 ++++++++++++++++++ atlas/test/Main.hs | 4 + .../zkfold-smart-contract-wallet-server.cabal | 86 ++++++++++++++ 11 files changed, 425 insertions(+) create mode 100644 atlas/CHANGELOG.md create mode 100644 atlas/LICENSE create mode 100644 atlas/app/Main.hs create mode 100644 atlas/cabal.project create mode 100644 atlas/data/compiled-scripts/smart-wallet.plutus create mode 100644 atlas/src/ZkFold/Cardano/SmartWallet/Api.hs create mode 100644 atlas/src/ZkFold/Cardano/SmartWallet/Constants.hs create mode 100644 atlas/src/ZkFold/Cardano/SmartWallet/Types.hs create mode 100644 atlas/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs create mode 100644 atlas/test/Main.hs create mode 100644 atlas/zkfold-smart-contract-wallet-server.cabal diff --git a/atlas/CHANGELOG.md b/atlas/CHANGELOG.md new file mode 100644 index 0000000..7bcd9af --- /dev/null +++ b/atlas/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for zkfold-smart-contract-wallet-server + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/atlas/LICENSE b/atlas/LICENSE new file mode 100644 index 0000000..c85fe66 --- /dev/null +++ b/atlas/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2025 Sourabh Aggarwal + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/atlas/app/Main.hs b/atlas/app/Main.hs new file mode 100644 index 0000000..d63a98d --- /dev/null +++ b/atlas/app/Main.hs @@ -0,0 +1,7 @@ +module Main where + +import ZkFold.Cardano.SmartWallet.Constants qualified as Constants + +main :: IO () +main = do + putStrLn "Hello, Haskell!" diff --git a/atlas/cabal.project b/atlas/cabal.project new file mode 100644 index 0000000..6a0e3ef --- /dev/null +++ b/atlas/cabal.project @@ -0,0 +1,101 @@ +repository cardano-haskell-packages + url: https://chap.intersectmbo.org/ + secure: True + root-keys: + 3e0cce471cf09815f930210f7827266fd09045445d65923e6d0238a6cd15126f + 443abb7fb497a134c343faf52f0b659bd7999bc06b7f63fa76dc99d631f9bea1 + a86a1f6ce86c449c46666bda44268677abf29b5b2d2eb5ec7af903ec2f117a82 + bcec67e8e99cabfa7764d75ad9b158d72bfacf70ca1d0ec8bc6b4406d1bf8413 + c00aae8461a256275598500ea0e187588c35a5d5d7454fb57eac18d9edb86a56 + d4a35cd3121aa00d18544bb0ac01c3e1691d618f462c46129271bccf39f7e8ee + +packages: . + +tests: true + +source-repository-package + type: git + location: https://github.com/geniusyield/atlas + tag: 375adbd73ae8d7bd333465dcca335e71132dfa77 + --sha256: FIXME: + +-------- Begin contents from @atlas@'s @cabal.project@ file. -------- + +-- repeating the index-state for hackage to work around hackage.nix parsing limitation +index-state: 2025-01-01T23:24:19Z + +-- NOTE: Do not bump chap index beyond that used by target cardano-node version. +index-state: + , hackage.haskell.org 2025-01-01T23:24:19Z + , cardano-haskell-packages 2025-02-11T21:18:23Z + +-- TODO: Default value should be @direct@ in upcoming 3.10 version of cabal, omit this line then. +test-show-details: direct + +package cardano-crypto-praos + flags: -external-libsodium-vrf + +source-repository-package + type: git + location: https://github.com/maestro-org/haskell-sdk + tag: 3e39a6d485d7c6f98222b1ca58aed2fb45e5ff27 + --sha256: sha256-plfrSgirKf7WGESYvEBqBkR1s673Qd0ZhGs0KzGfOig= + +-- TODO: Temporary, until proposed changes are in upstream (track https://github.com/mlabs-haskell/clb/pull/72) +source-repository-package + type: git + location: https://github.com/sourabhxyz/clb + tag: 1b084647dc9118520c1cc615cf2fa7c3dd8a394e + --sha256: sha256-QliJng5PmJIRJd/l644T0zxBBOKhuMkIgeu1B5ymfVU= + subdir: + clb + emulator + +-- TODO: Temporary, remove once we are on 10.9 or above. Fix relates to issue: https://github.com/IntersectMBO/cardano-api/issues/714. +source-repository-package + type: git + location: https://github.com/sourabhxyz/cardano-api + tag: 14674d6b099e8fc36e5044e206bfc32164f75cee + --sha256: sha256-Qr4rv9bLz+wJdICYjxDVnnzgsVwx+wsU+tSFwDYr/kE= + subdir: + cardano-api + cardano-api-gen + +-- Using latest version which is not on CHaP. +source-repository-package + type: git + location: https://github.com/IntersectMBO/cardano-node + tag: 36161c9e00850616bd64b8db0c06bd77eb1ec951 + --sha256: sha256-eaaj6kQb/TMGmxAhw355x4kBJfznKAem+rhC9SkmePs= + subdir: + cardano-node + cardano-testnet + trace-dispatcher + trace-forward + +-- Temporary until latest version is available on Hackage (or CHaP for that matter). Track https://github.com/IntersectMBO/cardano-addresses/issues/294. +source-repository-package + type: git + location: https://github.com/IntersectMBO/cardano-addresses + tag: d611632fc3d616d5b4038a70f864bf2613c125d0 + --sha256: sha256-vQ2XB95kw05IZuRkyK4cPQtaKZ1bZAoLtN9GrOOwQvM= + +------ Following is mostly from @cardano-node@'s @cabal.project@ file. ------- + +allow-newer: + katip:Win32 + , ekg-wai:time + +-- Using RDRAND instead of /dev/urandom as an entropy source for key +-- generation is dubious. Set the flag so we use /dev/urandom by default. +package cryptonite + flags: -support_rdrand + +package cardano-node + flags: -systemd + +package bitvec + flags: -simd + +-------- End contents from @cardano-node@'s @cabal.project@ file. -------- +-------- Begin contents from @atlas@'s @cabal.project@ file. -------- \ No newline at end of file diff --git a/atlas/data/compiled-scripts/smart-wallet.plutus b/atlas/data/compiled-scripts/smart-wallet.plutus new file mode 100644 index 0000000..865e982 --- /dev/null +++ b/atlas/data/compiled-scripts/smart-wallet.plutus @@ -0,0 +1,5 @@ +{ + "type": "PlutusScriptV3", + "description": "", + "cborHex": "59276259275f010100333232323232323232323232323232323232323232323232323232323232322225932329004914801c888a40224452007222222222222222232329013912801c9400a44a007250029191180100a981f912c800c60052219002912cacc80148a402644666ae68cdc7802001400200446400a45201322333573466e1c00c00600100223002464012452003222290049148014a00523304e225900189826c00a443200522300600391802801488c01800e520032300600392a999ab9a3371e00e0022260a3001004089803001a4600a00523005002240b6445200722222222222222223060007022600c00624a0041901591981f112c800c600522155333573466e3c00801446000226008002303500a92c994810c888888888888888a40a244444444444444444444444444a6646464644a666ae68c8c8c8c8cde319bc43232323232323232323232323232323232323232323232323232323232323232323232323232323376c66ed8cddb19bb801c0283377066e18cdc1198440081500e02c24105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c05266ed8cddb19bb63376c66ed8cddb19bb63376c66ed8cddb19bb63376c66ee0cc220040f40f0ddd82a19bb803d37760a666ee00f0ddd82919bb803b37760a26eec140cddc19b863370066e18cdc119b863370466e18cdc119b863370066e18cdc001e980b80e24105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c0289041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b863370066e18cdc001e19b8633704602e0ae0389041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c0289041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc019b863370007666e18cdc1180b82b00e24105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0700a24105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df07011a4105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c60160409041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0700c1bb73377066e18cdc119b863370466e18cdc119b863370400c0029041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df07011a4105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c03a9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0701aa4105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c6eec130cddc19b86337046610e0207a07066110020400469041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df071bbb04f3377066e18cdc019b863370466e18cdc119b863370402266e18cdc000f01c24105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b863370066e18cdc000781ba4105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc101081b24105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c02a9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df07180580724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c0346edccddc19b863370466e18cdc101a00124105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c02a9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0700d9bb733770a04c6466ed8cddb1bbb043337700026eec108cddc19844808008009bbb0413370c66e09409ccc220040700712082808080e0fffffffffbdfe5ffbf81a4fbceaa80bbe88488b0e79983a9dfcea9a69dedbe0e3377005403066ee007ccddb19bb6377609466ee00a8064cddc00f99bb63376c6eec124cddc01500d19bb801f3376c66ed8ddd82419bb802a01b3377003e66ed8ddd82719bb801f3376c6eec134cddc00f99bb6377608e66ee007ccddb00c99bb801f377608a6edccddc19b863370066e18cdc019b8633702900019b863370266e18cdc099b863370266e18cdc099b863370466e18cdc0a40000b69041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df07006a4105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc119b863370466e18cdc119b863370404600c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df07000a4105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc001d80a24105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0701aa4105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b863370404001a9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc119b8633704661100202a06866e18cdc000799b86337040420649041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0700124105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b863370401c01a9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b863370405406a9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc100f99b863370066e18cdc001e99b863370405406c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc100f99b863370066e18cdc001e19b86337040540689041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc100f99b863370066e18cdc001d99b86337040540669041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e18cdc100f99b863370007466e18cdc100f99b863370007266e18cdc100f99b863370007066e18cdc100f99b863370006e66e18cdc100f81924105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0724105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df071bbb4893097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb003370c0049041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b860024820a02020383ffffffffef7f97fefe0693ef3aaa02efa21222c39e660ea77f3aa69a77b6f838cdc000100899b8000300c3370c0069041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b860044820a02020383ffffffffef7f97fefe0693ef3aaa02efa21222c39e660ea77f3aa69a77b6f838cdc300124105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c66e000d0010cdc000301499b8000c00a307d002300b02d3370c66e08cc1ec12d40640912082808080e0fffffffffbdfe5ffbf81a4fbceaa80bbe88488b0e79983a9dfcea9a69dedbe0e3370c66e0801c0552082808080e0fffffffffbdfe5ffbf81a4fbceaa80bbe88488b0e79983a9dfcea9a69dedbe0e3370c66e0803c0092082808080e0fffffffffbdfe5ffbf81a4fbceaa80bbe88488b0e79983a9dfcea9a69dedbe0e3370c0049041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0719b860024820a02020383ffffffffef7f97fefe0693ef3aaa02efa21222c39e660ea77f3aa69a77b6f838cdc100781099b8048008038cde5250379066e2803922101020051323370c66e080040052082808080e0fffffffffbdfe5ffbf81a4fbceaa80bbe88488b0e79983a9dfcea9a69dedbe0e500f3370c66e0009cc0040912082808080e0fffffffffbdfe5ffbf81a4fbceaa80bbe88488b0e79983a9dfcea9a69dedbe0e307100637760586eec0c8ddd8149bbb02b501133794940de419b8a00548901010033794940de419b8a00448901040033794940de4006280219bca4a06f20cdc5000a45010300500e50021323370c66e080040052082808080e0fffffffffbdfe5ffbf81a4fbceaa80bbe88488b0e79983a9dfcea9a69dedbe0e5001133794940de42805099b8633702a002900124105010101c1fffffffff7bfcbff7f0349f79d550177d1091161cf330753bf9d534d3bdb7c1c2660aca00c0646f0922016093e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8003378866ed8004cddc0018011bc202e377602e6eec054cde5250379066e28004cdc500a80a19b8a500333714666f25282408002466e28ccde4a5048100044cdc5199bc94a0902000819b8a333792941204000f33714666f25282408001c66e28ccde4a5048100034cdc5199bc94a0902000619b8a333792941204000b33714666f25282408001466e28ccde4a5048100024cdc5199bc94a09020004199bc94a0902000388999ab9a3370e900119b8633704660ba00c05e660b8a00405a9041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0740020042002266f29281bc85001133714a00266e28050cdc5009809099b8a50013371402a028266e28cdc500d19b8a0190183371402e66e2805805460044800c888cde5251379066073003003c00c00b00333379294520403370c0029041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df07600603780002441004640064446049003333573466e1d2004303a013800400b003303b013c00cc8c8cccc0048c0fc004cc0048ccd5cd19b874800000600100222333573466e1c008006001002801c00522100c00e00291100801b00030000011911919119111980180100098239112c80144c014006442b200713008004910c802488ca401644b26602200a0051593300d004001899806004003460048ac980800244cc03002002a266006b2602000318004600600f800000c0c140889640063002910c80148964c05000626600e00c007159330150090028ac99808804000c4cc040030ccc15c8896400a200322133005001801801400c0060038c009133007801802401800600c11489640063000910c80148964c0200062600c00718010304422590018c00244320052259300700189803001c6004088ccd5cd19b8f00200180040096404e32d22220034886400a44440066006b202719691111999804802001801000a44320052222333300b004003002001300393303d4800804e444520092290029199ab9a3371e04a00300080148ccd5cd19b8f025001800400860000460041111480248a400a4666ae68cdc780e800c0020052333573466e3c074006001002024006444646464aa666ae68cdc3a4000004226465001375c6ae8400e6eb8d5d08014c8c8c954ccd5cd19b874800000844c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8ca0026eb8d5d080ccdd71aba10189bae3574202f375c6ae8405a6eb8d5d080acdd71aba10149bae35742027375c6ae8404a6eb8d5d0808cdd71aba10109bae3574201f375c6ae8403a6eb8d5d0806cdd69aba100c9bad35742017375a6ae8402a6eb4d5d0804cdd69aba10089bad3574200f375a6ae8401a6eb4d5d0802cdd69aba10049bad35742007375a6ae8400a6eb4d5d0800cc8c8c954ccd5cd19b874800000844dd69aba10011130464901035054310035573c0046aae74004dd51aba135744002357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba200111302b4901035054310035573c0046aae74004dd51aba10019919192a999ab9a3370e900000108c004dd71aba100108aa999ab9a3370e900100108c00cc8c8c954ccd5cd19b874800000844ca0026eb8d5d08014dd71aba10019bad357426ae880046ae8800444c0b92401035054310035573c0046aae74004dd51aba1001089815a49035054310035573c0046aae74004dd51aba135744002357440026ae8800444c0992401035054310035573c0046aae74004dd50010c8c8c954ccd5cd19b874800000844ca002646464aa666ae68cdc3a40000042264646464646464646464646464646464650013002357420253002357420233302f02235742021375a6ae8403e66006a04a6ae8403a6605e03e6ae8403666604c046eb4d5d08064c8c8c954ccd5cd19b874800000846002646464aa666ae68cdc3a4000004230013303375a6ae8400660646ae84d5d1000844c0f5241035054310035573c0046aae74004dd51aba10019919192a999ab9a3370e900000108c004cc0cdd69aba100198191aba13574400211303d4901035054310035573c0046aae74004dd51aba13574400211303a4901035054310035573c0046aae74004dd51aba100b99817bae35742015333026232323255333573466e1d2000002118019bae357420021155333573466e1d20020021180498129aba100108aa999ab9a3370e900200108c01cc09cd5d080084554ccd5cd19b8748018008460026eb4d5d0800cc08cd5d09aba200108aa999ab9a3370e900400108c02cc090d5d080084554ccd5cd19b87480280084600a6eb4d5d0800cc088d5d09aba200108981da481035054310035573c0046aae74004dd50008159aba1009998018159aba10089bae3574200f3330260203302602e232323255333573466e1d20000021180108aa999ab9a3370e900100108c0104554ccd5cd19b8748010008460002260769201035054310035573c0046aae74004dd50009aba10069981780f1aba100598009aba100498009aba135744008302375a605a4646464aa666ae68cdc3a4000004230013023357420033024357426ae880042260729201035054310035573c0046aae74004dd50009811bae357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae8800444c0992401035054310035573c0046aae74004dd51aba10029aba10019919192a999ab9a3370e900000108c00cdd71aba100108aa999ab9a3370e900100108c024c040d5d0800ccc04c068d5d09aba200108aa999ab9a3370e900200108c01cc048d5d080084554ccd5cd19b8748018008460026eb4d5d0800cc038d5d09aba200108aa999ab9a3370e900400108c02cc03cd5d080084554ccd5cd19b87480280084600a6eb4d5d0800cc034d5d09aba20010898132481035054310035573c0046aae74004dd51aba135744002357440022260449201035054310035573c0046aae74004dd5000c5268980fa490350543500180e912c800c60012215900289802000c6004180e1112a999ab9a3370e90000008880108998019980700100119b81001480088c8c8c954ccd5cd19b874800000844ca0026eb4d5d08014c028d5d0800cc8c8c954ccd5cd19b874800000844ca0126601e0326ae8400a6ae840066601eeb8d5d09aba20011aba20011155333573466e1d2002002118009980700c1aba10019919192a999ab9a3370e900000108c004dd69aba10019bad357426ae88004226048921035054310035573c0046aae74004dd51aba1357440021155333573466e1d200400211805999808006bad357420033300e75c6ae84d5d100084554ccd5cd19b87480180084600e6601c0306ae8400422aa666ae68cdc3a401000422646500d3301001a357420073301b00f3574200533301200f75a6ae840072646464aa666ae68cdc3a400000423001375a6ae840066eb4d5d09aba20010898132481035054310035573c0046aae74004dd51aba13574400322330170020010d5d10009aba20011155333573466e1d200a002118029980700c1aba10019919192a999ab9a3370e90000010899808bae357420022260489201035054310035573c0046aae74004dd51aba1357440021155333573466e1d200c00211801089810a481035054310035573c0046aae74004dd51aba1357440023574400222603a9201035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c034c020d5d0800ccc025d69aba1357440021155333573466e1d20020021180998041aba100199804bad357426ae8800422a64a666ae68cdc3a4008006230033009357420053001357426ae8800822aa666ae68cdc3a400c006226500b300a35742007300235742003375a6ae84d5d10008d5d100108aa999ab9a3370e900400188c024c024d5d08014dd69aba1357440041155333573466e1d200a0031180a98049aba100208aa999ab9a3370e900600188c044c024d5d08014dd69aba1357440041155333573466e1d200e003118029bae35742005375c6ae84d5d100104554ccd5cd19b874804000c4600e6eb8d5d08014dd69aba1357440041155333573466e1d20120031180098049aba100298049aba1357440041155333573466e1d20140031180798049aba100208980ea4810350543100232323255333573466e1d2000002118009bae35742002115325333573466e1d20020031180298009aba100208aa999ab9a3370e900200188c00cdd71aba100298009aba13574400411302149010350543100232323255333573466e1d20000021180098081aba100108aa999ab9a3370e900100108c0084554ccd5cd19b8748010008460082260489201035054310035573c0046aae74004dd50009aab9e00235573a0026ea8004d55cf0011aab9d00137540024646464aa666ae68cdc3a4000004230013007357420021155333573466e1d20020021180198039aba100108aa999ab9a3370e900200108c014dd71aba100108980da481035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c004dd71aba10019bad357426ae880042260349201035054310035573c0046aae74004dd500091919192a999ab9a3370e90000010899194004c8c8c954ccd5cd19b87480000084600260146ae84006660164646464aa666ae68cdc3a400000423001300e357420021155333573466e1d200200211328019bad35742005375a6ae840066eb4d5d09aba20011aba20011130224901035054310035573c0046aae74004dd50009aba13574400211301e491035054310035573c0046aae74004dd51aba10039998053ae50093574200532323255333573466e1d20000021180008aa999ab9a3370e900100108c014dd71aba100108aa999ab9a3370e900200108c00cd5d0800844c079241035054310035573c0046aae74004dd51aba1001998043ae357426ae880046ae88004d5d100088980ca49035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c004dd71aba100108aa999ab9a3370e900100108c00cdd71aba100108980c249035054310035573c0046aae74004dd5000911919192a999ab9a3370e900100108c0084554ccd5cd19b874800000846002600a6ae84004226030921035054310035573c0046aae74004dd5000899800bae75a4464460046eac004c05488cccd55cf800940008ca007001300635573a003300535573c00298021aba20031aba10028001119b86337020040029041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df071119b86337040040029041404040707ffffffffdeff2ffdfc0d27de755405df442445873ccc1d4efe754d34ef6df0718081112a999ab9a3370e9000000889808a48103505433001155333573466e200052000113300333702900000119b814800000444ca00266e1000c00666e1000800466008004002601e444aa666ae68cdc3a400000222004226600600266e1800800480048c8c8c954ccd5cd19b87480000084600422aa666ae68cdc3a40040042300011300f4901035054310035573c0046aae74004dd5000911919192a999ab9a3370e900000108c0084554ccd5cd19b874800800846002600a6ae8400422aa666ae68cdc3a40080042300411300f491035054310035573c0046aae74004dd500091919192a999ab9a3370e900000108c004dd71aba10019bad357426ae8800422601a9201035054310035573c0046aae74004dd5000919118011bac001300b2233335573e0025000280198021aba100298019aba20024000c02489640063000910c00c00a60080021804112c800c4800644246600a00466e2800400c600e44b200314800244266e01200230040011803112c800c6001221900291112c80144c0200164300290c0081802912c800c60012219002912a999ab9a3370e900000088980300188c00830042225900189802a490350543700910aa999ab9a3370e900000208801089980299b81004480080044b200321800460042326335738003000300122590018c00244300390029100126008002119180080091801119801001000999999999999999999a8910011111111111111111400404201f00e806c03201700a804c02200f006802c012007002800884a400a002482001120104837b0b3fd283b677232ec2a7db3a9b5f92bb83025ad213536e97027e43be8736b23fbf6f802d20a2daf5a08da2dfdeac9abd90b8afd49af8babfb4acd6b0bebcce89bbe1ebeea08ae6d2b30848283aad71eaea2e253a3273ee2df32e7b3227bef5a0adb42a77e5b824a8fda9b63e38e6e6c19220160c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000488130979c0891bb2bd071943cf0d0c2a0a8ab76c3d81a138ae70c84c85ce4cf60379f01c17fc52656120a9dcda8f15a18374700488130c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000488130c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048813098e171c79e81f723bb57b2560f154bf56baff5da96080fc11946d88488f9c599779760da06dea2bac9102157ddb77b6800488130a1b909e220793c6878193c326ad056406cff5aa284215f4c206391dd8465e7f339fa0ea7499448e6fa9f410e2d462ec800488130c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000488130b34cfa9b150b9be2892d9b51e7cbfa9663f72836316f13ed6d1b67bf6465527f962b974d3a09b72824e18f9d6b39263f00488130817144132b019cb12dc5e560a08864eac67ea44f38807d83aa56327103fe5e758420eee866ad3ad0371f866b8083c8b500488130a3d3b4ffec5b6268da75fa1448b7756f5724d92997f4da8ba57896684a80be9c2c427f12e35b20a767dbcbf9a7741e3f00488130c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003333512200222800801400442520050012450f68656c6c6f40676d61696c2e636f6d0048810564756d6d790001" +} diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Api.hs b/atlas/src/ZkFold/Cardano/SmartWallet/Api.hs new file mode 100644 index 0000000..1de4df8 --- /dev/null +++ b/atlas/src/ZkFold/Cardano/SmartWallet/Api.hs @@ -0,0 +1,32 @@ +module ZkFold.Cardano.SmartWallet.Api ( + sendFunds, +) where + +import Control.Monad.Reader (MonadReader (..)) +import GeniusYield.TxBuilder (GYTxQueryMonad (..), GYTxSkeleton, mustHaveOutput, mustHaveWithdrawal, throwAppError) +import GeniusYield.Types (GYAddress, GYBuildPlutusScript (..), GYCredential (GYCredentialByScript), GYRedeemer, GYStakeAddressInfo (..), GYTxBuildWitness (..), GYTxWdrl (..), GYValue, PlutusVersion (..), mkGYTxOutNoDatum, scriptHash, stakeAddressFromCredential, unitRedeemer) +import ZkFold.Cardano.SmartWallet.Constants (smartWalletValidator) +import ZkFold.Cardano.SmartWallet.Types + +-- | A dummy redeemer. We would update this with actual redeemer later on. +dummyRedeemer :: GYRedeemer +dummyRedeemer = unitRedeemer + +-- TODO: To not require @SetupBytes@ and @WalletSetup@, but rather have this function receive `GYScript` directly? + +-- | Send funds from a zk-wallet to a given address. +sendFunds :: (ZKWalletQueryMonad m) => SetupBytes -> WalletSetup -> GYAddress -> GYValue -> m (GYTxSkeleton 'PlutusV3) +sendFunds sb ws sendAddr sendVal = do + nid <- networkId + zkwbi <- ask + let stakeAddr = stakeAddressFromCredential nid (GYCredentialByScript $ scriptHash $ smartWalletValidator sb ws) + let mockStakeAddr = stakeAddressFromCredential nid (GYCredentialByScript $ scriptHash $ zkwbiMockStakeValidator zkwbi) + -- TODO: Perhaps stake address information is not required if we are sure that withdrawal amount would always be zero. + si <- + stakeAddressInfo stakeAddr >>= \case + Just si -> pure si + Nothing -> throwAppError $ ZKWEStakeAddressInfoNotFound stakeAddr + pure $ + mustHaveOutput (mkGYTxOutNoDatum sendAddr sendVal) + -- TODO: To make use of reference scripts? + <> mustHaveWithdrawal (GYTxWdrl{gyTxWdrlStakeAddress = mockStakeAddr, gyTxWdrlAmount = gyStakeAddressInfoAvailableRewards si, gyTxWdrlWitness = GYTxBuildWitnessPlutusScript (GYBuildPlutusScriptInlined (zkwbiMockStakeValidator zkwbi)) dummyRedeemer}) \ No newline at end of file diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Constants.hs b/atlas/src/ZkFold/Cardano/SmartWallet/Constants.hs new file mode 100644 index 0000000..2cc4fb5 --- /dev/null +++ b/atlas/src/ZkFold/Cardano/SmartWallet/Constants.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE TemplateHaskell #-} + +module ZkFold.Cardano.SmartWallet.Constants ( + smartWalletValidator, +) +where + +import Data.FileEmbed +import GeniusYield.Types +import ZkFold.Cardano.SmartWallet.Types (SetupBytes, WalletSetup) + +smartWalletValidator :: SetupBytes -> WalletSetup -> GYScript 'PlutusV3 +smartWalletValidator = + let fileBS = $(makeRelativeToProject "./data/compiled-scripts/smart-wallet.plutus" >>= embedFile) + in -- TODO: Check if script was correctly parsed. It might have been the case that we need to unwrap one CBOR layer of the script. + -- TODO: Load the parameterised script, perhaps via blueprint feature of Atlas? + case readScript' fileBS of + Left e -> error $ "Failed to read smart-wallet.plutus: " <> show e + Right script -> applyParam . applyParam script \ No newline at end of file diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Types.hs b/atlas/src/ZkFold/Cardano/SmartWallet/Types.hs new file mode 100644 index 0000000..51100db --- /dev/null +++ b/atlas/src/ZkFold/Cardano/SmartWallet/Types.hs @@ -0,0 +1,38 @@ +module ZkFold.Cardano.SmartWallet.Types ( + ZKWalletBuildInfo (..), + ZKWalletException (..), + ZKWalletQueryMonad, + module ZkFold.Cardano.SmartWallet.Types.Validator, +) where + +import Control.Exception (Exception) +import Control.Monad.Reader (MonadReader) +import Data.Text qualified as Text +import GeniusYield.HTTP.Errors (GYApiError (..), IsGYApiError (..)) +import GeniusYield.TxBuilder (GYTxQueryMonad) +import GeniusYield.Types (GYScript, GYStakeAddress, PlutusVersion (..)) +import Network.HTTP.Types (status500) +import ZkFold.Cardano.SmartWallet.Types.Validator + +-- | Information required to build transactions for a zk-wallet. +data ZKWalletBuildInfo = ZKWalletBuildInfo + { -- TODO: Mock validator can actually be part of constants... + zkwbiMockStakeValidator :: GYScript 'PlutusV3 + -- ^ Mock stake validator used to compute execution units. Since redeemer depends upon script context, and script context depends upon redeemer (as it influences fees and thus also influences change output(s)) we find ourselves in a chicken-and-egg problem. To solve this, we use a mock stake validator whose proofs server can easily compute as script has setup parameters that allow forging. + } + +type ZKWalletQueryMonad m = (GYTxQueryMonad m, MonadReader ZKWalletBuildInfo m) + +data ZKWalletException + = -- | Could not find stake address information for the given stake address. + ZKWEStakeAddressInfoNotFound !GYStakeAddress + deriving stock (Show) + deriving anyclass (Exception) + +instance IsGYApiError ZKWalletException where + toApiError (ZKWEStakeAddressInfoNotFound sa) = + GYApiError + { gaeErrorCode = "STAKE_ADDRESS_INFO_NOT_FOUND" + , gaeHttpStatus = status500 + , gaeMsg = Text.pack $ "Could not find stake address information for the given stake address: " <> show sa + } diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs b/atlas/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs new file mode 100644 index 0000000..29681ed --- /dev/null +++ b/atlas/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs @@ -0,0 +1,108 @@ +{-# LANGUAGE TemplateHaskell #-} + +module ZkFold.Cardano.SmartWallet.Types.Validator ( + WalletSetup (..), + SetupBytes (..), +) where + +import Data.Aeson (FromJSON, ToJSON) +import GHC.Generics (Generic) +import PlutusLedgerApi.Common (BuiltinByteString) +import PlutusTx (makeIsDataIndexed) + +-- TODO: Have these definitions in one-place. Currently it's also defined in zkfold-cardano but that comes with it's own bloat that we want to avoid here. +-- +-- Or avoid definition altogether by using blueprints? + +data WalletSetup = WalletSetup + { wsPubKeyHash :: !BuiltinByteString + , wsWeb2UserId :: !BuiltinByteString + } + deriving stock (Show, Generic) + +makeIsDataIndexed ''WalletSetup [('WalletSetup, 0)] + +newtype F = F Integer + deriving stock (Show, Generic) + deriving newtype (ToJSON, FromJSON) +makeIsDataIndexed ''F [('F, 0)] + +-- TODO: Make fields strict for types defined here? + +data SetupBytes = SetupBytes + { n :: Integer + , pow :: Integer + , omega :: F + , k1 :: F + , k2 :: F + , h1_bytes :: BuiltinByteString + , cmQm_bytes :: BuiltinByteString + , cmQl_bytes :: BuiltinByteString + , cmQr_bytes :: BuiltinByteString + , cmQo_bytes :: BuiltinByteString + , cmQc_bytes :: BuiltinByteString + , cmQk_bytes :: BuiltinByteString + , cmS1_bytes :: BuiltinByteString + , cmS2_bytes :: BuiltinByteString + , cmS3_bytes :: BuiltinByteString + , cmT1_bytes :: BuiltinByteString + } + deriving stock (Show, Generic) + +makeIsDataIndexed ''SetupBytes [('SetupBytes, 0)] + +data ProofBytes = ProofBytes + { cmA_bytes :: BuiltinByteString + , cmB_bytes :: BuiltinByteString + , cmC_bytes :: BuiltinByteString + , cmF_bytes :: BuiltinByteString + , cmH1_bytes :: BuiltinByteString + , cmH2_bytes :: BuiltinByteString + , cmZ1_bytes :: BuiltinByteString + , cmZ2_bytes :: BuiltinByteString + , cmQlow_bytes :: BuiltinByteString + , cmQmid_bytes :: BuiltinByteString + , cmQhigh_bytes :: BuiltinByteString + , proof1_bytes :: BuiltinByteString + , proof2_bytes :: BuiltinByteString + , a_xi_int :: Integer + , b_xi_int :: Integer + , c_xi_int :: Integer + , s1_xi_int :: Integer + , s2_xi_int :: Integer + , f_xi_int :: Integer + , t_xi_int :: Integer + , t_xi'_int :: Integer + , z1_xi'_int :: Integer + , z2_xi'_int :: Integer + , h1_xi'_int :: Integer + , h2_xi_int :: Integer + , l1_xi :: F + } + deriving stock (Show, Generic) + +makeIsDataIndexed ''ProofBytes [('ProofBytes, 0)] + +data Web2Creds = Web2Creds + { wUserId :: BuiltinByteString + , wTokenHash :: BuiltinByteString + , wAmount :: Integer + } + deriving stock (Show, Generic) + +makeIsDataIndexed ''Web2Creds [('Web2Creds, 0)] + +data SpendingCreds = SpendWithSignature BuiltinByteString | SpendWithWeb2Token Web2Creds + deriving stock (Show, Generic) + +makeIsDataIndexed ''SpendingCreds [('SpendWithSignature, 0), ('SpendWithWeb2Token, 1)] + +data WalletRedeemer = WalletRedeemer + { wrTxDate :: !BuiltinByteString + , wrTxRecipient :: !BuiltinByteString + , wrZkp :: !ProofBytes + , wrCreds :: !SpendingCreds + } + deriving stock (Show, Generic) + +makeIsDataIndexed ''WalletRedeemer [('WalletRedeemer, 0)] \ No newline at end of file diff --git a/atlas/test/Main.hs b/atlas/test/Main.hs new file mode 100644 index 0000000..3e2059e --- /dev/null +++ b/atlas/test/Main.hs @@ -0,0 +1,4 @@ +module Main (main) where + +main :: IO () +main = putStrLn "Test suite not yet implemented." diff --git a/atlas/zkfold-smart-contract-wallet-server.cabal b/atlas/zkfold-smart-contract-wallet-server.cabal new file mode 100644 index 0000000..14de738 --- /dev/null +++ b/atlas/zkfold-smart-contract-wallet-server.cabal @@ -0,0 +1,86 @@ +cabal-version: 3.4 +name: zkfold-smart-contract-wallet-server +version: 0.1.0.0 +-- synopsis: +-- description: +license: MIT +license-file: LICENSE +author: Sourabh Aggarwal +maintainer: info@zkfold.io +-- copyright: +build-type: Simple +extra-doc-files: CHANGELOG.md +data-dir: data +data-files: + compiled-scripts/smart-wallet.plutus + +common common + default-language: GHC2021 + default-extensions: + DataKinds + DeriveAnyClass + DerivingStrategies + DerivingVia + GADTs + LambdaCase + MultiWayIf + OverloadedStrings + RecordWildCards + RoleAnnotations + TemplateHaskell + TypeFamilies + TypeFamilyDependencies + UndecidableInstances + UnicodeSyntax + ViewPatterns + + ghc-options: + -Wall + -Wincomplete-uni-patterns + -Wunused-packages + +library + import: common + exposed-modules: + ZkFold.Cardano.SmartWallet.Api + ZkFold.Cardano.SmartWallet.Constants + ZkFold.Cardano.SmartWallet.Types + ZkFold.Cardano.SmartWallet.Types.Validator + + -- other-modules: + -- other-extensions: + build-depends: + -- TODO: Possible to get rid of `plutus-ledger-api` by defining higher types? + aeson, + atlas-cardano, + base ^>=4.18.2.1, + file-embed, + http-types, + mtl, + plutus-ledger-api, + plutus-tx, + text, + + hs-source-dirs: src + +executable zkfold-smart-contract-wallet-server + import: common + main-is: Main.hs + -- other-modules: + -- other-extensions: + build-depends: + base ^>=4.18.2.1, + zkfold-smart-contract-wallet-server, + + hs-source-dirs: app + +test-suite zkfold-smart-contract-wallet-server-test + import: common + -- other-modules: + -- other-extensions: + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Main.hs + build-depends: + base ^>=4.18.2.1, + zkfold-smart-contract-wallet-server, From 5e0ed66a6b8a9f4128915075b4f43b63d12cdac3 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Wed, 19 Mar 2025 14:11:30 +0530 Subject: [PATCH 02/18] feat(#72): restructure folders --- atlas/cabal.project | 3 ++- atlas/{ => zkfold-smart-wallet-api}/CHANGELOG.md | 0 atlas/{ => zkfold-smart-wallet-api}/LICENSE | 0 atlas/{ => zkfold-smart-wallet-api}/app/Main.hs | 0 .../data/compiled-scripts/smart-wallet.plutus | 0 .../src/ZkFold/Cardano/SmartWallet/Api.hs | 0 .../src/ZkFold/Cardano/SmartWallet/Constants.hs | 0 .../src/ZkFold/Cardano/SmartWallet/Types.hs | 0 .../src/ZkFold/Cardano/SmartWallet/Types/Validator.hs | 0 atlas/{ => zkfold-smart-wallet-api}/test/Main.hs | 0 .../zkfold-smart-wallet-api.cabal} | 6 +++--- 11 files changed, 5 insertions(+), 4 deletions(-) rename atlas/{ => zkfold-smart-wallet-api}/CHANGELOG.md (100%) rename atlas/{ => zkfold-smart-wallet-api}/LICENSE (100%) rename atlas/{ => zkfold-smart-wallet-api}/app/Main.hs (100%) rename atlas/{ => zkfold-smart-wallet-api}/data/compiled-scripts/smart-wallet.plutus (100%) rename atlas/{ => zkfold-smart-wallet-api}/src/ZkFold/Cardano/SmartWallet/Api.hs (100%) rename atlas/{ => zkfold-smart-wallet-api}/src/ZkFold/Cardano/SmartWallet/Constants.hs (100%) rename atlas/{ => zkfold-smart-wallet-api}/src/ZkFold/Cardano/SmartWallet/Types.hs (100%) rename atlas/{ => zkfold-smart-wallet-api}/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs (100%) rename atlas/{ => zkfold-smart-wallet-api}/test/Main.hs (100%) rename atlas/{zkfold-smart-contract-wallet-server.cabal => zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal} (92%) diff --git a/atlas/cabal.project b/atlas/cabal.project index 6a0e3ef..e231d6d 100644 --- a/atlas/cabal.project +++ b/atlas/cabal.project @@ -9,7 +9,8 @@ repository cardano-haskell-packages c00aae8461a256275598500ea0e187588c35a5d5d7454fb57eac18d9edb86a56 d4a35cd3121aa00d18544bb0ac01c3e1691d618f462c46129271bccf39f7e8ee -packages: . +packages: + zkfold-smart-wallet-api tests: true diff --git a/atlas/CHANGELOG.md b/atlas/zkfold-smart-wallet-api/CHANGELOG.md similarity index 100% rename from atlas/CHANGELOG.md rename to atlas/zkfold-smart-wallet-api/CHANGELOG.md diff --git a/atlas/LICENSE b/atlas/zkfold-smart-wallet-api/LICENSE similarity index 100% rename from atlas/LICENSE rename to atlas/zkfold-smart-wallet-api/LICENSE diff --git a/atlas/app/Main.hs b/atlas/zkfold-smart-wallet-api/app/Main.hs similarity index 100% rename from atlas/app/Main.hs rename to atlas/zkfold-smart-wallet-api/app/Main.hs diff --git a/atlas/data/compiled-scripts/smart-wallet.plutus b/atlas/zkfold-smart-wallet-api/data/compiled-scripts/smart-wallet.plutus similarity index 100% rename from atlas/data/compiled-scripts/smart-wallet.plutus rename to atlas/zkfold-smart-wallet-api/data/compiled-scripts/smart-wallet.plutus diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Api.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs similarity index 100% rename from atlas/src/ZkFold/Cardano/SmartWallet/Api.hs rename to atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Constants.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs similarity index 100% rename from atlas/src/ZkFold/Cardano/SmartWallet/Constants.hs rename to atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Types.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs similarity index 100% rename from atlas/src/ZkFold/Cardano/SmartWallet/Types.hs rename to atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs diff --git a/atlas/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs similarity index 100% rename from atlas/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs rename to atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types/Validator.hs diff --git a/atlas/test/Main.hs b/atlas/zkfold-smart-wallet-api/test/Main.hs similarity index 100% rename from atlas/test/Main.hs rename to atlas/zkfold-smart-wallet-api/test/Main.hs diff --git a/atlas/zkfold-smart-contract-wallet-server.cabal b/atlas/zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal similarity index 92% rename from atlas/zkfold-smart-contract-wallet-server.cabal rename to atlas/zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal index 14de738..fb75fda 100644 --- a/atlas/zkfold-smart-contract-wallet-server.cabal +++ b/atlas/zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal @@ -1,5 +1,5 @@ cabal-version: 3.4 -name: zkfold-smart-contract-wallet-server +name: zkfold-smart-wallet-api version: 0.1.0.0 -- synopsis: -- description: @@ -70,7 +70,7 @@ executable zkfold-smart-contract-wallet-server -- other-extensions: build-depends: base ^>=4.18.2.1, - zkfold-smart-contract-wallet-server, + zkfold-smart-wallet-api, hs-source-dirs: app @@ -83,4 +83,4 @@ test-suite zkfold-smart-contract-wallet-server-test main-is: Main.hs build-depends: base ^>=4.18.2.1, - zkfold-smart-contract-wallet-server, + zkfold-smart-wallet-api, From 6327c431e9aef4ccca8b3e6c17c4a96de65cc447 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Wed, 19 Mar 2025 21:56:57 +0530 Subject: [PATCH 03/18] feat(#72): add server options & configuration --- atlas/cabal.project | 4 +- atlas/zkfold-smart-wallet-api/CHANGELOG.md | 2 +- .../CHANGELOG.md | 5 + atlas/zkfold-smart-wallet-server-lib/LICENSE | 20 +++ .../app/Main.hs | 16 +++ .../Cardano/SmartWallet/Server/Config.hs | 122 ++++++++++++++++++ .../Cardano/SmartWallet/Server/Options.hs | 46 +++++++ .../zkfold-smart-wallet-server-lib.cabal | 78 +++++++++++ 8 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 atlas/zkfold-smart-wallet-server-lib/CHANGELOG.md create mode 100644 atlas/zkfold-smart-wallet-server-lib/LICENSE create mode 100644 atlas/zkfold-smart-wallet-server-lib/app/Main.hs create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs create mode 100644 atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal diff --git a/atlas/cabal.project b/atlas/cabal.project index e231d6d..786a0ad 100644 --- a/atlas/cabal.project +++ b/atlas/cabal.project @@ -11,14 +11,14 @@ repository cardano-haskell-packages packages: zkfold-smart-wallet-api + zkfold-smart-wallet-server-lib tests: true source-repository-package type: git location: https://github.com/geniusyield/atlas - tag: 375adbd73ae8d7bd333465dcca335e71132dfa77 - --sha256: FIXME: + tag: 6c6546161cb417a2523935dafd28764e81da5926 -------- Begin contents from @atlas@'s @cabal.project@ file. -------- diff --git a/atlas/zkfold-smart-wallet-api/CHANGELOG.md b/atlas/zkfold-smart-wallet-api/CHANGELOG.md index 7bcd9af..d988552 100644 --- a/atlas/zkfold-smart-wallet-api/CHANGELOG.md +++ b/atlas/zkfold-smart-wallet-api/CHANGELOG.md @@ -1,4 +1,4 @@ -# Revision history for zkfold-smart-contract-wallet-server +# Revision history for zkfold-smart-wallet-api ## 0.1.0.0 -- YYYY-mm-dd diff --git a/atlas/zkfold-smart-wallet-server-lib/CHANGELOG.md b/atlas/zkfold-smart-wallet-server-lib/CHANGELOG.md new file mode 100644 index 0000000..86be2b0 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for zkfold-smart-wallet-server-lib + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/atlas/zkfold-smart-wallet-server-lib/LICENSE b/atlas/zkfold-smart-wallet-server-lib/LICENSE new file mode 100644 index 0000000..c85fe66 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2025 Sourabh Aggarwal + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/atlas/zkfold-smart-wallet-server-lib/app/Main.hs b/atlas/zkfold-smart-wallet-server-lib/app/Main.hs new file mode 100644 index 0000000..369be9c --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/app/Main.hs @@ -0,0 +1,16 @@ +import Options.Applicative + +-- import ZkFold.Cardano.SmartWallet.Server.Options + +main ∷ IO () +main = runCommand =<< execParser opts + where + opts = + info + (parseCommand <**> helper) + ( fullDesc + <> progDesc "zkFold smart wallet helpful operations" + <> header "zkFold smart wallet" + ) + runCommand = undefined + parseCommand = undefined \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs new file mode 100644 index 0000000..223ca5c --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs @@ -0,0 +1,122 @@ +module ZkFold.Cardano.SmartWallet.Server.Config ( + ServerConfig (..), + serverConfigOptionalFPIO, + coreConfigFromServerConfig, + optionalSigningKeyFromServerConfig, +) where + +import Data.Aeson ( + eitherDecodeFileStrict, + eitherDecodeStrict, + ) +import Data.Word (Word32) +import Data.Yaml qualified as Yaml +import Deriving.Aeson +import GHC.IO.Exception (userError) +import GeniusYield.GYConfig (Confidential, GYCoreConfig (..), GYCoreProviderInfo) +import GeniusYield.Imports (Text) +import GeniusYield.Types hiding (Port) +import Network.Wai.Handler.Warp (Port) +import System.Envy +import System.FilePath (takeExtension) + +{- $setup + +>>> :set -XOverloadedStrings -XTypeApplications +>>> import qualified Data.Aeson as Aeson +>>> import qualified Data.ByteString.Lazy.Char8 as LBS8 +>>> import Data.Proxy +-} + +-- >>> Aeson.encode (MnemonicWallet (MnemonicWalletDetails ["hello"] (Just 1) (Just 2))) +-- "{\"tag\":\"mnemonicWallet\",\"contents\":{\"mnemonic\":[\"hello\"],\"acc_ix\":1,\"addr_ix\":2}}" +data UserWallet = MnemonicWallet !MnemonicWalletDetails | KeyPathWallet !FilePath + deriving stock (Generic) + deriving (FromJSON, ToJSON) via CustomJSON '[ConstructorTagModifier '[LowerFirst]] UserWallet + +-- | Details of a wallet to be created from a mnemonic. +data MnemonicWalletDetails = MnemonicWalletDetails + { mnemonic :: !Mnemonic + -- ^ Mnemonic (seed phrase). + , accIx :: !(Maybe Word32) + -- ^ Account index. + , addrIx :: !(Maybe Word32) + -- ^ Payment address index. + } + deriving stock (Generic) + deriving anyclass (FromJSON, ToJSON) + +-- | Configuration for the server. +data ServerConfig = ServerConfig + { scCoreProvider :: !GYCoreProviderInfo + , scNetworkId :: !GYNetworkId + , scLogging :: ![GYLogScribeConfig] + , scMaestroToken :: !(Confidential Text) + , scPort :: !Port + , scWallet :: !(Maybe UserWallet) + , scServerApiKey :: !(Confidential Text) + , scCollateral :: !(Maybe GYTxOutRef) + , scStakeAddress :: !(Maybe GYStakeAddressBech32) + } + deriving stock (Generic) + deriving + (FromJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix "sc", LowerFirst]] ServerConfig + +instance FromEnv ServerConfig where + fromEnv _ = forceFromJsonOrYaml <$> env "SERVER_CONFIG" + where + forceFromJsonOrYaml :: (FromJSON a) => String -> a + forceFromJsonOrYaml s = + let bs = fromString s + parseResults = eitherDecodeStrict bs :| [first show $ Yaml.decodeEither' bs] + in go parseResults + where + go (x :| []) = case x of + Left e -> error e + Right a -> a + go (x :| y : ys) = case x of + Left _ -> go (y :| ys) + Right a -> a + +eitherDecodeFileStrictJsonOrYaml :: (FromJSON a) => FilePath -> IO (Either String a) +eitherDecodeFileStrictJsonOrYaml fp = + case takeExtension fp of + ".json" -> eitherDecodeFileStrict fp + ".yaml" -> first show <$> Yaml.decodeFileEither fp + _ -> throwIO $ userError "Only .json or .yaml extensions are supported for configuration." + +serverConfigOptionalFPIO :: Maybe FilePath -> IO ServerConfig +serverConfigOptionalFPIO mfp = do + e <- maybe decodeEnv eitherDecodeFileStrictJsonOrYaml mfp + either (throwIO . userError) return e + +coreConfigFromServerConfig :: ServerConfig -> GYCoreConfig +coreConfigFromServerConfig ServerConfig{..} = + GYCoreConfig + { cfgCoreProvider = scCoreProvider + , cfgNetworkId = scNetworkId + , cfgLogging = scLogging + , cfgLogTiming = Nothing + } + +optionalSigningKeyFromServerConfig :: ServerConfig -> IO (Maybe (GYSomePaymentSigningKey, GYAddress)) +optionalSigningKeyFromServerConfig ServerConfig{..} = do + case scWallet of + Nothing -> pure Nothing + Just (MnemonicWallet MnemonicWalletDetails{..}) -> + let wk' = walletKeysFromMnemonicIndexed mnemonic (fromMaybe 0 accIx) (fromMaybe 0 addrIx) + in pure $ case wk' of + Left _ -> Nothing + Right wk -> Just (AGYExtendedPaymentSigningKey (walletKeysToExtendedPaymentSigningKey wk) :!: walletKeysToAddress wk scNetworkId) + Just (KeyPathWallet fp) -> do + skey <- readSomePaymentSigningKey fp + pure $ Just (skey, addressFromSomePaymentSigningKey scNetworkId skey) + where + addressFromSomePaymentSigningKey :: GYNetworkId -> GYSomePaymentSigningKey -> GYAddress + addressFromSomePaymentSigningKey nid skey = + let pkh = + case skey of + AGYPaymentSigningKey skey' -> paymentKeyHash . paymentVerificationKey $ skey' + AGYExtendedPaymentSigningKey skey' -> extendedVerificationKeyHash skey' + in addressFromPaymentKeyHash nid pkh diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs new file mode 100644 index 0000000..a68316a --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs @@ -0,0 +1,46 @@ +module ZkFold.Cardano.SmartWallet.Server.Options ( + Command (..), + ServeCommand (..), + parseCommand, + parseServeCommand, + runCommand, + runServeCommand, +) where + +-- import GeniusYield.Server.Run (runServer) +import Options.Applicative + +newtype Command = Serve ServeCommand + +newtype ServeCommand = ServeCommand (Maybe FilePath) + +parseCommand :: Parser Command +parseCommand = + subparser $ + mconcat + [ command + "serve" + ( info (Serve <$> parseServeCommand <**> helper) $ + progDesc "Serve endpoints" + ) + ] + +parseServeCommand :: Parser ServeCommand +parseServeCommand = + ServeCommand + <$> optional + ( strOption + ( long "config" + <> metavar "CONFIG" + <> short 'c' + <> help "Path of optional configuration file. If not provided, \"SERVER_CONFIG\" environment variable is used." + ) + ) + +runCommand :: Command -> IO () +runCommand (Serve serveCommand) = runServeCommand serveCommand + +runServeCommand :: ServeCommand -> IO () +runServeCommand (ServeCommand mcfp) = runServer mcfp + +runServer = undefined diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal new file mode 100644 index 0000000..e2abe1e --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -0,0 +1,78 @@ +cabal-version: 3.4 +name: zkfold-smart-wallet-server-lib +version: 0.1.0.0 +-- synopsis: +-- description: +license: MIT +license-file: LICENSE +author: Sourabh Aggarwal +maintainer: info@zkfold.io +-- copyright: +build-type: Simple +extra-doc-files: CHANGELOG.md + +common common + default-language: GHC2021 + default-extensions: + DataKinds + DeriveAnyClass + DerivingStrategies + DerivingVia + GADTs + LambdaCase + MultiWayIf + OverloadedStrings + RecordWildCards + RoleAnnotations + TemplateHaskell + TypeFamilies + TypeFamilyDependencies + UndecidableInstances + UnicodeSyntax + ViewPatterns + + ghc-options: + -Wall + -Wincomplete-uni-patterns + -Wunused-packages + +library + import: common + exposed-modules: + ZkFold.Cardano.SmartWallet.Server.Config + ZkFold.Cardano.SmartWallet.Server.Options + ZkFold.Cardano.SmartWallet.Server.Run + + -- other-modules: + -- other-extensions: + build-depends: + -- TODO: Possible to get rid of `plutus-ledger-api` by defining higher types? + aeson, + atlas-cardano, + base ^>=4.18.2.1, + deriving-aeson, + envy, + file-embed, + filepath, + http-types, + mtl, + optparse-applicative, + plutus-ledger-api, + plutus-tx, + text, + warp, + yaml, + + hs-source-dirs: src + +executable zkfold-smart-wallet-server + import: common + main-is: Main.hs + -- other-modules: + -- other-extensions: + build-depends: + base ^>=4.18.2.1, + optparse-applicative, + zkfold-smart-wallet-server-lib, + + hs-source-dirs: app \ No newline at end of file From 3cc3f0db8f65c76a4ead61ea922d05fc5f9f3e53 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Wed, 19 Mar 2025 22:15:54 +0530 Subject: [PATCH 04/18] feat(#72): add api authentication --- .../ZkFold/Cardano/SmartWallet/Server/Auth.hs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs new file mode 100644 index 0000000..62d3452 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs @@ -0,0 +1,47 @@ +module ZkFold.Cardano.SmartWallet.Server.Auth ( + V0, + ApiKey, + apiKeyFromText, + ApiKeyHeader, + apiKeyHeaderText, + apiKeyAuthHandler, + APIKeyAuthProtect, +) where + +import Data.ByteString (ByteString) +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Encoding (encodeUtf8) +import GHC.TypeLits (Symbol, symbolVal) +import Network.Wai (Request (requestHeaders)) +import Servant +import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData, mkAuthHandler) + +type V0 :: Symbol +type V0 = "v0" + +-- | The Api Key type. +newtype ApiKey = ApiKey ByteString + +apiKeyFromText :: Text -> ApiKey +apiKeyFromText = ApiKey . encodeUtf8 + +type ApiKeyHeader :: Symbol +type ApiKeyHeader = "api-key" + +apiKeyHeaderText :: Text +apiKeyHeaderText = symbolVal (Proxy :: Proxy ApiKeyHeader) & T.pack + +apiKeyAuthHandler :: ApiKey -> AuthHandler Request () +apiKeyAuthHandler (ApiKey key) = mkAuthHandler handler + where + handler req = case lookup "api-key" (requestHeaders req) of + Nothing -> throwError err401{errBody = "Missing API key (please pass the api key in the api-key HTTP header)"} + Just key' -> + if key' == key + then pure () + else throwError err403{errBody = "Invalid API key"} + +type APIKeyAuthProtect = AuthProtect ApiKeyHeader + +type instance AuthServerData APIKeyAuthProtect = () From 5e9390e0d343b519185ce946bc9d84c7552eeb62 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 11:32:06 +0530 Subject: [PATCH 05/18] feat(#72): add server ctx --- .../ZkFold/Cardano/SmartWallet/Server/Auth.hs | 1 + .../ZkFold/Cardano/SmartWallet/Server/Ctx.hs | 100 ++++++++++++++++++ .../zkfold-smart-wallet-server-lib.cabal | 8 ++ 3 files changed, 109 insertions(+) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs index 62d3452..a0a1908 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Auth.hs @@ -13,6 +13,7 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding (encodeUtf8) import GHC.TypeLits (Symbol, symbolVal) +import GeniusYield.Imports ((&)) import Network.Wai (Request (requestHeaders)) import Servant import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData, mkAuthHandler) diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs new file mode 100644 index 0000000..c2b1f54 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs @@ -0,0 +1,100 @@ +module ZkFold.Cardano.SmartWallet.Server.Ctx ( + Ctx (..), + runSkeletonI, + runSkeletonWithStrategyI, + runSkeletonF, + runSkeletonWithStrategyF, + runQuery, + runQueryWithReader, +) where + +import Control.Monad.Reader (ReaderT (..)) +import GeniusYield.Imports +import GeniusYield.Transaction +import GeniusYield.TxBuilder +import GeniusYield.Types +import ZkFold.Cardano.SmartWallet.Types (ZKWalletBuildInfo) + +-- | Server context: configuration & shared state. +data Ctx = Ctx + { ctxNetworkId :: !GYNetworkId + , ctxProviders :: !GYProviders + , ctxSmartWalletBuildInfo :: !ZKWalletBuildInfo + , ctxSigningKey :: !(Maybe (GYSomePaymentSigningKey, GYAddress)) + , ctxCollateral :: !(Maybe GYTxOutRef) + , ctxStakeAddress :: !(Maybe GYStakeAddressBech32) + } + +-- | Create 'TxBody' from a 'GYTxSkeleton'. +runSkeletonI :: + Ctx -> + -- | User's used addresses. Note that internally we prepend given change address to this list so that in case wallet's state isn't updated quickly to mark an earlier given change address as used, we'll be able to use UTxOs potentially present at this change address. + [GYAddress] -> + -- | User's change address. + GYAddress -> + -- | User's collateral. + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (GYTxSkeleton v) -> + IO GYTxBody +runSkeletonI = coerce (runSkeletonF @Identity) + +-- | Create 'TxBody' from a 'GYTxSkeleton', with the specified coin selection strategy. +runSkeletonWithStrategyI :: + GYCoinSelectionStrategy -> + Ctx -> + -- | User's used addresses. Note that internally we prepend given change address to this list so that in case wallet's state isn't updated quickly to mark an earlier given change address as used, we'll be able to use UTxOs potentially present at this change address. + [GYAddress] -> + -- | User's change address. + GYAddress -> + -- | User's collateral. + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (GYTxSkeleton v) -> + IO GYTxBody +runSkeletonWithStrategyI cstrat = coerce (runSkeletonWithStrategyF @Identity cstrat) + +runSkeletonF :: + (Traversable t) => + Ctx -> + -- | User's used addresses. Note that internally we prepend given change address to this list so that in case wallet's state isn't updated quickly to mark an earlier given change address as used, we'll be able to use UTxOs potentially present at this change address. + [GYAddress] -> + -- | User's change address. + GYAddress -> + -- | User's collateral. + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> + IO (t GYTxBody) +runSkeletonF = runSkeletonWithStrategyF GYRandomImproveMultiAsset + +runSkeletonWithStrategyF :: + (Traversable t) => + GYCoinSelectionStrategy -> + Ctx -> + -- | User's used addresses. Note that internally we prepend given change address to this list so that in case wallet's state isn't updated quickly to mark an earlier given change address as used, we'll be able to use UTxOs potentially present at this change address. + [GYAddress] -> + -- | User's change address. + GYAddress -> + -- | User's collateral. + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> + IO (t GYTxBody) +runSkeletonWithStrategyF cstrat ctx addrs addr mcollateral skeleton = do + let nid = ctxNetworkId ctx + providers = ctxProviders ctx + wi = ctxSmartWalletBuildInfo ctx + mcollateral' = do + collateral <- mcollateral + pure (collateral, False) + + runGYTxMonadNodeF cstrat nid providers (addr : addrs) addr mcollateral' $ runReaderT skeleton wi + +runQuery :: Ctx -> ReaderT ZKWalletBuildInfo GYTxQueryMonadIO a -> IO a +runQuery ctx = runQueryWithReader ctx (ctxSmartWalletBuildInfo ctx) + +runQueryWithReader :: Ctx -> a -> ReaderT a GYTxQueryMonadIO b -> IO b +runQueryWithReader ctx a q = do + let nid = ctxNetworkId ctx + providers = ctxProviders ctx + runGYTxQueryMonadIO nid providers $ runReaderT q a + +runGYTxMonadNodeF :: forall t v. (Traversable t) => GYCoinSelectionStrategy -> GYNetworkId -> GYProviders -> [GYAddress] -> GYAddress -> Maybe (GYTxOutRef, Bool) -> GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> IO (t GYTxBody) +runGYTxMonadNodeF strat nid providers addrs change collateral act = runGYTxBuilderMonadIO nid providers addrs change collateral $ act >>= traverse (buildTxBodyWithStrategy strat) diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index e2abe1e..1fd9f13 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -39,7 +39,9 @@ common common library import: common exposed-modules: + ZkFold.Cardano.SmartWallet.Server.Auth ZkFold.Cardano.SmartWallet.Server.Config + ZkFold.Cardano.SmartWallet.Server.Ctx ZkFold.Cardano.SmartWallet.Server.Options ZkFold.Cardano.SmartWallet.Server.Run @@ -50,18 +52,24 @@ library aeson, atlas-cardano, base ^>=4.18.2.1, + bytestring, deriving-aeson, envy, file-embed, filepath, + fmt, http-types, mtl, optparse-applicative, plutus-ledger-api, plutus-tx, + servant-server, text, + time-manager, + wai, warp, yaml, + zkfold-smart-wallet-api, hs-source-dirs: src From 501c0b09b86b0562fd088dffe3d600514b2ce1b4 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 11:49:21 +0530 Subject: [PATCH 06/18] feat(#16): add error middleware --- .../Cardano/SmartWallet/Server/Config.hs | 11 +- .../SmartWallet/Server/ErrorMiddleware.hs | 201 ++++++++++++++++++ .../zkfold-smart-wallet-server-lib.cabal | 2 + 3 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/ErrorMiddleware.hs diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs index 223ca5c..096230d 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs @@ -9,12 +9,15 @@ import Data.Aeson ( eitherDecodeFileStrict, eitherDecodeStrict, ) +import Data.Bifunctor (Bifunctor (..)) +import Data.List.NonEmpty (NonEmpty (..)) +import Data.Maybe (fromMaybe) +import Data.String (IsString (..)) import Data.Word (Word32) import Data.Yaml qualified as Yaml import Deriving.Aeson -import GHC.IO.Exception (userError) import GeniusYield.GYConfig (Confidential, GYCoreConfig (..), GYCoreProviderInfo) -import GeniusYield.Imports (Text) +import GeniusYield.Imports (Text, throwIO, (&)) import GeniusYield.Types hiding (Port) import Network.Wai.Handler.Warp (Port) import System.Envy @@ -108,7 +111,7 @@ optionalSigningKeyFromServerConfig ServerConfig{..} = do let wk' = walletKeysFromMnemonicIndexed mnemonic (fromMaybe 0 accIx) (fromMaybe 0 addrIx) in pure $ case wk' of Left _ -> Nothing - Right wk -> Just (AGYExtendedPaymentSigningKey (walletKeysToExtendedPaymentSigningKey wk) :!: walletKeysToAddress wk scNetworkId) + Right wk -> Just (AGYExtendedPaymentSigningKey (walletKeysToExtendedPaymentSigningKey wk), walletKeysToAddress wk scNetworkId) Just (KeyPathWallet fp) -> do skey <- readSomePaymentSigningKey fp pure $ Just (skey, addressFromSomePaymentSigningKey scNetworkId skey) @@ -118,5 +121,5 @@ optionalSigningKeyFromServerConfig ServerConfig{..} = do let pkh = case skey of AGYPaymentSigningKey skey' -> paymentKeyHash . paymentVerificationKey $ skey' - AGYExtendedPaymentSigningKey skey' -> extendedVerificationKeyHash skey' + AGYExtendedPaymentSigningKey skey' -> getExtendedVerificationKey skey' & extendedVerificationKeyHash in addressFromPaymentKeyHash nid pkh diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/ErrorMiddleware.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/ErrorMiddleware.hs new file mode 100644 index 0000000..4b25f64 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/ErrorMiddleware.hs @@ -0,0 +1,201 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} + +module ZkFold.Cardano.SmartWallet.Server.ErrorMiddleware ( + exceptionHandler, + errorJsonWrapMiddleware, + errorLoggerMiddleware, + apiErrorToServerError, + missingSignatoryCheck, +) where + +import Control.Exception (Exception (..), SomeException) +import Control.Monad (when) +import Data.Aeson ((.=)) +import Data.Aeson qualified as Aeson +import Data.ByteString.Builder (toLazyByteString) +import Data.ByteString.Lazy qualified as LBS +import Data.Char (toUpper) +import Data.IORef (modifyIORef', newIORef, readIORef) +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Lazy qualified as LT +import GeniusYield.HTTP.Errors +import GeniusYield.Imports (decodeUtf8Lenient, encodeUtf8, lazyDecodeUtf8Lenient, (&)) +import GeniusYield.Providers.Common (SubmitTxException (SubmitTxException)) +import GeniusYield.TxBuilder +import Network.HTTP.Types ( + Status (statusCode, statusMessage), + mkStatus, + status400, + status500, + ) +import Network.Wai qualified as Wai +import Servant.Server (ServerError (..)) + +{- | This is used for turning non-json responses into JSON. + +Example of responses which are not in JSON: Servant body parse error, url not found error etc. +-} +errorJsonWrapMiddleware :: Wai.Middleware +errorJsonWrapMiddleware app req respond = app req $ \res -> do + let (status, headers, body) = Wai.responseToStream res + if lookup "Content-Type" headers + /= Just "application/json" -- Don't overwrite responses which are already json! + && statusCode status + >= 400 + && statusCode status + < 600 + then do + lbs <- + if statusCode status == 404 + then -- The body in a 404 Servant err is empty for some reason. + pure . LBS.fromStrict $ "Not Found" + else sinkStreamingBody body + respond $ errorResponse status lbs + else respond res + +errorLoggerMiddleware :: (LT.Text -> IO ()) -> Wai.Middleware +errorLoggerMiddleware errorLogger app req respond = app req $ \res -> do + let (status, _headers, body) = Wai.responseToStream res + when (statusCode status >= 400 && statusCode status < 600) $ + sinkStreamingBody body + >>= errorLogger + . lazyDecodeUtf8Lenient + respond res + +{- | Reinterpret exceptions raised by the server (mostly contract exceptions) into 'GYApiError's. + +Use 'apiErrorToServerError' to construct a server response out of 'GYApiError'. +-} +exceptionHandler :: SomeException -> GYApiError +exceptionHandler = + catchesWaiExc + [ WH $ \case + SubmitTxException errBody -> + if "BadInputsUTxO" `T.isInfixOf` errBody -- See https://github.com/input-output-hk/cardano-ledger/blob/de7c29eef6d7eaabf5d704e976f7840a2edce355/eras/babbage/impl/src/Cardano/Ledger/Babbage/Rules/Utxo.hs#L350-L351. + then + GYApiError + { gaeErrorCode = "UTXO_CONSUMED" + , gaeHttpStatus = status500 + , gaeMsg = "Input UTxO referred to by transaction has already been consumed, complete submission error text: " <> errBody + } + else + if missingSignatoryCheck errBody + then + GYApiError + { gaeErrorCode = "MISSING_SIGNATORY" + , gaeHttpStatus = status500 + , gaeMsg = "User's wallet provider couldn't provide all required witnesses. This issue should likely be resolved by trying out provider with multi-address support enabled. Complete submission error text: " <> errBody + } + else + GYApiError + { gaeErrorCode = "SUBMISSION_FAILURE" + , gaeHttpStatus = status500 + , gaeMsg = errBody + } + , WH $ \case + ServerError{..} -> GYApiError{gaeErrorCode = "SERVER_ERROR", gaeHttpStatus = mkStatus errHTTPCode (errReasonPhrase & T.pack & encodeUtf8), gaeMsg = decodeUtf8Lenient (LBS.toStrict errBody)} + , WH $ \case + GYConversionException convErr -> someBackendError $ tShow convErr + GYQueryUTxOException txErr -> someBackendError $ tShow txErr + e@(GYBuildTxException buildErr) -> case buildErr of + GYBuildTxBalancingError (GYBalancingErrorInsufficientFunds x) -> + GYApiError + { gaeErrorCode = "INSUFFICIENT_BALANCE" + , gaeHttpStatus = status400 + , gaeMsg = "Value dip: " <> tShow x + } + GYBuildTxBalancingError GYBalancingErrorEmptyOwnUTxOs -> + GYApiError + { gaeErrorCode = "INSUFFICIENT_BALANCE" + , gaeHttpStatus = status400 + , gaeMsg = "No UTxOs available to build transaction from in wallet" + } + GYBuildTxBalancingError (GYBalancingErrorChangeShortFall a) -> + GYApiError + { gaeErrorCode = "INSUFFICIENT_BALANCE" + , gaeHttpStatus = status400 + , gaeMsg = "When trying to balance the transaction, our coin balancer felt short by " <> tShow a <> " lovelaces" + } + GYBuildTxCollateralShortFall req given -> + GYApiError + { -- This won't really happen as the collateral UTxO we choose has >= 5 ada. + gaeErrorCode = "INSUFFICIENT_BALANCE" + , gaeHttpStatus = status400 + , gaeMsg = "Total lovelaces required as collateral to build for this transaction " <> tShow req <> " but only available " <> tShow given + } + GYBuildTxNoSuitableCollateral -> + GYApiError + { gaeErrorCode = "NO_SUITABLE_COLLATERAL" + , gaeHttpStatus = status400 + , gaeMsg = "Could not find the suitable UTxO as collateral, wallet must have a UTxO containing more than " <> tShow collateralLovelace <> " lovelaces" + } + _anyOther -> someBackendError $ displayException' e + GYNoSuitableCollateralException minAmt addr -> + someBackendError $ + "No suitable collateral of at least " + <> tShow minAmt + <> " was found at the address " + <> tShow addr + GYSlotOverflowException slot advAmt -> + someBackendError $ + "Slot value " + <> tShow slot + <> " overflows when advanced by " + <> tShow advAmt + GYTimeUnderflowException sysStart time -> + someBackendError $ + "Timestamp " + <> tShow time + <> " is before known system start " + <> tShow sysStart + GYQueryDatumException qdErr -> someBackendError $ tShow qdErr + GYDatumMismatch actualDatum scriptWitness -> someBackendError $ "Actual datum in UTxO is: " <> tShow actualDatum <> ", but witness has wrong corresponding datum information: " <> tShow scriptWitness + GYApplicationException e -> toApiError e + ] + +sinkStreamingBody :: ((Wai.StreamingBody -> IO ()) -> IO ()) -> IO LBS.ByteString +sinkStreamingBody k = do + ref <- newIORef mempty + k $ \f -> f (\b -> modifyIORef' ref (<> b)) (return ()) + toLazyByteString <$> readIORef ref + +errorResponse :: Status -> LBS.ByteString -> Wai.Response +errorResponse status body = + Wai.responseLBS + status + [("Content-Type", "application/json")] + $ Aeson.encode + $ Aeson.object + [ "errorCode" Aeson..= bsMsgToCode (statusMessage status) + , "message" Aeson..= decodeUtf8Lenient (LBS.toStrict body) + ] + where + bsMsgToCode = T.map (\case ' ' -> '_'; x -> toUpper x) . decodeUtf8Lenient + +-- | Transform a 'GYApiError' to 'ServerError'. +apiErrorToServerError :: GYApiError -> ServerError +apiErrorToServerError GYApiError{gaeHttpStatus, gaeErrorCode, gaeMsg} = + ServerError + { errHTTPCode = statusCode gaeHttpStatus + , errReasonPhrase = T.unpack . decodeUtf8Lenient $ statusMessage gaeHttpStatus + , errBody = Aeson.encode $ Aeson.object ["errorCode" .= gaeErrorCode, "message" .= gaeMsg] + , errHeaders = [("Content-Type", "application/json")] + } + +data WaiExceptionHandler = forall e. (Exception e) => WH (e -> GYApiError) + +catchesWaiExc :: [WaiExceptionHandler] -> SomeException -> GYApiError +catchesWaiExc handlers e = foldr tryHandler (someBackendError $ displayException' e) handlers + where + tryHandler (WH handler) res = maybe res handler $ fromException e + +displayException' :: (Exception e) => e -> Text +displayException' = T.pack . displayException + +tShow :: (Show a) => a -> Text +tShow = T.pack . show + +missingSignatoryCheck :: Text -> Bool +missingSignatoryCheck errBody = "MissingVKeyWitnessesUTXOW" `T.isInfixOf` errBody || "MissingRequiredSigners" `T.isInfixOf` errBody diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index 1fd9f13..91adf1c 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -42,6 +42,7 @@ library ZkFold.Cardano.SmartWallet.Server.Auth ZkFold.Cardano.SmartWallet.Server.Config ZkFold.Cardano.SmartWallet.Server.Ctx + ZkFold.Cardano.SmartWallet.Server.ErrorMiddleware ZkFold.Cardano.SmartWallet.Server.Options ZkFold.Cardano.SmartWallet.Server.Run @@ -59,6 +60,7 @@ library filepath, fmt, http-types, + lens, mtl, optparse-applicative, plutus-ledger-api, From 8362e143dbbf07de40c8220d7174df84a92a5d8e Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 12:00:24 +0530 Subject: [PATCH 07/18] feat(#16): add server orphans module --- .../Cardano/SmartWallet/Server/Orphans.hs | 73 +++++++++++++++++++ .../zkfold-smart-wallet-server-lib.cabal | 6 ++ 2 files changed, 79 insertions(+) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Orphans.hs diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Orphans.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Orphans.hs new file mode 100644 index 0000000..7493820 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Orphans.hs @@ -0,0 +1,73 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + +module ZkFold.Cardano.SmartWallet.Server.Orphans () where + +import Control.Lens (at, (%~), (&), (.~), (?~)) +import Data.HashMap.Strict.InsOrd qualified as IOHM +import Data.OpenApi +import Data.Swagger qualified as Swagger +import Data.Swagger.Internal.Schema qualified as Swagger +import Data.Text (Text) +import Servant +import Servant.Foreign +import Servant.OpenApi +import ZkFold.Cardano.SmartWallet.Server.Auth (APIKeyAuthProtect, apiKeyHeaderText) + +instance Swagger.ToSchema Rational where + declareNamedSchema _ = do + integerSchema <- Swagger.declareSchemaRef @Integer Proxy + return $ + Swagger.named "Rational" $ + mempty + & Swagger.type_ + ?~ Swagger.SwaggerObject + & Swagger.properties + .~ IOHM.fromList + [ ("numerator", integerSchema) + , ("denominator", integerSchema) + ] + & Swagger.required + .~ ["numerator", "denominator"] + +instance (HasOpenApi api) => HasOpenApi (APIKeyAuthProtect :> api) where + toOpenApi _ = + toOpenApi (Proxy :: Proxy api) + & (components . securitySchemes) + .~ SecurityDefinitions (IOHM.fromList [(apiKeyHeaderText, apiKeySecurityScheme)]) + & allOperations + . security + .~ [SecurityRequirement (IOHM.singleton apiKeyHeaderText [])] + & allOperations + . responses + %~ addCommonResponses + where + apiKeySecurityScheme :: SecurityScheme + apiKeySecurityScheme = + SecurityScheme + { _securitySchemeType = SecuritySchemeApiKey (ApiKeyParams apiKeyHeaderText ApiKeyHeader) + , _securitySchemeDescription = Just "API key for accessing the server's API." + } + addCommonResponses :: Responses -> Responses + addCommonResponses resps = resps & at 401 ?~ Inline response401 & at 403 ?~ Inline response403 & at 500 ?~ Inline response500 + + response401 :: Response + response401 = mempty & description .~ "Unauthorized access - API key missing" + + response403 :: Response + response403 = mempty & description .~ "Forbidden - The API key does not have permission to perform the request" + + response500 :: Response + response500 = mempty & description .~ "Internal server error" + +-- `HasForeign` instance for `APIKeyAuthProtect :> api` is required to generate client code using libraries such as `servant-py`. +-- This is written with help from https://github.com/haskell-servant/servant-auth/issues/8#issue-185541839. +instance forall lang ftype api. (HasForeign lang ftype api, HasForeignType lang ftype Text) => HasForeign lang ftype (APIKeyAuthProtect :> api) where + type Foreign ftype (APIKeyAuthProtect :> api) = Foreign ftype api + foreignFor lang Proxy Proxy subR = foreignFor lang Proxy (Proxy :: Proxy api) subR' + where + subR' = subR{_reqHeaders = HeaderArg arg : _reqHeaders subR} + arg = + Arg + { _argName = "api-key" + , _argType = typeFor lang (Proxy :: Proxy ftype) (Proxy :: Proxy Text) + } diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index 91adf1c..046bc84 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -44,6 +44,7 @@ library ZkFold.Cardano.SmartWallet.Server.Ctx ZkFold.Cardano.SmartWallet.Server.ErrorMiddleware ZkFold.Cardano.SmartWallet.Server.Options + ZkFold.Cardano.SmartWallet.Server.Orphans ZkFold.Cardano.SmartWallet.Server.Run -- other-modules: @@ -60,12 +61,17 @@ library filepath, fmt, http-types, + insert-ordered-containers, lens, mtl, + openapi3, optparse-applicative, plutus-ledger-api, plutus-tx, + servant-foreign, + servant-openapi3, servant-server, + swagger2, text, time-manager, wai, From 25aabd93d4ada66718222327cea0cb4754fd514c Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 12:12:58 +0530 Subject: [PATCH 08/18] feat(#16): add request logger middleware and server utils module --- .../Server/RequestLoggerMiddleware.hs | 52 +++++++++++++++++++ .../Cardano/SmartWallet/Server/Utils.hs | 41 +++++++++++++++ .../zkfold-smart-wallet-server-lib.cabal | 6 +++ 3 files changed, 99 insertions(+) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/RequestLoggerMiddleware.hs create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Utils.hs diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/RequestLoggerMiddleware.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/RequestLoggerMiddleware.hs new file mode 100644 index 0000000..ab37c8d --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/RequestLoggerMiddleware.hs @@ -0,0 +1,52 @@ +module ZkFold.Cardano.SmartWallet.Server.RequestLoggerMiddleware (gcpReqLogger) where + +import Data.Aeson ((.=)) +import Data.Aeson qualified as Aeson +import Data.Binary.Builder (toLazyByteString) +import Data.ByteString (ByteString) +import Data.ByteString qualified as BS +import Data.Text qualified as T +import Data.Time (UTCTime, defaultTimeLocale, parseTimeOrError) +import GeniusYield.Imports (decodeUtf8Lenient, lazyDecodeUtf8Lenient) +import Network.HTTP.Types (statusCode) +import Network.Wai +import Network.Wai.Middleware.RequestLogger +import System.IO (stderr) +import System.Log.FastLogger +import ZkFold.Cardano.SmartWallet.Server.Utils (bytestringToString) + +-- See https://cloud.google.com/logging/docs/structured-logging. This Haskell code defines a middleware for logging HTTP requests in a Google Cloud Platform (GCP) compatible format. +gcpReqLogger :: IO Middleware +gcpReqLogger = + mkRequestLogger + defaultRequestLoggerSettings + { outputFormat = CustomOutputFormatWithDetails formatter + , destination = Handle stderr + } + where + formatter :: OutputFormatterWithDetails + formatter zonedDate req stat _ latency reqBodyChunks resp = + let statCode = statusCode stat + method = requestMethod req + rawLog = + toLogStr + . Aeson.encode + $ Aeson.object + [ "severity" .= T.pack (if statCode >= 500 then "ERROR" else "INFO") + , -- Only log response body for user-error and server-error responses. + "message" .= if statCode >= 400 then lazyDecodeUtf8Lenient $ toLazyByteString resp else "" + , "time" .= zonedDateToSensibleTime zonedDate + , "httpRequest" + .= Aeson.object + [ "requestMethod" .= bytestringToString method + , "requestUrl" .= decodeUtf8Lenient ("https://self" <> rawPathInfo req <> rawQueryString req) + , "status" .= statCode + , "latency" .= show latency + , "reqBody" .= decodeUtf8Lenient (BS.concat reqBodyChunks) + ] + ] + in rawLog <> "\n" -- Manually adding new line as there doesn't seem to be one in the GCP logs when being monitored through google cloud. + +-- Why does wai use ZonedDate from fast-logger + unix-time? +zonedDateToSensibleTime :: ByteString -> UTCTime +zonedDateToSensibleTime = parseTimeOrError False defaultTimeLocale (bytestringToString simpleTimeFormat) . bytestringToString diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Utils.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Utils.hs new file mode 100644 index 0000000..e21ffd0 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Utils.hs @@ -0,0 +1,41 @@ +module ZkFold.Cardano.SmartWallet.Server.Utils ( + ExceptionTypes (..), + isMatchedException, + logInfo, + logDebug, + dropSymbolAndCamelToSnake, + addSwaggerDescription, + addSwaggerExample, + bytestringToString, +) where + +import Control.Exception (Exception (..), SomeException) +import Data.ByteString (ByteString) +import Data.Text qualified as Text +import GeniusYield.Imports +import GeniusYield.Swagger.Utils (addSwaggerDescription, addSwaggerExample, dropSymbolAndCamelToSnake) +import GeniusYield.Types +import ZkFold.Cardano.SmartWallet.Server.Ctx + +logDebug :: (HasCallStack) => Ctx -> String -> IO () +logDebug ctx = gyLogDebug (ctxProviders ctx) mempty + +logInfo :: (HasCallStack) => Ctx -> String -> IO () +logInfo ctx = gyLogInfo (ctxProviders ctx) mempty + +type ExceptionTypes :: [Type] -> Type +data ExceptionTypes es where + ENil :: ExceptionTypes '[] + (:>>) :: (Exception e) => Proxy e -> ExceptionTypes es -> ExceptionTypes (e ': es) + +infixr 5 :>> + +isMatchedException :: ExceptionTypes es -> SomeException -> Bool +isMatchedException ENil _ = False +isMatchedException (etype :>> etypes) se = isJust (f etype) || isMatchedException etypes se + where + f :: forall e. (Exception e) => Proxy e -> Maybe e + f _ = fromException @e se + +bytestringToString :: ByteString -> String +bytestringToString = decodeUtf8Lenient >>> Text.unpack \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index 046bc84..a225124 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -45,7 +45,9 @@ library ZkFold.Cardano.SmartWallet.Server.ErrorMiddleware ZkFold.Cardano.SmartWallet.Server.Options ZkFold.Cardano.SmartWallet.Server.Orphans + ZkFold.Cardano.SmartWallet.Server.RequestLoggerMiddleware ZkFold.Cardano.SmartWallet.Server.Run + ZkFold.Cardano.SmartWallet.Server.Utils -- other-modules: -- other-extensions: @@ -54,9 +56,11 @@ library aeson, atlas-cardano, base ^>=4.18.2.1, + binary, bytestring, deriving-aeson, envy, + fast-logger, file-embed, filepath, fmt, @@ -73,8 +77,10 @@ library servant-server, swagger2, text, + time, time-manager, wai, + wai-extra, warp, yaml, zkfold-smart-wallet-api, From 3e51fad471cf846415968496dd79564b3a5cfedd Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 13:51:08 +0530 Subject: [PATCH 09/18] feat(#16): add Api and Run module for the server --- .../ZkFold/Cardano/SmartWallet/Constants.hs | 8 +- .../src/ZkFold/Cardano/SmartWallet/Types.hs | 5 +- .../ZkFold/Cardano/SmartWallet/Server/Api.hs | 164 ++++++++++++++++++ .../ZkFold/Cardano/SmartWallet/Server/Run.hs | 94 ++++++++++ .../zkfold-smart-wallet-server-lib.cabal | 10 +- 5 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs diff --git a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs index 2cc4fb5..7273c3b 100644 --- a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs +++ b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs @@ -2,6 +2,7 @@ module ZkFold.Cardano.SmartWallet.Constants ( smartWalletValidator, + mockSmartWalletStakeValidator, ) where @@ -9,6 +10,7 @@ import Data.FileEmbed import GeniusYield.Types import ZkFold.Cardano.SmartWallet.Types (SetupBytes, WalletSetup) +-- FIXME: put correct validator here. smartWalletValidator :: SetupBytes -> WalletSetup -> GYScript 'PlutusV3 smartWalletValidator = let fileBS = $(makeRelativeToProject "./data/compiled-scripts/smart-wallet.plutus" >>= embedFile) @@ -16,4 +18,8 @@ smartWalletValidator = -- TODO: Load the parameterised script, perhaps via blueprint feature of Atlas? case readScript' fileBS of Left e -> error $ "Failed to read smart-wallet.plutus: " <> show e - Right script -> applyParam . applyParam script \ No newline at end of file + Right script -> applyParam . applyParam script + +-- FIXME: +mockSmartWalletStakeValidator :: GYScript 'PlutusV3 +mockSmartWalletStakeValidator = undefined \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs index 51100db..93b00f4 100644 --- a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs +++ b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs @@ -16,8 +16,9 @@ import ZkFold.Cardano.SmartWallet.Types.Validator -- | Information required to build transactions for a zk-wallet. data ZKWalletBuildInfo = ZKWalletBuildInfo - { -- TODO: Mock validator can actually be part of constants... - zkwbiMockStakeValidator :: GYScript 'PlutusV3 + { zkwbiSmartWalletValidator :: SetupBytes -> WalletSetup -> GYScript 'PlutusV3 + -- ^ Smart wallet validator script. + , zkwbiMockStakeValidator :: GYScript 'PlutusV3 -- ^ Mock stake validator used to compute execution units. Since redeemer depends upon script context, and script context depends upon redeemer (as it influences fees and thus also influences change output(s)) we find ourselves in a chicken-and-egg problem. To solve this, we use a mock stake validator whose proofs server can easily compute as script has setup parameters that allow forging. } diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs new file mode 100644 index 0000000..381ecd7 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs @@ -0,0 +1,164 @@ +module ZkFold.Cardano.SmartWallet.Server.Api ( + ZkFoldSmartWalletAPI, + zkFoldSmartWalletAPI, + zkFoldSmartWalletServer, + MainAPI, + mainAPI, + mainServer, + zkFoldSmartWalletAPIOpenApi, +) where + +import Control.Lens ((.~), (?~)) +import Data.Aeson (ToJSON (..)) +import Data.Aeson qualified as Aeson +import Data.Aeson.Key qualified as K +import Data.Char (toLower) +import Data.Kind (Type) +import Data.List (isPrefixOf, sortBy) +import Data.Map.Strict qualified as Map +import Data.OpenApi +import Data.OpenApi qualified as OpenApi +import Data.Swagger qualified as Swagger +import Data.Swagger.Internal.Schema qualified as Swagger +import Data.Text qualified as T +import Data.Version (showVersion) +import Deriving.Aeson +import Fmt +import GHC.TypeLits (Symbol) +import GeniusYield.Imports ((&), (>>>)) +import GeniusYield.TxBuilder (GYTxQueryMonad (utxosAtAddress)) +import GeniusYield.Types +import GeniusYield.Types.OpenApi () +import PackageInfo_zkfold_smart_wallet_server_lib qualified as PackageInfo +import Servant +import Servant.OpenApi +import ZkFold.Cardano.SmartWallet.Server.Auth (APIKeyAuthProtect, V0) +import ZkFold.Cardano.SmartWallet.Server.Ctx +import ZkFold.Cardano.SmartWallet.Server.Orphans () +import ZkFold.Cardano.SmartWallet.Server.Utils +import ZkFold.Cardano.SmartWallet.Types (ZKWalletBuildInfo) + +{- $setup + +>>> :set -XOverloadedStrings -XTypeApplications +>>> import qualified Data.Aeson as Aeson +>>> import qualified Data.ByteString.Lazy.Char8 as LBS8 +>>> import Data.Proxy +>>> import qualified Data.Swagger as Swagger +>>> import GeniusYield.Types +-} + +------------------------------------------------------------------------------- +-- Settings. +------------------------------------------------------------------------------- + +type SettingsPrefix :: Symbol +type SettingsPrefix = "settings" + +data Settings = Settings + { settingsNetwork :: !String + , settingsVersion :: !String + , settingsAddress :: !(Maybe GYAddressBech32) + , settingsStakeAddress :: !(Maybe GYStakeAddressBech32) + , settingsCollateral :: !(Maybe GYTxOutRef) + } + deriving stock (Show, Eq, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix SettingsPrefix, CamelToSnake]] Settings + +instance Swagger.ToSchema Settings where + declareNamedSchema = + Swagger.genericDeclareNamedSchema Swagger.defaultSchemaOptions{Swagger.fieldLabelModifier = dropSymbolAndCamelToSnake @SettingsPrefix} + & addSwaggerDescription "Genius Yield Server settings." + +------------------------------------------------------------------------------- +-- Server's API. +------------------------------------------------------------------------------- + +type SettingsAPI = Summary "Server settings" :> Description "Get server settings such as network and version. Optionally if a collateral UTxO reference and signing key are individually configured in the server (provided server is spun up locally), then it's details are also returned." :> Get '[JSON] Settings + +type V0API = + "settings" :> SettingsAPI + +type ZkFoldSmartWalletAPI = APIKeyAuthProtect :> V0 :> V0API + +zkFoldSmartWalletAPI :: Proxy ZkFoldSmartWalletAPI +zkFoldSmartWalletAPI = Proxy + +infixr 4 +> + +type family (+>) (api1 :: k) (api2 :: Type) where + (+>) api1 api2 = APIKeyAuthProtect :> V0 :> api1 :> api2 + +zkFoldSmartWalletAPIOpenApi :: OpenApi +zkFoldSmartWalletAPIOpenApi = + toOpenApi zkFoldSmartWalletAPI + & info + . OpenApi.title + .~ "zkFold Smart Wallet Server API" + & info + . version + .~ "0.0.1" + & info + . license + ?~ ("Apache-2.0" & url ?~ URL "https://opensource.org/licenses/apache-2-0") + & info + . contact + ?~ ( mempty + & url + ?~ URL "https://zkfold.io/" + & email + ?~ "info@zkfold.io" + & name + ?~ "zkFold Technical Support" + ) + & info + . OpenApi.description + ?~ "API to interact with zkFold Smart Wallet." + & applyTagsFor (subOperations (Proxy :: Proxy ("settings" +> SettingsAPI)) (Proxy :: Proxy ZkFoldSmartWalletAPI)) ["Settings" & OpenApi.description ?~ "Endpoint to get server settings such as network and version"] + +zkFoldSmartWalletServer :: Ctx -> ServerT ZkFoldSmartWalletAPI IO +zkFoldSmartWalletServer ctx = + ignoredAuthResult $ + handleSettings ctx + where + ignoredAuthResult f _authResult = f + +type MainAPI = + ZkFoldSmartWalletAPI + +mainAPI :: Proxy MainAPI +mainAPI = Proxy + +mainServer :: Ctx -> ServerT MainAPI IO +mainServer = zkFoldSmartWalletServer + +handleSettings :: Ctx -> IO Settings +handleSettings ctx@Ctx{..} = do + logInfo ctx "Settings requested." + pure $ + Settings + { settingsNetwork = ctxNetworkId & customShowNetworkId + , settingsVersion = showVersion PackageInfo.version + , settingsAddress = fmap (addressToBech32 . snd) ctxSigningKey + , settingsStakeAddress = ctxStakeAddress + , settingsCollateral = ctxCollateral + } + +-- >>> customShowNetworkId GYMainnet +-- "mainnet" +-- >>> customShowNetworkId GYTestnetLegacy +-- "legacy" +-- >>> customShowNetworkId GYPrivnet +-- "privnet" +customShowNetworkId :: GYNetworkId -> String +customShowNetworkId = show >>> removePrefix "GY" >>> removePrefix "Testnet" >>> lowerFirstChar + where + removePrefix :: String -> String -> String + removePrefix pref str + | pref `isPrefixOf` str = drop (length pref) str + | otherwise = str + lowerFirstChar :: String -> String + lowerFirstChar "" = "" + lowerFirstChar (x : xs) = toLower x : xs diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs new file mode 100644 index 0000000..c08dbf9 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs @@ -0,0 +1,94 @@ +module ZkFold.Cardano.SmartWallet.Server.Run ( + runServer, +) where + +import Control.Exception (Exception (..), SomeException, try) +import Control.Monad.Except (ExceptT (..)) +import Data.ByteString qualified as B +import Data.Text.Lazy qualified as LT +import Data.Version (showVersion) +import Data.Yaml.Pretty qualified as Yaml +import Fmt +import GeniusYield.GYConfig +import GeniusYield.HTTP.Errors +import GeniusYield.Imports +import GeniusYield.Types +import Network.Wai qualified as Wai +import Network.Wai.Handler.Warp qualified as Warp +import PackageInfo_zkfold_smart_wallet_server_lib qualified as PackageInfo +import Servant +import Servant.Server.Experimental.Auth (AuthHandler) +import Servant.Server.Internal.ServerError (responseServerError) +import System.TimeManager (TimeoutThread (..)) +import ZkFold.Cardano.SmartWallet.Constants +import ZkFold.Cardano.SmartWallet.Server.Api +import ZkFold.Cardano.SmartWallet.Server.Auth +import ZkFold.Cardano.SmartWallet.Server.Config (ServerConfig (..), coreConfigFromServerConfig, optionalSigningKeyFromServerConfig, serverConfigOptionalFPIO) +import ZkFold.Cardano.SmartWallet.Server.Ctx +import ZkFold.Cardano.SmartWallet.Server.ErrorMiddleware +import ZkFold.Cardano.SmartWallet.Server.RequestLoggerMiddleware (gcpReqLogger) +import ZkFold.Cardano.SmartWallet.Server.Utils +import ZkFold.Cardano.SmartWallet.Types (ZKWalletBuildInfo (..)) + +runServer :: Maybe FilePath -> IO () +runServer mfp = do + serverConfig <- serverConfigOptionalFPIO mfp + optionalSigningKey <- optionalSigningKeyFromServerConfig serverConfig + let nid = scNetworkId serverConfig + coreCfg = coreConfigFromServerConfig serverConfig + -- writePythonForAPI (Proxy @MainAPI) requests "web/swagger/api.py" + withCfgProviders coreCfg "server" $ \providers -> do + let logInfoS = gyLogInfo providers mempty + logErrorS = gyLogError providers mempty + logInfoS $ "GeniusYield server version: " +| showVersion PackageInfo.version |+ "\nOptional collateral configuration: " +|| scCollateral serverConfig ||+ "\nAddress of optional wallet: " +|| fmap snd optionalSigningKey ||+ "\nOptional stake address: " +|| scStakeAddress serverConfig ||+ "" + B.writeFile "web/openapi/api.yaml" (Yaml.encodePretty Yaml.defConfig zkFoldSmartWalletAPI) + reqLoggerMiddleware <- gcpReqLogger + let + -- These are only meant to catch fatal exceptions, application thrown exceptions should be caught beforehand. + onException :: req -> SomeException -> IO () + onException _req exc = + displayException exc + & if isMatchedException exceptionsToIgnore exc + then logInfoS + else logErrorS + where + -- TimeoutThread and Warp.ConnectionClosedByPeer do not indicate that anything is wrong and + -- should not be logged as errors. See + -- https://magnus.therning.org/2021-07-03-the-timeout-manager-exception.html + -- https://www.rfc-editor.org/rfc/rfc5246#page-29 + exceptionsToIgnore = Proxy @TimeoutThread :>> Proxy @Warp.InvalidRequest :>> ENil + onExceptionResponse :: SomeException -> Wai.Response + onExceptionResponse _ = responseServerError . apiErrorToServerError $ someBackendError "Internal Server Error" + settings = + Warp.defaultSettings + & Warp.setPort (scPort serverConfig) + & Warp.setOnException onException + & Warp.setOnExceptionResponse onExceptionResponse + errLoggerMiddleware = errorLoggerMiddleware $ logErrorS . LT.unpack + ctx = + Ctx + { ctxProviders = providers + , ctxNetworkId = nid + , ctxSmartWalletBuildInfo = ZKWalletBuildInfo smartWalletValidator mockSmartWalletStakeValidator + , ctxSigningKey = optionalSigningKey + , ctxCollateral = scCollateral serverConfig + , ctxStakeAddress = scStakeAddress serverConfig + } + + logInfoS $ + "Starting zkFold-smart-wallet server on port " + +| scPort serverConfig + |+ "\nCore config:\n" + +| indentF 4 (fromString $ show coreCfg) + |+ "" + Warp.runSettings settings + . reqLoggerMiddleware + . errLoggerMiddleware + . errorJsonWrapMiddleware + $ let context = apiKeyAuthHandler (case scServerApiKey serverConfig of Confidential t -> apiKeyFromText t) :. EmptyContext + in serveWithContext mainAPI context + $ hoistServerWithContext + mainAPI + (Proxy :: Proxy '[AuthHandler Wai.Request ()]) + (\ioAct -> Handler . ExceptT $ first (apiErrorToServerError . exceptionHandler) <$> try ioAct) + $ mainServer ctx diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index a225124..183dec2 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -1,4 +1,4 @@ -cabal-version: 3.4 +cabal-version: 3.12 name: zkfold-smart-wallet-server-lib version: 0.1.0.0 -- synopsis: @@ -39,6 +39,7 @@ common common library import: common exposed-modules: + ZkFold.Cardano.SmartWallet.Server.Api ZkFold.Cardano.SmartWallet.Server.Auth ZkFold.Cardano.SmartWallet.Server.Config ZkFold.Cardano.SmartWallet.Server.Ctx @@ -49,6 +50,12 @@ library ZkFold.Cardano.SmartWallet.Server.Run ZkFold.Cardano.SmartWallet.Server.Utils + other-modules: + PackageInfo_zkfold_smart_wallet_server_lib + + autogen-modules: + PackageInfo_zkfold_smart_wallet_server_lib + -- other-modules: -- other-extensions: build-depends: @@ -58,6 +65,7 @@ library base ^>=4.18.2.1, binary, bytestring, + containers, deriving-aeson, envy, fast-logger, From 5e0d6cf4b063dfe5060ffdc5ac25bacf466dfe0b Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 14:00:24 +0530 Subject: [PATCH 10/18] feat(#16): tie server commands with executable --- atlas/zkfold-smart-wallet-api/app/Main.hs | 7 ------- .../src/ZkFold/Cardano/SmartWallet/Constants.hs | 2 +- .../zkfold-smart-wallet-api.cabal | 11 ----------- atlas/zkfold-smart-wallet-server-lib/app/Main.hs | 9 +++------ .../src/ZkFold/Cardano/SmartWallet/Server/Options.hs | 5 ++--- 5 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 atlas/zkfold-smart-wallet-api/app/Main.hs diff --git a/atlas/zkfold-smart-wallet-api/app/Main.hs b/atlas/zkfold-smart-wallet-api/app/Main.hs deleted file mode 100644 index d63a98d..0000000 --- a/atlas/zkfold-smart-wallet-api/app/Main.hs +++ /dev/null @@ -1,7 +0,0 @@ -module Main where - -import ZkFold.Cardano.SmartWallet.Constants qualified as Constants - -main :: IO () -main = do - putStrLn "Hello, Haskell!" diff --git a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs index 7273c3b..3dea87c 100644 --- a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs +++ b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Constants.hs @@ -22,4 +22,4 @@ smartWalletValidator = -- FIXME: mockSmartWalletStakeValidator :: GYScript 'PlutusV3 -mockSmartWalletStakeValidator = undefined \ No newline at end of file +mockSmartWalletStakeValidator = smartWalletValidator undefined undefined \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal b/atlas/zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal index fb75fda..a74ed6a 100644 --- a/atlas/zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal +++ b/atlas/zkfold-smart-wallet-api/zkfold-smart-wallet-api.cabal @@ -63,17 +63,6 @@ library hs-source-dirs: src -executable zkfold-smart-contract-wallet-server - import: common - main-is: Main.hs - -- other-modules: - -- other-extensions: - build-depends: - base ^>=4.18.2.1, - zkfold-smart-wallet-api, - - hs-source-dirs: app - test-suite zkfold-smart-contract-wallet-server-test import: common -- other-modules: diff --git a/atlas/zkfold-smart-wallet-server-lib/app/Main.hs b/atlas/zkfold-smart-wallet-server-lib/app/Main.hs index 369be9c..45679cf 100644 --- a/atlas/zkfold-smart-wallet-server-lib/app/Main.hs +++ b/atlas/zkfold-smart-wallet-server-lib/app/Main.hs @@ -1,8 +1,7 @@ import Options.Applicative +import ZkFold.Cardano.SmartWallet.Server.Options --- import ZkFold.Cardano.SmartWallet.Server.Options - -main ∷ IO () +main :: IO () main = runCommand =<< execParser opts where opts = @@ -11,6 +10,4 @@ main = runCommand =<< execParser opts ( fullDesc <> progDesc "zkFold smart wallet helpful operations" <> header "zkFold smart wallet" - ) - runCommand = undefined - parseCommand = undefined \ No newline at end of file + ) \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs index a68316a..f674c7a 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs @@ -9,6 +9,7 @@ module ZkFold.Cardano.SmartWallet.Server.Options ( -- import GeniusYield.Server.Run (runServer) import Options.Applicative +import ZkFold.Cardano.SmartWallet.Server.Run (runServer) newtype Command = Serve ServeCommand @@ -41,6 +42,4 @@ runCommand :: Command -> IO () runCommand (Serve serveCommand) = runServeCommand serveCommand runServeCommand :: ServeCommand -> IO () -runServeCommand (ServeCommand mcfp) = runServer mcfp - -runServer = undefined +runServeCommand (ServeCommand mcfp) = runServer mcfp \ No newline at end of file From 745c8705af06a54f651305c2c1af6b3b25397e45 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 19:11:01 +0530 Subject: [PATCH 11/18] feat(#16): modularize settings api --- .../ZkFold/Cardano/SmartWallet/Server/Api.hs | 88 +------------------ .../SmartWallet/Server/Api/Settings.hs | 70 +++++++++++++++ .../zkfold-smart-wallet-server-lib.cabal | 1 + 3 files changed, 74 insertions(+), 85 deletions(-) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs index 381ecd7..21feafd 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs @@ -9,75 +9,22 @@ module ZkFold.Cardano.SmartWallet.Server.Api ( ) where import Control.Lens ((.~), (?~)) -import Data.Aeson (ToJSON (..)) -import Data.Aeson qualified as Aeson -import Data.Aeson.Key qualified as K -import Data.Char (toLower) import Data.Kind (Type) -import Data.List (isPrefixOf, sortBy) -import Data.Map.Strict qualified as Map import Data.OpenApi import Data.OpenApi qualified as OpenApi -import Data.Swagger qualified as Swagger -import Data.Swagger.Internal.Schema qualified as Swagger -import Data.Text qualified as T -import Data.Version (showVersion) -import Deriving.Aeson -import Fmt -import GHC.TypeLits (Symbol) -import GeniusYield.Imports ((&), (>>>)) -import GeniusYield.TxBuilder (GYTxQueryMonad (utxosAtAddress)) -import GeniusYield.Types +import GeniusYield.Imports ((&)) import GeniusYield.Types.OpenApi () -import PackageInfo_zkfold_smart_wallet_server_lib qualified as PackageInfo import Servant import Servant.OpenApi +import ZkFold.Cardano.SmartWallet.Server.Api.Settings import ZkFold.Cardano.SmartWallet.Server.Auth (APIKeyAuthProtect, V0) import ZkFold.Cardano.SmartWallet.Server.Ctx import ZkFold.Cardano.SmartWallet.Server.Orphans () -import ZkFold.Cardano.SmartWallet.Server.Utils -import ZkFold.Cardano.SmartWallet.Types (ZKWalletBuildInfo) - -{- $setup - ->>> :set -XOverloadedStrings -XTypeApplications ->>> import qualified Data.Aeson as Aeson ->>> import qualified Data.ByteString.Lazy.Char8 as LBS8 ->>> import Data.Proxy ->>> import qualified Data.Swagger as Swagger ->>> import GeniusYield.Types --} - -------------------------------------------------------------------------------- --- Settings. -------------------------------------------------------------------------------- - -type SettingsPrefix :: Symbol -type SettingsPrefix = "settings" - -data Settings = Settings - { settingsNetwork :: !String - , settingsVersion :: !String - , settingsAddress :: !(Maybe GYAddressBech32) - , settingsStakeAddress :: !(Maybe GYStakeAddressBech32) - , settingsCollateral :: !(Maybe GYTxOutRef) - } - deriving stock (Show, Eq, Generic) - deriving - (FromJSON, ToJSON) - via CustomJSON '[FieldLabelModifier '[StripPrefix SettingsPrefix, CamelToSnake]] Settings - -instance Swagger.ToSchema Settings where - declareNamedSchema = - Swagger.genericDeclareNamedSchema Swagger.defaultSchemaOptions{Swagger.fieldLabelModifier = dropSymbolAndCamelToSnake @SettingsPrefix} - & addSwaggerDescription "Genius Yield Server settings." ------------------------------------------------------------------------------- -- Server's API. ------------------------------------------------------------------------------- -type SettingsAPI = Summary "Server settings" :> Description "Get server settings such as network and version. Optionally if a collateral UTxO reference and signing key are individually configured in the server (provided server is spun up locally), then it's details are also returned." :> Get '[JSON] Settings - type V0API = "settings" :> SettingsAPI @@ -132,33 +79,4 @@ mainAPI :: Proxy MainAPI mainAPI = Proxy mainServer :: Ctx -> ServerT MainAPI IO -mainServer = zkFoldSmartWalletServer - -handleSettings :: Ctx -> IO Settings -handleSettings ctx@Ctx{..} = do - logInfo ctx "Settings requested." - pure $ - Settings - { settingsNetwork = ctxNetworkId & customShowNetworkId - , settingsVersion = showVersion PackageInfo.version - , settingsAddress = fmap (addressToBech32 . snd) ctxSigningKey - , settingsStakeAddress = ctxStakeAddress - , settingsCollateral = ctxCollateral - } - --- >>> customShowNetworkId GYMainnet --- "mainnet" --- >>> customShowNetworkId GYTestnetLegacy --- "legacy" --- >>> customShowNetworkId GYPrivnet --- "privnet" -customShowNetworkId :: GYNetworkId -> String -customShowNetworkId = show >>> removePrefix "GY" >>> removePrefix "Testnet" >>> lowerFirstChar - where - removePrefix :: String -> String -> String - removePrefix pref str - | pref `isPrefixOf` str = drop (length pref) str - | otherwise = str - lowerFirstChar :: String -> String - lowerFirstChar "" = "" - lowerFirstChar (x : xs) = toLower x : xs +mainServer = zkFoldSmartWalletServer \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs new file mode 100644 index 0000000..548aa78 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs @@ -0,0 +1,70 @@ +module ZkFold.Cardano.SmartWallet.Server.Api.Settings ( + SettingsAPI, + handleSettings, +) where + +import Data.Char (toLower) +import Data.List (isPrefixOf) +import Data.Swagger qualified as Swagger +import Data.Version (showVersion) +import Deriving.Aeson +import GHC.TypeLits (Symbol) +import GeniusYield.Imports ((&), (>>>)) +import GeniusYield.Types +import GeniusYield.Types.OpenApi () +import PackageInfo_zkfold_smart_wallet_server_lib qualified as PackageInfo +import Servant +import ZkFold.Cardano.SmartWallet.Server.Ctx +import ZkFold.Cardano.SmartWallet.Server.Orphans () +import ZkFold.Cardano.SmartWallet.Server.Utils + +type SettingsPrefix :: Symbol +type SettingsPrefix = "settings" + +data Settings = Settings + { settingsNetwork :: !String + , settingsVersion :: !String + , settingsAddress :: !(Maybe GYAddressBech32) + , settingsStakeAddress :: !(Maybe GYStakeAddressBech32) + , settingsCollateral :: !(Maybe GYTxOutRef) + } + deriving stock (Show, Eq, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix SettingsPrefix, CamelToSnake]] Settings + +instance Swagger.ToSchema Settings where + declareNamedSchema = + Swagger.genericDeclareNamedSchema Swagger.defaultSchemaOptions{Swagger.fieldLabelModifier = dropSymbolAndCamelToSnake @SettingsPrefix} + & addSwaggerDescription "zkFold Smart Wallet Server settings." + +type SettingsAPI = Summary "Server settings" :> Description "Get server settings such as network and version. Optionally if a collateral UTxO reference and signing key are individually configured in the server (provided server is spun up locally), then it's details are also returned." :> Get '[JSON] Settings + +handleSettings :: Ctx -> IO Settings +handleSettings ctx@Ctx{..} = do + logInfo ctx "Settings requested." + pure $ + Settings + { settingsNetwork = ctxNetworkId & customShowNetworkId + , settingsVersion = showVersion PackageInfo.version + , settingsAddress = fmap (addressToBech32 . snd) ctxSigningKey + , settingsStakeAddress = ctxStakeAddress + , settingsCollateral = ctxCollateral + } + +-- >>> customShowNetworkId GYMainnet +-- "mainnet" +-- >>> customShowNetworkId GYTestnetLegacy +-- "legacy" +-- >>> customShowNetworkId GYPrivnet +-- "privnet" +customShowNetworkId :: GYNetworkId -> String +customShowNetworkId = show >>> removePrefix "GY" >>> removePrefix "Testnet" >>> lowerFirstChar + where + removePrefix :: String -> String -> String + removePrefix pref str + | pref `isPrefixOf` str = drop (length pref) str + | otherwise = str + lowerFirstChar :: String -> String + lowerFirstChar "" = "" + lowerFirstChar (x : xs) = toLower x : xs diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index 183dec2..924c69a 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -40,6 +40,7 @@ library import: common exposed-modules: ZkFold.Cardano.SmartWallet.Server.Api + ZkFold.Cardano.SmartWallet.Server.Api.Settings ZkFold.Cardano.SmartWallet.Server.Auth ZkFold.Cardano.SmartWallet.Server.Config ZkFold.Cardano.SmartWallet.Server.Ctx From 273138f5b3294608a4fd5dd88359951890cdf222 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Thu, 20 Mar 2025 21:47:25 +0530 Subject: [PATCH 12/18] feat(#16): wallet & txs api --- .../src/ZkFold/Cardano/SmartWallet/Api.hs | 24 +++++- .../src/ZkFold/Cardano/SmartWallet/Types.hs | 11 +++ .../ZkFold/Cardano/SmartWallet/Server/Api.hs | 8 ++ .../SmartWallet/Server/Api/Settings.hs | 8 +- .../Cardano/SmartWallet/Server/Api/Wallet.hs | 82 +++++++++++++++++++ .../Cardano/SmartWallet/Server/Config.hs | 33 ++++---- .../ZkFold/Cardano/SmartWallet/Server/Ctx.hs | 5 +- .../ZkFold/Cardano/SmartWallet/Server/Run.hs | 13 +-- .../ZkFold/Cardano/SmartWallet/Server/Tx.hs | 52 ++++++++++++ .../zkfold-smart-wallet-server-lib.cabal | 2 + 10 files changed, 203 insertions(+), 35 deletions(-) create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs create mode 100644 atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Tx.hs diff --git a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs index 1de4df8..f838243 100644 --- a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs +++ b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs @@ -1,11 +1,15 @@ module ZkFold.Cardano.SmartWallet.Api ( + validatorSetupFromEmail, + addressFromEmail, + addressFromValidatorSetup, sendFunds, + sendFunds', ) where +import Control.Monad ((>=>)) import Control.Monad.Reader (MonadReader (..)) import GeniusYield.TxBuilder (GYTxQueryMonad (..), GYTxSkeleton, mustHaveOutput, mustHaveWithdrawal, throwAppError) import GeniusYield.Types (GYAddress, GYBuildPlutusScript (..), GYCredential (GYCredentialByScript), GYRedeemer, GYStakeAddressInfo (..), GYTxBuildWitness (..), GYTxWdrl (..), GYValue, PlutusVersion (..), mkGYTxOutNoDatum, scriptHash, stakeAddressFromCredential, unitRedeemer) -import ZkFold.Cardano.SmartWallet.Constants (smartWalletValidator) import ZkFold.Cardano.SmartWallet.Types -- | A dummy redeemer. We would update this with actual redeemer later on. @@ -14,12 +18,24 @@ dummyRedeemer = unitRedeemer -- TODO: To not require @SetupBytes@ and @WalletSetup@, but rather have this function receive `GYScript` directly? +validatorSetupFromEmail :: (ZKWalletQueryMonad m) => Email -> m ValidatorSetup +validatorSetupFromEmail email = undefined -- FIXME: + +addressFromEmail :: (ZKWalletQueryMonad m) => Email -> m GYAddress +addressFromEmail = validatorSetupFromEmail >=> addressFromValidatorSetup + +addressFromValidatorSetup :: (ZKWalletQueryMonad m) => ValidatorSetup -> m GYAddress +addressFromValidatorSetup (sb, ws) = undefined -- FIXME: + -- | Send funds from a zk-wallet to a given address. -sendFunds :: (ZKWalletQueryMonad m) => SetupBytes -> WalletSetup -> GYAddress -> GYValue -> m (GYTxSkeleton 'PlutusV3) -sendFunds sb ws sendAddr sendVal = do +sendFunds :: (ZKWalletQueryMonad m) => Email -> GYAddress -> GYValue -> m (GYTxSkeleton 'PlutusV3) +sendFunds email sendAddr sendVal = validatorSetupFromEmail email >>= \validatorSetup -> sendFunds' validatorSetup sendAddr sendVal + +sendFunds' :: (ZKWalletQueryMonad m) => ValidatorSetup -> GYAddress -> GYValue -> m (GYTxSkeleton 'PlutusV3) +sendFunds' (sb, ws) sendAddr sendVal = do nid <- networkId zkwbi <- ask - let stakeAddr = stakeAddressFromCredential nid (GYCredentialByScript $ scriptHash $ smartWalletValidator sb ws) + let stakeAddr = stakeAddressFromCredential nid (GYCredentialByScript $ scriptHash $ zkwbiSmartWalletValidator zkwbi sb ws) let mockStakeAddr = stakeAddressFromCredential nid (GYCredentialByScript $ scriptHash $ zkwbiMockStakeValidator zkwbi) -- TODO: Perhaps stake address information is not required if we are sure that withdrawal amount would always be zero. si <- diff --git a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs index 93b00f4..41d0375 100644 --- a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs +++ b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Types.hs @@ -3,10 +3,13 @@ module ZkFold.Cardano.SmartWallet.Types ( ZKWalletException (..), ZKWalletQueryMonad, module ZkFold.Cardano.SmartWallet.Types.Validator, + Email, + ValidatorSetup, ) where import Control.Exception (Exception) import Control.Monad.Reader (MonadReader) +import Data.Text (Text) import Data.Text qualified as Text import GeniusYield.HTTP.Errors (GYApiError (..), IsGYApiError (..)) import GeniusYield.TxBuilder (GYTxQueryMonad) @@ -37,3 +40,11 @@ instance IsGYApiError ZKWalletException where , gaeHttpStatus = status500 , gaeMsg = Text.pack $ "Could not find stake address information for the given stake address: " <> show sa } + +-- TODO: To make a newtype and validate for emails? Or specifically gmails? + +-- | Email address. +type Email = Text + +-- | Parameters required by validator. +type ValidatorSetup = (SetupBytes, WalletSetup) \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs index 21feafd..0c589fb 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api.hs @@ -17,9 +17,11 @@ import GeniusYield.Types.OpenApi () import Servant import Servant.OpenApi import ZkFold.Cardano.SmartWallet.Server.Api.Settings +import ZkFold.Cardano.SmartWallet.Server.Api.Wallet (WalletAPI, handleWalletApi) import ZkFold.Cardano.SmartWallet.Server.Auth (APIKeyAuthProtect, V0) import ZkFold.Cardano.SmartWallet.Server.Ctx import ZkFold.Cardano.SmartWallet.Server.Orphans () +import ZkFold.Cardano.SmartWallet.Server.Tx (TxAPI, handleTxApi) ------------------------------------------------------------------------------- -- Server's API. @@ -27,6 +29,8 @@ import ZkFold.Cardano.SmartWallet.Server.Orphans () type V0API = "settings" :> SettingsAPI + :<|> "wallet" :> WalletAPI + :<|> "tx" :> TxAPI type ZkFoldSmartWalletAPI = APIKeyAuthProtect :> V0 :> V0API @@ -64,11 +68,15 @@ zkFoldSmartWalletAPIOpenApi = . OpenApi.description ?~ "API to interact with zkFold Smart Wallet." & applyTagsFor (subOperations (Proxy :: Proxy ("settings" +> SettingsAPI)) (Proxy :: Proxy ZkFoldSmartWalletAPI)) ["Settings" & OpenApi.description ?~ "Endpoint to get server settings such as network and version"] + & applyTagsFor (subOperations (Proxy :: Proxy ("wallet" +> WalletAPI)) (Proxy :: Proxy ZkFoldSmartWalletAPI)) ["Wallet" & OpenApi.description ?~ "Endpoint to interact with zk-wallet"] + & applyTagsFor (subOperations (Proxy :: Proxy ("tx" +> TxAPI)) (Proxy :: Proxy ZkFoldSmartWalletAPI)) ["Tx" & OpenApi.description ?~ "Endpoint to interact with built transactions"] zkFoldSmartWalletServer :: Ctx -> ServerT ZkFoldSmartWalletAPI IO zkFoldSmartWalletServer ctx = ignoredAuthResult $ handleSettings ctx + :<|> handleWalletApi ctx + :<|> handleTxApi ctx where ignoredAuthResult f _authResult = f diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs index 548aa78..9858aa8 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs @@ -24,9 +24,8 @@ type SettingsPrefix = "settings" data Settings = Settings { settingsNetwork :: !String , settingsVersion :: !String - , settingsAddress :: !(Maybe GYAddressBech32) - , settingsStakeAddress :: !(Maybe GYStakeAddressBech32) - , settingsCollateral :: !(Maybe GYTxOutRef) + , settingsCollateral :: !GYTxOutRef + , settingsCollateralAddress :: !GYAddressBech32 } deriving stock (Show, Eq, Generic) deriving @@ -47,9 +46,8 @@ handleSettings ctx@Ctx{..} = do Settings { settingsNetwork = ctxNetworkId & customShowNetworkId , settingsVersion = showVersion PackageInfo.version - , settingsAddress = fmap (addressToBech32 . snd) ctxSigningKey - , settingsStakeAddress = ctxStakeAddress , settingsCollateral = ctxCollateral + , settingsCollateralAddress = addressToBech32 . snd $ ctxCollateralKey } -- >>> customShowNetworkId GYMainnet diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs new file mode 100644 index 0000000..54f5075 --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs @@ -0,0 +1,82 @@ +module ZkFold.Cardano.SmartWallet.Server.Api.Wallet ( + WalletAPI, + handleWalletApi, +) where + +import Data.Swagger qualified as Swagger +import Data.Text (Text) +import Deriving.Aeson +import Fmt +import GHC.TypeLits (Symbol) +import GeniusYield.Imports ((&)) +import GeniusYield.Types +import GeniusYield.Types.OpenApi () +import Servant +import ZkFold.Cardano.SmartWallet.Api +import ZkFold.Cardano.SmartWallet.Server.Ctx +import ZkFold.Cardano.SmartWallet.Server.Orphans () +import ZkFold.Cardano.SmartWallet.Server.Tx (handleTxSignCollateral, handleTxSubmit) +import ZkFold.Cardano.SmartWallet.Server.Utils + +type SendFundsPrefix :: Symbol +type SendFundsPrefix = "sfp" + +data SendFundsParameters = SendFundsParameters + { sfpValue :: !GYValue + , sfpEmail :: !Text + , sfpSendAddress :: !GYAddressBech32 + } + deriving stock (Show, Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix SendFundsPrefix, CamelToSnake]] SendFundsParameters + +instance Swagger.ToSchema SendFundsParameters where + declareNamedSchema = + Swagger.genericDeclareNamedSchema Swagger.defaultSchemaOptions{Swagger.fieldLabelModifier = dropSymbolAndCamelToSnake @SendFundsPrefix} + & addSwaggerDescription "Send funds parameters." + +type SendFundsResponsePrefix :: Symbol +type SendFundsResponsePrefix = "sfr" + +data SendFundsResponse = SendFundsResponse + { sfrTransaction :: !GYTx + , sfrTransactionId :: !GYTxId + , sfrTransactionFee :: !GYNatural + } + deriving stock (Generic) + deriving + (FromJSON, ToJSON) + via CustomJSON '[FieldLabelModifier '[StripPrefix SendFundsResponsePrefix, CamelToSnake]] SendFundsResponse + +instance Swagger.ToSchema SendFundsResponse where + declareNamedSchema = + Swagger.genericDeclareNamedSchema Swagger.defaultSchemaOptions{Swagger.fieldLabelModifier = dropSymbolAndCamelToSnake @SendFundsResponsePrefix} + & addSwaggerDescription "Send funds response." + +type WalletAPI = + Summary "Send funds." + :> Description "Send funds from a zk based wallet to a given address." + :> "send-funds" + :> ReqBody '[JSON] SendFundsParameters + :> Post '[JSON] SendFundsResponse + +handleWalletApi :: Ctx -> ServerT WalletAPI IO +handleWalletApi ctx = handleSendFunds ctx + +handleSendFunds :: Ctx -> SendFundsParameters -> IO SendFundsResponse +handleSendFunds ctx@Ctx{..} sfp@SendFundsParameters{..} = do + logInfo ctx $ "Send funds requested. Parameters: " +|| sfp ||+ "" + validatorSetup <- runQuery ctx $ validatorSetupFromEmail sfpEmail + senderWalletAddress <- runQuery ctx $ addressFromValidatorSetup validatorSetup + logInfo ctx $ "Sender wallet address: " +|| senderWalletAddress ||+ "" + txBody <- runSkeletonI ctx [senderWalletAddress] senderWalletAddress (Just ctxCollateral) $ do + sendFunds' validatorSetup (addressFromBech32 sfpSendAddress) sfpValue + signedTx <- handleTxSignCollateral ctx $ unsignedTx txBody + tid <- handleTxSubmit ctx signedTx + pure $ + SendFundsResponse + { sfrTransaction = signedTx + , sfrTransactionId = tid + , sfrTransactionFee = fromIntegral $ txBodyFee txBody + } \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs index 096230d..9447d50 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs @@ -2,7 +2,7 @@ module ZkFold.Cardano.SmartWallet.Server.Config ( ServerConfig (..), serverConfigOptionalFPIO, coreConfigFromServerConfig, - optionalSigningKeyFromServerConfig, + signingKeyFromUserWallet, ) where import Data.Aeson ( @@ -56,10 +56,10 @@ data ServerConfig = ServerConfig , scLogging :: ![GYLogScribeConfig] , scMaestroToken :: !(Confidential Text) , scPort :: !Port - , scWallet :: !(Maybe UserWallet) , scServerApiKey :: !(Confidential Text) - , scCollateral :: !(Maybe GYTxOutRef) , scStakeAddress :: !(Maybe GYStakeAddressBech32) + , scCollateral :: !GYTxOutRef + , scCollateralWallet :: !UserWallet } deriving stock (Generic) deriving @@ -103,23 +103,20 @@ coreConfigFromServerConfig ServerConfig{..} = , cfgLogTiming = Nothing } -optionalSigningKeyFromServerConfig :: ServerConfig -> IO (Maybe (GYSomePaymentSigningKey, GYAddress)) -optionalSigningKeyFromServerConfig ServerConfig{..} = do - case scWallet of - Nothing -> pure Nothing - Just (MnemonicWallet MnemonicWalletDetails{..}) -> - let wk' = walletKeysFromMnemonicIndexed mnemonic (fromMaybe 0 accIx) (fromMaybe 0 addrIx) - in pure $ case wk' of - Left _ -> Nothing - Right wk -> Just (AGYExtendedPaymentSigningKey (walletKeysToExtendedPaymentSigningKey wk), walletKeysToAddress wk scNetworkId) - Just (KeyPathWallet fp) -> do - skey <- readSomePaymentSigningKey fp - pure $ Just (skey, addressFromSomePaymentSigningKey scNetworkId skey) +signingKeyFromUserWallet :: GYNetworkId -> UserWallet -> IO (Maybe (GYSomePaymentSigningKey, GYAddress)) +signingKeyFromUserWallet nid (MnemonicWallet MnemonicWalletDetails{..}) = do + let wk' = walletKeysFromMnemonicIndexed mnemonic (fromMaybe 0 accIx) (fromMaybe 0 addrIx) + in pure $ case wk' of + Left _ -> Nothing + Right wk -> Just (AGYExtendedPaymentSigningKey (walletKeysToExtendedPaymentSigningKey wk), walletKeysToAddress wk nid) +signingKeyFromUserWallet nid (KeyPathWallet fp) = do + skey <- readSomePaymentSigningKey fp + pure $ Just (skey, addressFromSomePaymentSigningKey skey) where - addressFromSomePaymentSigningKey :: GYNetworkId -> GYSomePaymentSigningKey -> GYAddress - addressFromSomePaymentSigningKey nid skey = + addressFromSomePaymentSigningKey :: GYSomePaymentSigningKey -> GYAddress + addressFromSomePaymentSigningKey skey = let pkh = case skey of AGYPaymentSigningKey skey' -> paymentKeyHash . paymentVerificationKey $ skey' AGYExtendedPaymentSigningKey skey' -> getExtendedVerificationKey skey' & extendedVerificationKeyHash - in addressFromPaymentKeyHash nid pkh + in addressFromPaymentKeyHash nid pkh \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs index c2b1f54..43602bc 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs @@ -20,9 +20,8 @@ data Ctx = Ctx { ctxNetworkId :: !GYNetworkId , ctxProviders :: !GYProviders , ctxSmartWalletBuildInfo :: !ZKWalletBuildInfo - , ctxSigningKey :: !(Maybe (GYSomePaymentSigningKey, GYAddress)) - , ctxCollateral :: !(Maybe GYTxOutRef) - , ctxStakeAddress :: !(Maybe GYStakeAddressBech32) + , ctxCollateral :: !GYTxOutRef + , ctxCollateralKey :: !(GYSomePaymentSigningKey, GYAddress) } -- | Create 'TxBody' from a 'GYTxSkeleton'. diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs index c08dbf9..672e4bd 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs @@ -23,7 +23,7 @@ import System.TimeManager (TimeoutThread (..)) import ZkFold.Cardano.SmartWallet.Constants import ZkFold.Cardano.SmartWallet.Server.Api import ZkFold.Cardano.SmartWallet.Server.Auth -import ZkFold.Cardano.SmartWallet.Server.Config (ServerConfig (..), coreConfigFromServerConfig, optionalSigningKeyFromServerConfig, serverConfigOptionalFPIO) +import ZkFold.Cardano.SmartWallet.Server.Config (ServerConfig (..), coreConfigFromServerConfig, serverConfigOptionalFPIO, signingKeyFromUserWallet) import ZkFold.Cardano.SmartWallet.Server.Ctx import ZkFold.Cardano.SmartWallet.Server.ErrorMiddleware import ZkFold.Cardano.SmartWallet.Server.RequestLoggerMiddleware (gcpReqLogger) @@ -33,14 +33,18 @@ import ZkFold.Cardano.SmartWallet.Types (ZKWalletBuildInfo (..)) runServer :: Maybe FilePath -> IO () runServer mfp = do serverConfig <- serverConfigOptionalFPIO mfp - optionalSigningKey <- optionalSigningKeyFromServerConfig serverConfig + collateralKey <- + signingKeyFromUserWallet (scNetworkId serverConfig) (scCollateralWallet serverConfig) + >>= \case + Nothing -> throwIO $ userError "Collateral signing key not found." + Just k -> pure k let nid = scNetworkId serverConfig coreCfg = coreConfigFromServerConfig serverConfig -- writePythonForAPI (Proxy @MainAPI) requests "web/swagger/api.py" withCfgProviders coreCfg "server" $ \providers -> do let logInfoS = gyLogInfo providers mempty logErrorS = gyLogError providers mempty - logInfoS $ "GeniusYield server version: " +| showVersion PackageInfo.version |+ "\nOptional collateral configuration: " +|| scCollateral serverConfig ||+ "\nAddress of optional wallet: " +|| fmap snd optionalSigningKey ||+ "\nOptional stake address: " +|| scStakeAddress serverConfig ||+ "" + logInfoS $ "zkFold smart wallet server version: " +| showVersion PackageInfo.version |+ "\ncollateral configuration: " +|| scCollateral serverConfig ||+ "\nAddress of collateral wallet: " +|| snd collateralKey ||+ "" B.writeFile "web/openapi/api.yaml" (Yaml.encodePretty Yaml.defConfig zkFoldSmartWalletAPI) reqLoggerMiddleware <- gcpReqLogger let @@ -70,9 +74,8 @@ runServer mfp = do { ctxProviders = providers , ctxNetworkId = nid , ctxSmartWalletBuildInfo = ZKWalletBuildInfo smartWalletValidator mockSmartWalletStakeValidator - , ctxSigningKey = optionalSigningKey , ctxCollateral = scCollateral serverConfig - , ctxStakeAddress = scStakeAddress serverConfig + , ctxCollateralKey = collateralKey } logInfoS $ diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Tx.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Tx.hs new file mode 100644 index 0000000..5fb063b --- /dev/null +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Tx.hs @@ -0,0 +1,52 @@ +module ZkFold.Cardano.SmartWallet.Server.Tx ( + TxAPI, + handleTxApi, + handleTxSignCollateral, + handleTxSignCollateralAndSubmit, + handleTxSubmit, +) where + +import Fmt +import GeniusYield.Types +import Servant +import ZkFold.Cardano.SmartWallet.Server.Ctx +import ZkFold.Cardano.SmartWallet.Server.Utils + +type TxAPI = + "sign-collateral" + :> Summary "Sign a transaction" + :> Description "Signs the given transaction using collateral key configured in server." + :> ReqBody '[JSON] GYTx + :> Post '[JSON] GYTx + :<|> "sign-collateral-and-submit" + :> Summary "Sign and submit a transaction" + :> Description "Signs the given transaction using collateral key configured in server and submits it to the network." + :> ReqBody '[JSON] GYTx + :> Post '[JSON] GYTxId + :<|> "submit" + :> Summary "Submit a transaction" + :> Description "Submits the given transaction to the network." + :> ReqBody '[JSON] GYTx + :> Post '[JSON] GYTxId + +handleTxApi :: Ctx -> ServerT TxAPI IO +handleTxApi ctx = + handleTxSignCollateral ctx + :<|> handleTxSignCollateralAndSubmit ctx + :<|> handleTxSubmit ctx + +handleTxSignCollateral :: Ctx -> GYTx -> IO GYTx +handleTxSignCollateral ctx@Ctx{..} tx = do + logInfo ctx $ "Signing transaction: " +| txToHex tx |+ "" + pure $ signGYTx' tx [somePaymentSigningKeyToSomeSigningKey $ fst ctxCollateralKey] + +handleTxSignCollateralAndSubmit :: Ctx -> GYTx -> IO GYTxId +handleTxSignCollateralAndSubmit ctx tx = do + logInfo ctx $ "Signing and submitting transaction: " +| txToHex tx |+ "" + signedTx <- handleTxSignCollateral ctx tx + handleTxSubmit ctx signedTx + +handleTxSubmit :: Ctx -> GYTx -> IO GYTxId +handleTxSubmit ctx@Ctx{..} tx = do + logInfo ctx $ "Submitting transaction: " +| txToHex tx |+ "" + gySubmitTx ctxProviders tx diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index 924c69a..7c73a44 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -41,6 +41,7 @@ library exposed-modules: ZkFold.Cardano.SmartWallet.Server.Api ZkFold.Cardano.SmartWallet.Server.Api.Settings + ZkFold.Cardano.SmartWallet.Server.Api.Wallet ZkFold.Cardano.SmartWallet.Server.Auth ZkFold.Cardano.SmartWallet.Server.Config ZkFold.Cardano.SmartWallet.Server.Ctx @@ -49,6 +50,7 @@ library ZkFold.Cardano.SmartWallet.Server.Orphans ZkFold.Cardano.SmartWallet.Server.RequestLoggerMiddleware ZkFold.Cardano.SmartWallet.Server.Run + ZkFold.Cardano.SmartWallet.Server.Tx ZkFold.Cardano.SmartWallet.Server.Utils other-modules: From ee44bf4e855994532713644ff23160239f4649bc Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Fri, 21 Mar 2025 19:19:36 +0530 Subject: [PATCH 13/18] feat(#16): incorporate atlas's option to provide extra tx build configuration --- atlas/cabal.project | 2 +- .../ZkFold/Cardano/SmartWallet/Server/Ctx.hs | 82 +++++++++++++++++-- .../zkfold-smart-wallet-server-lib.cabal | 1 + 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/atlas/cabal.project b/atlas/cabal.project index 786a0ad..1e6949a 100644 --- a/atlas/cabal.project +++ b/atlas/cabal.project @@ -18,7 +18,7 @@ tests: true source-repository-package type: git location: https://github.com/geniusyield/atlas - tag: 6c6546161cb417a2523935dafd28764e81da5926 + tag: cc48d63d33ca876a54c827edc34a0737c934fda2 -------- Begin contents from @atlas@'s @cabal.project@ file. -------- diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs index 43602bc..a0fce44 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Ctx.hs @@ -2,15 +2,21 @@ module ZkFold.Cardano.SmartWallet.Server.Ctx ( Ctx (..), runSkeletonI, runSkeletonWithStrategyI, + runSkeletonWithExtraConfigurationI, + runSkeletonWithStrategyAndExtraConfigurationI, runSkeletonF, runSkeletonWithStrategyF, + runSkeletonWithExtraConfigurationF, + runSkeletonWithStrategyAndExtraConfigurationF, runQuery, runQueryWithReader, ) where import Control.Monad.Reader (ReaderT (..)) +import Data.Default (Default (..)) import GeniusYield.Imports import GeniusYield.Transaction +import GeniusYield.Transaction.Common (GYTxExtraConfiguration) import GeniusYield.TxBuilder import GeniusYield.Types import ZkFold.Cardano.SmartWallet.Types (ZKWalletBuildInfo) @@ -51,6 +57,32 @@ runSkeletonWithStrategyI :: IO GYTxBody runSkeletonWithStrategyI cstrat = coerce (runSkeletonWithStrategyF @Identity cstrat) +-- | Create 'TxBody' from a 'GYTxSkeleton', with the specified extra transaction building configuration. +runSkeletonWithExtraConfigurationI :: + GYTxExtraConfiguration v -> + Ctx -> + -- | User's used addresses. Note that internally we prepend given change address to this list so that in case wallet's state isn't updated quickly to mark an earlier given change address as used, we'll be able to use UTxOs potentially present at this change address. + [GYAddress] -> + -- | User's change address. + GYAddress -> + -- | User's collateral. + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (GYTxSkeleton v) -> + IO GYTxBody +runSkeletonWithExtraConfigurationI ec = coerce (runSkeletonWithExtraConfigurationF @Identity ec) + +-- | Create 'TxBody' from a 'GYTxSkeleton', with the specified coin selection strategy and extra transaction building configuration. +runSkeletonWithStrategyAndExtraConfigurationI :: + GYCoinSelectionStrategy -> + GYTxExtraConfiguration v -> + Ctx -> + [GYAddress] -> + GYAddress -> + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (GYTxSkeleton v) -> + IO GYTxBody +runSkeletonWithStrategyAndExtraConfigurationI cstrat ec = coerce (runSkeletonWithStrategyAndExtraConfigurationF @Identity cstrat ec) + runSkeletonF :: (Traversable t) => Ctx -> @@ -62,7 +94,7 @@ runSkeletonF :: Maybe GYTxOutRef -> ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> IO (t GYTxBody) -runSkeletonF = runSkeletonWithStrategyF GYRandomImproveMultiAsset +runSkeletonF = runSkeletonWithStrategyF def runSkeletonWithStrategyF :: (Traversable t) => @@ -76,7 +108,36 @@ runSkeletonWithStrategyF :: Maybe GYTxOutRef -> ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> IO (t GYTxBody) -runSkeletonWithStrategyF cstrat ctx addrs addr mcollateral skeleton = do +runSkeletonWithStrategyF cstrat = runSkeletonWithStrategyAndExtraConfigurationF cstrat def + +runSkeletonWithExtraConfigurationF :: + (Traversable t) => + GYTxExtraConfiguration v -> + Ctx -> + -- | User's used addresses. Note that internally we prepend given change address to this list so that in case wallet's state isn't updated quickly to mark an earlier given change address as used, we'll be able to use UTxOs potentially present at this change address. + [GYAddress] -> + -- | User's change address. + GYAddress -> + -- | User's collateral. + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> + IO (t GYTxBody) +runSkeletonWithExtraConfigurationF = runSkeletonWithStrategyAndExtraConfigurationF def + +runSkeletonWithStrategyAndExtraConfigurationF :: + (Traversable t) => + GYCoinSelectionStrategy -> + GYTxExtraConfiguration v -> + Ctx -> + -- | User's used addresses. Note that internally we prepend given change address to this list so that in case wallet's state isn't updated quickly to mark an earlier given change address as used, we'll be able to use UTxOs potentially present at this change address. + [GYAddress] -> + -- | User's change address. + GYAddress -> + -- | User's collateral. + Maybe GYTxOutRef -> + ReaderT ZKWalletBuildInfo GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> + IO (t GYTxBody) +runSkeletonWithStrategyAndExtraConfigurationF cstrat ec ctx addrs addr mcollateral skeleton = do let nid = ctxNetworkId ctx providers = ctxProviders ctx wi = ctxSmartWalletBuildInfo ctx @@ -84,7 +145,7 @@ runSkeletonWithStrategyF cstrat ctx addrs addr mcollateral skeleton = do collateral <- mcollateral pure (collateral, False) - runGYTxMonadNodeF cstrat nid providers (addr : addrs) addr mcollateral' $ runReaderT skeleton wi + runGYTxMonadNodeF cstrat ec nid providers (addr : addrs) addr mcollateral' $ runReaderT skeleton wi runQuery :: Ctx -> ReaderT ZKWalletBuildInfo GYTxQueryMonadIO a -> IO a runQuery ctx = runQueryWithReader ctx (ctxSmartWalletBuildInfo ctx) @@ -95,5 +156,16 @@ runQueryWithReader ctx a q = do providers = ctxProviders ctx runGYTxQueryMonadIO nid providers $ runReaderT q a -runGYTxMonadNodeF :: forall t v. (Traversable t) => GYCoinSelectionStrategy -> GYNetworkId -> GYProviders -> [GYAddress] -> GYAddress -> Maybe (GYTxOutRef, Bool) -> GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> IO (t GYTxBody) -runGYTxMonadNodeF strat nid providers addrs change collateral act = runGYTxBuilderMonadIO nid providers addrs change collateral $ act >>= traverse (buildTxBodyWithStrategy strat) +runGYTxMonadNodeF :: + forall t v. + (Traversable t) => + GYCoinSelectionStrategy -> + GYTxExtraConfiguration v -> + GYNetworkId -> + GYProviders -> + [GYAddress] -> + GYAddress -> + Maybe (GYTxOutRef, Bool) -> + GYTxBuilderMonadIO (t (GYTxSkeleton v)) -> + IO (t GYTxBody) +runGYTxMonadNodeF strat ec nid providers addrs change collateral act = runGYTxBuilderMonadIO nid providers addrs change collateral $ act >>= traverse (buildTxBodyWithStrategyAndExtraConfiguration strat ec) diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index 7c73a44..d0dd75a 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -69,6 +69,7 @@ library binary, bytestring, containers, + data-default, deriving-aeson, envy, fast-logger, From a577ee6adebd42158692019d9e8578e6469724b2 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Fri, 21 Mar 2025 19:33:58 +0530 Subject: [PATCH 14/18] feat(#16): add openapi specification file --- atlas/web/openapi/api.yaml | 245 ++++++++++++++++++ .../SmartWallet/Server/Api/Settings.hs | 2 +- .../Cardano/SmartWallet/Server/Api/Wallet.hs | 17 +- .../Cardano/SmartWallet/Server/Config.hs | 2 - .../ZkFold/Cardano/SmartWallet/Server/Run.hs | 2 +- 5 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 atlas/web/openapi/api.yaml diff --git a/atlas/web/openapi/api.yaml b/atlas/web/openapi/api.yaml new file mode 100644 index 0000000..dfbbca5 --- /dev/null +++ b/atlas/web/openapi/api.yaml @@ -0,0 +1,245 @@ +components: + schemas: + GYAddressBech32: + description: An address, serialised as Bech32. + example: addr_test1qrsuhwqdhz0zjgnf46unas27h93amfghddnff8lpc2n28rgmjv8f77ka0zshfgssqr5cnl64zdnde5f8q2xt923e7ctqu49mg5 + format: bech32 + type: string + GYNatural: + description: A natural number which is a non-negative integer. Minimum value + is 0. + example: '123456789123456789123456789123456789123456789' + type: string + GYTx: + description: Transaction cbor hex string + example: 84a70082825820975e4c7f8d7937f8102e500714feb3f014c8766fcf287a11c10c686154fcb27501825820c887cba672004607a0f60ab28091d5c24860dbefb92b1a8776272d752846574f000d818258207a67cd033169e330c9ae9b8d0ef8b71de9eb74bbc8f3f6be90446dab7d1e8bfd00018282583900fd040c7a10744b79e5c80ec912a05dbdb3009e372b7f4b0f026d16b0c663651ffc046068455d2994564ba9d4b3e9b458ad8ab5232aebbf401a1abac7d882583900fd040c7a10744b79e5c80ec912a05dbdb3009e372b7f4b0f026d16b0c663651ffc046068455d2994564ba9d4b3e9b458ad8ab5232aebbf40821a0017ad4aa2581ca6bb5fd825455e7c69bdaa9d3a6dda9bcbe9b570bc79bd55fa50889ba1466e69636b656c1911d7581cb17cb47f51d6744ad05fb937a762848ad61674f8aebbaec67be0bb6fa14853696c6c69636f6e190258021a00072f3c0e8009a1581cb17cb47f51d6744ad05fb937a762848ad61674f8aebbaec67be0bb6fa14853696c6c69636f6e1902580b5820291b4e4c5f189cb896674e02e354028915b11889687c53d9cf4c1c710ff5e4aea203815908d45908d101000033332332232332232323232323232323232323232323232323232222223232323235500222222222225335333553024120013232123300122333500522002002001002350012200112330012253350021001102d02c25335325335333573466e3cd400488008d404c880080b40b04ccd5cd19b873500122001350132200102d02c102c3500122002102b102c00a132635335738921115554784f206e6f7420636f6e73756d65640002302115335333573466e3c048d5402c880080ac0a854cd4ccd5cd19b8701335500b2200102b02a10231326353357389210c77726f6e6720616d6f756e740002302113263533573892010b77726f6e6720746f6b656e00023021135500122222222225335330245027007162213500222253350041335502d00200122161353333573466e1cd55cea8012400046644246600200600464646464646464646464646666ae68cdc39aab9d500a480008cccccccccc888888888848cccccccccc00402c02802402001c01801401000c008cd40548c8c8cccd5cd19b8735573aa0049000119910919800801801180f1aba15002301a357426ae8940088c98d4cd5ce01381401301289aab9e5001137540026ae854028cd4054058d5d0a804999aa80c3ae501735742a010666aa030eb9405cd5d0a80399a80a80f1aba15006335015335502101f75a6ae854014c8c8c8cccd5cd19b8735573aa00490001199109198008018011919191999ab9a3370e6aae754009200023322123300100300233502475a6ae854008c094d5d09aba2500223263533573805605805405226aae7940044dd50009aba150023232323333573466e1cd55cea8012400046644246600200600466a048eb4d5d0a80118129aba135744a004464c6a66ae700ac0b00a80a44d55cf280089baa001357426ae8940088c98d4cd5ce01381401301289aab9e5001137540026ae854010cd4055d71aba15003335015335502175c40026ae854008c06cd5d09aba2500223263533573804604804404226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226aae7940044dd50009aba150023232323333573466e1d400520062321222230040053016357426aae79400c8cccd5cd19b875002480108c848888c008014c060d5d09aab9e500423333573466e1d400d20022321222230010053014357426aae7940148cccd5cd19b875004480008c848888c00c014dd71aba135573ca00c464c6a66ae7007807c07407006c0680644d55cea80089baa001357426ae8940088c98d4cd5ce00b80c00b00a9100109aab9e5001137540022464460046eb0004c8004d5406488cccd55cf8009280c119a80b98021aba100230033574400402446464646666ae68cdc39aab9d5003480008ccc88848ccc00401000c008c8c8c8cccd5cd19b8735573aa004900011991091980080180118099aba1500233500c012357426ae8940088c98d4cd5ce00b00b80a80a09aab9e5001137540026ae85400cccd5401dd728031aba1500233500875c6ae84d5d1280111931a99ab9c012013011010135744a00226aae7940044dd5000899aa800bae75a224464460046eac004c8004d5405c88c8cccd55cf8011280b919a80b19aa80c18031aab9d5002300535573ca00460086ae8800c0444d5d080089119191999ab9a3370ea0029000119091180100198029aba135573ca00646666ae68cdc3a801240044244002464c6a66ae7004004403c0380344d55cea80089baa001232323333573466e1cd55cea80124000466442466002006004600a6ae854008dd69aba135744a004464c6a66ae7003403803002c4d55cf280089baa0012323333573466e1cd55cea800a400046eb8d5d09aab9e500223263533573801601801401226ea8004488c8c8cccd5cd19b87500148010848880048cccd5cd19b875002480088c84888c00c010c018d5d09aab9e500423333573466e1d400d20002122200223263533573801c01e01a01801601426aae7540044dd50009191999ab9a3370ea0029001100911999ab9a3370ea0049000100911931a99ab9c00a00b009008007135573a6ea80048c8c8c8c8c8cccd5cd19b8750014803084888888800c8cccd5cd19b875002480288488888880108cccd5cd19b875003480208cc8848888888cc004024020dd71aba15005375a6ae84d5d1280291999ab9a3370ea00890031199109111111198010048041bae35742a00e6eb8d5d09aba2500723333573466e1d40152004233221222222233006009008300c35742a0126eb8d5d09aba2500923333573466e1d40192002232122222223007008300d357426aae79402c8cccd5cd19b875007480008c848888888c014020c038d5d09aab9e500c23263533573802402602202001e01c01a01801601426aae7540104d55cf280189aab9e5002135573ca00226ea80048c8c8c8c8cccd5cd19b875001480088ccc888488ccc00401401000cdd69aba15004375a6ae85400cdd69aba135744a00646666ae68cdc3a80124000464244600400660106ae84d55cf280311931a99ab9c00b00c00a009008135573aa00626ae8940044d55cf280089baa001232323333573466e1d400520022321223001003375c6ae84d55cf280191999ab9a3370ea004900011909118010019bae357426aae7940108c98d4cd5ce00400480380300289aab9d5001137540022244464646666ae68cdc39aab9d5002480008cd5403cc018d5d0a80118029aba135744a004464c6a66ae7002002401c0184d55cf280089baa00149924103505431001200132001355008221122253350011350032200122133350052200230040023335530071200100500400132001355007222533500110022213500222330073330080020060010033200135500622225335001100222135002225335333573466e1c005200000d00c13330080070060031333008007335009123330010080030020060031122002122122330010040031122123300100300212200212200111232300100122330033002002001482c0252210853696c6c69636f6e003351223300248920975e4c7f8d7937f8102e500714feb3f014c8766fcf287a11c10c686154fcb27500480088848cc00400c00880050581840100d87980821a001f372a1a358a2b14f5f6 + type: string + GYTxId: + description: Transaction id + example: a8d75b90a052302c1232bedd626720966b1697fe38de556c617c340233688935 + type: string + GYTxOutRef: + example: 4293386fef391299c9886dc0ef3e8676cbdbc2c9f2773507f1f838e00043a189#1 + format: hex + pattern: '[0-9a-fA-F]{64}#"d+' + type: string + GYValue: + additionalProperties: + type: integer + description: 'A multi asset quantity, represented as map where each key represents + an asset: policy ID and token name in hex concatenated by a dot.' + example: + ff80aaaf03a273b8f5c558168dc0e2377eea810badbae6eceefc14ef.474f4c44: 101 + lovelace: 22 + type: object + SendFundsParameters: + description: Send funds parameters. + properties: + email: + type: string + send_address: + $ref: '#/components/schemas/GYAddressBech32' + value: + $ref: '#/components/schemas/GYValue' + required: + - value + - email + - send_address + type: object + SendFundsResponse: + description: Send funds response. + properties: + transaction: + $ref: '#/components/schemas/GYTx' + transaction_fee: + $ref: '#/components/schemas/GYNatural' + transaction_id: + $ref: '#/components/schemas/GYTxId' + required: + - transaction + - transaction_id + - transaction_fee + type: object + Settings: + description: zkFold Smart Wallet Server settings. + properties: + collateral: + $ref: '#/components/schemas/GYTxOutRef' + collateral_address: + $ref: '#/components/schemas/GYAddressBech32' + network: + type: string + version: + type: string + required: + - network + - version + - collateral + - collateral_address + type: object + securitySchemes: + api-key: + description: API key for accessing the server's API. + in: header + name: api-key + type: apiKey +info: + contact: + email: info@zkfold.io + name: zkFold Technical Support + url: https://zkfold.io/ + description: API to interact with zkFold Smart Wallet. + license: + name: Apache-2.0 + url: https://opensource.org/licenses/apache-2-0 + title: zkFold Smart Wallet Server API + version: 0.0.1 +openapi: 3.0.0 +paths: + /v0/settings: + get: + description: Get server settings such as network and version. + responses: + '200': + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/Settings' + description: '' + '401': + description: Unauthorized access - API key missing + '403': + description: Forbidden - The API key does not have permission to perform + the request + '500': + description: Internal server error + security: + - api-key: [] + summary: Server settings + tags: + - Settings + /v0/tx/sign-collateral: + post: + description: Signs the given transaction using collateral key configured in + server. + requestBody: + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/GYTx' + responses: + '200': + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/GYTx' + description: '' + '400': + description: Invalid `body` + '401': + description: Unauthorized access - API key missing + '403': + description: Forbidden - The API key does not have permission to perform + the request + '500': + description: Internal server error + security: + - api-key: [] + summary: Sign a transaction + tags: + - Tx + /v0/tx/sign-collateral-and-submit: + post: + description: Signs the given transaction using collateral key configured in + server and submits it to the network. + requestBody: + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/GYTx' + responses: + '200': + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/GYTxId' + description: '' + '400': + description: Invalid `body` + '401': + description: Unauthorized access - API key missing + '403': + description: Forbidden - The API key does not have permission to perform + the request + '500': + description: Internal server error + security: + - api-key: [] + summary: Sign and submit a transaction + tags: + - Tx + /v0/tx/submit: + post: + description: Submits the given transaction to the network. + requestBody: + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/GYTx' + responses: + '200': + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/GYTxId' + description: '' + '400': + description: Invalid `body` + '401': + description: Unauthorized access - API key missing + '403': + description: Forbidden - The API key does not have permission to perform + the request + '500': + description: Internal server error + security: + - api-key: [] + summary: Submit a transaction + tags: + - Tx + /v0/wallet/send-funds: + post: + description: Send funds from a zk based wallet to a given address. + requestBody: + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/SendFundsParameters' + responses: + '200': + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/SendFundsResponse' + description: '' + '400': + description: Invalid `body` + '401': + description: Unauthorized access - API key missing + '403': + description: Forbidden - The API key does not have permission to perform + the request + '500': + description: Internal server error + security: + - api-key: [] + summary: Send funds. + tags: + - Wallet +tags: +- description: Endpoint to get server settings such as network and version + name: Settings +- description: Endpoint to interact with zk-wallet + name: Wallet +- description: Endpoint to interact with built transactions + name: Tx diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs index 9858aa8..4e472ae 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Settings.hs @@ -37,7 +37,7 @@ instance Swagger.ToSchema Settings where Swagger.genericDeclareNamedSchema Swagger.defaultSchemaOptions{Swagger.fieldLabelModifier = dropSymbolAndCamelToSnake @SettingsPrefix} & addSwaggerDescription "zkFold Smart Wallet Server settings." -type SettingsAPI = Summary "Server settings" :> Description "Get server settings such as network and version. Optionally if a collateral UTxO reference and signing key are individually configured in the server (provided server is spun up locally), then it's details are also returned." :> Get '[JSON] Settings +type SettingsAPI = Summary "Server settings" :> Description "Get server settings such as network and version." :> Get '[JSON] Settings handleSettings :: Ctx -> IO Settings handleSettings ctx@Ctx{..} = do diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs index 54f5075..11c5a8b 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs @@ -9,6 +9,7 @@ import Deriving.Aeson import Fmt import GHC.TypeLits (Symbol) import GeniusYield.Imports ((&)) +import GeniusYield.Transaction.Common (GYTxExtraConfiguration (..), GYTxInDetailed (..)) import GeniusYield.Types import GeniusYield.Types.OpenApi () import Servant @@ -70,7 +71,21 @@ handleSendFunds ctx@Ctx{..} sfp@SendFundsParameters{..} = do validatorSetup <- runQuery ctx $ validatorSetupFromEmail sfpEmail senderWalletAddress <- runQuery ctx $ addressFromValidatorSetup validatorSetup logInfo ctx $ "Sender wallet address: " +|| senderWalletAddress ||+ "" - txBody <- runSkeletonI ctx [senderWalletAddress] senderWalletAddress (Just ctxCollateral) $ do + let ec = + GYTxExtraConfiguration + { gytxecUtxoInputMapper = \GYUTxO{..} -> + GYTxInDetailed + { gyTxInDet = GYTxIn utxoRef undefined -- FIXME: Give script witness. + , gyTxInDetAddress = undefined -- FIXME: Change address to fake script that allows forge proofs. + , gyTxInDetValue = utxoValue + , gyTxInDetDatum = utxoOutDatum + , gyTxInDetScriptRef = utxoRefScript + } + , -- FIXME: Provide pre & post content mappers. + gytxecPreBodyContentMapper = undefined + , gytxecPostBodyContentMapper = undefined + } + txBody <- runSkeletonWithExtraConfigurationI ec ctx [senderWalletAddress] senderWalletAddress (Just ctxCollateral) $ do sendFunds' validatorSetup (addressFromBech32 sfpSendAddress) sfpValue signedTx <- handleTxSignCollateral ctx $ unsignedTx txBody tid <- handleTxSubmit ctx signedTx diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs index 9447d50..4a66951 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Config.hs @@ -54,10 +54,8 @@ data ServerConfig = ServerConfig { scCoreProvider :: !GYCoreProviderInfo , scNetworkId :: !GYNetworkId , scLogging :: ![GYLogScribeConfig] - , scMaestroToken :: !(Confidential Text) , scPort :: !Port , scServerApiKey :: !(Confidential Text) - , scStakeAddress :: !(Maybe GYStakeAddressBech32) , scCollateral :: !GYTxOutRef , scCollateralWallet :: !UserWallet } diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs index 672e4bd..b32705c 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs @@ -45,7 +45,7 @@ runServer mfp = do let logInfoS = gyLogInfo providers mempty logErrorS = gyLogError providers mempty logInfoS $ "zkFold smart wallet server version: " +| showVersion PackageInfo.version |+ "\ncollateral configuration: " +|| scCollateral serverConfig ||+ "\nAddress of collateral wallet: " +|| snd collateralKey ||+ "" - B.writeFile "web/openapi/api.yaml" (Yaml.encodePretty Yaml.defConfig zkFoldSmartWalletAPI) + B.writeFile "web/openapi/api.yaml" (Yaml.encodePretty Yaml.defConfig zkFoldSmartWalletAPIOpenApi) reqLoggerMiddleware <- gcpReqLogger let -- These are only meant to catch fatal exceptions, application thrown exceptions should be caught beforehand. From caad2d72f32314476a26769158c7a90f2cee2078 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Fri, 21 Mar 2025 20:31:59 +0530 Subject: [PATCH 15/18] feat(#16): add illustration for pre & post body mappers --- .../src/ZkFold/Cardano/SmartWallet/Api.hs | 8 +++++- .../Cardano/SmartWallet/Server/Api/Wallet.hs | 26 ++++++++++++++++--- .../zkfold-smart-wallet-server-lib.cabal | 2 ++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs index f838243..fc80121 100644 --- a/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs +++ b/atlas/zkfold-smart-wallet-api/src/ZkFold/Cardano/SmartWallet/Api.hs @@ -45,4 +45,10 @@ sendFunds' (sb, ws) sendAddr sendVal = do pure $ mustHaveOutput (mkGYTxOutNoDatum sendAddr sendVal) -- TODO: To make use of reference scripts? - <> mustHaveWithdrawal (GYTxWdrl{gyTxWdrlStakeAddress = mockStakeAddr, gyTxWdrlAmount = gyStakeAddressInfoAvailableRewards si, gyTxWdrlWitness = GYTxBuildWitnessPlutusScript (GYBuildPlutusScriptInlined (zkwbiMockStakeValidator zkwbi)) dummyRedeemer}) \ No newline at end of file + <> mustHaveWithdrawal + ( GYTxWdrl + { gyTxWdrlStakeAddress = mockStakeAddr + , gyTxWdrlAmount = gyStakeAddressInfoAvailableRewards si + , gyTxWdrlWitness = GYTxBuildWitnessPlutusScript (GYBuildPlutusScriptInlined (zkwbiMockStakeValidator zkwbi)) dummyRedeemer + } + ) \ No newline at end of file diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs index 11c5a8b..3f22b0b 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs @@ -3,6 +3,7 @@ module ZkFold.Cardano.SmartWallet.Server.Api.Wallet ( handleWalletApi, ) where +import Cardano.Api qualified as Api import Data.Swagger qualified as Swagger import Data.Text (Text) import Deriving.Aeson @@ -76,14 +77,33 @@ handleSendFunds ctx@Ctx{..} sfp@SendFundsParameters{..} = do { gytxecUtxoInputMapper = \GYUTxO{..} -> GYTxInDetailed { gyTxInDet = GYTxIn utxoRef undefined -- FIXME: Give script witness. - , gyTxInDetAddress = undefined -- FIXME: Change address to fake script that allows forge proofs. + , gyTxInDetAddress = undefined -- FIXME: Change address to fake script that allows forged proofs. , gyTxInDetValue = utxoValue , gyTxInDetDatum = utxoOutDatum , gyTxInDetScriptRef = utxoRefScript } , -- FIXME: Provide pre & post content mappers. - gytxecPreBodyContentMapper = undefined - , gytxecPostBodyContentMapper = undefined + gytxecPreBodyContentMapper = \body -> + -- When balancing, @makeTransactionBodyAutoBalance@ function of @cardano-api@ that is used internally inside Atlas, adds following output before computing execution units, thus we need to do same here to make sure that script execution doesn't fail. + let bodyWithExtraOut = + body + { Api.txOuts = Api.txOuts body <> [txOutToApi (GYTxOut senderWalletAddress (valueFromLovelace $ 2 ^ (64 :: Integer) - 1) Nothing Nothing)] + } + in bodyWithExtraOut + { Api.txWithdrawals = + Api.TxWithdrawals Api.ShelleyBasedEraConway $ + [ txWdrlToApi $ + GYTxWdrl + { gyTxWdrlStakeAddress = undefined + , gyTxWdrlAmount = undefined + , gyTxWdrlWitness = GYTxBuildWitnessPlutusScript (GYBuildPlutusScriptInlined @'PlutusV3 @'PlutusV3 undefined) undefined + } + ] + } + , gytxecPostBodyContentMapper = \body -> + -- Correct address of inputs. + -- Correct withdrawal address. + undefined } txBody <- runSkeletonWithExtraConfigurationI ec ctx [senderWalletAddress] senderWalletAddress (Just ctxCollateral) $ do sendFunds' validatorSetup (addressFromBech32 sfpSendAddress) sfpValue diff --git a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal index d0dd75a..e319f98 100644 --- a/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal +++ b/atlas/zkfold-smart-wallet-server-lib/zkfold-smart-wallet-server-lib.cabal @@ -68,6 +68,8 @@ library base ^>=4.18.2.1, binary, bytestring, + cardano-api, + cardano-api:internal, containers, data-default, deriving-aeson, From 488fe394c476012da33a86eefbe61f4a736e39cf Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Fri, 21 Mar 2025 20:47:50 +0530 Subject: [PATCH 16/18] docs(#16): add readme, update license --- atlas/readme.md | 100 +++++++++++++++++++ atlas/zkfold-smart-wallet-api/LICENSE | 35 +++---- atlas/zkfold-smart-wallet-server-lib/LICENSE | 35 +++---- 3 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 atlas/readme.md diff --git a/atlas/readme.md b/atlas/readme.md new file mode 100644 index 0000000..2c396ba --- /dev/null +++ b/atlas/readme.md @@ -0,0 +1,100 @@ +# zkFold Smart Wallet API + +This repository houses off-chain code and server endpoints to interact with zk based smart wallet by [zkFold](https://zkfold.io/). + +## Table of Contents + +- [zkFold Smart Wallet API](#zkfold-smart-wallet-api) + - [Table of Contents](#table-of-contents) + - [Structure of repository](#structure-of-repository) + - [zkFold Smart Wallet API Server](#zkfold-smart-wallet-api-server) + - [Building locally from source using the Haskell Toolchain](#building-locally-from-source-using-the-haskell-toolchain) + - [OpenApi documentation](#openapi-documentation) + +## Structure of repository + +- [`zkfold-smart-wallet-api`](./zkfold-smart-wallet-api/) provides off-chain code to interact with the wallet. +- [`zkfold-smart-wallet-server-lib`](./zkfold-smart-wallet-server-lib/) serves endpoints using our off-chain code to easily interact with the wallet. + +## zkFold Smart Wallet API Server + +### Building locally from source using the Haskell Toolchain + +1. Make sure your environment is configured properly, consult ["How to build?"](https://atlas-app.io/getting-started/how-to-build) section of Atlas documentation for it. +2. Prepare a configuration, which can be stored either in file or in `SERVER_CONFIG` environment variable. Structure of it is as follows: + + ```yaml + # Blockchain provider used by Atlas, our off-chain transaction building tool. + # Head over to https://atlas-app.io/getting-started/endpoints#providing-data-provider section to know how to configure `coreProvider` and what all options are available for it. + coreProvider: + maestroToken: YOUR_MAESTRO_TOKEN + turboSubmit: false + # Network id, only `mainnet` and `preprod` are supported for at the moment. + networkId: mainnet + # Logging configuration. It's an array to cater for potentially multiple scribes. + # See it's description mentioned at https://atlas-app.io/getting-started/endpoints#providing-data-provider for more information. + logging: + - type: + tag: stderr + # Possible values of `severity` are `Debug`, `Info`, `Warning` and `Error`. + severity: Debug + # Possible values of `verbosity` are `V0`, `V1`, `V2`, `V3` and `V4`. Consult https://hackage.haskell.org/package/katip-0.8.8.0/docs/Katip.html#t:Verbosity for more information about it. + verbosity: V2 + # Port to serve endpoints at. + port: 8082 + # API key to protect server endpoints with. It's value must be provided under `api-key` header of request. + serverApiKey: YOUR_SECRET_KEY + # UTxO to be used as collateral. + collateral: tx-id#tx-ix + # Wallet that provides UTxO to be used as collateral. + collateralWallet: + tag: mnemonicWallet + contents: + mnemonic: + - health + - unable + - dog + - lend + - artefact + - arctic + - dinner + - energy + - silent + - wealth + - shock + - safe + - glad + - mail + - gas + - flag + - beauty + - penalty + - mixed + - garbage + - erupt + - wonder + - magnet + - around + # Account index. + accIx: 0 + # Payment address index. + addrIx: 0 + ``` +3. Run the server with command `cabal run zkfold-smart-wallet-server -- serve -c my-config.yaml`. + + Call: `cabal run zkfold-smart-wallet-server -- -h` for help. 😉 + +4. Test if server is running successfully by calling, say, `/settings` endpoint. Example `curl` request: `curl -H 'api-key: YOUR_SECRET_KEY' -X GET http://localhost:8082/v0/settings | jq`, assuming port was specified as `8082`. On success, it should return something akin to: + +```json +{ + "network":"mainnet", + "version":"0.1.0", + "collateral":"tx-id#tx-ix", + "collateral_address":"addr1qx...w60mw" +} +``` + +### OpenApi documentation + +Endpoints made available by server are specified [here](./web/openapi/api.yaml). diff --git a/atlas/zkfold-smart-wallet-api/LICENSE b/atlas/zkfold-smart-wallet-api/LICENSE index c85fe66..a17f8ee 100644 --- a/atlas/zkfold-smart-wallet-api/LICENSE +++ b/atlas/zkfold-smart-wallet-api/LICENSE @@ -1,20 +1,21 @@ -Copyright (c) 2025 Sourabh Aggarwal +MIT License -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Copyright (c) 2025 zkFold SA -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/atlas/zkfold-smart-wallet-server-lib/LICENSE b/atlas/zkfold-smart-wallet-server-lib/LICENSE index c85fe66..a17f8ee 100644 --- a/atlas/zkfold-smart-wallet-server-lib/LICENSE +++ b/atlas/zkfold-smart-wallet-server-lib/LICENSE @@ -1,20 +1,21 @@ -Copyright (c) 2025 Sourabh Aggarwal +MIT License -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Copyright (c) 2025 zkFold SA -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From d96f665510d402ad9400f5f42df2a4cf6fe6db75 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Fri, 21 Mar 2025 21:07:54 +0530 Subject: [PATCH 17/18] feat(#16): update redeemer inside original body --- .../src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs index 3f22b0b..79210c8 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Api/Wallet.hs @@ -89,7 +89,7 @@ handleSendFunds ctx@Ctx{..} sfp@SendFundsParameters{..} = do body { Api.txOuts = Api.txOuts body <> [txOutToApi (GYTxOut senderWalletAddress (valueFromLovelace $ 2 ^ (64 :: Integer) - 1) Nothing Nothing)] } - in bodyWithExtraOut + in body { Api.txWithdrawals = Api.TxWithdrawals Api.ShelleyBasedEraConway $ [ txWdrlToApi $ @@ -97,6 +97,7 @@ handleSendFunds ctx@Ctx{..} sfp@SendFundsParameters{..} = do { gyTxWdrlStakeAddress = undefined , gyTxWdrlAmount = undefined , gyTxWdrlWitness = GYTxBuildWitnessPlutusScript (GYBuildPlutusScriptInlined @'PlutusV3 @'PlutusV3 undefined) undefined + -- compute proof using inputs & outputs of @bodyWithExtraOut@. } ] } From 564a4787d53316bf7b323358982fc14977745c85 Mon Sep 17 00:00:00 2001 From: sourabhxyz Date: Fri, 21 Mar 2025 21:49:23 +0530 Subject: [PATCH 18/18] style(#16): remove not required comments --- .../src/ZkFold/Cardano/SmartWallet/Server/Options.hs | 1 - .../src/ZkFold/Cardano/SmartWallet/Server/Run.hs | 1 - 2 files changed, 2 deletions(-) diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs index f674c7a..022295b 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Options.hs @@ -7,7 +7,6 @@ module ZkFold.Cardano.SmartWallet.Server.Options ( runServeCommand, ) where --- import GeniusYield.Server.Run (runServer) import Options.Applicative import ZkFold.Cardano.SmartWallet.Server.Run (runServer) diff --git a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs index b32705c..f26a93a 100644 --- a/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs +++ b/atlas/zkfold-smart-wallet-server-lib/src/ZkFold/Cardano/SmartWallet/Server/Run.hs @@ -40,7 +40,6 @@ runServer mfp = do Just k -> pure k let nid = scNetworkId serverConfig coreCfg = coreConfigFromServerConfig serverConfig - -- writePythonForAPI (Proxy @MainAPI) requests "web/swagger/api.py" withCfgProviders coreCfg "server" $ \providers -> do let logInfoS = gyLogInfo providers mempty logErrorS = gyLogError providers mempty