Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b21a7cc
experiment: signing & verifying
ajuggler Aug 27, 2025
4170e83
Seller posts sell order
ajuggler Sep 3, 2025
736b821
Added buyer's Tx
ajuggler Sep 7, 2025
1b0ad08
Added 'sell offer' on client
ajuggler Sep 9, 2025
109f062
Cleanup
ajuggler Sep 9, 2025
41ca7aa
Functioning complete app
ajuggler Sep 12, 2025
06eff80
Cleanup
ajuggler Sep 13, 2025
f1e3fba
Added warning if trying to cancel too early
ajuggler Sep 13, 2025
aeab8fd
Correction
ajuggler Sep 13, 2025
926371b
Cleanup
ajuggler Sep 13, 2025
1ba258f
Updated structure of database
ajuggler Sep 14, 2025
4ddcadb
Added DB setter for fiat's signature
ajuggler Sep 15, 2025
5a2b7ca
Prevent tampering sell info in between steps
ajuggler Sep 15, 2025
9f4313a
Warns if attempting to claim without fiat's sig
ajuggler Sep 15, 2025
9de369f
Merge branch 'main' into 18-web-page-running-on-testnet
ajuggler Sep 15, 2025
f8bc12f
stylish-haskell auto-commit
ajuggler Sep 15, 2025
819f7ce
Cleanup
ajuggler Sep 15, 2025
9497829
Minor cleanup
ajuggler Sep 15, 2025
0c394a8
Some improvements
ajuggler Sep 16, 2025
5fc765c
Updated configuration setup
ajuggler Sep 16, 2025
63517f1
Moved some code to new module SellOrders
ajuggler Sep 16, 2025
e3bc0c1
Updated README
ajuggler Sep 16, 2025
be366ec
Cleanup
ajuggler Sep 16, 2025
af98cd3
stylish-haskell auto-commit
ajuggler Sep 16, 2025
1ce2c51
Minor update to template config
ajuggler Sep 16, 2025
d55f617
Updated tests
ajuggler Sep 16, 2025
92538df
Updated README
ajuggler Sep 16, 2025
4f251be
Minor cleanup
ajuggler Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ assets
e2e-test/local-testnet/example/
e2e-test/*/keys
e2e-test/*/priv
maestro-config.json
onramp-config.json
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Revision history for p2p-onramp

## 0.1.0.0 -- YYYY-mm-dd
## 0.1.0.0

* First version. Released on an unsuspecting world.
* End-to-end testing

## 0.1.1.0

* Prototype web-app
64 changes: 39 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,58 @@

Trustless P2P on-ramp is a smart contract app that connects cryptocurrency sellers and buyers. A seller deposits funds into the smart contract and ask for a fiat payment. Once the payment is delivered, the buyer can generate a cryptographic proof and unlock the funds from the smart contract.

## End-to-end testing instructions
Prototype web-app can be tested at (under construction...)

This repo contains the off-chain code for building and balancing the transactions implementing the buy/sell trading sequence enabled by the P2P on-ramp smart contract.
## Web-app instructions

To execute the end-to-end testing code, make `e2e-test` your active directory.
The app allows for "sellers" to post sell orders (crypto for fiat) and for "buyers" to choose and buy a sell order. The web-app contains four sections:
1) Wallet selector
2) Seller's panel
3) Listing of sell orders
4) Buyer's panel

### Wallet initialization
A seller operates with sections 1, 2, and 3; the buyer with sections 1, 3 and 4.

Code is provided to work with either a local testnet or the public *Preview* testnet.
### Seller's panel

#### *Preview* testnet
- **Posting a sell order.** The seller commits to a *sell offer* by filling the required fields and pressing the **Sell Offer** button. This prompts the seller to sign a message (needed to collect the seller's public key). Next, the seller presses button **Submit Tx** to sign and submit the transaction that sends the seller's offered crypto, together with the corresponding sell-offer's datum, to the p2p-onramp smart contract. The seller should take note of the *OrderID* associated to his/her offer.

- Execute `./p2p/preview-ini.sh`.
- Fund Alice's wallet (address displayed)
- **Cancelling a sell order.** If after a while there is no buyer for the seller's offer, the seller can cancel the corresponding order by entering its OrderID and pressing the **Cancel Order** button. This allows the seller to recover the offered crypto. If a buyer has commited to the seller's offer, but has not deposited the fiat within the "claim grace period", the seller can go ahead and cancel the order.

#### Local testnet
### Listing of sell orders

Simply execute `./p2p/local-ini.sh`.
Press the **Available sell orders** button to refresh the list of available sell orders. Those that have a commited buyer are highlighted.

(Instructions for setting up a local testnet can be found [here](https://github.com/zkFold/zkfold-cardano/tree/main/e2e-test).)
### Buyer's panel

### Buy/sell trading sequence
The buyer commits to a sell order by entering the corresponding OrderID and pressing the *Sign Tx* button. The corresponding submitted transaction updates the order with the buyer's pub-key-hash and a time-lock associated to the "claim grace period" for the buyer to deposit the fiat. Next, the buyer presses the **Verify fiat** button for the platfrom to "verify the fiat deposit" and generating the corresponding cryptographic signature. Finally, the buyer presses the **Claim crypto** button to claim the bought crypto.

To reproduce the buy/sell sequence of transactions, execute:
## Installation instructions

- `./p2p/01-p2p-init-transaction.sh` serializes the `onRamp` plutus script and initializes the sellers' and buyer's wallets.
- `./p2p/02-p2p-add-sellers.sh` adds sell offers to the onRamp script.
- `cardano-cli conway query utxo --address $(cat ./p2p/keys/onRamp.addr) --testnet-magic 2` verify sell offers are onchain
- `./p2p/03-p2p-buy-order.sh` selects a "best" offer and updates the corresponding datum with a buy order.
- `./p2p/04-p2p-claim-transaction` buyer claims the funds after fiat witness provides cryptographic proof.
- `./p2p/05-p2p-cancel-transaction.sh` sellers whose orders were not executed can reclaim funds after deadline has passed.
- `./p2p/reset.sh` erases keys and assets to **reset the system**.
If you so desire, you can run the P2P-OnRamp web app locally.

Notes:
### Requirements

- Make sure to reset system before trying the sequence again.
- For third step, magic number is 2 for preview testnet and 42 for local testnet.
Compilation was tested with GHC 9.6.6 and Cabal 3.10.2.1. Other library requirements are described in [this](https://github.com/input-output-hk/cardano-node-wiki/blob/602fe3a56a13a773cd6c0e00420ee3e5c56f2857/docs/getting-started/install.md) guide. Additionally, `libpq-dev` or `postgresql` need to be installed as otherwise an error suggesting missing pg_config can occur.

## Documentation
### p2p-onramp server

For details, see document [progress_report.pdf](https://github.com/zkFold/p2p-onramp/raw/main/progress_report.pdf).
To run the p2p-onramp server, execute:
```shell
cabal run p2p-server -- provider-config.json onramp-config.json
```
where
1. `provider-config.json` contains your configuration for network and provider. File `maestro-config-TEMPLATE.json` provides a template configuration.
2. `onramp-config.json` contains the P2P-OnRamp's configuration parameters. File `onramp-config-TEMPLATE.json` provides a guide for the platform's configuration.

*Note:* File `onramp-config-TEMPLATE.json` makes reference to files `fiat.addr`, `fiat.vkey` and `fiat.skey`. These can be generated with `cardano-cli` as usual:
```shell
cardano-cli conway address key-gen --verification-key-file fiat.vkey --signing-key-file fiat.skey
cardano-cli conway address build --payment-verification-key-file fiat.vkey --out-file fiat.addr --testnet-magic 2
```

The p2p-onramp server on this repository was written using the [Atlas](https://atlas-app.io) framework.

### p2p-onramp client

The p2p-onramp frontend can be found [here](https://github.com/zkFold/p2p-onramp-client).
55 changes: 55 additions & 0 deletions app/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module Main where

import Data.Aeson (eitherDecodeFileStrict)
import GeniusYield.GYConfig (coreConfigIO, withCfgProviders)
import Network.Wai.Handler.Warp
import Prelude
import System.Directory (createDirectoryIfMissing)
import System.Environment (getArgs)
import System.FilePath ((</>))

import P2POnRamp.Api (app)
import P2POnRamp.Api.Context (Ctx (..), OnRampConfig (..), dbFile)
import P2POnRamp.OrdersDB


-- | Getting path for our core configuration.
parseArgs :: IO (FilePath, FilePath)
parseArgs = do
args <- getArgs
case args of
coreCfg : onrampCfg : _ -> return (coreCfg, onrampCfg)
_invalidArgument -> fail "Error: wrong arguments, needed 1) path to the CoreConfig JSON configuration file and 2) path to the OnRamp JSON configuration file.\n"

main :: IO ()
main = do
let path = "."
assetsPath = path </> "assets"
createDirectoryIfMissing True assetsPath

let dbPath = assetsPath </> dbFile
initDB dbPath

(coreCfgPath, onrampCfgPath) <- parseArgs

putStrLn "parsing CoreConfig ..."
coreCfg <- coreConfigIO coreCfgPath

putStrLn "parsing OnRamp config..."
orConfigE <- eitherDecodeFileStrict onrampCfgPath
let orConfig = case orConfigE of
Left err -> error $ "Error decoding JSON: " ++ err
Right cfg -> cfg

putStrLn "Loading Proviers ..."
withCfgProviders coreCfg "api-server" $ \providers -> do
let port = 8080
ctx = Ctx { ctxCoreCfg = coreCfg
, ctxProviders = providers
, ctxOnRampParams = orParams orConfig
, ctxFiatSKeyFilePath = orFiatSKeyFilePath orConfig
, ctxClaimGracePeriod = orClaimGracePeriod orConfig
}

putStrLn $ "Serving on http://localhost:" ++ show port
run port $ app ctx assetsPath
76 changes: 76 additions & 0 deletions app/P2POnRamp/Api.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{-# LANGUAGE TypeOperators #-}

module P2POnRamp.Api where

import Control.Exception (try)
import Control.Monad.Trans.Except (ExceptT (..))
import qualified Network.HTTP.Types as HttpTypes
import Network.Wai.Middleware.Cors
import Prelude
import Servant

import P2POnRamp.Api.BuyerClaim (ClaimCrypto, ClaimCryptoResponse,
FiatVerified, FiatVerify,
handleBuildClaimTx,
handleFiatSign,
handleSubmitClaimTx)
import P2POnRamp.Api.BuyerCommit (BuyCommit, handleBuildBuyTx,
handleSubmitBuyTx)
import P2POnRamp.Api.Context (Ctx (..))
import P2POnRamp.Api.Seller (CancelOrder, CancelOrderResponse,
NewOrder, SellTxResponse,
SellerData, SellerTx,
handleBuildCancelTx,
handleBuildSellTx,
handleSellerData,
handleSubmitCancelTx,
handleSubmitSellTx)
import P2POnRamp.Api.SellOrders (SellOrder, handleSellOrders)
import P2POnRamp.Api.Tx (AddSubmitParams, SubmitTxResult,
UnsignedTxResponse)


-- | Type for our Servant API.
type API = "seller-data" :> ReqBody '[JSON] SellerData
:> Post '[JSON] NewOrder
:<|> "sell-build" :> ReqBody '[JSON] SellerTx
:> Post '[JSON] SellTxResponse
:<|> "sell-submit" :> ReqBody '[JSON] AddSubmitParams
:> Post '[JSON] SubmitTxResult
:<|> "cancel-build" :> ReqBody '[JSON] CancelOrder
:> Post '[JSON] CancelOrderResponse
:<|> "cancel-submit" :> ReqBody '[JSON] AddSubmitParams
:> Post '[JSON] SubmitTxResult
:<|> "sell-orders" :> Get '[JSON] [SellOrder]
:<|> "buy-build" :> ReqBody '[JSON] BuyCommit
:> Post '[JSON] UnsignedTxResponse
:<|> "buy-submit" :> ReqBody '[JSON] AddSubmitParams
:> Post '[JSON] SubmitTxResult
:<|> "fiat-verify" :> ReqBody '[JSON] FiatVerify
:> Post '[JSON] FiatVerified
:<|> "claim-build" :> ReqBody '[JSON] ClaimCrypto
:> Post '[JSON] ClaimCryptoResponse
:<|> "claim-submit" :> ReqBody '[JSON] AddSubmitParams
:> Post '[JSON] SubmitTxResult

-- | Server Handler
server :: Ctx -> FilePath -> ServerT API IO
server ctx path = handleSellerData path
:<|> handleBuildSellTx ctx path
:<|> handleSubmitSellTx ctx path
:<|> handleBuildCancelTx ctx path
:<|> handleSubmitCancelTx ctx path
:<|> handleSellOrders ctx path
:<|> handleBuildBuyTx ctx path
:<|> handleSubmitBuyTx ctx path
:<|> handleFiatSign ctx path
:<|> handleBuildClaimTx ctx path
:<|> handleSubmitClaimTx ctx path

appApi :: Proxy API
appApi = Proxy

app :: Ctx -> FilePath -> Application
app ctx path = cors (const $ Just simpleCorsResourcePolicy { corsRequestHeaders = [HttpTypes.hContentType] }) $
serve appApi $ hoistServer appApi (Handler . ExceptT . try) $
server ctx path
28 changes: 28 additions & 0 deletions app/P2POnRamp/Api/Addr.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}


module P2POnRamp.Api.Addr where

import Data.Text (Text)
import GeniusYield.GYConfig (GYCoreConfig (..))
import GeniusYield.Types
import Prelude

import P2POnRamp.Api.Context (Ctx (..), badRequest, onRampValidator)


--------------------------------------------------------------------------------
-- Handler: get OnRamp address

handleGetOnRampAddr :: Ctx -> IO Text
handleGetOnRampAddr ctx = do
case onRampValidator $ ctxOnRampParams ctx of
Left err -> badRequest err
Right onRampScript -> do
let nid = cfgNetworkId $ ctxCoreCfg ctx
onRampAddress' = addressFromValidator nid onRampScript
onRampAddress = addressToText onRampAddress'

return onRampAddress
Loading