Plutus HTLC: Atomic Swap Smart Contract

📌 Table of Contents

  1. 🔍 Overview

  2. ⚙️ Contract Structure

  3. 🧩 HTLCParams – Contract Parameters

  4. 🎯 HTLCRedeemer – User Actions

  5. 🧪 mkValidator – Core Logic

  6. 📜 Typed Validator & Wrapping

  7. 📫 Script Address

  8. 📘 Glossary of Terms


1️⃣ 🔍 Overview

This contract implements a Hashed Timelock Contract (HTLC), which enables secure, trustless, and time-limited swaps of assets between two parties. The contract has two unlock conditions:

  • Hashlock: Receiver must reveal the preimage of a given hash before the deadline.

  • Timelock: If deadline passes, the sender can reclaim the funds.


2️⃣ ⚙️ Contract Structure

The contract consists of:

  • 📦 HTLCParams: The configuration (sender, receiver, hash, and deadline).

  • 🎯 HTLCRedeemer: The action chosen (Redeem with a secret, or Refund).

  • 🧪 mkValidator: The validator logic.

  • 🧵 Typed validator & script address.


3️⃣ 🧩 HTLCParams — Contract Parameters

data HTLCParams = HTLCParams
  { sender    :: PubKeyHash
  , receiver  :: PubKeyHash
  , hashlock  :: BuiltinByteString  -- sha2_256 of secret
  , deadline  :: POSIXTime
  }

🔍 Explanation:

  • sender: Person initiating the swap (can reclaim funds after deadline).

  • receiver: Person claiming funds with the secret.

  • hashlock: A SHA-256 hash of the secret.

  • deadline: Time after which refund is allowed.

🚀 Make this value once off-chain, and lift it using Template Haskell:

PlutusTx.makeLift ''HTLCParams

4️⃣ 🎯 HTLCRedeemer — User Actions

data HTLCRedeemer = Redeem BuiltinByteString | Refund

✨ Purpose:

  • Redeem secret — Receiver provides the secret before the deadline.

  • Refund — Sender reclaims after the deadline.

Must be serializable:

PlutusTx.unstableMakeIsData ''HTLCRedeemer

5️⃣ 🧪 mkValidator — Core Validation Logic

mkValidator :: HTLCParams -> () -> HTLCRedeemer -> ScriptContext -> Bool
mkValidator htlc _ redeemer ctx =
  case redeemer of
    Redeem secret ->
      traceIfFalse "Hash mismatch" correctHash &&
      traceIfFalse "Not signed by receiver" signedByReceiver &&
      traceIfFalse "Too late" withinDeadline
      where
        correctHash     = sha2_256 secret == hashlock htlc
        signedByReceiver = txSignedBy info (receiver htlc)
        withinDeadline   = to (deadline htlc) `contains` txInfoValidRange info

    Refund ->
      traceIfFalse "Too early" pastDeadline &&
      traceIfFalse "Not signed by sender" signedBySender
      where
        pastDeadline    = from (deadline htlc) `contains` txInfoValidRange info
        signedBySender  = txSignedBy info (sender htlc)
  where
    info = scriptContextTxInfo ctx

🔍 What this does:

🔑 If Redeem secret:

  • Checks sha2_256(secret) == hashlock

  • Ensures the receiver signed the tx

  • Ensures the current time is before the deadline

💸 If Refund:

  • Checks time is after the deadline

  • Ensures the sender signed it

All conditions use traceIfFalse for on-chain debugging.


6️⃣ 📜 Typed Validator & Wrapping

✂️ Define the validator types:

data HTLC
instance Scripts.ValidatorTypes HTLC where
  type instance DatumType HTLC = ()
  type instance RedeemerType HTLC = HTLCRedeemer

🧵 Compile the validator:

typedValidator :: HTLCParams -> Scripts.TypedValidator HTLC
typedValidator htlc = Scripts.mkTypedValidator @HTLC
  ($$(PlutusTx.compile [|| mkValidator ||]) `PlutusTx.applyCode` PlutusTx.liftCode htlc)
  $$(PlutusTx.compile [|| wrap ||])
  where
    wrap = Scripts.wrapValidator @() @HTLCRedeemer

🔁 This connects the raw mkValidator to Plutus's typed validation system.


7️⃣ 📫 Script Address

This lets you lock funds at the HTLC address on-chain:

validator :: HTLCParams -> Validator
validator = Scripts.validatorScript . typedValidator

scrAddress :: HTLCParams -> Address
scrAddress = scriptAddress . validator

8️⃣ 📘 Glossary of Terms

Term
Meaning

sha2_256

Hash function used for locking the secret

PubKeyHash

Public key identifier

txSignedBy

Verifies transaction is signed by a given party

scriptContextTxInfo

Retrieves transaction info for validation

contains

Checks if a time range contains a point

traceIfFalse

On-chain debugging that fails with a message

POSIXTime

UTC timestamp used in Plutus timelocks

Validator

A Plutus smart contract function

BuiltinByteString

On-chain byte string

Redeemer

Input that unlocks a validator

TypedValidator

Type-safe validator using Plutus type system


🧪 Sample Off-Chain Parameter Setup

exampleParams = HTLCParams
  { sender    = <senderPubKeyHash>
  , receiver  = <receiverPubKeyHash>
  , hashlock  = sha2_256 "superSecret123"
  , deadline  = <somePOSIXTime>
  }

You'd pass this into typedValidator and derive the address to fund it.


✅ Summary

This HTLC Plutus contract allows:

  • Atomic swaps between chains or users

  • Time-bound claim and refund

  • Secure hash-based locking

It's ideal for use cases like:

  • 💱 Cross-chain asset exchanges

  • 🧾 Conditional token access

  • 🔐 Trustless payments with refund guarantees


Last updated