OSDN Git Service

13a5e266513c6614188eab4cd8e33bcbcb3eae8d
[bytom/shuttle.git] / swap / transaction.go
1 package swap
2
3 import (
4         "encoding/hex"
5         "encoding/json"
6         "errors"
7         "fmt"
8
9         "github.com/bytom/crypto/ed25519/chainkd"
10 )
11
12 var (
13         errXPrvLength = errors.New("XPrv length is invalid.")
14 )
15
16 const (
17         fee           = uint64(40000000)
18         confirmations = uint64(1)
19 )
20
21 type TxOutput struct {
22         UTXOID      string `json:"utxo_id"`
23         Script      string `json:"script"`
24         Address     string `json:"address"`
25         AssetID     string `json:"asset"`
26         AssetAmount uint64 `json:"amount"`
27 }
28
29 type getTxReq struct {
30         TxID string `json:"tx_id"`
31 }
32
33 type getTxResp struct {
34         TxOutputs []TxOutput `json:"outputs"`
35 }
36
37 // getUTXOID get UTXO ID by transaction ID.
38 func getUTXOID(s *Server, txID, controlProgram string) (string, error) {
39         payload, err := json.Marshal(getTxReq{TxID: txID})
40         if err != nil {
41                 return "", err
42         }
43
44         res := new(getTxResp)
45         if err := s.request(getTransactionURL, payload, res); err != nil {
46                 return "", err
47         }
48
49         for _, v := range res.TxOutputs {
50                 if v.Script == controlProgram {
51                         return v.UTXOID, nil
52                 }
53         }
54
55         return "", errFailedGetContractUTXOID
56 }
57
58 type SigningInstruction struct {
59         DerivationPath []string `json:"derivation_path"`
60         SignData       []string `json:"sign_data"`
61         DataWitness    []byte   `json:"-"`
62
63         // only shown for a single-signature tx
64         Pubkey string `json:"pubkey,omitempty"`
65 }
66
67 type SpendUTXOInput struct {
68         Type     string `json:"type"`
69         OutputID string `json:"output_id"`
70 }
71
72 type SpendWalletInput struct {
73         Type    string `json:"type"`
74         AssetID string `json:"asset"`
75         Amount  uint64 `json:"amount"`
76 }
77
78 type ControlAddressOutput struct {
79         Type    string `json:"type"`
80         Amount  uint64 `json:"amount"`
81         AssetID string `json:"asset"`
82         Address string `json:"address"`
83 }
84
85 type ControlProgramOutput struct {
86         Type           string `json:"type"`
87         Amount         uint64 `json:"amount"`
88         AssetID        string `json:"asset"`
89         ControlProgram string `json:"control_program"`
90 }
91
92 type buildTxReq struct {
93         GUID          string        `json:"guid"`
94         Fee           uint64        `json:"fee"`
95         Confirmations uint64        `json:"confirmations"`
96         Inputs        []interface{} `json:"inputs"`
97         Outputs       []interface{} `json:"outputs"`
98 }
99
100 type buildTxResp struct {
101         RawTx               string                `json:"raw_transaction"`
102         SigningInstructions []*SigningInstruction `json:"signing_instructions"`
103         Fee                 uint64                `json:"fee"`
104 }
105
106 // BuildTx build tx.
107 func BuildTx(s *Server, guid, outputID, lockedAsset, contractProgram string, lockedAmount uint64) (string, error) {
108         // inputs:
109         spendUTXOInput := SpendUTXOInput{
110                 Type:     "spend_utxo",
111                 OutputID: outputID,
112         }
113         spendWalletInput := SpendWalletInput{
114                 Type:    "spend_wallet",
115                 AssetID: BTMAssetID,
116                 Amount:  fee,
117         }
118
119         // outputs:
120         controlProgramOutput := ControlProgramOutput{
121                 Type:           "control_program",
122                 Amount:         lockedAmount,
123                 AssetID:        lockedAsset,
124                 ControlProgram: contractProgram,
125         }
126
127         var inputs, outputs []interface{}
128         inputs = append(inputs, spendUTXOInput, spendWalletInput)
129         outputs = append(outputs, controlProgramOutput)
130         payload, err := json.Marshal(buildTxReq{
131                 GUID:          guid,
132                 Fee:           fee,
133                 Confirmations: confirmations,
134                 Inputs:        inputs,
135                 Outputs:       outputs,
136         })
137         if err != nil {
138                 return "", err
139         }
140
141         fmt.Println("buildTx:", string(payload))
142
143         res := new(buildTxResp)
144         if err := s.request(buildTransactionURL, payload, res); err != nil {
145                 return "", err
146         }
147
148         r, err := json.MarshalIndent(res, "", "\t")
149         if err != nil {
150                 return "", err
151         }
152
153         return string(r), nil
154 }
155
156 type submitPaymentReq struct {
157         GUID       string     `json:"guid"`
158         RawTx      string     `json:"raw_transaction"`
159         Signatures [][]string `json:"signatures"`
160         Memo       string     `json:"memo"`
161 }
162
163 type submitPaymentResp struct {
164         TxID string `json:"transaction_hash"`
165 }
166
167 // submitPayment submit raw transaction and return transaction ID.
168 func submitPayment(s *Server, guid, rawTx, memo string, sigs [][]string) (string, error) {
169         payload, err := json.Marshal(submitPaymentReq{
170                 GUID:       guid,
171                 RawTx:      rawTx,
172                 Signatures: sigs,
173                 Memo:       memo,
174         })
175         if err != nil {
176                 return "", err
177         }
178
179         fmt.Println("submitPayment:", string(payload))
180
181         res := new(submitPaymentResp)
182         if err := s.request(submitTransactionURL, payload, res); err != nil {
183                 return "", err
184         }
185
186         return res.TxID, nil
187 }
188
189 // SignMsg sign message, return sig.
190 func SignMsg(signData, xprv string) (string, error) {
191         xprvBytes, err := hex.DecodeString(xprv)
192         if err != nil {
193                 return "", err
194         }
195         if len(xprvBytes) != 64 {
196                 return "", errXPrvLength
197         }
198
199         var newXPrv chainkd.XPrv
200         copy(newXPrv[:], xprvBytes[:])
201
202         msg, err := hex.DecodeString(signData)
203         if err != nil {
204                 return "", err
205         }
206         sig := newXPrv.Sign(msg)
207         return hex.EncodeToString(sig), nil
208 }
209
210 // buildUnlockedTx build unlocked contract tx.
211 func buildUnlockedTx(s *Server, guid, contractUTXOID, contractAsset, receiver string, spendWalletAmount, contractAmount uint64) (string, error) {
212         // inputs:
213         spendUTXOInput := SpendUTXOInput{
214                 Type:     "spend_utxo",
215                 OutputID: contractUTXOID,
216         }
217
218         spendWalletInput := SpendWalletInput{
219                 Type:    "spend_wallet",
220                 AssetID: BTMAssetID,
221                 Amount:  spendWalletAmount,
222         }
223
224         // outputs:
225         controlAddressOutput := ControlAddressOutput{
226                 Type:    "control_address",
227                 Amount:  contractAmount,
228                 AssetID: contractAsset,
229                 Address: receiver,
230         }
231
232         var inputs, outputs []interface{}
233         inputs = append(inputs, spendUTXOInput, spendWalletInput)
234         outputs = append(outputs, controlAddressOutput)
235         payload, err := json.Marshal(buildTxReq{
236                 GUID:          guid,
237                 Fee:           fee,
238                 Confirmations: confirmations,
239                 Inputs:        inputs,
240                 Outputs:       outputs,
241         })
242         if err != nil {
243                 return "", err
244         }
245
246         fmt.Println("build unlocked contract tx:", string(payload))
247
248         res := new(buildTxResp)
249         if err := s.request(buildTransactionURL, payload, res); err != nil {
250                 return "", err
251         }
252
253         r, err := json.MarshalIndent(res, "", "\t")
254         if err != nil {
255                 return "", err
256         }
257
258         return string(r), nil
259 }
260
261 // submitUnlockedPayment submit raw transaction and return transaction ID.
262 func submitUnlockedPayment(s *Server, guid, rawTx, memo, spendWalletSig string, spendUTXOSignatures []string) (string, error) {
263         // spendUTXOSignatures := append([]string{}, preimage, spendUTXOSig, "")
264         spendWalletSignatures := append([]string{}, spendWalletSig)
265         sigs := append([][]string{}, spendUTXOSignatures, spendWalletSignatures)
266
267         payload, err := json.Marshal(submitPaymentReq{
268                 GUID:       guid,
269                 RawTx:      rawTx,
270                 Signatures: sigs,
271                 Memo:       memo,
272         })
273         if err != nil {
274                 return "", err
275         }
276
277         fmt.Println("submitUnlockedPayment:", string(payload))
278
279         res := new(submitPaymentResp)
280         if err := s.request(submitTransactionURL, payload, res); err != nil {
281                 return "", err
282         }
283
284         return res.TxID, nil
285 }
286
287 // buildCallTradeoffTx build unlocked tradeoff contract tx.
288 func buildCallTradeoffTx(s *Server, guid, contractUTXOID, seller, assetRequested string, spendWalletAmount, contractAmount, amountRequested uint64) (*buildTxResp, error) {
289         // inputs:
290         spendUTXOInput := SpendUTXOInput{
291                 Type:     "spend_utxo",
292                 OutputID: contractUTXOID,
293         }
294
295         spendWalletInput := SpendWalletInput{
296                 Type:    "spend_wallet",
297                 AssetID: BTMAssetID,
298                 Amount:  spendWalletAmount,
299         }
300
301         spendWalletUnlockTradeoffInput := SpendWalletInput{
302                 Type:    "spend_wallet",
303                 AssetID: assetRequested,
304                 Amount:  amountRequested,
305         }
306
307         // outputs:
308         controlProgramOutput := ControlProgramOutput{
309                 Type:           "control_program",
310                 Amount:         amountRequested,
311                 AssetID:        assetRequested,
312                 ControlProgram: seller,
313         }
314
315         var inputs, outputs []interface{}
316         inputs = append(inputs, spendUTXOInput, spendWalletInput, spendWalletUnlockTradeoffInput)
317         outputs = append(outputs, controlProgramOutput)
318         payload, err := json.Marshal(buildTxReq{
319                 GUID:          guid,
320                 Fee:           fee,
321                 Confirmations: confirmations,
322                 Inputs:        inputs,
323                 Outputs:       outputs,
324         })
325         if err != nil {
326                 return nil, err
327         }
328
329         fmt.Println("build unlocked contract tx:", string(payload))
330
331         res := new(buildTxResp)
332         if err := s.request(buildTransactionURL, payload, res); err != nil {
333                 return nil, err
334         }
335
336         return res, nil
337 }