├── .DS_Store ├── .gitignore ├── README.md ├── address ├── address_pkg.go ├── address_test.go ├── addresses.go ├── wallets.go └── wif.go ├── api_appendrawchange.go ├── api_appendrawdata.go ├── api_appendrawtransaction.go ├── api_approvestreamfilter.go ├── api_approvetransactionfilter.go ├── api_create.go ├── api_create_test.go ├── api_createkeypair.go ├── api_createrawexchange.go ├── api_createrawsendfrom.go ├── api_createrawtransaction.go ├── api_createstreamfilter.go ├── api_createtransactionfilter.go ├── api_decoderawexchange.go ├── api_decoderawtransaction.go ├── api_dumpprivkey.go ├── api_getaddressbalances.go ├── api_getaddresses.go ├── api_getbestblockhash.go ├── api_getblock.go ├── api_getblockchainparams.go ├── api_getinfo.go ├── api_getnewaddress.go ├── api_getrawtransaction.go ├── api_getruntimeparams.go ├── api_getstreamfilters.go ├── api_getstreamitem.go ├── api_gettransactionfilters.go ├── api_gettxout.go ├── api_grant.go ├── api_grantfrom.go ├── api_importaddress.go ├── api_importprivkey.go ├── api_issue.go ├── api_issuemore.go ├── api_listaddresses.go ├── api_listaddresstransactions.go ├── api_liststreamitems.go ├── api_liststreamkeyitems.go ├── api_liststreamqueryitems.go ├── api_liststreamqueryitems_test.go ├── api_liststreams.go ├── api_liststreams_test.go ├── api_liststreamtxitems.go ├── api_listunspent.go ├── api_preparelockunspent.go ├── api_preparelockunspentfrom.go ├── api_publish.go ├── api_purgepublisheditems.go ├── api_purgestreamitems.go ├── api_revoke.go ├── api_runstreamfilter.go ├── api_runtransactionfilter.go ├── api_sendassetfrom.go ├── api_sendassettoaddress.go ├── api_sendrawtransaction.go ├── api_signmessage.go ├── api_signrawtransaction.go ├── api_subscribe.go ├── api_subscribe_test.go ├── api_teststreamfilter.go ├── api_testtransactionfilter.go ├── api_upgrade.go ├── client.go ├── go.mod ├── go.sum ├── models.go ├── multichain.go ├── multichain_client_suite_test.go ├── multichain_test.go ├── opcodes └── pkg.go ├── params ├── params.dat ├── params_pkg.go └── params_test.go └── signature ├── buffers.go ├── buffers_test.go ├── ref.js └── transaction.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golangdaddy/multichain-client/70c5f680e69ab91cbefa2f1e12694b9b2624070a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang client library for MultiChain blockchain 2 | 3 | This library will allow you to complete a basic set of functions with a MultiChain node. 4 | 5 | You should be able to issue, and send assets between addresses. 6 | 7 | If you wish to contribute to flesh out the remaining API calls, please make pull requests. 8 | 9 | ## Testing 10 | 11 | To fully test this package it is neccesary to have a full hot node running at the given parameters. 12 | 13 | ``` 14 | 15 | chain := flag.String("chain", "", "is the name of the chain") 16 | host := flag.String("host", "localhost", "is a string for the hostname") 17 | port := flag.String("port", "80", "is a string for the host port") 18 | username := flag.String("username", "multichainrpc", "is a string for the username") 19 | password := flag.String("password", "12345678", "is a string for the password") 20 | 21 | flag.Parse() 22 | 23 | client := multichain.NewClient( 24 | *chain, 25 | *host, 26 | *port, 27 | *username, 28 | *password, 29 | ) 30 | 31 | obj, err := client.GetInfo() 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | fmt.Println(obj) 37 | 38 | ``` 39 | ## Deterministic Wallets 40 | 41 | Using the address package within this repo, you can create a deterministic keypair with WIF-encoded private-key, and Bitcoin or MultiChain encoded public address. 42 | 43 | ``` 44 | 45 | type KeyPair struct { 46 | Type string 47 | Index int 48 | Public string 49 | Private string 50 | } 51 | 52 | ``` 53 | 54 | Here is an example of making a multichain HD wallet. 55 | 56 | ``` 57 | package main 58 | 59 | import ( 60 | "fmt" 61 | "gitlab.com/golangdaddy/multichain-client/address" 62 | "gitlab.com/golangdaddy/multichain-client/params" 63 | ) 64 | 65 | const ( 66 | CONST_BCRYPT_DIFF = 10 67 | ) 68 | 69 | func main() { 70 | 71 | // you need to refer to your params.dat file to get the needed config parameters 72 | cfg, err := params.Open("./multichain-cold/params.dat") 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | // The address package handles the encoding of keys, so it needs to be configued. 78 | address.Configure(&address.Config{ 79 | PrivateKeyVersion: cfg.String("private-key-version"), 80 | AddressPubkeyhashVersion: cfg.String("address-pubkeyhash-version"), 81 | AddressChecksumValue: cfg.String("address-checksum-value"), 82 | }) 83 | 84 | seed := []byte("seed") 85 | keyChildIndex := 0 86 | 87 | // create a new wallet 88 | keyPair, err := address.MultiChainWallet(seed, CONST_BCRYPT_DIFF, keyChildIndex) 89 | if err != nil { 90 | panic(err) 91 | } 92 | 93 | fmt.Println(keyPair) 94 | } 95 | ``` 96 | 97 | If you have an existing private key, you can export it as WIF (Wallet Import Format) which can be used with importprivkey API command with MultiChain. 98 | 99 | ``` 100 | 101 | wif, err := address.MultiChainWIF(privKeyBytes) 102 | 103 | ``` 104 | 105 | ...or it's MultiChain address from the public key with the MultiChainAddress function. 106 | 107 | ``` 108 | 109 | addr, err := address.MultiChainAddress(pubKeyBytes) 110 | 111 | ``` 112 | -------------------------------------------------------------------------------- /address/address_pkg.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "golang.org/x/crypto/ripemd160" 7 | ) 8 | 9 | const ( 10 | CONST_UNCONFIGURED = "CALL THE CONFIGURE METHOD WITH YOUR BLOCKCHAIN PARAMS FIRST" 11 | ) 12 | 13 | var configued bool 14 | 15 | var private_key_version []byte 16 | var address_pubkeyhash_version []byte 17 | var address_checksum_value []byte 18 | 19 | type Config struct { 20 | PrivateKeyVersion string 21 | AddressPubkeyhashVersion string 22 | AddressChecksumValue string 23 | } 24 | 25 | func Configure(config *Config) { 26 | private_key_version, _ = hex.DecodeString(config.PrivateKeyVersion) 27 | address_pubkeyhash_version, _ = hex.DecodeString(config.AddressPubkeyhashVersion) 28 | address_checksum_value, _ = hex.DecodeString(config.AddressChecksumValue) 29 | configued = true 30 | } 31 | 32 | func ripemd(b []byte) []byte { 33 | h := ripemd160.New() 34 | h.Write(b) 35 | return h.Sum(nil) 36 | } 37 | 38 | func sha(b []byte) []byte { 39 | c := sha256.Sum256(b) 40 | return c[:] 41 | } 42 | 43 | func safeXORBytes(dst, a, b []byte) int { 44 | n := len(a) 45 | if len(b) < n { 46 | n = len(b) 47 | } 48 | for i := 0; i < n; i++ { 49 | dst[i] = a[i] ^ b[i] 50 | } 51 | return n 52 | } 53 | 54 | func DebugKeyPair() *KeyPair { 55 | 56 | Configure(&Config{ 57 | PrivateKeyVersion: "8025B89E", 58 | AddressPubkeyhashVersion: "00AFEA21", 59 | AddressChecksumValue: "7B7AEF76", 60 | }) 61 | 62 | b, _ := hex.DecodeString("0284E5235E299AF81EBE1653AC5F06B60E13A3A81F918018CBD10CE695095B3E24") 63 | 64 | pubAddress, err := MultiChainAddress(b) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | b, _ = hex.DecodeString("B69CA8FFAE36F11AD445625E35BF6AC57D6642DDBE470DD3E7934291B2000D78") 70 | 71 | return &KeyPair{ 72 | Type: "MultiChainDebug", 73 | Private: MultiChainWIF(b), 74 | Public: pubAddress, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /address/address_test.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | CONST_BCRYPT_DIFF = 2000 10 | ) 11 | 12 | func TestAddress(t *testing.T) { 13 | 14 | Configure(&Config{ 15 | PrivateKeyVersion: "8025B89E", 16 | AddressPubkeyhashVersion: "00AFEA21", 17 | AddressChecksumValue: "953ABC69", 18 | }) 19 | 20 | t.Run("Test MultiChain wallet generation", func (t *testing.T) { 21 | 22 | seed := []byte("seed") 23 | 24 | keyPair, err := MultiChainWallet(seed, CONST_BCRYPT_DIFF, 0) 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | 29 | fmt.Println(keyPair) 30 | 31 | }) 32 | 33 | t.Run("Test public address generation", func (t *testing.T) { 34 | 35 | keyPair := DebugKeyPair() 36 | 37 | if keyPair.Public != "1Yu2BuptuZSiBWfr2Qy4aic6qEVnwPWrjddvYh" { 38 | t.Error("INVALID PUBLIC ADDRESSS GENERATED") 39 | } 40 | 41 | }) 42 | 43 | Configure(&Config{ 44 | PrivateKeyVersion: "8025B89E", 45 | AddressPubkeyhashVersion: "00AFEA21", 46 | AddressChecksumValue: "7B7AEF76", 47 | }) 48 | 49 | t.Run("Test private key wif generation", func (t *testing.T) { 50 | 51 | keyPair := DebugKeyPair() 52 | 53 | if keyPair.Private != "VEEWgYhDhqWnNnDCXXjirJYXGDFPjH1B8v6hmcnj1kLXrkpxArmz7xXw" { 54 | t.Error("INVALID PRIVATE ADDRESSS GENERATED") 55 | } 56 | 57 | }) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /address/addresses.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "github.com/mr-tron/base58/base58" 5 | ) 6 | 7 | func MultiChainAddress(input []byte) (string, error) { 8 | 9 | if !configued { panic(CONST_UNCONFIGURED) } 10 | 11 | x := int(20 / len(address_pubkeyhash_version)) 12 | 13 | b := ripemd(sha(input)) 14 | 15 | extended := []byte{} 16 | for index := 0; index < len(address_pubkeyhash_version); index++ { 17 | extended = append(extended, address_pubkeyhash_version[ index : (index + 1) ]...) 18 | extended = append(extended, b[ (x * index) : (index * x) + x ]...) 19 | } 20 | 21 | b = make([]byte, 4) 22 | safeXORBytes(b, address_checksum_value, sha(sha(extended))[:4]) 23 | 24 | b = append(extended, b...) 25 | 26 | return string(base58.FastBase58Encoding(b)), nil 27 | } 28 | -------------------------------------------------------------------------------- /address/wallets.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "github.com/tyler-smith/go-bip32" 7 | "crypto/sha512" 8 | ) 9 | 10 | type KeyPair struct { 11 | Type string 12 | Index int 13 | Public string 14 | PublicKey []byte 15 | Private string 16 | PrivateKey []byte 17 | } 18 | 19 | func KeyFromSeed(input []byte, difficulty, index int) (*bip32.Key, *bip32.Key, error) { 20 | 21 | if !configued { panic(CONST_UNCONFIGURED) } 22 | 23 | t := time.Now() 24 | 25 | for x := 0; x < difficulty; x++ { 26 | 27 | h := sha512.New512_256() 28 | h.Write(input) 29 | input = append(input, h.Sum(nil)...) 30 | 31 | } 32 | 33 | h := sha512.New512_256() 34 | h.Write(input) 35 | input = h.Sum(nil) 36 | 37 | fmt.Println("INPUT:", input) 38 | 39 | fmt.Printf("key lengthening difficulty %v elaspsed: %v\n", difficulty, time.Since(t)) 40 | 41 | masterKey, err := bip32.NewMasterKey(input) 42 | if err != nil { 43 | return nil, nil, err 44 | } 45 | 46 | childKey, err := masterKey.NewChildKey(uint32(index)) 47 | if err != nil { 48 | return nil, nil, err 49 | } 50 | 51 | return masterKey, childKey, nil 52 | } 53 | 54 | func MultiChainWallet(seed []byte, difficulty, index int) (*KeyPair, error) { 55 | 56 | if !configued { panic(CONST_UNCONFIGURED) } 57 | 58 | _, key, err := KeyFromSeed(seed, difficulty, index) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | publicKey, err := MultiChainAddress(key.PublicKey().Key) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | keyPair := &KeyPair{ 69 | Type: "MultiChain", 70 | Index: index, 71 | Public: publicKey, 72 | PublicKey: key.PublicKey().Key, 73 | Private: MultiChainWIF(key.Key), 74 | PrivateKey: key.Key, 75 | } 76 | 77 | return keyPair, nil 78 | } 79 | -------------------------------------------------------------------------------- /address/wif.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "bytes" 5 | "github.com/mr-tron/base58/base58" 6 | ) 7 | 8 | func MultiChainWIF(key []byte) string { 9 | 10 | stage1 := bytes.Join( 11 | [][]byte{ 12 | key, 13 | []byte{0x01}, 14 | }, 15 | nil, 16 | ) 17 | 18 | x := int(33 / len(private_key_version)) 19 | 20 | var index int 21 | extended := []byte{} 22 | for index = 0; index < len(private_key_version); index++ { 23 | extended = append(extended, private_key_version[ index : (index + 1) ]...) 24 | extended = append(extended, stage1[ (x * index) : (index * x) + x ]...) 25 | } 26 | 27 | extended = append(extended, stage1[ index * x : ]...) 28 | 29 | stage2 := sha( 30 | sha( 31 | extended, 32 | ), 33 | )[:4] 34 | 35 | checksum := make([]byte, 4) 36 | safeXORBytes(checksum, stage2, address_checksum_value) 37 | 38 | stage3 := bytes.Join( 39 | [][]byte{ 40 | extended, 41 | checksum, 42 | }, 43 | nil, 44 | ) 45 | 46 | return base58.FastBase58Encoding(stage3) 47 | } 48 | 49 | func BitcoinWIF(key []byte) string { 50 | 51 | stage1 := bytes.Join( 52 | [][]byte{ 53 | []byte{private_key_version[0]}, 54 | key, 55 | }, 56 | nil, 57 | ) 58 | 59 | stage2 := sha( 60 | sha( 61 | stage1, 62 | ), 63 | )[:4] 64 | 65 | stage3 := bytes.Join( 66 | [][]byte{ 67 | stage1, 68 | stage2, 69 | }, 70 | nil, 71 | ) 72 | 73 | return base58.FastBase58Encoding(stage3) 74 | } 75 | -------------------------------------------------------------------------------- /api_appendrawchange.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) AppendRawChange(tx, address string) (Response, error) { 4 | 5 | return client.Post( 6 | client.Command( 7 | "appendrawchange", 8 | []interface{}{ 9 | tx, 10 | address, 11 | }, 12 | ), 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /api_appendrawdata.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) AppendRawData(tx, stream, key, data string) (Response, error) { 4 | 5 | return client.Post( 6 | client.Command( 7 | "appendrawdata", 8 | []interface{}{ 9 | tx, 10 | map[string]string{ 11 | "for": stream, 12 | "key": key, 13 | "data": data, 14 | }, 15 | }, 16 | ), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /api_appendrawtransaction.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) AppendRawTransaction(tx string, inputs []*Unspent, outputs map[string]map[string]float64) (Response, error) { 4 | 5 | return client.Post( 6 | client.Command( 7 | "appendrawtransaction", 8 | []interface{}{ 9 | tx, 10 | inputs, 11 | outputs, 12 | }, 13 | ), 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /api_approvestreamfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) ApproveStreamFilter(address, stream, filter string) (Response, error) { 4 | 5 | in := "{\"for\":" + stream + ", \"approve\":true}" 6 | msg := client.Command( 7 | "approvefrom", 8 | []interface{}{ 9 | address, 10 | filter, 11 | in, 12 | }, 13 | ) 14 | 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /api_approvetransactionfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) ApproveTransactionFilter(address, filter string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "approvefrom", 7 | []interface{}{ 8 | address, 9 | filter, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_create.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | //Creates a new stream on the blockchain called name. For now, always pass the value "stream" in the type parameter – this is designed for future functionality. If open is true then anyone with global send permissions can publish to the stream, otherwise publishers must be explicitly granted per-stream write permissions. 4 | func (client *Client) Create(typeName, name string, args map[string]interface{}) (Response, error) { 5 | 6 | msg := client.Command( 7 | "create", 8 | []interface{}{ 9 | typeName, 10 | name, 11 | args, 12 | }, 13 | ) 14 | 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /api_create_test.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCreate(t *testing.T) { 8 | 9 | client = NewClient( 10 | "", 11 | "localhost", 12 | "multichainrpc", 13 | "12345678", 14 | 80, 15 | ) 16 | _, err := client.Create("stream", "testStream", nil) 17 | if err != nil { 18 | t.Fail() 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /api_createkeypair.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "github.com/golangdaddy/multichain-client/address" 5 | ) 6 | 7 | func (client *Client) CreateKeypair() ([]*address.KeyPair, error) { 8 | 9 | msg := client.Command( 10 | "createkeypairs", 11 | []interface{}{}, 12 | ) 13 | 14 | obj, err := client.Post(msg) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | array := obj["result"].([]interface{}) 20 | 21 | addresses := make([]*address.KeyPair, len(array)) 22 | 23 | for i, v := range array { 24 | 25 | result := v.(map[string]interface{}) 26 | 27 | addresses[i] = &address.KeyPair{ 28 | Public: result["address"].(string), 29 | Private: result["privkey"].(string), 30 | } 31 | 32 | } 33 | 34 | return addresses, nil 35 | } 36 | -------------------------------------------------------------------------------- /api_createrawexchange.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) CreateRawExchange(txid string, vout int, assets map[string]float64) (Response, error) { 4 | 5 | msg := client.Command( 6 | "createrawexchange", 7 | []interface{}{ 8 | txid, 9 | vout, 10 | assets, 11 | }, 12 | ) 13 | 14 | return client.Post(msg) 15 | } 16 | -------------------------------------------------------------------------------- /api_createrawsendfrom.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // This works like createrawtransaction, except it automatically selects the transaction inputs from those belonging to from-address, to cover the appropriate amounts. One or more change outputs going back to from-address will also be added to the end of the transaction. 4 | func (client *Client) CreateRawSendFrom(watchAddress, destinationAddress string, assets map[string]float64) (Response, error) { 5 | 6 | msg := client.Command( 7 | "createrawsendfrom", 8 | []interface{}{ 9 | watchAddress, 10 | map[string]interface{}{ 11 | destinationAddress: assets, 12 | }, 13 | }, 14 | ) 15 | 16 | return client.Post(msg) 17 | } 18 | -------------------------------------------------------------------------------- /api_createrawtransaction.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) CreateRawTransaction(inputs []*Unspent, outputs map[string]map[string]float64) (Response, error) { 4 | 5 | msg := client.Command( 6 | "createrawtransaction", 7 | []interface{}{ 8 | inputs, 9 | outputs, 10 | []string{}, 11 | "lock", 12 | }, 13 | ) 14 | 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /api_createstreamfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) CreateStreamFilter(name, restrictions, code string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "create", 7 | []interface{}{ 8 | "streamfilter", 9 | name, 10 | restrictions, 11 | code, 12 | }, 13 | ) 14 | 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /api_createtransactionfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) CreateTransactionFilter(name, restrictions, code string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "create", 7 | []interface{}{ 8 | "txfilter", 9 | name, 10 | restrictions, 11 | code, 12 | }, 13 | ) 14 | 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /api_decoderawexchange.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Decodes the raw exchange transaction in tx-hex, given by a previous call to createrawexchange or appendrawexchange. Returns details on the offer represented by the exchange and its present state. The offer field in the response lists the quantity of native currency and/or assets which are being offered for exchange. The ask field lists the native currency and/or assets which are being asked for. The candisable field specifies whether this wallet can disable the exchange transaction by double-spending against one of its inputs. The cancomplete field specifies whether this wallet has the assets required to complete the exchange. The complete field specifies whether the exchange is already complete (i.e. balanced) and ready for sending. If verbose is true then all of the individual stages in the exchange are listed. Other fields relating to fees are only relevant for blockchains which use a native currency. 4 | func (client *Client) DecodeRawExchange(rawExchange string) (Response, error) { 5 | 6 | msg := client.Command( 7 | "decoderawexchange", 8 | []interface{}{ 9 | rawExchange, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_decoderawtransaction.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Adds address (or a full public key, or an array of either) to the wallet, without an associated private key. This creates one or more watch-only addresses, whose activity and balance can be retrieved via various APIs (e.g. with the includeWatchOnly parameter), but whose funds cannot be spent by this node. If rescan is true, the entire blockchain is checked for transactions relating to all addresses in the wallet, including the added ones. Returns null if successful. 4 | func (client *Client) DecodeRawTransaction(rawTransaction string) (Response, error) { 5 | 6 | msg := client.Command( 7 | "decoderawtransaction", 8 | []interface{}{ 9 | rawTransaction, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_dumpprivkey.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) DumpPrivKey(address string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "dumpprivkey", 7 | []interface{}{ 8 | address, 9 | }, 10 | ) 11 | 12 | return client.Post(msg) 13 | } 14 | -------------------------------------------------------------------------------- /api_getaddressbalances.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetAddressBalances(address string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "getaddressbalances", 7 | []interface{}{ 8 | address, 9 | }, 10 | ) 11 | 12 | return client.Post(msg) 13 | } 14 | -------------------------------------------------------------------------------- /api_getaddresses.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetAddresses(verbose bool) (Response, error) { 4 | 5 | msg := client.Command( 6 | "getaddresses", 7 | []interface{}{ 8 | verbose, 9 | }, 10 | ) 11 | 12 | return client.Post(msg) 13 | } 14 | -------------------------------------------------------------------------------- /api_getbestblockhash.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetBestBlockHash(heightOrHash string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "getbestblockhash", 7 | []interface{}{}, 8 | ) 9 | 10 | return client.Post(msg) 11 | } 12 | -------------------------------------------------------------------------------- /api_getblock.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetBlock(heightOrHash interface{}, verbosity bool) (Response, error) { 4 | 5 | msg := client.Command( 6 | "getblock", 7 | []interface{}{ 8 | heightOrHash, 9 | verbosity, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_getblockchainparams.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetBlockchainParams() (Response, error) { 4 | 5 | msg := client.Command( 6 | "getblockchainparams", 7 | []interface{}{}, 8 | ) 9 | 10 | return client.Post(msg) 11 | } 12 | -------------------------------------------------------------------------------- /api_getinfo.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetInfo() (Response, error) { 4 | 5 | msg := client.Command( 6 | "getinfo", 7 | []interface{}{}, 8 | ) 9 | 10 | return client.Post(msg) 11 | } 12 | -------------------------------------------------------------------------------- /api_getnewaddress.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetNewAddress() (Response, error) { 4 | 5 | msg := client.Command( 6 | "getnewaddress", 7 | []interface{}{}, 8 | ) 9 | 10 | return client.Post(msg) 11 | } 12 | -------------------------------------------------------------------------------- /api_getrawtransaction.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetRawTransaction(id string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "getrawtransaction", 7 | []interface{}{ 8 | id, 9 | }, 10 | ) 11 | 12 | return client.Post(msg) 13 | } 14 | -------------------------------------------------------------------------------- /api_getruntimeparams.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetRuntimeParams() (Response, error) { 4 | 5 | msg := client.Command( 6 | "getruntimeparams", 7 | []interface{}{}, 8 | ) 9 | 10 | return client.Post(msg) 11 | } 12 | -------------------------------------------------------------------------------- /api_getstreamfilters.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetStreamFilters(filters string, verbose bool) (Response, error) { 4 | 5 | msg := client.Command( 6 | "liststeamfilters", 7 | []interface{}{ 8 | filters, 9 | verbose, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_getstreamitem.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Retrieves a specific item with txid from stream, passed as a stream name, ref or creation txid, to which the 4 | // node must be subscribed. 5 | // Set verbose to true for additional information about the item’s transaction. 6 | // If an item’s data as stored in the transaction is larger than the maxshowndata runtime parameter, it will be 7 | // returned as an object with parameters for gettxoutdata. 8 | // If the transaction contains more than one item for the stream, this will return an error – 9 | // use liststreamtxitems instead. 10 | func (client *Client) GetStreamItem(stream, txid string, verbose bool) (Response, error) { 11 | 12 | return client.Post( 13 | map[string]interface{}{ 14 | "jsonrpc": "1.0", 15 | "id": CONST_ID, 16 | "method": "getstreamitem", 17 | "params": []interface{}{ 18 | stream, 19 | txid, 20 | verbose, 21 | }, 22 | }, 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /api_gettransactionfilters.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetTransactionFilters(filters string, verbose bool) (Response, error) { 4 | 5 | msg := client.Command( 6 | "listtxfilters", 7 | []interface{}{ 8 | filters, 9 | verbose, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_gettxout.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) GetTxOut(txid string, vout int) (Response, error) { 4 | 5 | msg := client.Command( 6 | "gettxoutdata", 7 | []interface{}{ 8 | txid, 9 | vout, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_grant.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Grants permissions to addresses, a comma-separated list of addresses. For global permissions, set permissions to one of connect, send, receive, create, issue, mine, activate, admin, or a comma-separated list thereof. For per-asset or per-stream permissions, use the form entity.issue or entity.write,admin where entity is an asset or stream name, ref or creation txid. If the chain uses a native currency, you can send some to each recipient using the native-amount parameter. Returns the txid of the transaction granting the permissions. 8 | func (client *Client) Grant(addresses, permissions []string) (Response, error) { 9 | 10 | msg := client.Command( 11 | "grant", 12 | []interface{}{ 13 | strings.Join(addresses, ","), 14 | strings.Join(permissions, ","), 15 | }, 16 | ) 17 | 18 | return client.Post(msg) 19 | } 20 | -------------------------------------------------------------------------------- /api_grantfrom.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // This works like grant, but with control over the from-address used to grant the permissions. It is useful if the node has multiple addresses with administrator permissions. 8 | func (client *Client) GrantFrom(fromAddress string, addresses, permissions []string) (Response, error) { 9 | 10 | msg := client.Command( 11 | "grantfrom", 12 | []interface{}{ 13 | fromAddress, 14 | strings.Join(addresses, ","), 15 | strings.Join(permissions, ","), 16 | }, 17 | ) 18 | 19 | return client.Post(msg) 20 | } 21 | -------------------------------------------------------------------------------- /api_importaddress.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Adds address (or a full public key, or an array of either) to the wallet, without an associated private key. This creates one or more watch-only addresses, whose activity and balance can be retrieved via various APIs (e.g. with the includeWatchOnly parameter), but whose funds cannot be spent by this node. If rescan is true, the entire blockchain is checked for transactions relating to all addresses in the wallet, including the added ones. Returns null if successful. 4 | func (client *Client) ImportAddress(pubKey, label string, rescan bool) (Response, error) { 5 | 6 | msg := client.Command( 7 | "importaddress", 8 | []interface{}{ 9 | pubKey, 10 | label, 11 | rescan, 12 | }, 13 | ) 14 | 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /api_importprivkey.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | //Adds a privkey private key (or an array thereof) to the wallet, together with its associated public address. If rescan is true, the entire blockchain is checked for transactions relating to all addresses in the wallet, including the added ones. Returns null if successful. 4 | func (client *Client) ImportPrivKey(privKey, label string, rescan bool) (Response, error) { 5 | 6 | msg := map[string]interface{}{ 7 | "jsonrpc": "1.0", 8 | "id": CONST_ID, 9 | "method": "importprivkey", 10 | "params": []interface{}{ 11 | []string{privKey}, 12 | label, 13 | rescan, 14 | }, 15 | } 16 | 17 | return client.Post(msg) 18 | } 19 | -------------------------------------------------------------------------------- /api_issue.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) Issue(isOpen bool, accountAddress, assetName string, quantity float64, units float64) (Response, error) { 4 | 5 | msg := client.Command( 6 | "issue", 7 | []interface{}{ 8 | accountAddress, 9 | map[string]interface{}{ 10 | "name": assetName, 11 | "open": isOpen, 12 | }, 13 | quantity, 14 | units, 15 | }, 16 | ) 17 | 18 | return client.Post(msg) 19 | } 20 | -------------------------------------------------------------------------------- /api_issuemore.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) IssueMore(accountAddress, assetName string, value float64) (Response, error) { 4 | 5 | msg := client.Command( 6 | "issuemore", 7 | []interface{}{ 8 | accountAddress, 9 | assetName, 10 | value, 11 | }, 12 | ) 13 | 14 | return client.Post(msg) 15 | } 16 | -------------------------------------------------------------------------------- /api_listaddresses.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import "fmt" 4 | 5 | func (client *Client) ListAddresses(verbose bool, addresses ...string) (Response, error) { 6 | 7 | v := fmt.Sprintf("verbose=%v", verbose) 8 | 9 | var params []interface{} 10 | 11 | if len(addresses) > 0 { 12 | params = []interface{}{ 13 | addresses, 14 | v, 15 | } 16 | } else { 17 | if verbose { 18 | params = []interface{}{ 19 | []string{}, 20 | v, 21 | } 22 | } 23 | } 24 | 25 | msg := client.Command( 26 | "listaddresses", 27 | params, 28 | ) 29 | 30 | return client.Post(msg) 31 | } 32 | -------------------------------------------------------------------------------- /api_listaddresstransactions.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import "fmt" 4 | 5 | func (client *Client) ListAddressTransactions(address string, count, skip int, verbose bool) (Response, error) { 6 | 7 | msg := client.Command( 8 | "listaddresstransactions", 9 | []interface{}{ 10 | address, 11 | fmt.Sprintf("count=%v", count), 12 | fmt.Sprintf("skip=%v", skip), 13 | fmt.Sprintf("verbose=%v", verbose), 14 | }, 15 | ) 16 | 17 | return client.Post(msg) 18 | } 19 | -------------------------------------------------------------------------------- /api_liststreamitems.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Lists items in stream, passed as a stream name, ref or creation txid. 4 | // Set verbose to true for additional information about each item’s transaction. Use count and start to retrieve 5 | // part of the list only, with negative start values (like the default) indicating the most recent items. 6 | // Set local-ordering to true to order items by when first seen by this node, rather than their order in the chain. 7 | // If an item’s data is larger than the maxshowndata runtime parameter, it will be returned as an object whose 8 | // fields can be used with gettxoutdata. 9 | func (client *Client) ListStreamItems(stream string, start, count int, verbose, localOrdering bool) (Response, error) { 10 | return client.Post( 11 | map[string]interface{}{ 12 | "jsonrpc": "1.0", 13 | "id": CONST_ID, 14 | "method": "liststreamitems", 15 | "params": []interface{}{ 16 | stream, 17 | verbose, 18 | count, 19 | start, 20 | localOrdering, 21 | }, 22 | }, 23 | ) 24 | } -------------------------------------------------------------------------------- /api_liststreamkeyitems.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // This works like liststreamitems, but listing items with the given key only. 4 | func (client *Client) ListStreamKeyItems(stream, key string, count int, verbose bool) (Response, error) { 5 | 6 | return client.Post( 7 | map[string]interface{}{ 8 | "jsonrpc": "1.0", 9 | "id": CONST_ID, 10 | "method": "liststreamkeyitems", 11 | "params": []interface{}{ 12 | stream, 13 | key, 14 | verbose, 15 | count, 16 | }, 17 | }, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /api_liststreamqueryitems.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // This works like liststreamitems, but listing items in stream which match all of the specified keys and/or 8 | // publishers in query. 9 | // The query is an object with a key or keys field, and/or a publisher or publishers field. If present, key and 10 | // publisher should specify a single key or publisher respectively, whereas keys and publishers should specify 11 | // arrays thereof. 12 | // Note that, unlike other stream retrieval APIs, liststreamqueryitems cannot rely completely on prior indexing, 13 | // so the maxqueryscanitems runtime parameter limits how many items will be scanned after using the best index. 14 | // If more than this is needed, an error will be returned. 15 | func (client *Client) ListStreamQueryItems(stream string, keys, publishers []string, verbose bool) (Response, error) { 16 | 17 | q, err := getQuery(keys, publishers) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return client.Post( 23 | map[string]interface{}{ 24 | "jsonrpc": "1.0", 25 | "id": CONST_ID, 26 | "method": "liststreamqueryitems", 27 | "params": []interface{}{ 28 | stream, 29 | q, 30 | verbose, 31 | }, 32 | }, 33 | ) 34 | } 35 | 36 | func getQuery(keys, publishers []string) (map[string]interface{}, error) { 37 | lenKeys := len(keys) 38 | lenPublishers := len(publishers) 39 | if lenKeys == 0 && lenPublishers == 0 { 40 | return nil, errors.New("either keys or publishers slices must contain at least one entry") 41 | } 42 | q := make(map[string]interface{}) 43 | if lenKeys == 1 { 44 | q["key"] = keys[0] 45 | } else if lenKeys > 1 { 46 | q["keys"] = keys 47 | } 48 | if lenPublishers == 1 { 49 | q["publisher"] = publishers[0] 50 | } else if lenPublishers > 1 { 51 | q["publishers"] = publishers 52 | } 53 | return q, nil 54 | } 55 | -------------------------------------------------------------------------------- /api_liststreamqueryitems_test.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/ginkgo/extensions/table" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | const ( 10 | key1 = "key1" 11 | key2 = "key2" 12 | publisher1 = "publisher1" 13 | publisher2 = "publisher2" 14 | ) 15 | 16 | type queryKeys struct { 17 | KeysKey, KeysKeyNotExisting, PublishersKey, PublishersKeyNotExisting string 18 | } 19 | 20 | var _ = Describe("ApiListstreamqueryitems", func() { 21 | 22 | DescribeTable("getQuery func provides correct query object for", 23 | func(keys, publishers []string, qKeys queryKeys, expKey, expPublishers interface{}) { 24 | q, err := getQuery(keys, publishers) 25 | Expect(err).ToNot(HaveOccurred()) 26 | Expect(q[qKeys.KeysKey]).To(Equal(expKey)) 27 | Expect(q[qKeys.PublishersKey]).To(Equal(expPublishers)) 28 | Expect(q[qKeys.KeysKeyNotExisting]).To(BeNil()) 29 | Expect(q[qKeys.PublishersKeyNotExisting]).To(BeNil()) 30 | }, 31 | Entry("keys and publishers slices each having just one object", 32 | []string{key1}, 33 | []string{publisher1}, 34 | queryKeys{"key", "keys", "publisher", "publishers"}, 35 | key1, 36 | publisher1, 37 | ), 38 | Entry("keys slice having multiple, publishers slices just one object", 39 | []string{key1, key2}, 40 | []string{publisher1}, 41 | queryKeys{"keys", "key", "publisher", "publishers"}, 42 | []string{key1, key2}, 43 | publisher1, 44 | ), 45 | Entry("keys slice having just one, publishers slices multiple objects", 46 | []string{key1}, 47 | []string{publisher1, publisher2}, 48 | queryKeys{"key", "keys", "publishers", "publisher"}, 49 | key1, 50 | []string{publisher1, publisher2}, 51 | ), 52 | Entry("keys and publishers slices each having multiple objects", 53 | []string{key1, key2}, 54 | []string{publisher1, publisher2}, 55 | queryKeys{"keys", "key", "publishers", "publisher"}, 56 | []string{key1, key2}, 57 | []string{publisher1, publisher2}, 58 | ), 59 | ) 60 | 61 | DescribeTable("getQuery func provides correct query object without publishers", 62 | func(keys, publishers []string, expKey string, expValue interface{}) { 63 | q, err := getQuery(keys, publishers) 64 | Expect(err).ToNot(HaveOccurred()) 65 | Expect(q[expKey]).To(Equal(expValue)) 66 | Expect(q["publisher"]).To(BeNil()) 67 | Expect(q["publishers"]).To(BeNil()) 68 | }, 69 | Entry("just one keys", []string{key1}, []string{}, "key", key1), 70 | Entry("multiple keys", []string{key1, key2}, []string{}, "keys", []string{key1, key2}), 71 | ) 72 | 73 | DescribeTable("getQuery func provides correct query object without keys", 74 | func(publishers, keys []string, expKey string, expValue interface{}) { 75 | q, err := getQuery(keys, publishers) 76 | Expect(err).ToNot(HaveOccurred()) 77 | Expect(q[expKey]).To(Equal(expValue)) 78 | Expect(q["key"]).To(BeNil()) 79 | Expect(q["keys"]).To(BeNil()) 80 | }, 81 | Entry("just one publisher", []string{publisher1}, []string{}, "publisher", publisher1), 82 | Entry("multiple publishers", []string{publisher1, publisher2}, []string{}, "publishers", []string{publisher1, publisher2}), 83 | ) 84 | 85 | Specify("getQuery func provides an error if keys and publishers slices are empty", func() { 86 | keys := []string{} 87 | publishers := []string{} 88 | _, err := getQuery(keys, publishers) 89 | Expect(err).To(HaveOccurred()) 90 | }) 91 | 92 | }) 93 | -------------------------------------------------------------------------------- /api_liststreams.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Returns information about streams created on the blockchain. 8 | // Pass a slice of stream names, refs or creation txids to retrieve information about respective streams. 9 | // Extra fields are shown for streams to which this node has subscribed. 10 | func (client *Client) ListStreams(streams []string, verbose bool) (Response, error) { 11 | return client.listStreams(strings.Join(streams, ","), verbose) 12 | } 13 | 14 | // Returns information about all streams created on the blockchain. 15 | // Extra fields are shown for streams to which this node has subscribed. 16 | func (client *Client) ListAllStreams(verbose bool) (Response, error) { 17 | return client.listStreams("*", verbose) 18 | } 19 | 20 | func (client *Client) listStreams(streams string, verbose bool) (Response, error) { 21 | 22 | params := getListStreamsParams(streams, verbose) 23 | 24 | return client.Post( 25 | map[string]interface{}{ 26 | "jsonrpc": "1.0", 27 | "id": CONST_ID, 28 | "method": "liststreams", 29 | "params": params, 30 | }, 31 | ) 32 | } 33 | 34 | func getListStreamsParams(streams string, verbose bool) []interface{} { 35 | if len(streams) == 0 { 36 | streams = "*" 37 | } 38 | params := []interface{}{} 39 | var streamsParam interface{} 40 | if streams == "*" { 41 | streamsParam = streams 42 | } else { 43 | streamsParam = strings.Split(streams, ",") 44 | } 45 | params = append(params, streamsParam) 46 | params = append(params, verbose) 47 | return params 48 | } 49 | -------------------------------------------------------------------------------- /api_liststreams_test.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/ginkgo/extensions/table" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("ApiListstreams", func() { 10 | 11 | DescribeTable("getListStreamsParams function provides correct params for", 12 | 13 | func(streams string, verbose bool, expectedParams []interface{}) { 14 | actual := getListStreamsParams(streams, verbose) 15 | Expect(actual).To(Equal(expectedParams)) 16 | }, 17 | Entry("all streams (asterisk), verbosely", 18 | "*", true, []interface{}{"*", true}), 19 | Entry("all streams (empty string), verbosely", 20 | "", true, []interface{}{"*", true}), 21 | Entry("all streams (asterisk), non-verbosely", 22 | "*", false, []interface{}{"*", false}), 23 | Entry("all streams (empty string), non-verbosely", 24 | "", false, []interface{}{"*", false}), 25 | ) 26 | 27 | DescribeTable("getListStreamsParams function provides correct params for", 28 | 29 | func(streams string, verbose bool, expectedParams []interface{}) { 30 | actual := getListStreamsParams(streams, verbose) 31 | Expect(len(actual)).To(Equal(len(expectedParams))) 32 | Expect(actual[0]).To(Equal(expectedParams[0])) 33 | Expect(actual[1]).To(Equal(expectedParams[1])) 34 | }, 35 | Entry("single stream, verbosely", 36 | "my-stream", true, []interface{}{[]string{"my-stream"}, true}), 37 | Entry("multiple streams, non-verbosely", 38 | "my-stream-1,my-stream-2", false, []interface{}{[]string{"my-stream-1", "my-stream-2"}, false}), 39 | ) 40 | 41 | }) 42 | -------------------------------------------------------------------------------- /api_liststreamtxitems.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // This works like liststreamitems, but listing items in stream within the given txid only. 4 | // It should be used as a replacement for getstreamitem if multiple items are being published to a single stream 5 | // in a single transaction. 6 | func (client *Client) ListStreamTxItems(stream, txid string, verbose bool) (Response, error) { 7 | 8 | return client.Post( 9 | map[string]interface{}{ 10 | "jsonrpc": "1.0", 11 | "id": CONST_ID, 12 | "method": "liststreamtxitems", 13 | "params": []interface{}{ 14 | stream, 15 | txid, 16 | verbose, 17 | }, 18 | }, 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /api_listunspent.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Returns a list of unspent transaction outputs in the wallet, with between minconf and maxconf confirmations. For a MultiChain blockchain, each transaction output includes assets and permissions fields listing any assets or permission changes encoded within that output. If the third parameter is provided, only outputs which pay an address in this array will be included. 4 | func (client *Client) ListUnspent(address string) (Response, error) { 5 | 6 | msg := client.Command( 7 | "listunspent", 8 | []interface{}{ 9 | 0, 10 | 999999, 11 | []string{ 12 | address, 13 | }, 14 | }, 15 | ) 16 | 17 | return client.Post(msg) 18 | } 19 | -------------------------------------------------------------------------------- /api_preparelockunspent.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import "fmt" 4 | 5 | // Prepares an unspent transaction output (useful for building atomic exchange transactions) containing qty units of asset, where asset is an asset name, ref or issuance txid. Multiple items can be specified within the first parameter to include several assets within the output. The output will be locked against automatic selection for spending unless the optional lock parameter is set to false. Returns the txid and vout of the prepared output. 6 | func (client *Client) PrepareLockUnspent(asset string, quantity float64, lock bool) (Response, error) { 7 | 8 | msg := map[string]interface{}{ 9 | "jsonrpc": "1.0", 10 | "id": CONST_ID, 11 | "method": "preparelockunspent", 12 | "params": []interface{}{ 13 | map[string]float64{ 14 | asset: quantity, 15 | }, 16 | fmt.Sprintf("lock=%v", lock), 17 | }, 18 | } 19 | 20 | return client.Post(msg) 21 | } 22 | -------------------------------------------------------------------------------- /api_preparelockunspentfrom.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import "fmt" 4 | 5 | // This works like preparelockunspent, but with control over the from-address whose funds are used to prepare the unspent transaction output. Any change from the transaction is send back to from-address. 6 | func (client *Client) PrepareLockUnspentFrom(address, asset string, quantity float64, lock bool) (Response, error) { 7 | 8 | msg := client.Command( 9 | "preparelockunspentfrom", 10 | []interface{}{ 11 | address, 12 | map[string]float64{ 13 | asset: quantity, 14 | }, 15 | fmt.Sprintf("lock=%v", lock), 16 | }, 17 | ) 18 | 19 | return client.Post(msg) 20 | } 21 | -------------------------------------------------------------------------------- /api_publish.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Publishes an item in a stream (passed as a stream name, ref or creation txid). Data is held inside blockchain. 4 | // 5 | // Pass a single textual key or an array thereof in the key(s) parameter. 6 | // The data parameter can accept raw hexadecimal data like a1b2c3d4, a reference to the binary cache 7 | // {"cache":"Ev1HQV1aUCY"}, textual data {"text":"hello world"} or JSON data {"json":{"i":[1,2],"j":"yes"}}. 8 | // Returns the txid of the transaction sent. 9 | // To easily publish multiple items in a single transaction, see the publishmulti(from) command. 10 | func (client *Client) PublishOnchain(stream string, keys []string, data interface{}) (Response, error) { 11 | return client.publish(stream, keys, data, false) 12 | } 13 | 14 | // Publishes an item in a stream (passed as a stream name, ref or creation txid). Data is held outside blockchain 15 | // (offchain). 16 | // 17 | // Pass a single textual key or an array thereof in the key(s) parameter. 18 | // The data parameter can accept raw hexadecimal data like a1b2c3d4, a reference to the binary cache 19 | // {"cache":"Ev1HQV1aUCY"}, textual data {"text":"hello world"} or JSON data {"json":{"i":[1,2],"j":"yes"}}. 20 | // Returns the txid of the transaction sent. 21 | // To easily publish multiple items in a single transaction, see the publishmulti(from) command. 22 | func (client *Client) PublishOffchain(stream string, keys []string, data interface{}) (Response, error) { 23 | return client.publish(stream, keys, data, true) 24 | } 25 | 26 | func (client *Client) publish(stream string, keys []string, data interface{}, offchain bool) (Response, error) { 27 | 28 | params := []interface{}{ 29 | stream, 30 | keys, 31 | data, 32 | } 33 | if offchain { 34 | params = append(params, "offchain") 35 | } 36 | msg := client.Command( 37 | "publish", 38 | params, 39 | ) 40 | 41 | return client.Post(msg) 42 | } 43 | -------------------------------------------------------------------------------- /api_purgepublisheditems.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | type TransactionOutput struct { 4 | Txid string 5 | Vout int 6 | } 7 | type blocks struct { 8 | Blocks string 9 | } 10 | 11 | // PurgeAllPublishedItems purges ALL off-chain items published by this node from local storage. 12 | // Use with caution – this can lead to permanent data loss if no other node has retrieved the items. 13 | // 14 | // Returns statistics on how many chunks were purged and their total size. If this node is subscribed to the 15 | // stream in which a purged item appears, the item will no longer be available. However this does not purge 16 | // any item retrieved by this node from the network, unless that item is identical to a purged published item, 17 | // and neither item is salted. To avoid these collisions, create streams with {"salted":true}. To explicitly 18 | // purge retrieved items, use purgestreamitems. 19 | // 20 | // HINT: Available in MultiChain Enterprise only. 21 | // 22 | func (client *Client) PurgeAllPublishedItems() (Response, error) { 23 | return client.purgePublishedItems([]interface{}{ 24 | "all", 25 | }) 26 | } 27 | 28 | // PurgePublishedItemsByTxid purges off-chain items created within transactions of given txids, 29 | // published by this node from local storage. 30 | // Use with caution – this can lead to permanent data loss if no other node has retrieved the items. 31 | // 32 | // Returns statistics on how many chunks were purged and their total size. If this node is subscribed to the 33 | // stream in which a purged item appears, the item will no longer be available. However this does not purge 34 | // any item retrieved by this node from the network, unless that item is identical to a purged published item, 35 | // and neither item is salted. To avoid these collisions, create streams with {"salted":true}. To explicitly 36 | // purge retrieved items, use purgestreamitems. 37 | // 38 | // HINT: Available in MultiChain Enterprise only. 39 | // 40 | func (client *Client) PurgePublishedItemsByTxids(txids []string) (Response, error) { 41 | return client.purgePublishedItems([]interface{}{ 42 | txids, 43 | }) 44 | } 45 | 46 | // PurgePublishedItemsByTxOutputs purges off-chain items created within transactions of given txOuts, published 47 | // by this node from local storage. 48 | // Use with caution – this can lead to permanent data loss if no other node has retrieved the items. 49 | // 50 | // The txOuts parameter accepts a slice of {"txid":"id","vout":n} transaction outputs. 51 | // 52 | // Returns statistics on how many chunks were purged and their total size. If this node is subscribed to the 53 | // stream in which a purged item appears, the item will no longer be available. However this does not purge 54 | // any item retrieved by this node from the network, unless that item is identical to a purged published item, 55 | // and neither item is salted. To avoid these collisions, create streams with {"salted":true}. To explicitly 56 | // purge retrieved items, use purgestreamitems. 57 | // 58 | // HINT: Available in MultiChain Enterprise only. 59 | // 60 | func (client *Client) PurgePublishedItemsByTxOutputs(txOuts []TransactionOutput) (Response, error) { 61 | return client.purgePublishedItems([]interface{}{ 62 | txOuts, 63 | }) 64 | } 65 | 66 | // PurgePublishedItemsByBlockset purges off-chain items created within blocks defined by given blockSet and published 67 | // by this node from local storage. 68 | // Use with caution – this can lead to permanent data loss if no other node has retrieved the items. 69 | // 70 | // The blockSet parameter accepts a {"blocks":blocks-set} set of blocks (where blocks-set 71 | // takes any format accepted by listblocks). 72 | // 73 | // Returns statistics on how many chunks were purged and their total size. If this node is subscribed to the 74 | // stream in which a purged item appears, the item will no longer be available. However this does not purge 75 | // any item retrieved by this node from the network, unless that item is identical to a purged published item, 76 | // and neither item is salted. To avoid these collisions, create streams with {"salted":true}. To explicitly 77 | // purge retrieved items, use purgestreamitems. 78 | // 79 | // HINT: Available in MultiChain Enterprise only. 80 | // 81 | func (client *Client) PurgePublishedItemsByBlockset(blockSet string) (Response, error) { 82 | return client.purgePublishedItems([]interface{}{ 83 | blocks{Blocks: blockSet}, 84 | }) 85 | } 86 | 87 | func (client *Client) purgePublishedItems(params []interface{}) (Response, error) { 88 | 89 | return client.Post( 90 | map[string]interface{}{ 91 | "jsonrpc": "1.0", 92 | "id": CONST_ID, 93 | "method": "purgepublisheditems", 94 | "params": params, 95 | }, 96 | ) 97 | } 98 | -------------------------------------------------------------------------------- /api_purgestreamitems.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // PurgeAllStreamItems purges ALL selected off-chain items retrieved from stream (specified by name, ref or creation txid) 4 | // from local storage and prevents their future automatic retrieval. 5 | // 6 | // Returns statistics on how many items were matched and how many chunks were purged and their total size. 7 | // This does not purge items published by this node – see purgepublisheditems. Note also that if another stream 8 | // contains an identical item, and neither item is salted, then the purged item may still be available in this 9 | // stream. To avoid these collisions, create streams with {"salted":true}. 10 | // 11 | // HINT: Available in MultiChain Enterprise only. 12 | // 13 | func (client *Client) PurgeAllStreamItems(stream string) (Response, error) { 14 | return client.purgeStreamItems([]interface{}{ 15 | stream, 16 | "all", 17 | }) 18 | } 19 | // PurgeStreamItemsByTxid purges selected off-chain items retrieved from stream (specified by name, ref or creation txid) 20 | // which got created within transactions defined by txids from local storage and prevents their future automatic retrieval. 21 | // 22 | // The txids parameter accepts a slice of transaction IDs. 23 | // 24 | // Returns statistics on how many items were matched and how many chunks were purged and their total size. 25 | // This does not purge items published by this node – see purgepublisheditems. Note also that if another stream 26 | // contains an identical item, and neither item is salted, then the purged item may still be available in this 27 | // stream. To avoid these collisions, create streams with {"salted":true}. 28 | // 29 | // HINT: Available in MultiChain Enterprise only. 30 | // 31 | func (client *Client) PurgeStreamItemsByTxids(stream string, txids []string) (Response, error) { 32 | return client.purgeStreamItems([]interface{}{ 33 | stream, 34 | txids, 35 | }) 36 | } 37 | // PurgeStreamItemsByTxOutputs purges selected off-chain items retrieved from stream (specified by name, ref or creation txid) 38 | // which got created within transactions of given txOuts from local storage and prevents their future automatic retrieval. 39 | // 40 | // The txOuts parameter accepts a slice of {"txid":"id","vout":n} transaction outputs. 41 | // 42 | // Returns statistics on how many items were matched and how many chunks were purged and their total size. 43 | // This does not purge items published by this node – see purgepublisheditems. Note also that if another stream 44 | // contains an identical item, and neither item is salted, then the purged item may still be available in this 45 | // stream. To avoid these collisions, create streams with {"salted":true}. 46 | // 47 | // HINT: Available in MultiChain Enterprise only. 48 | // 49 | func (client *Client) PurgeStreamItemsByTxOutputs(stream string, txOuts []TransactionOutput) (Response, error) { 50 | return client.purgeStreamItems([]interface{}{ 51 | stream, 52 | txOuts, 53 | }) 54 | } 55 | // PurgeAllStreamItems purges selected off-chain items retrieved from stream (specified by name, ref or creation txid) 56 | // which got created within blocks defined by given blockSet from local storage and prevents their future automatic retrieval. 57 | // 58 | // The blockSet parameter accepts a {"blocks":blocks-set} set of blocks (where blocks-set 59 | // takes any format accepted by listblocks). 60 | // 61 | // Returns statistics on how many items were matched and how many chunks were purged and their total size. 62 | // This does not purge items published by this node – see purgepublisheditems. Note also that if another stream 63 | // contains an identical item, and neither item is salted, then the purged item may still be available in this 64 | // stream. To avoid these collisions, create streams with {"salted":true}. 65 | // 66 | // HINT: Available in MultiChain Enterprise only. 67 | // 68 | func (client *Client) PurgeStreamItemsByBlockset(stream string, blockSet string) (Response, error) { 69 | return client.purgeStreamItems([]interface{}{ 70 | stream, 71 | blocks{Blocks: blockSet}, 72 | }) 73 | } 74 | // PurgeAllStreamItems purges selected off-chain items retrieved from stream (specified by name, ref or creation txid) 75 | // which match given keys and publishers from local storage and prevents their future automatic retrieval. 76 | // 77 | // The keys and publishers parameters define a query such as {"keys": keys, "publishers": publishers} accepted 78 | // by liststreamqueryitems. 79 | // 80 | // Returns statistics on how many items were matched and how many chunks were purged and their total size. 81 | // This does not purge items published by this node – see purgepublisheditems. Note also that if another stream 82 | // contains an identical item, and neither item is salted, then the purged item may still be available in this 83 | // stream. To avoid these collisions, create streams with {"salted":true}. 84 | // 85 | // HINT: Available in MultiChain Enterprise only. 86 | // 87 | func (client *Client) PurgeStreamItemsByQuery(stream string, keys, publishers []string) (Response, error) { 88 | q, err := getQuery(keys, publishers) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return client.purgeStreamItems([]interface{}{ 93 | stream, 94 | q, 95 | }) 96 | } 97 | 98 | func (client *Client) purgeStreamItems(params []interface{}) (Response, error) { 99 | 100 | return client.Post( 101 | map[string]interface{}{ 102 | "jsonrpc": "1.0", 103 | "id": CONST_ID, 104 | "method": "purgestreamitems", 105 | "params": params, 106 | }, 107 | ) 108 | } 109 | -------------------------------------------------------------------------------- /api_revoke.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Revokes permissions from addresses, a comma-separated list of addresses. The permissions parameter works the same as for grant. This is equivalent to calling grant with start-block=0 and end-block=0. Returns the txid of transaction revoking the permissions. For more information, see permissions management. 8 | func (client *Client) Revoke(addresses, permissions []string) (Response, error) { 9 | 10 | msg := client.Command( 11 | "revoke", 12 | []interface{}{ 13 | strings.Join(addresses, ","), 14 | strings.Join(permissions, ","), 15 | }, 16 | ) 17 | 18 | return client.Post(msg) 19 | } 20 | -------------------------------------------------------------------------------- /api_runstreamfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) RunStreamFilter(filter string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "runstreamfilter", 7 | []interface{}{ 8 | filter, 9 | }, 10 | ) 11 | 12 | return client.Post(msg) 13 | } 14 | -------------------------------------------------------------------------------- /api_runtransactionfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) RunTransactionFilter(filter string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "runtxfilter", 7 | []interface{}{ 8 | filter, 9 | }, 10 | ) 11 | 12 | return client.Post(msg) 13 | } 14 | -------------------------------------------------------------------------------- /api_sendassetfrom.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) SendAssetFrom(fromAddress, toAddress, assetType string, value float64) (Response, error) { 4 | 5 | msg := client.Command( 6 | "sendassetfrom", 7 | []interface{}{ 8 | fromAddress, 9 | toAddress, 10 | assetType, 11 | value, 12 | }, 13 | ) 14 | 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /api_sendassettoaddress.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) SendAssetToAddress(accountAddress, assetName string, value float64) (Response, error) { 4 | 5 | msg := client.Command( 6 | "sendassettoaddress", 7 | []interface{}{ 8 | accountAddress, 9 | assetName, 10 | value, 11 | }, 12 | ) 13 | 14 | return client.Post(msg) 15 | } 16 | -------------------------------------------------------------------------------- /api_sendrawtransaction.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) SendRawTransaction(rawTransaction string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "sendrawtransaction", 7 | []interface{}{ 8 | rawTransaction, 9 | }, 10 | ) 11 | 12 | return client.Post(msg) 13 | } 14 | -------------------------------------------------------------------------------- /api_signmessage.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | // Returns a base64-encoded digital signature which proves that message was approved by the owner of address (which must belong to this wallet) or any other private key given in privkey. The signature can be verified by any node using the verifymessage command. 4 | func (client *Client) SignMessage(addressOrPrivKey, message string) (Response, error) { 5 | 6 | msg := client.Command( 7 | "signmessage", 8 | []interface{}{ 9 | addressOrPrivKey, 10 | message, 11 | }, 12 | ) 13 | 14 | return client.Post(msg) 15 | } 16 | -------------------------------------------------------------------------------- /api_signrawtransaction.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) SignRawTransaction(rawTransaction string, txDataArray []*Unspent, privateKey string, args ...string) (Response, error) { 4 | 5 | if txDataArray == nil { 6 | txDataArray = []*Unspent{} 7 | } 8 | 9 | params := []interface{}{ 10 | rawTransaction, 11 | txDataArray, 12 | []string{privateKey}, 13 | } 14 | for _, arg := range args { 15 | params = append(params, arg) 16 | } 17 | 18 | msg := client.Command( 19 | "signrawtransaction", 20 | params, 21 | ) 22 | 23 | return client.Post(msg) 24 | } 25 | -------------------------------------------------------------------------------- /api_subscribe.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | type IndexType string 9 | 10 | const ( 11 | IndexItems IndexType = "items" 12 | IndexKeys IndexType = "keys" 13 | IndexPublishers IndexType = "publishers" 14 | IndexItemsLocal IndexType = "items-local" 15 | IndexKeysLocal IndexType = "keys-local" 16 | IndexPublishersLocal IndexType = "publishers-local" 17 | ) 18 | 19 | func (sp IndexType) IsValid() error { 20 | switch sp { 21 | case IndexItems, IndexKeys, IndexPublishers, IndexItemsLocal, IndexKeysLocal, IndexPublishersLocal: 22 | return nil 23 | } 24 | return errors.New("Invalid IndexType.") 25 | } 26 | 27 | // Instructs the node to start tracking one or more asset(s) or stream(s). 28 | // 29 | // These are specified using a name, ref or creation/issuance txid, or for multiple items, an array thereof. 30 | // If rescan is true, the node will reindex all items from when the assets and/or streams were created, as well as 31 | // those in other subscribed entities. 32 | // If retrieveAllOffchain is true, all offchain items are retrieved. 33 | // List of IndexTypes are built for stream. 34 | // Returns null if successful. 35 | // See also the autosubscribe runtime parameter. 36 | func (client *Client) Subscribe(streamUid string, rescan bool, retrieveAllOffchain bool, indexTypes []IndexType) (Response, error) { 37 | 38 | params := []interface{}{ 39 | streamUid, 40 | rescan, 41 | } 42 | params, err := appendInnerParams(indexTypes, retrieveAllOffchain, params) 43 | if err != nil { 44 | return nil, err 45 | } 46 | msg := client.Command( 47 | "subscribe", 48 | params, 49 | ) 50 | 51 | return client.Post(msg) 52 | } 53 | 54 | func appendInnerParams(indexTypes []IndexType, retrieveAllOffchain bool, params []interface{}) ([]interface{}, error) { 55 | var innerParams []string 56 | seen := make(map[IndexType]bool) 57 | for _, indexType := range indexTypes { 58 | if err := indexType.IsValid(); err != nil { 59 | return nil, err 60 | } 61 | if !seen[indexType] { 62 | seen[indexType] = true 63 | innerParams = append(innerParams, string(indexType)) 64 | } 65 | } 66 | if retrieveAllOffchain { 67 | innerParams = append(innerParams, "retrieve") 68 | } 69 | if len(innerParams) > 0 { 70 | params = append(params, strings.Join(innerParams, ",")) 71 | } 72 | return params, nil 73 | } 74 | -------------------------------------------------------------------------------- /api_subscribe_test.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "fmt" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/ginkgo/extensions/table" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("ApiSubscribes", func() { 11 | 12 | DescribeTable("appendInnerParams function derives inner params correctly for", 13 | func(indexTypes []IndexType, retrieveAllOffchain bool, expected []interface{}) { 14 | actual, err := appendInnerParams(indexTypes, retrieveAllOffchain, []interface{}{}) 15 | Expect(err).ToNot(HaveOccurred()) 16 | Expect(actual).To(Equal(expected)) 17 | }, 18 | Entry("no index types and retrieveAllOffchain = false", 19 | []IndexType{}, false, 20 | []interface{}{}), 21 | Entry("no index types and retrieveAllOffchain = true", 22 | []IndexType{}, true, 23 | []interface{}{"retrieve"}), 24 | Entry("one index type and retrieveAllOffchain = false", 25 | []IndexType{ IndexItems }, false, 26 | []interface{}{ string(IndexItems) }), 27 | Entry("one index type and retrieveAllOffchain = true", 28 | []IndexType{ IndexItems }, true, 29 | []interface{}{ fmt.Sprintf("%s,%s", IndexItems, "retrieve") }), 30 | Entry("some index types and retrieveAllOffchain = false", 31 | []IndexType{ IndexItems, IndexKeys, IndexKeysLocal }, false, 32 | []interface{}{ fmt.Sprintf("%s,%s,%s", IndexItems, IndexKeys, IndexKeysLocal) }), 33 | Entry("some index types and retrieveAllOffchain = true", 34 | []IndexType{ IndexItems, IndexKeys, IndexKeysLocal }, true, 35 | []interface{}{ fmt.Sprintf("%s,%s,%s,%s", IndexItems, IndexKeys, IndexKeysLocal, "retrieve") }), 36 | Entry("duplicated index types", 37 | []IndexType{ IndexItems, IndexKeys, IndexKeys }, false, 38 | []interface{}{ fmt.Sprintf("%s,%s", IndexItems, IndexKeys) }), 39 | ) 40 | 41 | DescribeTable("appendInnerParams function fails for invalid data due to", 42 | func(indexTypes []IndexType, retrieveAllOffchain bool) { 43 | _, err := appendInnerParams(indexTypes, retrieveAllOffchain, []interface{}{}) 44 | Expect(err).To(HaveOccurred()) 45 | }, 46 | Entry("one IndexItem of multiple invalid", 47 | []IndexType{ IndexItems, IndexKeys, "not-valid" }, false), 48 | Entry("multiple IndexItems invalid", 49 | []IndexType{ "not-valid", "not-valid-at-all" }, false), 50 | Entry("one single IndexItem invalid", 51 | []IndexType{ "not-valid" }, false), 52 | ) 53 | 54 | }) 55 | -------------------------------------------------------------------------------- /api_teststreamfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) TestStreamFilter(restrictions, code string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "teststreamfilter", 7 | []interface{}{ 8 | restrictions, 9 | code, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_testtransactionfilter.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | func (client *Client) TestTransactionFilter(restrictions, code string) (Response, error) { 4 | 5 | msg := client.Command( 6 | "testtxfilter", 7 | []interface{}{ 8 | restrictions, 9 | code, 10 | }, 11 | ) 12 | 13 | return client.Post(msg) 14 | } 15 | -------------------------------------------------------------------------------- /api_upgrade.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | //When using create upgrade, the following blockchain parameters can now be included: target-block-time maximum-block-size max-std-tx-size max-std-op-returns-count max-std-op-return-size max-std-op-drops-count max-std-element-size. Note that these upgrade parameters can only be applied to chains running multichain protocol 20002 or later. In addition, to prevent abrupt changes in blockchain capacity and performance, the following constraints apply: The target-block-time parameter cannot be changed more than once per 100 blocks. All seven capacity-related parameters cannot be changed to less than half, or more than double, their previous size. e.g. create upgrade upgrade1 false '{"target-block-time":10,"maximum-block-size":16777216}' 4 | func (client *Client) Upgrade(name string, params map[string]int) (Response, error) { 5 | 6 | msg := client.Command( 7 | "create", 8 | []interface{}{ 9 | "upgrade", 10 | name, 11 | false, 12 | params, 13 | }, 14 | ) 15 | return client.Post(msg) 16 | } 17 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-resty/resty/v2" 6 | ) 7 | 8 | type Client struct { 9 | Resty *resty.Client 10 | chainName string 11 | host string 12 | username string 13 | password string 14 | timeout []int 15 | debug bool 16 | } 17 | 18 | func (c *Client) String() string { 19 | return fmt.Sprintf("{chainName: %s, host: %s, username: ***, password: ***, timeout: %p, debug: %t}", 20 | c.chainName, c.host, c.timeout, c.debug) 21 | } 22 | 23 | func NewClient(chainName, host, username, password string, port int) *Client { 24 | return &Client{ 25 | Resty: resty.New(), 26 | chainName: chainName, 27 | host: fmt.Sprintf("http://%s:%d", host, port), 28 | username: username, 29 | password: password, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/golangdaddy/multichain-client 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect 7 | github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect 8 | github.com/btcsuite/btcd v0.20.1-beta 9 | github.com/go-resty/resty/v2 v2.3.0 10 | github.com/mr-tron/base58 v1.2.0 11 | github.com/onsi/ginkgo v1.12.3 12 | github.com/onsi/gomega v1.10.1 13 | github.com/tyler-smith/go-bip32 v0.0.0-20170922074101-2c9cfd177564 14 | gitlab.com/TheDarkula/jsonrouter v0.0.0-20200219172133-1676a5eaa269 15 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 16 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= 5 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 6 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.44.3 h1:0sMegbmn/8uTwpNkB0q9cLEpZ2W5a6kl+wtBQgPWBJQ= 8 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 9 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 10 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 11 | cloud.google.com/go/logging v1.0.0 h1:kaunpnoEh9L4hu6JUsBa8Y20LBfKnCuDhKUgdZp7oK8= 12 | cloud.google.com/go/logging v1.0.0/go.mod h1:V1cc3ogwobYzQq5f2R7DS/GvRIrI4FKj01Gs5glwAls= 13 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 14 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 15 | github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc= 16 | github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw= 17 | github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc= 18 | github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= 19 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 20 | github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= 21 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 22 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 23 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 24 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 25 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 26 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 27 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 28 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 29 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 30 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY= 34 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 35 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 36 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 37 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 38 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 39 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 40 | github.com/go-resty/resty v1.12.0 h1:L1P5qymrXL5H/doXe2pKUr1wxovAI5ilm2LdVLbwThc= 41 | github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= 42 | github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= 43 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 44 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 45 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 46 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 47 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 48 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 49 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 50 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 51 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 52 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 53 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 54 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 55 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 56 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 57 | github.com/golangdaddy/go.uuid v2.0.0+incompatible h1:EazqKxtVY7MVNKlQelMsj5eM0psTI88v01AW5YQ3IcM= 58 | github.com/golangdaddy/go.uuid v2.0.0+incompatible/go.mod h1:/g9wwbdR/2S5LpsboXtfo1hKKktOK2i0dBcPt5Qvvaw= 59 | github.com/golangdaddy/tarantula v0.0.0-20190104150304-3a6b306edcd3/go.mod h1:AEaOfKDbqcJi7SAyVJD2kBstcy8h2cElPmL6xtGYaSo= 60 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 61 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 62 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 63 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 64 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 65 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 66 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 67 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 68 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 69 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 70 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 71 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 72 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 73 | github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= 74 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 75 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 76 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 77 | github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= 78 | github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 79 | github.com/hjmodha/goDevice v0.0.0-20170127191456-6bec48aa958b/go.mod h1:aSv9oWQiY1ixoNRoNrANkmqTrGpqc78TvvAn8QCeLKA= 80 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 81 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 82 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 83 | github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= 84 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 85 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 86 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 87 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 88 | github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 89 | github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 90 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 91 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 92 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 93 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 94 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 95 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 96 | github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= 97 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 98 | github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= 99 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 100 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 101 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 102 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 103 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 104 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 105 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 106 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 107 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 108 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 109 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 110 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 111 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 112 | github.com/onsi/ginkgo v1.12.3 h1:+RYp9QczoWz9zfUyLP/5SLXQVhfr6gZOoKGfQqHuLZQ= 113 | github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 114 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 115 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 116 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 117 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 118 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 119 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 120 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 121 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 122 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 123 | github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w= 124 | github.com/ttacon/libphonenumber v1.0.1/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M= 125 | github.com/tyler-smith/go-bip32 v0.0.0-20170922074101-2c9cfd177564 h1:NXXyQVeRVLK8Xu27/hkkjwVOZLk5v4ZBEvvMtqMqznM= 126 | github.com/tyler-smith/go-bip32 v0.0.0-20170922074101-2c9cfd177564/go.mod h1:0/YuQQF676+d4CMNclTqGUam1EDwz0B8o03K9pQqA3c= 127 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 128 | github.com/valyala/fasthttp v1.4.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= 129 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 130 | gitlab.com/TheDarkula/jsonrouter v0.0.0-20200219172133-1676a5eaa269 h1:+HOSduwjOv23M2PGGGEjAuzGErK0WJv87plhvgC6r8Q= 131 | gitlab.com/TheDarkula/jsonrouter v0.0.0-20200219172133-1676a5eaa269/go.mod h1:jO4k7CPfTjm73MAo3EWBaEHuuoR1fYvRCDBdkahwkLg= 132 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 133 | go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= 134 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 135 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 136 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 137 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 138 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 139 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 140 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 141 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 142 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 143 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 144 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 145 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 146 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 147 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 148 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 149 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 150 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 151 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 152 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 153 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 154 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 155 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 156 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 157 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 158 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 159 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 160 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 161 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 162 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 163 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 164 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 165 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 166 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= 167 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 168 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= 169 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 170 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= 171 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 172 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 173 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 174 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 175 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 176 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 177 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 178 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 179 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 180 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 181 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 182 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 183 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 184 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 185 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 186 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 187 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 189 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 190 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 191 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 192 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 193 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 194 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 195 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 196 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 197 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= 198 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 199 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38= 200 | golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 201 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 202 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 203 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 204 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 205 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 206 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 207 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 208 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 209 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 210 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 211 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 212 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 213 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 214 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 215 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 216 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 217 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 218 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 219 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 220 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 221 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 222 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 223 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 224 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 225 | google.golang.org/api v0.8.0 h1:VGGbLNyPF7dvYHhcUGYBBGCRDDK0RRJAI6KCvo0CL+E= 226 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 227 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 228 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 229 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 230 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 231 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 232 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 233 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 234 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 235 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 236 | google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 237 | google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 238 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 239 | google.golang.org/genproto v0.0.0-20190817000702-55e96fffbd48 h1:P/BlPoYr1gpKHOHL0/Opzbiu5X5yb55Ef4P/YGrRwno= 240 | google.golang.org/genproto v0.0.0-20190817000702-55e96fffbd48/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 241 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 242 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 243 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 244 | google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= 245 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 246 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 247 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 248 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 249 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 250 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 251 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 252 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 253 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 254 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 255 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 256 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 257 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 258 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 259 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 260 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 261 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 262 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 263 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 264 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 265 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 266 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 267 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 268 | honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 269 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 270 | -------------------------------------------------------------------------------- /models.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | type Unspent struct { 4 | Txid string `json:"txid"` 5 | Vout int `json:"vout"` 6 | ScriptPubKey string `json:"scriptPubKey"` 7 | } 8 | -------------------------------------------------------------------------------- /multichain.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import ( 4 | "fmt" 5 | "errors" 6 | ) 7 | 8 | const ( 9 | CONST_ID = "multichain-client" 10 | ) 11 | 12 | type Response map[string]interface{} 13 | 14 | func (r Response) Result() interface{} { 15 | return r["result"] 16 | } 17 | 18 | func (client *Client) IsDebugMode() bool { 19 | return client.debug 20 | } 21 | 22 | func (client *Client) DebugMode() *Client { 23 | client.debug = true 24 | return client 25 | } 26 | 27 | func (client *Client) msg(params []interface{}) map[string]interface{} { 28 | return map[string]interface{}{ 29 | "jsonrpc": "1.0", 30 | "id": CONST_ID, 31 | "params": params, 32 | } 33 | } 34 | 35 | func (client *Client) Command(method string, params []interface{}) map[string]interface{} { 36 | 37 | msg := client.msg(params) 38 | msg["method"] = fmt.Sprintf("%s", method) 39 | 40 | return msg 41 | } 42 | 43 | func (client *Client) Post(msg interface{}) (Response, error) { 44 | 45 | fmt.Println("POSTING: "+client.host, msg) 46 | 47 | obj := make(Response) 48 | if _, err := client.Resty.R().SetBody(msg).SetResult(&obj).SetBasicAuth(client.username, client.password).Post(client.host); err != nil { 49 | return nil, err 50 | } 51 | 52 | if obj == nil || obj["error"] != nil { 53 | e := obj["error"].(map[string]interface{}) 54 | var s string 55 | m, ok := msg.(map[string]interface{}) 56 | if ok { 57 | s = fmt.Sprintf("multichaind - '%s': %s", m["method"], e["message"].(string)) 58 | } else { 59 | s = fmt.Sprintf("multichaind - %s", e["message"].(string)) 60 | } 61 | return nil, errors.New(s) 62 | } 63 | 64 | return obj, nil 65 | } 66 | -------------------------------------------------------------------------------- /multichain_client_suite_test.go: -------------------------------------------------------------------------------- 1 | package multichain_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestMultichainClien(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "MultichainClient Suite") 13 | } 14 | -------------------------------------------------------------------------------- /multichain_test.go: -------------------------------------------------------------------------------- 1 | package multichain 2 | 3 | import "flag" 4 | 5 | var client *Client 6 | 7 | func initFunc() { 8 | 9 | chain := flag.String("chain", "", "is the name of the chain") 10 | host := flag.String("host", "localhost", "is a string for the hostname") 11 | port := flag.Int("port", 80, "is an int for the host port") 12 | username := flag.String("username", "multichainrpc", "is a string for the username") 13 | password := flag.String("password", "12345678", "is a string for the password") 14 | 15 | flag.Parse() 16 | 17 | client = NewClient( 18 | *chain, 19 | *host, 20 | *username, 21 | *password, 22 | *port, 23 | ) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /opcodes/pkg.go: -------------------------------------------------------------------------------- 1 | package opcodes 2 | 3 | const ( 4 | OP_FALSE = 0 5 | OP_0 = 0 6 | OP_PUSHDATA1 = 76 7 | OP_PUSHDATA2 = 77 8 | OP_PUSHDATA4 = 78 9 | OP_1NEGATE = 79 10 | OP_RESERVED = 80 11 | OP_1 = 81 12 | OP_TRUE = 81 13 | OP_2 = 82 14 | OP_3 = 83 15 | OP_4 = 84 16 | OP_5 = 85 17 | OP_6 = 86 18 | OP_7 = 87 19 | OP_8 = 88 20 | OP_9 = 89 21 | OP_10 = 90 22 | OP_11 = 91 23 | OP_12 = 92 24 | OP_13 = 93 25 | OP_14 = 94 26 | OP_15 = 95 27 | OP_16 = 96 28 | 29 | OP_NOP = 97 30 | OP_VER = 98 31 | OP_IF = 99 32 | OP_NOTIF = 100 33 | OP_VERIF = 101 34 | OP_VERNOTIF = 102 35 | OP_ELSE = 103 36 | OP_ENDIF = 104 37 | OP_VERIFY = 105 38 | OP_RETURN = 106 39 | 40 | OP_TOALTSTACK = 107 41 | OP_FROMALTSTACK = 108 42 | OP_2DROP = 109 43 | OP_2DUP = 110 44 | OP_3DUP = 111 45 | OP_2OVER = 112 46 | OP_2ROT = 113 47 | OP_2SWAP = 114 48 | OP_IFDUP = 115 49 | OP_DEPTH = 116 50 | OP_DROP = 117 51 | OP_DUP = 118 52 | OP_NIP = 119 53 | OP_OVER = 120 54 | OP_PICK = 121 55 | OP_ROLL = 122 56 | OP_ROT = 123 57 | OP_SWAP = 124 58 | OP_TUCK = 125 59 | 60 | OP_CAT = 126 61 | OP_SUBSTR = 127 62 | OP_LEFT = 128 63 | OP_RIGHT = 129 64 | OP_SIZE = 130 65 | 66 | OP_INVERT = 131 67 | OP_AND = 132 68 | OP_OR = 133 69 | OP_XOR = 134 70 | OP_EQUAL = 135 71 | OP_EQUALVERIFY = 136 72 | OP_RESERVED1 = 137 73 | OP_RESERVED2 = 138 74 | 75 | OP_1ADD = 139 76 | OP_1SUB = 140 77 | OP_2MUL = 141 78 | OP_2DIV = 142 79 | OP_NEGATE = 143 80 | OP_ABS = 144 81 | OP_NOT = 145 82 | OP_0NOTEQUAL = 146 83 | OP_ADD = 147 84 | OP_SUB = 148 85 | OP_MUL = 149 86 | OP_DIV = 150 87 | OP_MOD = 151 88 | OP_LSHIFT = 152 89 | OP_RSHIFT = 153 90 | 91 | OP_BOOLAND = 154 92 | OP_BOOLOR = 155 93 | OP_NUMEQUAL = 156 94 | OP_NUMEQUALVERIFY = 157 95 | OP_NUMNOTEQUAL = 158 96 | OP_LESSTHAN = 159 97 | OP_GREATERTHAN = 160 98 | OP_LESSTHANOREQUAL = 161 99 | OP_GREATERTHANOREQUAL = 162 100 | OP_MIN = 163 101 | OP_MAX = 164 102 | 103 | OP_WITHIN = 165 104 | 105 | OP_RIPEMD160 = 166 106 | OP_SHA1 = 167 107 | OP_SHA256 = 168 108 | OP_HASH160 = 169 109 | OP_HASH256 = 170 110 | OP_CODESEPARATOR = 171 111 | OP_CHECKSIG = 172 112 | OP_CHECKSIGVERIFY = 173 113 | OP_CHECKMULTISIG = 174 114 | OP_CHECKMULTISIGVERIFY = 175 115 | 116 | OP_NOP1 = 176 117 | OP_NOP2 = 177 118 | OP_CHECKLOCKTIMEVERIFY = 177 119 | 120 | OP_NOP3 = 178 121 | OP_NOP4 = 179 122 | OP_NOP5 = 180 123 | OP_NOP6 = 181 124 | OP_NOP7 = 182 125 | OP_NOP8 = 183 126 | OP_NOP9 = 184 127 | OP_NOP10 = 185 128 | 129 | OP_PUBKEYHASH = 253 130 | OP_PUBKEY = 254 131 | OP_INVALIDOPCODE = 255 132 | ) 133 | -------------------------------------------------------------------------------- /params/params.dat: -------------------------------------------------------------------------------- 1 | # ==== MultiChain configuration file ==== 2 | 3 | # Created by multichain-util 4 | # Protocol version: 10009 5 | 6 | # This parameter set is VALID. 7 | # To join network please run "multichaind theblockchain". 8 | 9 | # The following parameters can only be edited if this file is a prototype of another configuration file. 10 | # Please run "multichain-util clone theblockchain " to generate new network. 11 | 12 | # Basic chain parameters 13 | 14 | test-float = 0.98 15 | 16 | only-accept-std-txs = true # Only accept and relay transactions which qualify as 'standard'. 17 | max-std-tx-size = 4194304 # Maximum size of standard transactions, in bytes. (1024 - 100000000) 18 | max-std-op-returns-count = 10 # Maximum number of OP_RETURN metadata outputs in standard transactions. (0 - 1024) 19 | max-std-op-return-size = 2097152 # Maximum size of OP_RETURN metadata in standard transactions, in bytes. (0 - 67108864) 20 | max-std-op-drops-count = 5 # Maximum number of OP_DROPs per output in standard transactions. (0 - 100) 21 | max-std-element-size = 8192 # Maximum size of data elements in standard transactions, in bytes. (128 - 32768) 22 | 23 | # The following parameters were generated by multichain-util. 24 | # They SHOULD ONLY BE EDITED IF YOU KNOW WHAT YOU ARE DOING. 25 | 26 | default-network-port = 4403 # Default TCP/IP port for peer-to-peer connection with other nodes. 27 | chain-name = theblockchain # Chain name, used as first argument for multichaind and multichain-cli. 28 | protocol-version = 10009 # Protocol version at the moment of blockchain genesis. 29 | network-message-start = fbf5d9e0 # Magic value sent as the first 4 bytes of every peer-to-peer message. 30 | address-pubkeyhash-version = 003520c0 # Version bytes used for pay-to-pubkeyhash addresses. 31 | private-key-version = 80ae7a06 # Version bytes used for exporting private keys. 32 | address-checksum-value = 217cab53 # Bytes used for XOR in address checksum calculation. 33 | -------------------------------------------------------------------------------- /params/params_pkg.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "strings" 5 | "strconv" 6 | "io/ioutil" 7 | ) 8 | 9 | type Params map[string]string 10 | 11 | func Open(pathToFile string) (Params, []byte, error) { 12 | 13 | params := Params{} 14 | 15 | b, err := ioutil.ReadFile(pathToFile) 16 | if err != nil { 17 | return nil, nil, err 18 | } 19 | 20 | blob := string(b) 21 | 22 | for _, line := range strings.Split(blob, "\n") { 23 | 24 | line = strings.TrimSpace(line) 25 | 26 | parts := strings.Split(line, "#") 27 | 28 | if len(parts[0]) == 0 { continue } 29 | 30 | kv := strings.Split(strings.TrimSpace(parts[0]), "=") 31 | 32 | if len(kv) == 1 { 33 | continue 34 | } 35 | 36 | k := strings.TrimSpace(kv[0]) 37 | v := strings.TrimSpace(kv[1]) 38 | 39 | params[k] = v 40 | 41 | } 42 | 43 | return params, b, nil 44 | } 45 | 46 | // Params methods 47 | 48 | func (params Params) Bool(key string) bool { 49 | 50 | value := params[key] 51 | 52 | switch value { 53 | 54 | case "true": 55 | 56 | return true 57 | 58 | case "false": 59 | 60 | return false 61 | 62 | } 63 | 64 | panic("Invalid BOOL value for key: "+key+" - "+value) 65 | 66 | return false 67 | } 68 | 69 | func (params Params) Int(key string) int { 70 | 71 | value := params[key] 72 | 73 | i, err := strconv.Atoi(value) 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | return i 79 | } 80 | 81 | func (params Params) Float64(key string) float64 { 82 | 83 | value := params[key] 84 | 85 | i, err := strconv.ParseFloat(value, 64) 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | return i 91 | } 92 | 93 | func (params Params) String(key string) string { 94 | 95 | return params[key] 96 | } 97 | -------------------------------------------------------------------------------- /params/params_test.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPackage(t *testing.T) { 8 | 9 | params, _, err := Open("params.dat") 10 | if err != nil { 11 | panic(err) 12 | } 13 | 14 | params.Bool("only-accept-std-txs") 15 | 16 | params.Int("max-std-op-return-size") 17 | 18 | params.String("chain-name") 19 | 20 | params.Float64("test-float") 21 | } 22 | -------------------------------------------------------------------------------- /signature/buffers.go: -------------------------------------------------------------------------------- 1 | package signature 2 | 3 | import ( 4 | "fmt" 5 | "bytes" 6 | // "strconv" 7 | "crypto/sha256" 8 | "encoding/hex" 9 | "encoding/binary" 10 | // 11 | "github.com/btcsuite/btcd/btcec" 12 | // 13 | "gitlab.com/golangdaddy/multichain-client/opcodes" 14 | ) 15 | 16 | type SigReq struct { 17 | transaction *Transaction 18 | publicKey []byte 19 | privateKey []byte 20 | } 21 | 22 | /* 23 | 24 | def makeSignedTransaction(privateKey, outputTransactionHash, sourceIndex, scriptPubKey, outputs): 25 | myTxn_forSig = (makeRawTransaction(outputTransactionHash, sourceIndex, scriptPubKey, outputs) 26 | + "01000000") # hash code 27 | 28 | s256 = hashlib.sha256(hashlib.sha256(myTxn_forSig.decode('hex')).digest()).digest() 29 | sk = ecdsa.SigningKey.from_string(privateKey.decode('hex'), curve=ecdsa.SECP256k1) 30 | sig = sk.sign_digest(s256, sigencode=ecdsa.util.sigencode_der) + '\01' # 01 is hashtype 31 | pubKey = keyUtils.privateKeyToPublicKey(privateKey) 32 | scriptSig = utils.varstr(sig).encode('hex') + utils.varstr(pubKey.decode('hex')).encode('hex') 33 | signed_txn = makeRawTransaction(outputTransactionHash, sourceIndex, scriptSig, outputs) 34 | verifyTxnSignature(signed_txn) 35 | return signed_txn 36 | 37 | */ 38 | 39 | func (self *SigReq) Sign(index int) error { 40 | 41 | hashType := 0x01 42 | 43 | buf, err := toBuffer(self.transaction, 0) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | hashForSignature := sha256.Sum256( 49 | bytes.Join( 50 | [][]byte{ 51 | buf, 52 | uint32Buffer(uint32(hashType)), 53 | }, 54 | nil, 55 | ), 56 | ) 57 | 58 | privkey, _ := btcec.PrivKeyFromBytes(btcec.S256(), self.privateKey) 59 | sig, err := privkey.Sign(hashForSignature[:]) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | scriptSignature := bytes.Join( 65 | [][]byte{ 66 | sig.Serialize(), 67 | uint8Buffer(uint8(hashType)), 68 | }, 69 | nil, 70 | ) 71 | 72 | self.transaction.Vin[index].ScriptSig.Hex = hex.EncodeToString( 73 | bytes.Join( 74 | [][]byte{ 75 | pushDataIntBuffer(len(scriptSignature)), 76 | scriptSignature, 77 | pushDataIntBuffer(len(self.publicKey)), 78 | self.publicKey, 79 | }, 80 | nil, 81 | ), 82 | ) 83 | 84 | return nil 85 | } 86 | 87 | func toBuffer(tx *Transaction, index int) ([]byte, error) { 88 | 89 | chunks := [][]byte{} 90 | 91 | for _, txIn := range tx.Vin { 92 | 93 | txid, _ := hex.DecodeString(txIn.Txid) 94 | chunks = append( 95 | chunks, 96 | txid, 97 | uint8Buffer(uint8(txIn.Vout)), 98 | ) 99 | 100 | script := txIn.ScriptSig.Hex 101 | 102 | if len(script) > 0 { 103 | 104 | chunks = append( 105 | chunks, 106 | varIntBuffer(uint64(len(script))), 107 | ) 108 | 109 | chunks = append( 110 | chunks, 111 | []byte(script), 112 | ) 113 | 114 | } else { 115 | 116 | chunks = append( 117 | chunks, 118 | varIntBuffer(0), 119 | ) 120 | 121 | } 122 | 123 | chunks = append( 124 | chunks, 125 | uint32Buffer(uint32(txIn.Sequence)), 126 | ) 127 | 128 | } 129 | 130 | for _, txOut := range tx.Vout { 131 | 132 | script, _ := hex.DecodeString(txOut.ScriptPubKey.Hex) 133 | chunks = append( 134 | chunks, 135 | uint64Buffer(uint64(txOut.Value)), 136 | varIntBuffer(uint64(len(script))), 137 | script, 138 | ) 139 | 140 | } 141 | 142 | return bytes.Join(chunks, nil), nil 143 | } 144 | 145 | func pushDataIntBuffer(num int) []byte { 146 | 147 | var chunks [][]byte 148 | 149 | if num < opcodes.OP_PUSHDATA1 { 150 | 151 | chunks = append( 152 | chunks, 153 | uint8Buffer(uint8(num)), 154 | ) 155 | 156 | } else if num < 0xff { 157 | 158 | chunks = append( 159 | chunks, 160 | uint8Buffer(opcodes.OP_PUSHDATA1), 161 | uint8Buffer(uint8(num)), 162 | ) 163 | 164 | } else if num < 0xffff { 165 | 166 | chunks = append( 167 | chunks, 168 | uint8Buffer(opcodes.OP_PUSHDATA2), 169 | uint16Buffer(uint16(num)), 170 | ) 171 | 172 | } else { 173 | 174 | chunks = append( 175 | chunks, 176 | uint8Buffer(opcodes.OP_PUSHDATA4), 177 | uint32Buffer(uint32(num)), 178 | ) 179 | 180 | } 181 | 182 | return bytes.Join(chunks, nil) 183 | } 184 | 185 | func varIntBuffer(num uint64) []byte { 186 | 187 | var chunks [][]byte 188 | 189 | if num < 253 { 190 | 191 | chunks = append( 192 | chunks, 193 | uint8Buffer(uint8(num)), 194 | ) 195 | 196 | } else if num < 0x10000 { 197 | 198 | chunks = append( 199 | chunks, 200 | uint16Buffer(uint16(num)), 201 | ) 202 | 203 | } else if num < 0x100000000 { 204 | 205 | chunks = append( 206 | chunks, 207 | uint32Buffer(uint32(num)), 208 | ) 209 | 210 | } else { 211 | 212 | chunks = append( 213 | chunks, 214 | uint64Buffer(uint64(num)), 215 | ) 216 | 217 | } 218 | 219 | return bytes.Join(chunks, nil) 220 | } 221 | 222 | /* 223 | var uint8Buffer = function(number) { 224 | var buffer = new Buffer(1); 225 | buffer.writeUInt8(number, 0); 226 | 227 | return buffer; 228 | }; 229 | */ 230 | 231 | func uint8Buffer(num uint8) []byte { 232 | 233 | return []byte{byte(num)} 234 | } 235 | 236 | /* 237 | var uint16Buffer = function(number) { 238 | var buffer = new Buffer(2); 239 | buffer.writeUInt16LE(number, 0); 240 | 241 | return buffer; 242 | }; 243 | */ 244 | 245 | func uint16Buffer(num uint16) []byte { 246 | 247 | b := make([]byte, 3) 248 | binary.LittleEndian.PutUint16(b[0:], 0xFD) 249 | binary.LittleEndian.PutUint16(b[1:], num) 250 | 251 | return b 252 | } 253 | 254 | /* 255 | var uint32Buffer = function(number) { 256 | var buffer = new Buffer(4); 257 | buffer.writeUInt32LE(number, 0); 258 | return buffer; 259 | }; 260 | */ 261 | 262 | func uint32Buffer(num uint32) []byte { 263 | 264 | b := make([]byte, 5) 265 | binary.LittleEndian.PutUint16(b[0:], 0xFE) 266 | binary.LittleEndian.PutUint32(b[1:], num) 267 | 268 | return b 269 | } 270 | 271 | /* 272 | var uint64Buffer = function(number) { 273 | var buffer = new Buffer(8); 274 | buffer.writeInt32LE(number & -1, 0) 275 | buffer.writeUInt32LE(Math.floor(number / 0x100000000), 4) 276 | 277 | return buffer; 278 | }; 279 | */ 280 | 281 | func uint64Buffer(num uint64) []byte { 282 | 283 | b := make([]byte, 9) 284 | binary.LittleEndian.PutUint16(b[0:], 0xFF) 285 | binary.LittleEndian.PutUint64(b[1:], num) 286 | 287 | return b 288 | } 289 | -------------------------------------------------------------------------------- /signature/buffers_test.go: -------------------------------------------------------------------------------- 1 | package signature 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "encoding/json" 7 | // 8 | "gitlab.com/golangdaddy/multichain-client/address" 9 | ) 10 | 11 | const ( 12 | CONST_TX_TEST = "01000000013c17411f8b705c4fe6b9cebf8501d3b6e59aaa2052f376962eea5283b6fde2270000000000ffffffff0200000000000000003776a91421ce0c375fca548b4c39847c5a0df06c8d1bd0df88ac1c73706b71e59aaa2052f376962eea5283b6fde227e8030000000000007500000000000000003776a914fca3548c1a26d433964f713bada36ee66e66836a88ac1c73706b71e59aaa2052f376962eea5283b6fde227b8820100000000007500000000" 13 | CONST_TX_DECODED = `{"txid":"bdb94203dce2fc8f804157c7ec0fd1a84247f87f197a15d5ec5edc33a5e6f996","version":1,"locktime":0,"vin":[{"txid":"27e2fdb68352ea2e9676f35220aa9ae5b6d30185bfceb9e64f5c708b1f41173c","vout":0,"scriptSig":{"asm":"","hex":""},"sequence":4294967295}],"vout":[{"value":0,"n":0,"scriptPubKey":{"asm":"OP_DUP OP_HASH160 21ce0c375fca548b4c39847c5a0df06c8d1bd0df OP_EQUALVERIFY OP_CHECKSIG 73706b71e59aaa2052f376962eea5283b6fde227e803000000000000 OP_DROP","hex":"76a91421ce0c375fca548b4c39847c5a0df06c8d1bd0df88ac1c73706b71e59aaa2052f376962eea5283b6fde227e80300000000000075","reqSigs":1,"type":"pubkeyhash","addresses":["15ZzkADmZxWf2fHK6xgkZgjGU9Ynj5JPDQ45Pc"]},"assets":[{"name":"4e9d9c77b69fabfd2c385afffabb1273","issuetxid":"27e2fdb68352ea2e9676f35220aa9ae5b6d30185bfceb9e64f5c708b1f41173c","assetref":null,"qty":10,"raw":1000,"type":"transfer"}]},{"value":0,"n":1,"scriptPubKey":{"asm":"OP_DUP OP_HASH160 fca3548c1a26d433964f713bada36ee66e66836a OP_EQUALVERIFY OP_CHECKSIG 73706b71e59aaa2052f376962eea5283b6fde227b882010000000000 OP_DROP","hex":"76a914fca3548c1a26d433964f713bada36ee66e66836a88ac1c73706b71e59aaa2052f376962eea5283b6fde227b88201000000000075","reqSigs":1,"type":"pubkeyhash","addresses":["1b9RYmRa1pXSWSPEycZEV3S8zUkCcpRxJUp2HY"]},"assets":[{"name":"4e9d9c77b69fabfd2c385afffabb1273","issuetxid":"27e2fdb68352ea2e9676f35220aa9ae5b6d30185bfceb9e64f5c708b1f41173c","assetref":null,"qty":990,"raw":99000,"type":"transfer"}]}]}` 14 | ) 15 | 16 | func TestBuffers(t *testing.T) { 17 | 18 | t.Run( 19 | "test 7000000", 20 | func (t *testing.T) { 21 | 22 | x := varIntBuffer(70000000000) 23 | 24 | fmt.Printf("%x\n", x) 25 | 26 | }, 27 | ) 28 | 29 | t.Run( 30 | "test 70000", 31 | func (t *testing.T) { 32 | 33 | x := varIntBuffer(998000) 34 | 35 | fmt.Printf("%x\n", x) 36 | 37 | }, 38 | ) 39 | 40 | t.Run( 41 | "test 515", 42 | func (t *testing.T) { 43 | 44 | x := varIntBuffer(515) 45 | 46 | fmt.Printf("%x\n", x) 47 | 48 | }, 49 | ) 50 | 51 | t.Run( 52 | "test 128", 53 | func (t *testing.T) { 54 | 55 | x := varIntBuffer(106) 56 | 57 | fmt.Printf("%x\n", x) 58 | 59 | }, 60 | ) 61 | 62 | address.Configure( 63 | &address.Config{ 64 | PrivateKeyVersion: "8097af59", 65 | AddressPubkeyhashVersion: "00ddc3a9", 66 | AddressChecksumValue: "8fdcaf22", 67 | }, 68 | ) 69 | 70 | t.Run( 71 | "test sig", 72 | func (t *testing.T) { 73 | seed := []byte("seed") 74 | 75 | keyPair, err := address.MultiChainWallet(seed, 2000, 0) 76 | if err != nil { 77 | t.Error(err) 78 | return 79 | } 80 | 81 | sigReq := &SigReq{ 82 | transaction: &Transaction{}, 83 | privateKey: keyPair.PrivateKey, 84 | publicKey: keyPair.PublicKey, 85 | } 86 | 87 | err = json.Unmarshal([]byte(CONST_TX_DECODED), sigReq.transaction) 88 | if err != nil { 89 | t.Error(err) 90 | return 91 | } 92 | 93 | b, _ := json.Marshal(sigReq.transaction) 94 | if err != nil { 95 | t.Error(err) 96 | return 97 | } 98 | 99 | fmt.Println(string(b)) 100 | 101 | err = sigReq.Sign(0) 102 | if err != nil { 103 | t.Error(err) 104 | return 105 | } 106 | 107 | b, _ = json.Marshal(sigReq.transaction) 108 | if err != nil { 109 | t.Error(err) 110 | return 111 | } 112 | 113 | fmt.Println(string(b)) 114 | }, 115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /signature/ref.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var randomBytes = require('randombytes'), 4 | logger = require('./logger'), 5 | secp256k1 = require('secp256k1'), 6 | crypto = require('./crypto'), 7 | base58 = require('bs58'), 8 | xor = require('buffer-xor'), 9 | compare = require('buffer-compare'), 10 | Promise = require('bluebird'), 11 | clone = require('clone'); 12 | 13 | var OPS = require('./opcodes'); 14 | 15 | var _generatePrivateKey, _createPublicKey, _generateAddress, _extendWithVersion, _generateChecksum, _extractVersion, _toBuffer; 16 | 17 | var addressFactory = {}; 18 | 19 | addressFactory.generateNew = function(pubKeyHashVersion, checksumValue, randomBytesGenerator) { 20 | return _generateAddress(_generatePrivateKey(randomBytesGenerator), pubKeyHashVersion, checksumValue, false); 21 | }; 22 | 23 | addressFactory.fromWIF = function(wif, publicKeyHashVersion, privateKeyVersion, checksumValue) { 24 | privateKeyVersion = Buffer.isBuffer(privateKeyVersion) ? privateKeyVersion : Buffer.from(privateKeyVersion, 'hex'); 25 | checksumValue = Buffer.isBuffer(checksumValue) ? checksumValue : Buffer.from(checksumValue, 'hex'); 26 | 27 | var decodedWIF = new Buffer(base58.decode(wif)); 28 | logger.log('[from WIF]', 'decoded WIF', decodedWIF.toString('hex')); 29 | 30 | var extractedChecksum = decodedWIF.slice(decodedWIF.length - checksumValue.length), 31 | extendedPrivateKey = decodedWIF.slice(0, decodedWIF.length - checksumValue.length), 32 | generatedChecksum = _generateChecksum(extendedPrivateKey, checksumValue.length), 33 | xorChecksum = xor(generatedChecksum, checksumValue); 34 | 35 | logger.log('[from WIF]', 'extracted checksum', extractedChecksum.toString('hex')); 36 | logger.log('[from WIF]', 'extended private key', extendedPrivateKey.toString('hex')); 37 | 38 | logger.log('[from WIF]', 'generated checksum', generatedChecksum.toString('hex')); 39 | logger.log('[from WIF]', 'xor checksum', xorChecksum.toString('hex')); 40 | 41 | if (compare(extractedChecksum, xorChecksum) !== 0) { 42 | throw new Error('Extracted checksum and generated checksum do not match (' + extractedChecksum.toString('hex') + ', ' + xorChecksum.toString('hex') + ')'); 43 | } 44 | 45 | var extractedData = _extractVersion(extendedPrivateKey, privateKeyVersion.length, 8); 46 | 47 | if (compare(extractedData['version'], privateKeyVersion) !== 0) { 48 | throw new Error('Extracted private key does not match the given private key (' + extractedData['version'].toString('hex') + ', ' + privateKeyVersion.toString('hex') + ')'); 49 | } 50 | 51 | var privateKey = extractedData['hash'], 52 | compressed = false; 53 | 54 | logger.log('[from WIF]', 'extracted private key', privateKey.toString('hex')); 55 | 56 | if (privateKey.length !== 32) { 57 | if (privateKey.length === 33 && privateKey[32] === 1) { 58 | compressed = true; 59 | privateKey = privateKey.slice(0, 32); 60 | } else { 61 | throw new Error('Private key length invalid ' + privateKey.length + ' bytes'); 62 | } 63 | } 64 | 65 | return _generateAddress(privateKey, publicKeyHashVersion, checksumValue, compressed); 66 | }; 67 | 68 | module.exports = addressFactory; 69 | 70 | function Address(encodedAddress, pubKey, privKey, compressed) { 71 | this.address = encodedAddress; 72 | this.publicKey = pubKey; 73 | this.privateKey = privKey; 74 | this.compressed = compressed 75 | } 76 | 77 | Address.prototype.toString = function() { 78 | return this.address + ' (' + this.privateKey.toString('hex') + ')'; 79 | }; 80 | 81 | Address.prototype.sign = function(rawTransaction, index, getInputScript) { 82 | rawTransaction = clone(rawTransaction); 83 | index = index || 0; 84 | 85 | logger.log('[sign]', 'unsigned to buffer', _toBuffer(rawTransaction).toString('hex')); 86 | 87 | var scriptPromise; 88 | switch(typeof getInputScript) { 89 | case 'function': 90 | scriptPromise = getInputScript(rawTransaction.vin[index]['txid'], rawTransaction.vin[index]['vout']); 91 | break; 92 | case 'object': 93 | scriptPromise = getInputScript; 94 | break; 95 | case 'string': 96 | scriptPromise = Promise.resolve(getInputScript); 97 | break; 98 | } 99 | 100 | var self = this; 101 | 102 | return scriptPromise.then(function(script) { 103 | rawTransaction.vin[index].script = Buffer.isBuffer(script) ? script : Buffer.from(script, 'hex'); 104 | 105 | logger.log('[sign]', 'script', script.toString('hex')); 106 | 107 | var hashType = 0x01; // SIGHASH_ALL 108 | var hashForSignature = crypto.hash256(Buffer.concat([_toBuffer(rawTransaction), uint32Buffer(hashType)])); 109 | 110 | logger.log('[sign]', 'hash for signature', hashForSignature.toString('hex')); 111 | 112 | var signature = secp256k1.sign(hashForSignature, self.privateKey).signature; 113 | var signatureDER = secp256k1.signatureExport(signature); 114 | 115 | logger.log('[sign]', 'signature', signature.toString('hex')); 116 | logger.log('[sign]', 'signature DER', signatureDER.toString('hex')); 117 | 118 | var scriptSignature = Buffer.concat([signatureDER, uint8Buffer(hashType)]); // public key hash input 119 | 120 | logger.log('[sign]', 'script signature', scriptSignature.toString('hex')); 121 | 122 | var scriptSig = Buffer.concat([pushDataIntBuffer(scriptSignature.length), scriptSignature, pushDataIntBuffer(self.publicKey.length), self.publicKey]); 123 | 124 | logger.log('[sign]', 'script sig', scriptSig.toString('hex')); 125 | 126 | rawTransaction.vin[index].script = scriptSig; 127 | 128 | rawTransaction.toBuffer = function() { 129 | 130 | return _toBuffer(this); 131 | } 132 | 133 | return rawTransaction; 134 | }); 135 | }; 136 | 137 | // @todo better way of doing this ... wrapping it in an object? move to utils? 138 | Address.prototype.toBuffer = function(rawTransaction) { 139 | 140 | return _toBuffer(rawTransaction); 141 | } 142 | 143 | Address.prototype.toWIF = function(privateKeyVersion, checksumValue) { 144 | privateKeyVersion = Buffer.isBuffer(privateKeyVersion) ? privateKeyVersion : Buffer.from(privateKeyVersion, 'hex'); 145 | checksumValue = Buffer.isBuffer(checksumValue) ? checksumValue : Buffer.from(checksumValue, 'hex'); 146 | 147 | var privateKey = this.privateKey; 148 | 149 | logger.log('[to WIF]', 'private key', privateKey.toString('hex')); 150 | 151 | if (this.compressed) { 152 | privateKey = Buffer.concat(privateKey, Buffer.from('01', 'hex')); 153 | logger.log('[to WIF]', 'add compressed flag', privateKey.toString('hex')); 154 | } 155 | 156 | var extendedPrivateKey = _extendWithVersion(privateKey, privateKeyVersion, 8); 157 | 158 | logger.log('[to WIF]', 'extended private key', extendedPrivateKey.toString('hex')); 159 | 160 | var checksum = _generateChecksum(extendedPrivateKey, checksumValue.length), 161 | xorChecksum = xor(checksum, checksumValue); 162 | 163 | logger.log('[to WIF]', 'checksum', checksum.toString('hex')); 164 | logger.log('[to WIF]', 'xor checksum', xorChecksum.toString('hex')); 165 | 166 | var decodedWIF = Buffer.concat([extendedPrivateKey, xorChecksum]); 167 | logger.log('[to WIF]', 'decoded WIF', decodedWIF.toString('hex')); 168 | 169 | var encodedWIF = base58.encode(decodedWIF); 170 | 171 | logger.log('[to WIF]', 'encoded WIF', encodedWIF); 172 | 173 | return encodedWIF; 174 | }; 175 | 176 | _generatePrivateKey = function(randomBytesGenerator) { 177 | var privKey; 178 | 179 | randomBytesGenerator = randomBytesGenerator && typeof randomBytesGenerator === 'function' ? randomBytesGenerator : randomBytes; 180 | 181 | do { 182 | privKey = randomBytesGenerator(32); 183 | if (!Buffer.isBuffer(privKey)) { 184 | if (typeof privKey === 'string') { 185 | privKey = Buffer.from(privKey, 'hex'); 186 | } else { 187 | throw new Error('Invalid random bytes generator'); 188 | } 189 | } 190 | } while (!secp256k1.privateKeyVerify(privKey)); 191 | 192 | return privKey; 193 | }; 194 | 195 | _generateAddress = function(privKey, pubKeyHashVersion, checksumValue, compressed) { 196 | pubKeyHashVersion = Buffer.isBuffer(pubKeyHashVersion) ? pubKeyHashVersion : Buffer.from(pubKeyHashVersion, 'hex'); 197 | checksumValue = Buffer.isBuffer(checksumValue) ? checksumValue : Buffer.from(checksumValue, 'hex'); 198 | 199 | logger.log('[Generate address]', 'private key', privKey.toString('hex')); 200 | 201 | var pubKey = _createPublicKey(privKey, compressed); 202 | 203 | logger.log('[Generate address]', 'public key', pubKey.toString('hex')); 204 | 205 | var ripemd160 = crypto.ripemd160(crypto.sha256(pubKey)), 206 | extendedRipemd160 = _extendWithVersion(ripemd160, pubKeyHashVersion, 5); 207 | 208 | logger.log('[Generate address]', 'ripemd160', ripemd160.toString('hex')); 209 | logger.log('[Generate address]', 'public key hash value', pubKeyHashVersion.toString('hex')); 210 | logger.log('[Generate address]', 'extended ripemd160', extendedRipemd160.toString('hex')); 211 | 212 | var checksum = _generateChecksum(extendedRipemd160, checksumValue.length), 213 | xorChecksum = xor(checksum, checksumValue); 214 | logger.log('[Generate address]', 'checksum', checksum.toString('hex')); 215 | logger.log('[Generate address]', 'xor checksum', xorChecksum.toString('hex')); 216 | 217 | var decodedAddress = Buffer.concat([extendedRipemd160, xorChecksum]); 218 | logger.log('[Generate address]', 'decoded address', decodedAddress.toString('hex')); 219 | 220 | var encodedAddress = base58.encode(decodedAddress); 221 | 222 | logger.log('[Generate address]', 'encoded address', encodedAddress); 223 | 224 | return new Address(encodedAddress, pubKey, privKey, compressed); 225 | }; 226 | 227 | _createPublicKey = function(privKey, combined) { 228 | combined = combined || false; 229 | 230 | return secp256k1.publicKeyCreate(privKey, combined); 231 | }; 232 | 233 | _extendWithVersion = function(hash, versionHash, nbSpacerBytes) { 234 | var extendedParts = [], index = 0, fromIndex, toIndex; 235 | 236 | for (; index < versionHash.length; index++) { 237 | extendedParts.push(versionHash.slice(index, index + 1)); 238 | 239 | fromIndex = index * nbSpacerBytes; 240 | toIndex = (index + 1) * nbSpacerBytes; 241 | 242 | extendedParts.push(hash.slice(fromIndex, toIndex)); 243 | } 244 | 245 | if ((index * nbSpacerBytes) < hash.length) { 246 | extendedParts.push(hash.slice(index * nbSpacerBytes)); 247 | } 248 | 249 | return Buffer.concat(extendedParts); 250 | }; 251 | 252 | _extractVersion = function(extendedHash, versionLength, nbSpacerBytes) { 253 | var versionParts = [], 254 | hashParts = [], index = 0, fromIndex, toIndex; 255 | 256 | for (; index < versionLength; index++) { 257 | versionParts.push(extendedHash.slice(index * nbSpacerBytes + index, index * nbSpacerBytes + index + 1)); 258 | 259 | fromIndex = index * nbSpacerBytes + index + 1; 260 | toIndex = (index + 1) * nbSpacerBytes + index + 1; 261 | 262 | hashParts.push(extendedHash.slice(fromIndex, toIndex)); 263 | } 264 | 265 | if ((index * nbSpacerBytes + index) < extendedHash.length) { 266 | hashParts.push(extendedHash.slice(index * nbSpacerBytes + index)); 267 | } 268 | 269 | return { 270 | 'version': Buffer.concat(versionParts), 271 | 'hash': Buffer.concat(hashParts) 272 | }; 273 | }; 274 | 275 | _generateChecksum = function(extendedHash, checksumLength) { 276 | return crypto.hash256(extendedHash).slice(0, checksumLength); 277 | }; 278 | 279 | 280 | _toBuffer = function(decodedTransaction) { 281 | var chunks = []; 282 | 283 | chunks.push(uint32Buffer(decodedTransaction.version)); 284 | chunks.push(varIntBuffer(decodedTransaction.vin.length)); 285 | 286 | decodedTransaction.vin.forEach(function (txIn, index) { 287 | var hash = [].reverse.call(new Buffer(txIn.txid, 'hex')); 288 | chunks.push(hash); 289 | chunks.push(uint32Buffer(txIn.vout)); // index 290 | 291 | if (txIn.script != null) { 292 | logger.log('[toBuffer]', Buffer.concat(chunks).toString('hex')); 293 | logger.log('[toBuffer]', 'length', txIn.script.length); 294 | chunks.push(varIntBuffer(txIn.script.length)); 295 | logger.log('[toBuffer]', 'script', txIn.script.toString('hex')); 296 | chunks.push(txIn.script); 297 | } else { 298 | chunks.push(varIntBuffer(0)); 299 | } 300 | 301 | chunks.push(uint32Buffer(txIn.sequence)); 302 | }); 303 | 304 | chunks.push(varIntBuffer(decodedTransaction.vout.length)); 305 | decodedTransaction.vout.forEach(function (txOut) { 306 | chunks.push(uint64Buffer(txOut.value)); 307 | 308 | var script = Buffer.from(txOut.scriptPubKey.hex, 'hex'); 309 | 310 | chunks.push(varIntBuffer(script.length)); 311 | chunks.push(script); 312 | }); 313 | 314 | chunks.push(uint32Buffer(decodedTransaction.locktime)); 315 | 316 | return Buffer.concat(chunks); 317 | }; 318 | 319 | var pushDataIntBuffer = function(number) { 320 | var chunks = []; 321 | 322 | var pushDataSize = number < OPS.OP_PUSHDATA1 ? 1 323 | : number < 0xff ? 2 324 | : number < 0xffff ? 3 325 | : 5; 326 | 327 | if (pushDataSize === 1) { 328 | chunks.push(uint8Buffer(number)); 329 | } else if (pushDataSize === 2) { 330 | chunks.push(uint8Buffer(OPS.OP_PUSHDATA1)); 331 | chunks.push(uint8Buffer(number)); 332 | } else if (pushDataSize === 3) { 333 | chunks.push(uint8Buffer(OPS.OP_PUSHDATA2)); 334 | chunks.push(uint16Buffer(number)); 335 | } else { 336 | chunks.push(uint8Buffer(OPS.OP_PUSHDATA4)); 337 | chunks.push(uint32Buffer(number)); 338 | } 339 | 340 | return Buffer.concat(chunks); 341 | }; 342 | 343 | var varIntBuffer = function(number) { 344 | var chunks = []; 345 | 346 | var size = number < 253 ? 1 347 | : number < 0x10000 ? 3 348 | : number < 0x100000000 ? 5 349 | : 9; 350 | 351 | // 8 bit 352 | if (size === 1) { 353 | chunks.push(uint8Buffer(number)); 354 | 355 | // 16 bit 356 | } else if (size === 3) { 357 | chunks.push(uint8Buffer(253)); 358 | chunks.push(uint16Buffer(number)); 359 | 360 | // 32 bit 361 | } else if (size === 5) { 362 | chunks.push(uint8Buffer(254)); 363 | chunks.push(uint32Buffer(number)); 364 | 365 | // 64 bit 366 | } else { 367 | chunks.push(uint8Buffer(255)); 368 | chunks.push(uint64Buffer(number)); 369 | } 370 | 371 | return Buffer.concat(chunks); 372 | }; 373 | 374 | var uint8Buffer = function(number) { 375 | var buffer = new Buffer(1); 376 | buffer.writeUInt8(number, 0); 377 | 378 | return buffer; 379 | }; 380 | 381 | var uint16Buffer = function(number) { 382 | var buffer = new Buffer(2); 383 | buffer.writeUInt16LE(number, 0); 384 | 385 | return buffer; 386 | }; 387 | 388 | var uint32Buffer = function(number) { 389 | var buffer = new Buffer(4); 390 | buffer.writeUInt32LE(number, 0); 391 | 392 | return buffer; 393 | }; 394 | 395 | var uint64Buffer = function(number) { 396 | var buffer = new Buffer(8); 397 | buffer.writeInt32LE(number & -1, 0) 398 | buffer.writeUInt32LE(Math.floor(number / 0x100000000), 4) 399 | 400 | return buffer; 401 | }; -------------------------------------------------------------------------------- /signature/transaction.go: -------------------------------------------------------------------------------- 1 | package signature 2 | 3 | type Transaction struct { 4 | Txid string `json:"txid"` 5 | Version int `json:"version"` 6 | Locktime int `json:"locktime"` 7 | Vin []struct { 8 | Txid string `json:"txid"` 9 | Vout int `json:"vout"` 10 | ScriptSig struct { 11 | Asm string `json:"asm"` 12 | Hex string `json:"hex"` 13 | } `json:"scriptSig"` 14 | Sequence int64 `json:"sequence"` 15 | } `json:"vin"` 16 | Vout []struct { 17 | Value int `json:"value"` 18 | N int `json:"n"` 19 | ScriptPubKey struct { 20 | Asm string `json:"asm"` 21 | Hex string `json:"hex"` 22 | ReqSigs int `json:"reqSigs"` 23 | Type string `json:"type"` 24 | Addresses []string `json:"addresses"` 25 | } `json:"scriptPubKey"` 26 | Assets []struct { 27 | Name string `json:"name"` 28 | Issuetxid string `json:"issuetxid"` 29 | Assetref interface{} `json:"assetref"` 30 | Qty int `json:"qty"` 31 | Raw int `json:"raw"` 32 | Type string `json:"type"` 33 | } `json:"assets"` 34 | } `json:"vout"` 35 | } 36 | --------------------------------------------------------------------------------