├── .gitignore ├── .travis.yml ├── LICENSE ├── shapeshift_coins.go ├── shapeshift_test.go ├── shapeshift.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | os: 4 | - linux 5 | 6 | go: 7 | - 1.7 8 | - 1.8 9 | 10 | script: 11 | - go get golang.org/x/tools/cmd/cover 12 | - go get github.com/mattn/goveralls 13 | - go install github.com/mattn/goveralls 14 | - go test -v -covermode=count -coverprofile=coverage.out 15 | - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hunter Long 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /shapeshift_coins.go: -------------------------------------------------------------------------------- 1 | package shapeshift 2 | 3 | type Coin struct { 4 | Name string `json:"name"` 5 | Symbol string `json:"symbol"` 6 | Status string `json:"status"` 7 | Image string `json:"image,omitempty"` 8 | SpecialReturn bool `json:"specialReturn,omitempty"` 9 | SpecialOutgoing bool `json:"specialOutgoing,omitempty"` 10 | SpecialIncoming bool `json:"specialIncoming,omitempty"` 11 | FieldName string `json:"fieldName,omitempty"` 12 | FieldKey string `json:"fieldKey,omitempty"` 13 | QrName string `json:"qrName,omitempty"` 14 | } 15 | 16 | type CoinsResponse struct { 17 | FIRST struct { 18 | Coin 19 | } `json:"1ST"` 20 | ANT struct { 21 | Coin 22 | } `json:"ANT"` 23 | BAT struct { 24 | Coin 25 | } `json:"BAT"` 26 | BCH struct { 27 | Coin 28 | } `json:"BCH"` 29 | BCY struct { 30 | Coin 31 | } `json:"BCY"` 32 | BLK struct { 33 | Coin 34 | } `json:"BLK"` 35 | BNT struct { 36 | Coin 37 | } `json:"BNT"` 38 | BTC struct { 39 | Coin 40 | } `json:"BTC"` 41 | BTCD struct { 42 | Coin 43 | } `json:"BTCD"` 44 | BTG struct { 45 | Coin 46 | } `json:"BTG"` 47 | BTS struct { 48 | Coin 49 | } `json:"BTS"` 50 | CLAM struct { 51 | Coin 52 | } `json:"CLAM"` 53 | CVC struct { 54 | Coin 55 | } `json:"CVC"` 56 | DASH struct { 57 | Coin 58 | } `json:"DASH"` 59 | DCR struct { 60 | Coin 61 | } `json:"DCR"` 62 | DGB struct { 63 | Coin 64 | } `json:"DGB"` 65 | DNT struct { 66 | Coin 67 | } `json:"DNT"` 68 | DOGE struct { 69 | Coin 70 | } `json:"DOGE"` 71 | EDG struct { 72 | Coin 73 | } `json:"EDG"` 74 | EMC struct { 75 | Coin 76 | } `json:"EMC"` 77 | EOS struct { 78 | Coin 79 | } `json:"EOS"` 80 | ETC struct { 81 | Coin 82 | } `json:"ETC"` 83 | ETH struct { 84 | Coin 85 | } `json:"ETH"` 86 | FCT struct { 87 | Coin 88 | } `json:"FCT"` 89 | FUN struct { 90 | Coin 91 | } `json:"FUN"` 92 | GAME struct { 93 | Coin 94 | } `json:"GAME"` 95 | GNO struct { 96 | Coin 97 | } `json:"GNO"` 98 | GNT struct { 99 | Coin 100 | } `json:"GNT"` 101 | GUP struct { 102 | Coin 103 | } `json:"GUP"` 104 | KMD struct { 105 | Coin 106 | } `json:"KMD"` 107 | LBC struct { 108 | Coin 109 | } `json:"LBC"` 110 | LSK struct { 111 | Coin 112 | } `json:"LSK"` 113 | LTC struct { 114 | Coin 115 | } `json:"LTC"` 116 | MAID struct { 117 | Coin 118 | } `json:"MAID"` 119 | MLN struct { 120 | Coin 121 | } `json:"MLN"` 122 | MONA struct { 123 | Coin 124 | } `json:"MONA"` 125 | MSC struct { 126 | Coin 127 | } `json:"MSC"` 128 | MTL struct { 129 | Coin 130 | } `json:"MTL"` 131 | NBT struct { 132 | Coin 133 | } `json:"NBT"` 134 | NEO struct { 135 | Coin 136 | } `json:"NEO"` 137 | NMC struct { 138 | Coin 139 | } `json:"NMC"` 140 | NMR struct { 141 | Coin 142 | } `json:"NMR"` 143 | NVC struct { 144 | Coin 145 | } `json:"NVC"` 146 | NXT struct { 147 | Coin 148 | } `json:"NXT"` 149 | OMG struct { 150 | Coin 151 | } `json:"OMG"` 152 | POT struct { 153 | Coin 154 | } `json:"POT"` 155 | PPC struct { 156 | Coin 157 | } `json:"PPC"` 158 | QTUM struct { 159 | Coin 160 | } `json:"QTUM"` 161 | RCN struct { 162 | Coin 163 | } `json:"RCN"` 164 | RDD struct { 165 | Coin 166 | } `json:"RDD"` 167 | REP struct { 168 | Coin 169 | } `json:"REP"` 170 | RLC struct { 171 | Coin 172 | } `json:"RLC"` 173 | SALT struct { 174 | Coin 175 | } `json:"SALT"` 176 | SC struct { 177 | Coin 178 | } `json:"SC"` 179 | SNGLS struct { 180 | Coin 181 | } `json:"SNGLS"` 182 | SNT struct { 183 | Coin 184 | } `json:"SNT"` 185 | START struct { 186 | Coin 187 | } `json:"START"` 188 | STEEM struct { 189 | Coin 190 | } `json:"STEEM"` 191 | STORJ struct { 192 | Coin 193 | } `json:"STORJ"` 194 | SWT struct { 195 | Coin 196 | } `json:"SWT"` 197 | TRST struct { 198 | Coin 199 | } `json:"TRST"` 200 | USDT struct { 201 | Coin 202 | } `json:"USDT"` 203 | VOX struct { 204 | Coin 205 | } `json:"VOX"` 206 | VRC struct { 207 | Coin 208 | } `json:"VRC"` 209 | VTC struct { 210 | Coin 211 | } `json:"VTC"` 212 | WAVES struct { 213 | Coin 214 | } `json:"WAVES"` 215 | WINGS struct { 216 | Coin 217 | } `json:"WINGS"` 218 | XCP struct { 219 | Coin 220 | } `json:"XCP"` 221 | XEM struct { 222 | Coin 223 | } `json:"XEM"` 224 | XMR struct { 225 | Coin 226 | } `json:"XMR"` 227 | XRP struct { 228 | Coin 229 | } `json:"XRP"` 230 | ZEC struct { 231 | Coin 232 | } `json:"ZEC"` 233 | ZRX struct { 234 | Coin 235 | } `json:"ZRX"` 236 | } 237 | -------------------------------------------------------------------------------- /shapeshift_test.go: -------------------------------------------------------------------------------- 1 | package shapeshift 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var newSendToAddress string 8 | var newSendToAddress2 string 9 | var newTranxId string 10 | 11 | func TestPairs(t *testing.T) { 12 | 13 | pair := Pair{"eth_btc"} 14 | 15 | rate, err := pair.GetRates() 16 | if err != nil { 17 | t.Fail() 18 | } 19 | 20 | t.Log("Rate: ", rate) 21 | 22 | } 23 | 24 | func TestErrorPairs(t *testing.T) { 25 | 26 | pair := Pair{"xxx_btc"} 27 | 28 | rate, err := pair.GetRates() 29 | if err != nil { 30 | t.Fail() 31 | } 32 | 33 | t.Log("Rate: ", rate) 34 | 35 | } 36 | 37 | func TestLimits(t *testing.T) { 38 | 39 | pair := Pair{"eth_btc"} 40 | 41 | limits, err := pair.GetLimits() 42 | 43 | if err != nil { 44 | t.Fail() 45 | } 46 | 47 | t.Log("Limit: ", limits) 48 | 49 | } 50 | 51 | func TestMarketInfo(t *testing.T) { 52 | 53 | pair := Pair{"btc_eth"} 54 | 55 | info, err := pair.GetInfo() 56 | if err != nil { 57 | t.Fail() 58 | } 59 | 60 | if !info.isOk() { 61 | t.Log(info.ErrorMsg()) 62 | } 63 | 64 | t.Log("Pair: ", info.Pair) 65 | t.Log("Min: ", info.Min) 66 | t.Log("Miner Fee: ", info.MinerFee) 67 | t.Log("Limit: ", info.Limit) 68 | t.Log("Rate: ", info.Rate) 69 | 70 | } 71 | 72 | func TestRecentTransactions(t *testing.T) { 73 | 74 | recent, err := RecentTransactions("5") 75 | if err != nil { 76 | t.Fail() 77 | } 78 | 79 | for _, v := range *recent { 80 | t.Log("In: ", v.CurIn) 81 | t.Log("Out: ", v.CurOut) 82 | t.Log("Amount: ", v.Amount) 83 | t.Log("Timestamp: ", v.Timestamp) 84 | t.Log("-------------------------------") 85 | } 86 | 87 | } 88 | 89 | func TestValidateAddress(t *testing.T) { 90 | 91 | address, err := Validate("16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh", "btc") 92 | if err != nil { 93 | t.Fail() 94 | } 95 | t.Log("Address is: ", address.Valid) 96 | 97 | address2, err := Validate("1JP7QWC9GbpKEHSvefygWk5woFy9xeQHKc", "btc") 98 | if err != nil { 99 | t.Fail() 100 | } 101 | t.Log("Second Address is: ", address2.Valid) 102 | t.Log("Second Error: ", address2.ErrorMsg()) 103 | 104 | } 105 | 106 | func TestDepositStatus(t *testing.T) { 107 | 108 | status, err := DepositStatus("1JP7QWC9GbpKEHSvefygWk5woFy9xeQHKc") 109 | if err != nil { 110 | t.Fail() 111 | } 112 | 113 | if status.isOk() { 114 | 115 | t.Log("Deposit Status: ", status.Status) 116 | 117 | t.Log("Incoming Coin: ", status.IncomingCoin) 118 | t.Log("Incoming Type: ", status.IncomingType) 119 | t.Log("Outgoing Coin: ", status.OutgoingCoin) 120 | t.Log("Outgoing Type: ", status.OutgoingType) 121 | t.Log("Address: ", status.Address) 122 | t.Log("Transaction ID: ", status.Transaction) 123 | t.Log("Withdraw: ", status.Withdraw) 124 | 125 | } 126 | 127 | if status.Status != "complete" { 128 | t.Fail() 129 | } 130 | 131 | newTranxId = status.Transaction 132 | 133 | } 134 | 135 | func TestGetCoinsAsList(t *testing.T) { 136 | 137 | coins, err := CoinsAsList() 138 | if err != nil || len(coins) == 0 { 139 | t.Fail() 140 | } 141 | 142 | t.Log("Coin: ", coins[0].Name) 143 | t.Log("Status: ", coins[0].Status) 144 | 145 | } 146 | 147 | func TestGetSupportedCoins(t *testing.T) { 148 | 149 | coins, err := Coins() 150 | if err != nil { 151 | t.Fail() 152 | } 153 | 154 | eth := coins.ETH 155 | t.Log("Coin: ", eth.Name) 156 | t.Log("Status: ", eth.Status) 157 | 158 | } 159 | 160 | func TestNewTransaction(t *testing.T) { 161 | 162 | new := New{ 163 | Pair: "eth_btc", 164 | ToAddress: "16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh", 165 | FromAddress: "0xcf2f204aC8D7714990912fA422874371c001217D", 166 | } 167 | 168 | response, err := new.Shift() 169 | if err != nil { 170 | t.Fail() 171 | } 172 | 173 | if response.isOk() { 174 | 175 | t.Log("Send To Address: ", response.SendTo) 176 | t.Log("Send Type: ", response.SendType) 177 | t.Log("Receiving at Address: ", response.ReturnTo) 178 | t.Log("Receiving Type: ", response.ReturnType) 179 | t.Log("Send Type: ", response.SendType) 180 | t.Log("API Key: ", response.ApiKey) 181 | t.Log("Public Data: ", response.Public) 182 | t.Log("XrpDestTag: ", response.XrpDestTag) 183 | 184 | if response.SendType != "ETH" || response.ReturnType != "BTC" { 185 | t.Fail() 186 | } 187 | 188 | } 189 | 190 | newSendToAddress = response.SendTo 191 | 192 | } 193 | 194 | func TestEmailReceipt(t *testing.T) { 195 | 196 | info := Receipt{ 197 | Email: "info@socialeck.com", 198 | TransactionID: newTranxId, 199 | } 200 | 201 | response, err := info.Send() 202 | if err != nil { 203 | t.Fail() 204 | } 205 | 206 | if response.isOk() { 207 | t.Log("Response was good!") 208 | } else { 209 | t.Log(response.ErrorMsg()) 210 | } 211 | 212 | t.Log(response) 213 | 214 | } 215 | 216 | func TestNewFixedTransaction(t *testing.T) { 217 | 218 | new := New{ 219 | Pair: "eth_btc", 220 | Amount: 0.25, 221 | ToAddress: "16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh", 222 | FromAddress: "0xcf2f204aC8D7714990912fA422874371c001217D", 223 | } 224 | 225 | response, err := new.FixedShift() 226 | if err != nil { 227 | t.Fail() 228 | } 229 | 230 | if response.isOk() { 231 | 232 | t.Log("Pair: ", response.Pair) 233 | t.Log("Quoted Rate: ", response.QuotedRate) 234 | t.Log("Deposit Address: ", response.Deposit) 235 | t.Log("Deposit Amount: ", response.DepositAmount) 236 | t.Log("Withdraw Amount: ", response.WithdrawalAmount) 237 | t.Log("Withdraw Address: ", response.Withdrawal) 238 | t.Log("Expiration: ", response.Expiration) 239 | 240 | } else { 241 | t.Log(response.ErrorMsg()) 242 | } 243 | 244 | newSendToAddress2 = response.Deposit 245 | 246 | if response.Withdrawal != "16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh" { 247 | t.Fail() 248 | } 249 | 250 | if response.WithdrawalAmount != "0.25" { 251 | t.Fail() 252 | } 253 | 254 | if response.Pair != "eth_btc" { 255 | t.Fail() 256 | } 257 | } 258 | 259 | func TestTimeRemaining(t *testing.T) { 260 | 261 | status, err := TimeRemaining("1JP7QWC9GbpKEHSvefygWk5woFy9xeQHKc") 262 | if err != nil { 263 | panic(err) 264 | } 265 | 266 | if status.isOk() { 267 | t.Log("Seconds Remaining: ", status.Seconds) 268 | t.Log("Status: ", status.Status) 269 | } else { 270 | t.Log(status.ErrorMsg()) 271 | } 272 | 273 | } 274 | 275 | func TestCancelTransaction(t *testing.T) { 276 | 277 | old := Address{ 278 | Id: newSendToAddress, 279 | } 280 | 281 | response, err := old.Cancel() 282 | if err != nil { 283 | t.Fail() 284 | } 285 | 286 | if response.isOk() { 287 | t.Log(response.Success) 288 | } else { 289 | t.Log(response.ErrorMsg()) 290 | } 291 | 292 | } 293 | 294 | func TestListTransactionsFromAPI(t *testing.T) { 295 | 296 | api := API{ 297 | Key: "oskdfoijsfuhsdhufhewhuf", 298 | } 299 | 300 | list, err := api.ListTransactions() 301 | if err != nil { 302 | t.Fail() 303 | } 304 | 305 | for _, v := range list { 306 | t.Log("Input: ", v.InputAddress) 307 | t.Log("Amount: ", v.InputAmount) 308 | } 309 | 310 | t.Log(list) 311 | 312 | } 313 | 314 | func TestListAddressTransactionsFromAPI(t *testing.T) { 315 | 316 | api := API{ 317 | Key: "oskdfoijsfuhsdhufhewhuf", 318 | Address: "1JP7QWC9GbpKEHSvefygWk5woFy9xeQHKc", 319 | } 320 | 321 | list, err := api.ListTransactions() 322 | if err != nil { 323 | t.Fail() 324 | } 325 | 326 | for _, v := range list { 327 | t.Log("Input: ", v.InputAddress) 328 | t.Log("Amount: ", v.InputAmount) 329 | } 330 | 331 | t.Log(list) 332 | } 333 | -------------------------------------------------------------------------------- /shapeshift.go: -------------------------------------------------------------------------------- 1 | package shapeshift 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "net/http" 9 | "strconv" 10 | ) 11 | 12 | var apiUrl string = "https://shapeshift.io" 13 | 14 | // ShapeShift's API responds in float and string for decimals for different functions. 15 | // Since we arn't really using 'big numbers' I think it's ok to be using this. 16 | // This golang package is not doing any math, just responding back from ShapeShift API. 17 | func ToFloat(s string) float64 { 18 | f, err := strconv.ParseFloat(s, 64) 19 | if err != nil { 20 | return 0.0 21 | } 22 | return f 23 | } 24 | 25 | type Pair struct { 26 | Name string `json:"pair,omitempty"` 27 | } 28 | 29 | type RateResponse struct { 30 | Pair string `json:"pair,omitempty"` 31 | Rate string `json:"rate"` 32 | Error 33 | } 34 | 35 | type LimitResponse struct { 36 | Pair string `json:"pair,omitempty"` 37 | Limit string `json:"limit"` 38 | Error 39 | } 40 | 41 | type MarketInfoResponse struct { 42 | Pair string `json:"pair,omitempty"` 43 | Rate float64 `json:"rate,string,omitempty"` 44 | Limit float64 `json:"limit,omitempty"` 45 | Min float64 `json:"min,omitempty"` 46 | MinerFee float64 `json:"minerFee,omitempty"` 47 | Error 48 | } 49 | 50 | type RecentTranxResponse []struct { 51 | CurIn string `json:"curIn"` 52 | CurOut string `json:"curOut"` 53 | Timestamp float64 `json:"timestamp"` 54 | Amount float64 `json:"amount"` 55 | Error 56 | } 57 | 58 | type DepositStatusResponse struct { 59 | Status string `json:"status"` 60 | Address string `json:"address"` 61 | Withdraw string `json:"withdraw,omitempty"` 62 | IncomingCoin float64 `json:"incomingCoin,omitempty"` 63 | IncomingType string `json:"incomingType,omitempty"` 64 | OutgoingCoin string `json:"outgoingCoin,omitempty"` 65 | OutgoingType string `json:"outgoingType,omitempty"` 66 | Transaction string `json:"transaction,omitempty"` 67 | Error 68 | } 69 | 70 | type Receipt struct { 71 | Email string `json:"email"` 72 | TransactionID string `json:"txid"` 73 | } 74 | 75 | type ValidateResponse struct { 76 | Valid bool `json:"isValid"` 77 | Error 78 | } 79 | 80 | type CancelResponse struct { 81 | Success string `json:"success,omitempty"` 82 | Error 83 | } 84 | 85 | type Address struct { 86 | Id string `json:"address"` 87 | } 88 | 89 | type New struct { 90 | Pair string `json:"pair,omitempty"` 91 | ToAddress string `json:"withdrawal"` 92 | FromAddress string `json:"returnAddress,omitempty"` 93 | DestTag string `json:"destTag,omitempty"` 94 | RsAddress string `json:"rsAddress,omitempty"` 95 | ApiKey string `json:"apiKey,omitempty"` 96 | Amount float64 `json:"amount,omitempty"` 97 | } 98 | 99 | type NewTransactionResponse struct { 100 | SendTo string `json:"deposit"` 101 | SendType string `json:"depositType"` 102 | ReturnTo string `json:"withdrawal"` 103 | ReturnType string `json:"withdrawalType"` 104 | Public string `json:"public"` 105 | XrpDestTag string `json:"xrpDestTag"` 106 | ApiKey string `json:"apiPubKey"` 107 | Error 108 | } 109 | 110 | type FixedTransactionResponse struct { 111 | Response NewFixedTransactionResponse `json:"success"` 112 | Error 113 | } 114 | 115 | type NewFixedTransactionResponse struct { 116 | OrderID string `json:"orderId"` 117 | Pair string `json:"pair,omitempty"` 118 | Withdrawal string `json:"withdrawal"` 119 | WithdrawalAmount string `json:"withdrawalAmount"` 120 | Deposit string `json:"deposit"` 121 | DepositAmount string `json:"depositAmount"` 122 | Expiration int64 `json:"expiration"` 123 | QuotedRate string `json:"quotedRate"` 124 | MaxLimit float64 `json:"maxLimit"` 125 | ReturnAddress string `json:"returnAddress"` 126 | APIPubKey string `json:"apiPubKey"` 127 | MinerFee string `json:"minerFee"` 128 | Error 129 | } 130 | 131 | type ListTransactionsAPIResponse struct { 132 | Transactions []Transaction 133 | Error 134 | } 135 | 136 | type ErrorMsg interface { 137 | ErrorMsg() string 138 | isOk() bool 139 | } 140 | 141 | func (e Error) ErrorMsg() string { 142 | return e.Message 143 | } 144 | 145 | func (e Error) isOk() bool { 146 | if e.Message == "" { 147 | return true 148 | } 149 | return false 150 | } 151 | 152 | type Error struct { 153 | Message string `json:"error,omitempty"` 154 | } 155 | 156 | type Transaction struct { 157 | InputTXID string `json:"inputTXID"` 158 | InputAddress string `json:"inputAddress"` 159 | InputCurrency string `json:"inputCurrency,omitempty"` 160 | InputAmount float64 `json:"inputAmount,omitempty"` 161 | OutputTXID string `json:"outputTXID,omitempty"` 162 | OutputAddress string `json:"outputAddress,omitempty"` 163 | OutputCurrency string `json:"outputCurrency,omitempty"` 164 | OutputAmount string `json:"outputAmount,omitempty"` 165 | ShiftRate string `json:"shiftRate,omitempty"` 166 | Status string `json:"status,omitempty"` 167 | } 168 | 169 | type ReceiptResponse struct { 170 | Email struct { 171 | Status string `json:"status"` 172 | Message string `json:"message"` 173 | } `json:"email"` 174 | Error 175 | } 176 | 177 | type API struct { 178 | Key string 179 | Address string 180 | } 181 | 182 | type TimeRemainingResponse struct { 183 | Status string `json:"status"` 184 | Seconds string `json:"seconds_remaining"` 185 | Error 186 | } 187 | 188 | func (p Pair) GetRates() (float64, error) { 189 | r, err := DoHttp("GET", "rate", p.Name) 190 | if err != nil { 191 | return 0.0, err 192 | } 193 | var g RateResponse 194 | err = json.Unmarshal(r, &g) 195 | return ToFloat(g.Rate), err 196 | } 197 | 198 | func (p Pair) GetLimits() (float64, error) { 199 | r, err := DoHttp("GET", "limit", p.Name) 200 | if err != nil { 201 | return 0.0, err 202 | } 203 | var g LimitResponse 204 | err = json.Unmarshal(r, &g) 205 | return ToFloat(g.Limit), err 206 | } 207 | 208 | func MarketInfo() ([]MarketInfoResponse, error) { 209 | r, err := DoHttp("GET", "marketinfo", "") 210 | if err != nil { 211 | return nil, err 212 | } 213 | var arr []MarketInfoResponse 214 | err = json.Unmarshal(r, &arr) 215 | return arr, err 216 | } 217 | 218 | func (p Pair) GetInfo() (*MarketInfoResponse, error) { 219 | r, err := DoHttp("GET", "marketinfo", p.Name) 220 | if err != nil { 221 | return nil, err 222 | } 223 | var g MarketInfoResponse 224 | err = json.Unmarshal(r, &g) 225 | return &g, err 226 | } 227 | 228 | func RecentTransactions(count string) (*RecentTranxResponse, error) { 229 | r, err := DoHttp("GET", "recenttx", count) 230 | if err != nil { 231 | return nil, err 232 | } 233 | var g RecentTranxResponse 234 | err = json.Unmarshal(r, &g) 235 | return &g, err 236 | } 237 | 238 | func DepositStatus(addr string) (*DepositStatusResponse, error) { 239 | r, err := DoHttp("GET", "txStat", addr) 240 | if err != nil { 241 | return nil, err 242 | } 243 | var g DepositStatusResponse 244 | err = json.Unmarshal(r, &g) 245 | return &g, err 246 | } 247 | 248 | func TimeRemaining(addr string) (*TimeRemainingResponse, error) { 249 | r, err := DoHttp("GET", "timeremaining", addr) 250 | if err != nil { 251 | return nil, err 252 | } 253 | var g TimeRemainingResponse 254 | err = json.Unmarshal(r, &g) 255 | return &g, err 256 | } 257 | 258 | func CoinsAsList() ([]Coin, error) { 259 | var coins []Coin 260 | r, err := DoHttp("GET", "getcoins", "") 261 | if err != nil { 262 | return nil, err 263 | } 264 | 265 | // Use json.RawMessage to delay marshalling to support arbitrary top level keys 266 | var coinmap map[string]*json.RawMessage 267 | if err := json.Unmarshal(r, &coinmap); err != nil { 268 | return coins, err 269 | } 270 | 271 | for _, coinJSON := range coinmap { 272 | var c Coin 273 | err := json.Unmarshal([]byte(*coinJSON), &c) 274 | if err != nil { 275 | //log.Println("Error unmarshalling coin:", err) 276 | continue 277 | } 278 | coins = append(coins, c) 279 | } 280 | 281 | return coins, nil 282 | } 283 | 284 | func Coins() (*CoinsResponse, error) { 285 | r, err := DoHttp("GET", "getcoins", "") 286 | if err != nil { 287 | return nil, err 288 | } 289 | var g CoinsResponse 290 | err = json.Unmarshal(r, &g) 291 | return &g, err 292 | } 293 | 294 | func (r Receipt) Send() (*ReceiptResponse, error) { 295 | q, err := DoPostHttp("POST", "mail", r) 296 | if err != nil { 297 | return nil, err 298 | } 299 | var g ReceiptResponse 300 | err = json.Unmarshal(q, &g) 301 | return &g, err 302 | } 303 | 304 | func (n New) Shift() (*NewTransactionResponse, error) { 305 | r, err := DoPostHttp("POST", "shift", n) 306 | if err != nil { 307 | return nil, err 308 | } 309 | var g NewTransactionResponse 310 | err = json.Unmarshal(r, &g) 311 | return &g, err 312 | } 313 | 314 | func (n New) FixedShift() (*NewFixedTransactionResponse, error) { 315 | r, err := DoPostHttp("POST", "sendamount", n) 316 | if err != nil { 317 | return nil, err 318 | } 319 | var g FixedTransactionResponse 320 | err = json.Unmarshal(r, &g) 321 | return &g.Response, err 322 | } 323 | 324 | func (n Address) Cancel() (*CancelResponse, error) { 325 | r, err := DoPostHttp("POST", "cancelpending", n) 326 | if err != nil { 327 | return nil, err 328 | } 329 | var g CancelResponse 330 | err = json.Unmarshal(r, &g) 331 | return &g, err 332 | } 333 | 334 | func Validate(addr string, coin string) (*ValidateResponse, error) { 335 | r, err := DoHttp("GET", "validateAddress/"+addr, coin) 336 | if err != nil { 337 | return nil, err 338 | } 339 | var g ValidateResponse 340 | err = json.Unmarshal(r, &g) 341 | return &g, err 342 | } 343 | 344 | func (i API) ListTransactions() ([]Transaction, error) { 345 | var r []byte 346 | var err error 347 | var g []Transaction 348 | if i.Address != "" { 349 | r, err = DoHttp("GET", "txbyaddress/"+i.Address, i.Key) 350 | } else { 351 | r, err = DoHttp("GET", "txbyapikey", i.Key) 352 | } 353 | err = json.Unmarshal(r, &g) 354 | return g, err 355 | } 356 | 357 | func DoPostHttp(method string, apimethod string, data interface{}) ([]byte, error) { 358 | new, err := json.Marshal(data) 359 | if err != nil { 360 | return nil, err 361 | } 362 | req, err := http.NewRequest(method, apiUrl+"/"+apimethod, bytes.NewBuffer(new)) 363 | if err != nil { 364 | return nil, err 365 | } 366 | req.Header.Set("Content-Type", "application/json") 367 | client := &http.Client{} 368 | resp, err := client.Do(req) 369 | if err != nil { 370 | return nil, err 371 | } 372 | if resp.StatusCode != 200 { 373 | return nil, errors.New("There was an error creating your order, Please try again later") 374 | } 375 | defer resp.Body.Close() 376 | body, err := ioutil.ReadAll(resp.Body) 377 | body = bytes.TrimPrefix(body, []byte("\xef\xbb\xbf")) 378 | return body, err 379 | } 380 | 381 | func DoHttp(method string, apimethod string, url string) ([]byte, error) { 382 | req, err := http.NewRequest(method, apiUrl+"/"+apimethod+"/"+url, bytes.NewBuffer([]byte(""))) 383 | if err != nil { 384 | return nil, err 385 | } 386 | client := &http.Client{} 387 | resp, err := client.Do(req) 388 | if err != nil { 389 | return nil, err 390 | } 391 | defer resp.Body.Close() 392 | body, err := ioutil.ReadAll(resp.Body) 393 | body = bytes.TrimPrefix(body, []byte("\xef\xbb\xbf")) 394 | return body, err 395 | } 396 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | shapeshift api golang 3 |

4 | 5 | # ShapeShift in Go Language 6 | [![Build Status](https://travis-ci.org/hunterlong/shapeshift.svg?branch=master)](https://travis-ci.org/hunterlong/shapeshift) [![Coverage Status](https://coveralls.io/repos/github/hunterlong/shapeshift/badge.svg?branch=master)](https://coveralls.io/github/hunterlong/shapeshift?branch=master) [![GoDoc](https://godoc.org/github.com/hunterlong/shapeshift?status.svg)](https://godoc.org/github.com/hunterlong/shapeshift) [![Go Report Card](https://goreportcard.com/badge/github.com/hunterlong/shapeshift)](https://goreportcard.com/report/github.com/hunterlong/shapeshift) 7 | 8 | This Go Language Package will allow you to use the [ShapeShift API](https://info.shapeshift.io/) and convert your cryptocurrencies in your very own application. It includes most of the ShapeShift API requests listed on their references website. Below you'll find a perfect example of a new ShapeShift transaction. 9 | 10 | ```go 11 | go get -u github.com/hunterlong/shapeshift 12 | ``` 13 | ###### get the most up to date version 14 | ```go 15 | import "github.com/hunterlong/shapeshift" 16 | ``` 17 | Once you've imported shapeshift into your golang project, you can use any of the requests below. Checkout the Travis CI test logs for responses of each function. See an issue? PR it! 18 | 19 | *** 20 | 21 | # :new: New ShapeShift Transaction 22 | I want to convert Ethereum to Bitcoin. The 'ToAddress' is my Bitcoin address. Once I run this, I'll get a Ethereum address from ShapeShift. 23 | ```go 24 | new := shapeshift.New{ 25 | Pair: "eth_btc", 26 | ToAddress: "16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh", 27 | // FromAddress: "0xcf2f204aC8D7714990912fA422874371c001217D", (Optional Return To Ethereum Address) 28 | } 29 | 30 | response, err := new.Shift() 31 | 32 | if err != nil { 33 | panic(error) 34 | } 35 | 36 | if response.isOk() { 37 | 38 | sendToAddress := response.SendTo 39 | // i will send Ether to this address 40 | 41 | fmt.Println("Send To Address: ", sendToAddress) 42 | fmt.Println("Send Type: ", response.SendType) 43 | fmt.Println("Receiving at Address: ", response.ReturnTo) 44 | fmt.Println("Receiving Type: ", response.ReturnType) 45 | fmt.Println("Send Type: ", response.SendType) 46 | fmt.Println("API Key: ", response.ApiKey) 47 | fmt.Println("Public Data: ", response.Public) 48 | fmt.Println("XrpDestTag: ", response.XrpDestTag) 49 | 50 | } else { 51 | fmt.Println(response.ErrorMsg()) 52 | } 53 | ``` 54 | *** 55 | 56 | # :repeat: Get Status of Transaction 57 | Once I sent some Ethereum to the given Ethereum address, I want to check the status of my ShapeShift transaction by inserting the Etheruem address 'sendToAddress' that ShapeShift gave me in previous function. 58 | ```go 59 | var newTransactionId string 60 | 61 | status, err := shapeshift.DepositStatus(sendToAddress) 62 | 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | if !response.isOk() { 68 | fmt.Println(status.ErrorMsg()) 69 | } 70 | 71 | fmt.Println(status.Status) 72 | // no_deposits 73 | // received 74 | // complete 75 | // failed 76 | 77 | if status.Status == "complete" { 78 | fmt.Println("Incoming Coin: ", status.IncomingCoin) 79 | fmt.Println("Incoming Type: ", status.IncomingType) 80 | fmt.Println("Outgoing Coin: ", status.OutgoingCoin) 81 | fmt.Println("Outgoing Type: ", status.OutgoingType) 82 | fmt.Println("Address: ", status.Address) 83 | fmt.Println("Transaction ID: ", status.Transaction) 84 | fmt.Println("Withdraw: ", status.Withdraw) 85 | 86 | newTransactionId = status.Transaction 87 | // saving transaction ID so i can send a receipt 88 | } 89 | ``` 90 | *** 91 | 92 | # :arrow_double_up: Send an Email Receipt 93 | Want to send a receipt of this transaction? Just include an email address and the transaction ID affiliated with the ShapeShift transaction. 94 | ```go 95 | receipt := shapeshift.Receipt{ 96 | Email: "user@myemailer.com", 97 | TransactionID: newTransactionId, 98 | } 99 | 100 | response, err := receipt.Send() 101 | 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | if response.isOk() { 107 | fmt.Println("Receipt was sent to user") 108 | } else { 109 | fmt.Println(status.ErrorMsg()) 110 | } 111 | 112 | ``` 113 | *** 114 | 115 | # Additional Functions 116 | The other ShapeShift API requests are available for you to use. 117 | 118 | ### :white_check_mark: Get Rate 119 | Gets the current rate offered by Shapeshift. This is an estimate because the rate can occasionally change rapidly depending on the markets. The rate is also a 'use-able' rate not a direct market rate. Meaning multiplying your input coin amount times the rate should give you a close approximation of what will be sent out. This rate does not include the transaction (miner) fee taken off every transaction. 120 | ```go 121 | pair := shapeshift.Pair{"eth_btc"} 122 | 123 | rate, err := pair.GetRates() 124 | 125 | if err != nil { 126 | panic(err) 127 | } 128 | 129 | 130 | fmt.Println("Rate: ", rate) 131 | ``` 132 | *** 133 | 134 | ### :white_check_mark: Deposit Limits 135 | Gets the current deposit limit set by Shapeshift. Amounts deposited over this limit will be sent to the return address if one was entered, otherwise the user will need to contact ShapeShift support to retrieve their coins. This is an estimate because a sudden market swing could move the limit. 136 | ```go 137 | pair := shapeshift.Pair{"eth_btc"} 138 | limits, err := pair.GetLimits() 139 | 140 | if err != nil { 141 | panic(err) 142 | } 143 | 144 | fmt.Println("Limit: ", limits) 145 | ``` 146 | *** 147 | 148 | ### :white_check_mark: Market Info 149 | This gets the market info (pair, rate, limit, minimum limit, miner fee) 150 | ```go 151 | pair := shapeshift.Pair{"btc_eth"} 152 | info, err := pair.GetInfo() 153 | 154 | if err != nil { 155 | panic(err) 156 | } 157 | 158 | fmt.Println("Pair: ", info.Pair) 159 | fmt.Println("Min: ", info.Min) 160 | fmt.Println("Miner Fee: ", info.MinerFee) 161 | fmt.Println("Limit: ", info.Limit) 162 | fmt.Println("Rate: ", info.Rate) 163 | ``` 164 | *** 165 | 166 | ### :white_check_mark: Recent Transactions 167 | ```go 168 | recent, err := shapeshift.RecentTransactions("5") 169 | 170 | if err != nil { 171 | panic(err) 172 | } 173 | 174 | for _, v := range recent { 175 | fmt.Println("In: ", v.CurIn) 176 | fmt.Println("Out: ", v.CurOut) 177 | fmt.Println("Amount: ", v.Amount) 178 | fmt.Println("Timestamp: ", v.Timestamp) 179 | fmt.Println("-------------------------------") 180 | } 181 | ``` 182 | *** 183 | 184 | ### :white_check_mark: Deposit Address Status 185 | This returns the status of the most recent deposit transaction to the address. 186 | ```go 187 | status, err := shapeshift.DepositStatus("1JP7QWC9GbpKEHSvefygWk5woFy9xeQHKc") 188 | 189 | if err != nil { 190 | panic(err) 191 | } 192 | 193 | fmt.Println("Deposit Status: ", status.Status) 194 | fmt.Println("Incoming Coin: ", status.IncomingCoin) 195 | fmt.Println("Incoming Type: ", status.IncomingType) 196 | fmt.Println("Outgoing Coin: ", status.OutgoingCoin) 197 | fmt.Println("Outgoing Type: ", status.OutgoingType) 198 | fmt.Println("Address: ", status.Address) 199 | fmt.Println("Transaction ID: ", status.Transaction) 200 | fmt.Println("Withdraw: ", status.Withdraw) 201 | ``` 202 | *** 203 | 204 | ### :white_check_mark: Time Remaining on Fixed Transaction Amount 205 | Get a list of the most recent transactions. 206 | ```go 207 | status, err := shapeshift.TimeRemaining("16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh") 208 | 209 | if err != nil { 210 | panic(err) 211 | } 212 | 213 | fmt.Println(status.Status) 214 | ``` 215 | *** 216 | 217 | ### :white_check_mark: Get Available Coins 218 | Allows anyone to get a list of all the currencies that Shapeshift currently supports at any given time. The list will include the name, symbol, availability status, and an icon link for each. 219 | ```go 220 | coins, err := shapeshift.Coins() 221 | 222 | if err != nil { 223 | panic(err) 224 | } 225 | 226 | eth := coins.ETH 227 | fmt.Println("Coin: ", eth.Name) 228 | fmt.Println("Status: ", eth.Status) 229 | ``` 230 | *** 231 | 232 | ### :white_check_mark: Validate Address with Coin Symbol 233 | Allows user to verify that their receiving address is a valid address according to a given wallet daemon. If isvalid returns true, this address is valid according to the coin daemon indicated by the currency symbol. 234 | ```go 235 | address, err := shapeshift.Validate("16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh", "btc") 236 | 237 | if err != nil { 238 | panic(err) 239 | } 240 | 241 | fmt.Println("Address is: ", address.Valid) 242 | fmt.Println("Error: ",address.Error) 243 | ``` 244 | *** 245 | 246 | # Primary Requests 247 | 248 | ### :white_check_mark: Create New Transaction 249 | This is the primary data input into ShapeShift. 250 | ```go 251 | new := shapeshift.New{ 252 | Pair: "eth_btc", 253 | ToAddress: "16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh", 254 | FromAddress: "0xcf2f204aC8D7714990912fA422874371c001217D", 255 | } 256 | 257 | response, err := new.Shift() 258 | 259 | if err != nil { 260 | panic(err) 261 | } 262 | 263 | fmt.Println("Send To Address: ", response.SendTo) 264 | fmt.Println("Send Type: ", response.SendType) 265 | fmt.Println("Receiving at Address: ", response.ReturnTo) 266 | fmt.Println("Receiving Type: ", response.ReturnType) 267 | fmt.Println("Send Type: ", response.SendType) 268 | fmt.Println("API Key: ", response.ApiKey) 269 | fmt.Println("Public Data: ", response.Public) 270 | fmt.Println("XrpDestTag: ", response.XrpDestTag) 271 | ``` 272 | *** 273 | 274 | ### :white_check_mark: Request Email Receipt 275 | This call requests a receipt for a transaction. The email address will be added to the conduit associated with that transaction as well. (Soon it will also send receipts to subsequent transactions on that conduit) 276 | ```go 277 | info := shapeshift.Receipt{ 278 | Email: "user@awesome.com", 279 | TransactionID: "owkdwodkkwokdwdw", 280 | } 281 | 282 | response, err := info.Send(); 283 | 284 | if err != nil { 285 | panic(err) 286 | } 287 | 288 | fmt.Println(response) 289 | ``` 290 | *** 291 | 292 | ### :white_check_mark: Fixed Amount Transaction 293 | When a transaction is created with a fixed amount requested there is a 10 minute window for the deposit. After the 10 minute window if the deposit has not been received the transaction expires and a new one must be created. This api call returns how many seconds are left before the transaction expires. Please note that if the address is a ripple address, it will include the "?dt=destTagNUM" appended on the end, and you will need to use the URIEncodeComponent() function on the address before sending it in as a param, to get a successful response. 294 | ```go 295 | new := shapeshift.New{ 296 | Pair: "eth_btc", 297 | Amount: 0.25, 298 | ToAddress: "16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh", 299 | FromAddress: "0xcf2f204aC8D7714990912fA422874371c001217D", 300 | } 301 | 302 | response, err := new.FixedShift() 303 | 304 | if err != nil { 305 | panic(err) 306 | } 307 | 308 | fmt.Println("Pair: ", response.Pair) 309 | fmt.Println("Quoted Rate: ", response.QuotedRate) 310 | fmt.Println("Deposit Address: ", response.Deposit) 311 | fmt.Println("Deposit Amount: ", response.DepositAmount) 312 | fmt.Println("Withdraw Amount: ", response.WithdrawalAmount) 313 | fmt.Println("Withdraw Address: ", response.Withdrawal) 314 | fmt.Println("Expiration: ", response.Expiration) 315 | ``` 316 | *** 317 | 318 | ### :white_check_mark: Cancel Pending Transaction 319 | This call allows you to request for canceling a pending transaction by the deposit address. If there is fund sent to the deposit address, this pending transaction cannot be canceled. 320 | ```go 321 | old := shapeshift.Address{ 322 | Id: newSendToAddress, 323 | } 324 | 325 | response, err := old.Cancel() 326 | 327 | if err != nil { 328 | panic(err) 329 | } 330 | 331 | ``` 332 | *** 333 | 334 | 335 | # API Key Required Requests 336 | 337 | ### :white_check_mark: Get Transactions from Private API Key 338 | Allows vendors to get a list of all transactions that have ever been done using a specific API key. Transactions are created with an affilliate PUBLIC KEY, but they are looked up using the linked PRIVATE KEY, to protect the privacy of our affiliates' account details. 339 | ```go 340 | api := shapeshift.API{ 341 | Key: "oskdfoijsfuhsdhufhewhuf", 342 | } 343 | 344 | list, err := api.ListTransactions() 345 | 346 | if err != nil { 347 | panic(err) 348 | } 349 | 350 | for _,v := range list.Transactions { 351 | fmt.Println("Input: ",v.InputAddress) 352 | fmt.Println("Amount: ",v.InputAmount) 353 | } 354 | ``` 355 | ###### there was no way for me to test this transaction since i'm not a vendor 356 | *** 357 | 358 | ### :white_check_mark: Get Transactions from Output Address 359 | Allows vendors to get a list of all transactions that have ever been sent to one of their addresses. The affilliate's PRIVATE KEY must be provided, and will only return transactions that were sent to output address AND were created using / linked to the affiliate's PUBLIC KEY. Please note that if the address is a ripple address and it includes the "?dt=destTagNUM" appended on the end, you will need to use the URIEncodeComponent() function on the address before sending it in as a param, to get a successful response. 360 | ```go 361 | api := shapeshift.API{ 362 | Key: "oskdfoijsfuhsdhufhewhuf", 363 | Address: "1JP7QWC9GbpKEHSvefygWk5woFy9xeQHKc", 364 | } 365 | 366 | list, err := api.ListTransactions() 367 | 368 | if err != nil { 369 | panic(err) 370 | } 371 | 372 | for _,v := range list.Transactions { 373 | fmt.Println("Input: ",v.InputAddress) 374 | fmt.Println("Amount: ",v.InputAmount) 375 | } 376 | ``` 377 | ###### there was no way for me to test this transaction since i'm not a vendor 378 | *** 379 | 380 | # Coin Pairs 381 | Many of the requests require a 'coin pair'. A coin pair is of the format deposit_withdrawal. Example: 'btc_ltc'. Valid pairs are any combination of the below listed valid coins.* The list will grow as we add more: 382 | ``` 383 | btc, ltc, ppc, drk, doge, nmc, ftc, blk, nxt, btcd, qrk, rdd, nbt, bts, bitusd, xcp, xmr 384 | ``` 385 | [ShapeShift Coins](https://shapeshift.io/#/coins) 386 | * If a particular coin goes offline any pairs using it will return a message stating that pair is temporarily unavailable. 387 | 388 | All requests are only available via HTTPS, in the interest of security best practices we do not support API calls over HTTP. 389 | *** 390 | 391 | ### Package Useful? :beer: :bug: 392 | If this package saved you some time, or if you're excited to make that next crypto-bot, feel free to throw some coins my way. If you see an issue with this golang package, please submit a pull request. 393 | ``` 394 | ETH: 0x9741C5522B85E195B92C71CE29B54A4C99D76c13 395 | BTC: 16FdfRFVPUwiKAceRSqgEfn1tmB4sVUmLh 396 | ``` 397 | *** 398 | 399 | # License 400 | This golang package is built for the cryptocurrency community and is released with MIT license. 401 | 402 | :thumbsup: :thumbsup: [ShapeShift.io](https://shapeshift.io) :thumbsup: :thumbsup: 403 | --------------------------------------------------------------------------------