-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathPaymentChannel.java
147 lines (136 loc) · 4.26 KB
/
PaymentChannel.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package bt.sample;
import bt.Contract;
import bt.ui.EmulatorWindow;
import bt.Timestamp;
import bt.Address;
import bt.BT;
/**
* A unidirectional (micro) payment channel.
*
* The creator is the payer and someone else is the payee. There is no way for
* the payer to reduce the BURST amount in an open channel, only to increase it
* by depositing more BURST on it.
*
* So the payer should write "checks" ({@link #approveAmount(long, long)} signed
* messages) and send them to the payee (off-chain). Neither the payer nor the
* payee usually broadcast these messages, since that would cost a transaction
* fee without additional benefit.
*
* Whenever the payee finds suitable (but before the channel timeout) the
* payee broadcast the payer mesage for the largest value approved and then
* closes the channel {@link #closeChannel(long)} to receive the BURST amount.
*
* By extending this contract, a bi-directional channel could be implemented.
* Either that or simply open two different channels with payer and payee roles
* inverted.
*
* @author jjos
*/
public class PaymentChannel extends Contract {
Address payee;
Timestamp timeout;
long amountApproved;
long nonce;
/**
* Open a new channel for the given payee and timeout.
*
* Only the creator can open the channel and it must not be currently open.
*
* @param payee
* @param timeout
*/
public void openChannel(Address payee, long timeout) {
checkTimeout();
// only creator can open a channel, and it must be currenctly closed (payee==null)
if (getCurrentTxSender().equals(getCreator()) && this.payee == null) {
this.payee = payee;
this.timeout = getBlockTimestamp().addMinutes(timeout);
this.amountApproved = 0;
}
}
/**
* A message that approves the transfer of the given amount to the payee.
*
* This message usually will be signed and sent from the payer to the payee
* off-chain. The payee can check the message contents and signature off-chain
* and then accept the payment **instantly**.
*
* @param amount
* @param nonce
*/
public void approveAmount(long amount, long nonce) {
checkTimeout();
if (getCurrentTxSender().equals(getCreator()) && this.nonce == nonce) {
// Only creator can approve command must be valid:
// - before timeout
// - correct nonce (avoids double spend)
if (amount > amountApproved)
amountApproved = amount;
}
}
/**
* Closes the channel and pays the approved amount to the payee.
*
* Only the payee can close the channel.
*
* The given nonce must match the nonce stored on the contract. When the channel
* is closed the nonce is incremented avoiding double spending on this contract
* and allowing to reuse this contract by calling
* {@link #openChannel(Address, long)} again.
*/
public void closeChannel(long nonce) {
checkTimeout();
if (getCurrentTxSender().equals(payee) && this.nonce == nonce) {
// Only payee can close the channel:
// - before timeout
// - correct nonce (avoids double spend)
sendAmount(amountApproved, payee);
// increment the nonce, so any previous payment order becomes invalid
nonce++;
payee = null;
timeout = null;
}
}
/**
* Utility function to get the channel balance back.
*
* Only the creator can call this function. It would be used to get back the
* balance of a closed channel or when the channel timeout.
*/
public void getBalance() {
checkTimeout();
// only creator can get the balance back, the channel must be currently
// closed (payee==null or timedout)
if (!getCurrentTxSender().equals(getCreator()))
return;
if (payee == null)
sendBalance(getCreator());
}
/**
* This contract only accepts the public method calls above.
*
* If an unrecognized method was called we do nothing
*/
public void txReceived() {
}
/**
* Private method, not available from blockchain messages, for checking
* if this channel timedout.
*/
private void checkTimeout() {
if (getBlockTimestamp().ge(timeout)) {
// expired
nonce++;
payee = null;
}
}
/**
* A main function for debugging purposes only.
*
* This function is not compiled into bytecode and do not go to the blockchain.
*/
public static void main(String[] args) throws Exception {
BT.activateCIP20(true);
new EmulatorWindow(PaymentChannel.class);
}
}