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}