Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

swap.gno

2.60 Kb · 101 lines
  1package atomicswap
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/hex"
  6	"time"
  7
  8	"gno.land/p/nt/ufmt/v0"
  9)
 10
 11// Swap represents an atomic swap contract.
 12type Swap struct {
 13	sender    address
 14	recipient address
 15	hashlock  string
 16	timelock  time.Time
 17	claimed   bool
 18	refunded  bool
 19	amountStr string
 20	sendFn    func(cur realm, to address)
 21}
 22
 23func newSwap(
 24	sender address,
 25	recipient address,
 26	hashlock string,
 27	timelock time.Time,
 28	amountStr string,
 29	sendFn func(realm, address),
 30) *Swap {
 31	require(time.Now().Before(timelock), "timelock must be in the future")
 32	require(hashlock != "", "hashlock must not be empty")
 33	return &Swap{
 34		recipient: recipient,
 35		sender:    sender,
 36		hashlock:  hashlock,
 37		timelock:  timelock,
 38		claimed:   false,
 39		refunded:  false,
 40		sendFn:    sendFn,
 41		amountStr: amountStr,
 42	}
 43}
 44
 45// Claim allows the recipient to claim the funds if they provide the correct preimage.
 46// rlm is the cur of the surrounding crossing wrapper; rlm.Previous() is
 47// the immediate caller of that wrapper, against which we authorize.
 48func (s *Swap) Claim(_ int, rlm realm, preimage string) {
 49	require(rlm.IsCurrent(), "unauthorized: rlm is not the caller's live cur")
 50	require(!s.claimed, "already claimed")
 51	require(!s.refunded, "already refunded")
 52	require(rlm.Previous().Address() == s.recipient, "unauthorized")
 53
 54	hashlock := sha256.Sum256([]byte(preimage))
 55	hashlockHex := hex.EncodeToString(hashlock[:])
 56	require(hashlockHex == s.hashlock, "invalid preimage")
 57
 58	s.claimed = true
 59	s.sendFn(cross(rlm), s.recipient)
 60}
 61
 62// Refund allows the sender to refund the funds after the timelock has expired.
 63func (s *Swap) Refund(_ int, rlm realm) {
 64	require(rlm.IsCurrent(), "unauthorized: rlm is not the caller's live cur")
 65	require(!s.claimed, "already claimed")
 66	require(!s.refunded, "already refunded")
 67	require(rlm.Previous().Address() == s.sender, "unauthorized")
 68	require(time.Now().After(s.timelock), "timelock not expired")
 69
 70	s.refunded = true
 71	s.sendFn(cross(rlm), s.sender)
 72}
 73
 74func (s Swap) Status() string {
 75	switch {
 76	case s.refunded:
 77		return "refunded"
 78	case s.claimed:
 79		return "claimed"
 80	case s.TimeRemaining() < 0:
 81		return "expired"
 82	default:
 83		return "active"
 84	}
 85}
 86
 87func (s Swap) TimeRemaining() time.Duration {
 88	remaining := time.Until(s.timelock)
 89	if remaining < 0 {
 90		return 0
 91	}
 92	return remaining
 93}
 94
 95// String returns the current state of the swap.
 96func (s Swap) String() string {
 97	return ufmt.Sprintf(
 98		"- status: %s\n- sender: %s\n- recipient: %s\n- amount: %s\n- hashlock: %s\n- timelock: %s\n- remaining: %s",
 99		s.Status(), s.sender, s.recipient, s.amountStr, s.hashlock, s.timelock.Format(time.RFC3339), s.TimeRemaining().String(),
100	)
101}