OSDN Git Service

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