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")
18 type AccountInfo struct {
25 type AssetAmount struct {
30 type ContractArgs struct {
36 type compileLockContractResponse struct {
37 Program string `json:"program"`
40 var compileLockContractPayload = `{
41 "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}}",
58 // compileLockContract return contract control program
59 func compileLockContract(contractArgs ContractArgs) (string, error) {
60 payload := []byte(fmt.Sprintf(
61 compileLockContractPayload,
63 strconv.FormatUint(contractArgs.Amount, 10),
65 contractArgs.CancelKey,
67 res := new(compileLockContractResponse)
68 if err := request(compileURL, payload, res); err != nil {
71 return res.Program, nil
74 var buildLockTransactionPayload = `{
80 "use_unconfirmed":true,
81 "type":"spend_account"
86 "control_program":"%s",
87 "type":"control_program"
92 "asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
93 "use_unconfirmed":true,
94 "type":"spend_account"
98 "base_transaction":null
101 // buildLockTransaction build locked contract transaction.
102 func buildLockTransaction(accountInfo AccountInfo, contractValue AssetAmount, contractControlProgram string) (interface{}, error) {
103 payload := []byte(fmt.Sprintf(
104 buildLockTransactionPayload,
105 accountInfo.AccountID,
106 strconv.FormatUint(contractValue.Amount, 10),
108 strconv.FormatUint(contractValue.Amount, 10),
110 contractControlProgram,
111 accountInfo.AccountID,
112 strconv.FormatUint(accountInfo.TxFee, 10),
114 res := new(interface{})
115 if err := request(buildTransactionURL, payload, res); err != nil {
121 type signTransactionRequest struct {
122 Password string `json:"password"`
123 Transaction interface{} `json:"transaction"`
126 type Transaction struct {
127 RawTransaction string `json:"raw_transaction"`
130 type signTransactionResponse struct {
131 Tx Transaction `json:"transaction"`
134 // signTransaction sign built contract transaction.
135 func signTransaction(password string, transaction interface{}) (string, error) {
136 payload, err := json.Marshal(signTransactionRequest{Password: password, Transaction: transaction})
141 res := new(signTransactionResponse)
142 if err := request(signTransactionURL, payload, res); err != nil {
146 return res.Tx.RawTransaction, nil
149 type submitTransactionRequest struct {
150 RawTransaction string `json:"raw_transaction"`
153 type submitTransactionResponse struct {
154 TransactionID string `json:"tx_id"`
157 // submitTransaction submit raw singed contract transaction.
158 func submitTransaction(rawTransaction string) (string, error) {
159 payload, err := json.Marshal(submitTransactionRequest{RawTransaction: rawTransaction})
164 res := new(submitTransactionResponse)
165 if err := request(submitTransactionURL, payload, res); err != nil {
169 return res.TransactionID, nil
172 type getContractUTXOIDRequest struct {
173 TransactionID string `json:"tx_id"`
176 type TransactionOutput struct {
177 TransactionOutputID string `json:"id"`
178 ControlProgram string `json:"control_program"`
181 type getContractUTXOIDResponse struct {
182 TransactionOutputs []TransactionOutput `json:"outputs"`
185 // getContractUTXOID get contract UTXO ID by transaction ID and contract control program.
186 func getContractUTXOID(transactionID, controlProgram string) (string, error) {
187 payload, err := json.Marshal(getContractUTXOIDRequest{TransactionID: transactionID})
192 res := new(getContractUTXOIDResponse)
193 if err := request(getTransactionURL, payload, res); err != nil {
197 for _, v := range res.TransactionOutputs {
198 if v.ControlProgram == controlProgram {
199 return v.TransactionOutputID, nil
203 return "", errFailedGetContractUTXOID
206 var buildUnlockContractTransactionPayload = `{
209 "type":"spend_account_unspent_output",
218 "use_unconfirmed":true,
224 "control_program":"%s",
225 "type":"control_program"
231 "use_unconfirmed":true,
232 "type":"spend_account"
237 "asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
238 "use_unconfirmed":true,
239 "type":"spend_account"
244 "control_program":"%s",
245 "type":"control_program"
249 "base_transaction":null
252 // buildUnlockContractTransaction build unlocked contract transaction.
253 func buildUnlockContractTransaction(accountInfo AccountInfo, contractUTXOID string, contractArgs ContractArgs, contractValue AssetAmount) (interface{}, error) {
254 payload := []byte(fmt.Sprintf(
255 buildUnlockContractTransactionPayload,
257 strconv.FormatUint(contractArgs.Amount, 10),
260 accountInfo.AccountID,
261 strconv.FormatUint(contractArgs.Amount, 10),
263 accountInfo.AccountID,
264 strconv.FormatUint(accountInfo.TxFee, 10),
265 strconv.FormatUint(contractValue.Amount, 10),
267 accountInfo.Receiver,
269 res := new(interface{})
270 if err := request(buildTransactionURL, payload, res); err != nil {
276 type listUnspentOutputsResponse struct {
277 AssetID string `json:"asset_id"`
278 AssetAmount uint64 `json:"amount"`
279 Program string `json:"program"`
282 var listUnspentOutputsPayload = `{
285 "smart_contract": true
288 func ListUnspentOutputs(contractUTXOID string) (string, *AssetAmount, error) {
289 payload := []byte(fmt.Sprintf(
290 listUnspentOutputsPayload,
293 var res []listUnspentOutputsResponse
294 if err := request(listUnspentOutputsURL, payload, &res); err != nil {
298 contractLockedValue := new(AssetAmount)
300 contractLockedValue.Asset = res[0].AssetID
301 contractLockedValue.Amount = res[0].AssetAmount
302 return res[0].Program, contractLockedValue, nil
304 return "", nil, errListUnspentOutputs
307 type decodeProgramResponse struct {
308 Instructions string `json:"instructions"`
311 var decodeProgramPayload = `{
315 func DecodeProgram(program string) (*ContractArgs, error) {
316 payload := []byte(fmt.Sprintf(
317 decodeProgramPayload,
320 res := new(decodeProgramResponse)
321 if err := request(decodeProgramURL, payload, res); err != nil {
325 instructions := strings.Fields(res.Instructions)
326 contractArgs := new(ContractArgs)
327 contractArgs.CancelKey = instructions[1]
328 contractArgs.Seller = instructions[3]
329 contractArgs.AssetAmount.Asset = instructions[7]
331 amount, err := parseUint64(instructions[5])
336 contractArgs.AssetAmount.Amount = amount
337 return contractArgs, nil
340 func parseUint64(s string) (uint64, error) {
341 data, err := hex.DecodeString(s)
346 for i := 0; i < len(data)/2; i++ {
347 data[i], data[len(data)-1-i] = data[len(data)-1-i], data[i]
349 s = hex.EncodeToString(data)
350 num, err := strconv.ParseUint(s, 16, 64)
358 // DeployContract deploy contract.
359 func DeployContract(accountInfo AccountInfo, contractArgs ContractArgs, contractValue AssetAmount) (string, error) {
360 // compile locked contract
361 contractControlProgram, err := compileLockContract(contractArgs)
366 // build locked contract
367 txLocked, err := buildLockTransaction(accountInfo, contractValue, contractControlProgram)
372 // sign locked contract transaction
373 signedTransaction, err := signTransaction(accountInfo.Password, txLocked)
378 // submit signed transaction
379 txID, err := submitTransaction(signedTransaction)
384 // get contract output ID
385 contractUTXOID, err := getContractUTXOID(txID, contractControlProgram)
389 return contractUTXOID, nil
392 // CallContract call contract.
393 func CallContract(accountInfo AccountInfo, contractUTXOID string, contractArgs ContractArgs, contractValue AssetAmount) (string, error) {
394 // build unlocked contract transaction
395 txUnlocked, err := buildUnlockContractTransaction(accountInfo, contractUTXOID, contractArgs, contractValue)
400 // sign unlocked contract transaction
401 signedTransaction, err := signTransaction(accountInfo.Password, txUnlocked)
406 // submit signed unlocked contract transaction
407 txID, err := submitTransaction(signedTransaction)