P2P Escrow service

Private token sale, open, trustless and decentralized

About the P2P Escrow service

The open P2P Escrow service is a smart contract that enables trustless, person-to-person (P2P), private sales on the Ergo blockchain for a small fee. The contract accepts Ergo tokens, such as a non-fungible token (NFT), 100 SigUSD, or other token(s) from the seller. Once in the contract, only a defined buyer sending a defined amount of ERG can access the token(s). If this happens, the contract sends the NFT, SigUSD or other token(s) to the buyer and the ERG to the seller.
The seller can cancel the contract at any time before the exchange is made.

The contract is open source (see below) and anyone can interact directly or by using other services with it. When using Tokenjay, the contract charges both buyer and seller 0.5% of the ERG of the transaction in fees when the transaction is completed.

How to sell a token with the P2P Escrow service on TokenJay

  1. You need an ErgoPay compatible wallet application at hand. We recommend the mobile Ergo Wallet App
  2. Open the TokenJay app and navigate to the Token sales panel. If you are using Ergo Wallet App on Android, you can alternatively activate Mosaik plugins in the settings and navigate to tokenjay.app in the dApp section.
  3. Connect a wallet if not already done. Click on the New Sale button.
  4. Select the token to sell, enter the token amount, the amount to pay by the buyer and the buyer's p2pk address.
  5. After clicking the Sell token button, complete the transaction with ErgoPay. The token is sent to the smart contract.
  6. Note that it needs some time for the sale to show up in the Escrow dashboard as your transaction has to be executed and confirmed first.
  7. You can cancel the sale from your dashboard at any time.

Open app

How to buy a token with the P2P Escrow service on TokenJay

  1. You need an ErgoPay compatible wallet application at hand. We recommend the mobile Ergo Wallet App
  2. Make sure the seller completed the steps outlined above.
  3. Open the TokenJay app and navigate to the Token purchases panel. If you are using Ergo Wallet App on Android, you can alternatively activate Mosaik plugins in the settings and navigate to tokenjay.app in the dApp section.
  4. Connect a wallet if not already done. Token offers for you will be listed. If an offer is not listed yet, wait a few minutes until the transaction is confirmed.
  5. Go to the token sale page and validate the token id, price and everything else you negotiated with the seller. When everything is alright, click the Buy token button.
  6. Complete the transaction with ErgoPay.

Open app

The contract

Please check the Escrow contract before using it. The contract is deployed at address DaJygexrs8GgHiucBGuPQcrryQPESkfsKtgMAEtnFeYBBST2x2kNQS3iucFhNE9d...

{
  // only a single token and ERG value is secured by this script

  // R4: The seller's ergo tree
  // R5: price in nanoerg
  // R6: UI fee in thousand
  // R7: UI fee ErgoTree
  // R8: buyer's ergo tree
  val seller = SELF.R4[Coll[Byte]].get
  val ergAmountToPay = SELF.R5[Long].get
  val buyer = SELF.R8[Coll[Byte]].get
  val tokenToSell = SELF.tokens(0)
  val uiFeePerThousand = SELF.R6[Long].get
  val uiFeeAddress = SELF.R7[Coll[Byte]].get

  // token is bought
  // paybox.R4: SELF.id
  val isSold = {
    val payBox = OUTPUTS(0)
    val buyBox = OUTPUTS(1)
    val feeBox = OUTPUTS(2)

    val feeAmount = max((ergAmountToPay * uiFeePerThousand) / 1000, 1000000)

    payBox.propositionBytes == seller &&
      payBox.value >= ergAmountToPay - feeAmount &&
      feeBox.propositionBytes == uiFeeAddress &&
      feeBox.value >= feeAmount &&
      payBox.R4[Coll[Byte]].get == SELF.id && // prevent attack
      buyBox.tokens(0) == tokenToSell &&
      buyBox.propositionBytes == buyer
  }

  // refund to seller
  val isRefundToSeller = {
    val refundBox = OUTPUTS(0)
    refundBox.tokens.size > 0 &&
      refundBox.tokens(0) == tokenToSell &&
      refundBox.propositionBytes == seller &&
      refundBox.R4[Coll[Byte]].get == SELF.id && // prevent attack
      INPUTS(1).propositionBytes == seller && // don't let others start the refund
      refundBox.value >= SELF.value
  }

  sigmaProp(isRefundToSeller || isSold)
}