13 errFailedGetContractUTXOID = errors.New("Failed to get contract UTXO ID")
14 errMarshal = errors.New("Failed to marshal")
15 errListUnspentOutputs = errors.New("Failed to list unspent outputs")
16 errTradeOffParametersInvalid = errors.New("Trade off parameters invalid")
17 errFailedSignTx = errors.New("Failed to sign transaction")
18 errFailedGetPublicKey = errors.New("Failed to get public key")
21 type compileLockContractResp struct {
22 Program string `json:"program"`
25 var compileLockContractReq = `{
26 "contract":"contract TradeOffer(assetRequested: Asset, amountRequested: Amount, seller: Program, cancelKey: PublicKey) locks valueAmount of valueAsset { clause trade() { lock amountRequested of assetRequested with seller unlock valueAmount of valueAsset } clause cancel(sellerSig: Signature) { verify checkTxSig(cancelKey, sellerSig) unlock valueAmount of valueAsset}}",
43 // compileLockContract return contract control program
44 func compileLockContract(s *Server, contractArgs ContractArgs) (string, error) {
45 payload := []byte(fmt.Sprintf(compileLockContractReq,
49 contractArgs.CancelKey,
51 res := new(compileLockContractResp)
52 if err := s.request(compileURL, payload, res); err != nil {
55 return res.Program, nil
58 var buildLockTxReq = `{
64 "use_unconfirmed":true,
65 "type":"spend_account"
70 "control_program":"%s",
71 "type":"control_program"
76 "asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
77 "use_unconfirmed":true,
78 "type":"spend_account"
82 "base_transaction":null
85 // buildLockTransaction build locked contract transaction.
86 func buildLockTransaction(s *Server, accountInfo AccountInfo, contractValue AssetAmount, contractControlProgram string) (interface{}, error) {
87 payload := []byte(fmt.Sprintf(buildLockTxReq,
88 accountInfo.AccountID,
93 contractControlProgram,
94 accountInfo.AccountID,
97 res := new(interface{})
98 if err := s.request(buildTransactionURL, payload, res); err != nil {
104 type signTxReq struct {
105 Password string `json:"password"`
106 Transaction interface{} `json:"transaction"`
109 type Transaction struct {
110 RawTransaction string `json:"raw_transaction"`
113 type signTxResp struct {
114 Tx Transaction `json:"transaction"`
115 SignComplete bool `json:"sign_complete"`
118 // signTransaction sign built contract transaction.
119 func signTransaction(s *Server, password string, transaction interface{}) (string, error) {
120 payload, err := json.Marshal(signTxReq{Password: password, Transaction: transaction})
125 res := new(signTxResp)
126 if err := s.request(signTransactionURL, payload, res); err != nil {
130 if !res.SignComplete {
131 return "", errFailedSignTx
134 return res.Tx.RawTransaction, nil
137 type submitTxReq struct {
138 RawTransaction string `json:"raw_transaction"`
141 type submitTxResp struct {
142 TransactionID string `json:"tx_id"`
145 // submitTransaction submit raw singed contract transaction.
146 func submitTransaction(s *Server, rawTransaction string) (string, error) {
147 payload, err := json.Marshal(submitTxReq{RawTransaction: rawTransaction})
152 res := new(submitTxResp)
153 if err := s.request(submitTransactionURL, payload, res); err != nil {
157 return res.TransactionID, nil
160 type getContractUTXOIDReq struct {
161 TransactionID string `json:"tx_id"`
164 type TransactionOutput struct {
165 TransactionOutputID string `json:"id"`
166 ControlProgram string `json:"control_program"`
169 type getContractUTXOIDResp struct {
170 TransactionOutputs []TransactionOutput `json:"outputs"`
173 // getContractUTXOID get contract UTXO ID by transaction ID and contract control program.
174 func getContractUTXOID(s *Server, transactionID, controlProgram string) (string, error) {
175 payload, err := json.Marshal(getContractUTXOIDReq{TransactionID: transactionID})
180 res := new(getContractUTXOIDResp)
181 if err := s.request(getTransactionURL, payload, res); err != nil {
185 for _, v := range res.TransactionOutputs {
186 if v.ControlProgram == controlProgram {
187 return v.TransactionOutputID, nil
191 return "", errFailedGetContractUTXOID
194 var buildUnlockContractTxReq = `{
197 "type":"spend_account_unspent_output",
206 "use_unconfirmed":true,
212 "control_program":"%s",
213 "type":"control_program"
219 "use_unconfirmed":true,
220 "type":"spend_account"
225 "asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
226 "use_unconfirmed":true,
227 "type":"spend_account"
232 "control_program":"%s",
233 "type":"control_program"
237 "base_transaction":null
240 // buildUnlockContractTransaction build unlocked contract transaction.
241 func buildUnlockContractTransaction(s *Server, accountInfo AccountInfo, contractUTXOID string) (interface{}, error) {
242 program, contractValue, err := ListUnspentOutputs(s, contractUTXOID)
247 contractArgs, err := decodeProgram(s, program)
252 payload := []byte(fmt.Sprintf(buildUnlockContractTxReq,
257 accountInfo.AccountID,
260 accountInfo.AccountID,
262 contractValue.Amount,
264 accountInfo.Receiver,
266 res := new(interface{})
267 if err := s.request(buildTransactionURL, payload, res); err != nil {
273 type listUnspentOutputsResp struct {
274 AssetID string `json:"asset_id"`
275 AssetAmount uint64 `json:"amount"`
276 Program string `json:"program"`
279 type listUnspentOutputsReq struct {
280 UTXOID string `json:"id"`
281 Unconfirmed bool `json:"unconfirmed"`
282 SmartContract bool `json:"smart_contract"`
285 func ListUnspentOutputs(s *Server, contractUTXOID string) (string, *AssetAmount, error) {
286 payload, err := json.Marshal(listUnspentOutputsReq{
287 UTXOID: contractUTXOID,
295 var res []listUnspentOutputsResp
296 if err := s.request(listUnspentOutputsURL, payload, &res); err != nil {
301 return "", nil, errListUnspentOutputs
304 contractLockedValue := new(AssetAmount)
305 contractLockedValue.Asset = res[0].AssetID
306 contractLockedValue.Amount = res[0].AssetAmount
307 return res[0].Program, contractLockedValue, nil
310 type decodeProgramResp struct {
311 Instructions string `json:"instructions"`
314 type decodeProgramReq struct {
315 Program string `json:"program"`
318 func decodeProgram(s *Server, program string) (*ContractArgs, error) {
319 payload, err := json.Marshal(decodeProgramReq{Program: program})
324 res := new(decodeProgramResp)
325 if err := s.request(decodeProgramURL, payload, res); err != nil {
329 instructions := strings.Fields(res.Instructions)
330 contractArgs := new(ContractArgs)
331 contractArgs.CancelKey = instructions[1]
332 contractArgs.Seller = instructions[3]
333 contractArgs.AssetAmount.Asset = instructions[7]
334 if len(contractArgs.CancelKey) != 64 || len(contractArgs.AssetAmount.Asset) != 64 {
335 return nil, errTradeOffParametersInvalid
338 amount, err := parseUint64(instructions[5])
343 contractArgs.AssetAmount.Amount = amount
344 return contractArgs, nil
347 func parseUint64(s string) (uint64, error) {
348 data, err := hex.DecodeString(s)
354 copy(padded[:], data)
355 num := binary.LittleEndian.Uint64(padded[:])
360 type listPublicKeysReq struct {
361 AccountID string `json:"account_id"`
364 type PubkeyInfo struct {
365 PublicKey string `json:"pubkey"`
366 DerivationPath []string `json:"derivation_path"`
369 type listPublicKeysResp struct {
370 RootXPub string `json:"root_xpub"`
371 PubkeyInfos []PubkeyInfo `json:"pubkey_infos"`
374 type XPubKeyInfo struct {
375 XPubKey string `json:"xpub"`
376 DerivationPath []string `json:"derivation_path"`
379 func getXPubKeyInfo(s *Server, accountID, publicKey string) (*XPubKeyInfo, error) {
380 payload, err := json.Marshal(listPublicKeysReq{AccountID: accountID})
385 res := new(listPublicKeysResp)
386 if err := s.request(listPubkeysURL, payload, res); err != nil {
390 xpubKeyInfo := new(XPubKeyInfo)
391 xpubKeyInfo.XPubKey = res.RootXPub
392 for _, PubkeyInfo := range res.PubkeyInfos {
393 if PubkeyInfo.PublicKey == publicKey {
394 xpubKeyInfo.DerivationPath = PubkeyInfo.DerivationPath
395 return xpubKeyInfo, nil
398 return nil, errFailedGetPublicKey
401 var buildCancelContractTxReq = `{
404 "type": "spend_account_unspent_output",
407 "type": "raw_tx_signature",
417 "use_unconfirmed":true,
423 "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
424 "use_unconfirmed":true,
425 "type": "spend_account"
430 "control_program": "%s",
431 "type": "control_program"
435 "base_transaction": null
438 func buildCancelContractTransaction(s *Server, accountInfo AccountInfo, contractUTXOID string, xpubKeyInfo *XPubKeyInfo, contractValue *AssetAmount) (interface{}, error) {
439 xpubKeyInfoStr, err := json.Marshal(xpubKeyInfo)
443 payload := []byte(fmt.Sprintf(buildCancelContractTxReq,
446 accountInfo.AccountID,
448 contractValue.Amount,
450 accountInfo.Receiver,
452 res := new(interface{})
453 if err := s.request(buildTransactionURL, payload, res); err != nil {
459 // DeployContract deploy contract.
460 func DeployContract(s *Server, accountInfo AccountInfo, contractArgs ContractArgs, contractValue AssetAmount) (string, error) {
461 // compile locked contract
462 contractControlProgram, err := compileLockContract(s, contractArgs)
467 // build locked contract
468 txLocked, err := buildLockTransaction(s, accountInfo, contractValue, contractControlProgram)
473 // sign locked contract transaction
474 signedTransaction, err := signTransaction(s, accountInfo.Password, txLocked)
479 // submit signed transaction
480 txID, err := submitTransaction(s, signedTransaction)
485 // get contract output ID
486 contractUTXOID, err := getContractUTXOID(s, txID, contractControlProgram)
490 return contractUTXOID, nil
493 // CallContract call contract.
494 func CallContract(s *Server, accountInfo AccountInfo, contractUTXOID string) (string, error) {
495 // build unlocked contract transaction
496 txUnlocked, err := buildUnlockContractTransaction(s, accountInfo, contractUTXOID)
501 // sign unlocked contract transaction
502 signedTransaction, err := signTransaction(s, accountInfo.Password, txUnlocked)
507 // submit signed unlocked contract transaction
508 txID, err := submitTransaction(s, signedTransaction)
516 // CancelTradeoffContract cancel tradeoff contract.
517 func CancelTradeoffContract(s *Server, accountInfo AccountInfo, contractUTXOID string) (string, error) {
518 // get contract control program by contract UTXOID
519 contractControlProgram, contractValue, err := ListUnspentOutputs(s, contractUTXOID)
524 // get public key by contract control program
525 contractArgs, err := decodeProgram(s, contractControlProgram)
530 // get public key path and root xpub by contract args
531 xpubInfo, err := getXPubKeyInfo(s, accountInfo.AccountID, contractArgs.CancelKey)
536 // build cancel contract transaction
537 builtTx, err := buildCancelContractTransaction(s, accountInfo, contractUTXOID, xpubInfo, contractValue)
542 // sign cancel contract transaction
543 signedTx, err := signTransaction(s, accountInfo.Password, builtTx)
548 // submit signed unlocked contract transaction
549 txID, err := submitTransaction(s, signedTx)