bchavez / Coinbase

:moneybag: A .NET/C# implementation of the Coinbase API.
https://developers.coinbase.com/api/v2
MIT License
170 stars 93 forks source link

Can't place buy orders #62

Closed astrohart closed 5 years ago

astrohart commented 5 years ago

I am utilizing the very latest version of this library. It's excellent! However, I try to place a buy order for an amount of Bitcoin with it, and I keep getting 400 Bad Request from the server.

I think I know why. The example on the site https://developers.coinbase.com/api/v2#place-buy-order says that the request body is supposed to be:

{
    "amount": "10",
    "currency": "BTC",
    "payment_method": "83562370-3e5c-51db-87da-752af5ab9559"
}

Notice how the quantity of BTC to buy is in quotes, i.e., is a string type. However, in your PlaceBuy object, I see that the Amount property is a decimal, and I did some digging in the exception and managed to dig up the request body that I am sending as such:

{
    "amount": 0.30807114,
    "currency": "BTC",
    "payment_method": "1cc4f84e-dee5-54e3-a226-c3f5ad1aae11",
    "agree_btc_amount_varies": true,
    "commit": true,
    "quote": false
}

The emphasis is mine, obviously. But notice the discrepancy. The docs want the amount of BTC as a JSON String, but your library is sending it out as a JSON number type. I think there is a bug.

How do we get this fixed? Thanks! :-)

astrohart commented 5 years ago

As a hunch, I modified the Amount property of the PlaceBuy class to be a string data type instead of decimal? in the Coinbase.Models.PlaceBuy class. It should really just be that easy.

I will wait and see if my bot bites on it, and then let you know.

bchavez commented 5 years ago

Hi Brian,

There should be a JSON error message returned somewhere in the response. Can you provide the HTTP request and response JSON bodies when the error occurs?

Thanks, Brian

astrohart commented 5 years ago

Hi,

Yes, I know that. However, I've tried calling FlurlHttpException.GetJsonResponseStringAsync and it says, "Cannot access a disposed object.".

I have also tried the HoistResponse method in your README.md, and I get the same ObjectDisposedException. It seems that, as per usual, Microsoft, in their infinite wisdom, do not actually initialize the proper field of the HttpContent object and you can only get the response body with Reflection.

Is there another way?

Here is my pertinent code:

            // okay, now place a buy order
            var buyTask = _client
                .Buys
                .PlaceBuyOrderAsync(AccountId,
                    placeBuy, CancellationToken.None);
            buyTask.Wait();

            buy = buyTask.Result;

            ApiIssueHelper.HandleErrorsAndWarnings(buy);
            if (buy != null &&
                (buy.HasError() || buy.HasWarnings()))
                return;

My APIErrorHelper object is implemented thus:

    public static class ApiIssueHelper
    {
        public static void HandleErrorsAndWarnings(JsonResponse response)
        {
            if (response.HasError())
            {
                LogAPIErrors(response.Errors);
            }

            if (response.HasWarnings())
            {
                LogAPIWarnings(response.Warnings);
            }
        }

        private static void LogAPIErrors(ICollection<Error> errors)
        {
            if (errors == null || errors.Count == 0)
                return;

            foreach (var error in errors)
                DebugUtils.WriteLine(DebugLevel.Error, $@"ApiIssueHelper.LogAPIErrors: {error.Message}");
        }

        private static void LogAPIWarnings(ICollection<Warning> warnings)
        {
            if (warnings == null || warnings.Count == 0)
                return;

            foreach (var warning in warnings)
                DebugUtils.WriteLine(DebugLevel.Warning, $@"ApiIssueHelper.LogAPIWarnings: {warning.Message}");
        }
    }

This code never gets called. The FlurlHttpException is thrown byPlaceBuyOrderAsyncand it seems as if the400 Bad Responsegetting returned by the server does not allow it to fill theErrorsandWarningscollections of a validResponse``` object. How DO I access the errors and warnings from this object?

I am going to go into the sources of your library and fiddle with the implementation of PlaceBuyOrderAsync.

bchavez commented 5 years ago
  1. Install Fiddler.

  2. Configure Fiddler to decrypt HTTPS traffic; IE: trust Fiddler's root certificate.

    Fiddler_3542

  3. Configure Fiddler to enable proxy mode on localhost port 8888.

    Fiddler_2965

  4. Reboot.

  5. Start Fiddler.

  6. Call coinbaseClient.EnableFiddlerDebugProxy("http://localhost.:8888")

    var cfg = new Coinbase.ApiKeyConfig
    {
      ApiKey = "fff",
      ApiSecret = "ggg"
    };
    var c = new Coinbase.CoinbaseClient(cfg);
    
    c.EnableFiddlerDebugProxy("http://localhost.:8888");
    
    var price = await c.Data.GetBuyPriceAsync("BTC-USD");
    price.Data.Dump();

    LINQPad_3540

  7. Observe HTTPS traffic:

Fiddler_3539

astrohart commented 5 years ago

Okay, so I have done as you recommended above. The request body has the following JSON:

{
    "amount": 0.30754205,
    "currency": "BTC",
    "payment_method": "1cc4f84e-dee5-54e3-a226-c3f5ad1aae11",
    "agree_btc_amount_varies": true,
    "commit": true,
    "quote": false
}

The response JSON is:

{
    "errors": [
        {
            "id": "validation_error",
            "message": "You do not have enough money in this account.",
            "field": "base"
        }
    ]
}

I do not understand this response. I am sure I have enough. The payment method is the ID for my USD wallet, and I am sure I have enough funds: fig01

astrohart commented 5 years ago

Maybe part of my confusion stems from the API docs not being very clear about how to utilize the parameters of the request JSON.

What I'm trying to do is to utilize all the funds in my USD Wallet to buy the max Bitcoin. I have an object in my code which utilizes the Commission schedule information listed on the page https://support.coinbase.com/customer/en/portal/articles/2109597-coinbase-pricing-fees-disclosures. In that, it takes the amount of currency I want to utilize from the USD wallet and then "charges" the "commission" on it and then expresses it in BTC utilizing the current spot price that it's just read from the API, and then I place the buy order.

So, I do not understand why it is telling me I don't have enough money to spend on the BTC.

astrohart commented 5 years ago

So I issued a request to the API endpoint https://api.coinbase.com/v2/payment-methods and got the following JSON:

{
    "pagination": {
        "ending_before": null,
        "starting_after": null,
        "previous_ending_before": null,
        "next_starting_after": null,
        "limit": 25,
        "order": "desc",
        "previous_uri": null,
        "next_uri": null
    },
    "data": [
        {
            /* ... */
        },
        {
            /* ... */
        },
        {
            "id": "1cc4f84e-dee5-54e3-a226-c3f5ad1aae11",
            "type": "fiat_account",
            "name": "USD Wallet",
            "currency": "USD",
            "primary_buy": true,
            "primary_sell": true,
            "instant_buy": true,
            "instant_sell": true,
            "created_at": "2017-12-09T17:15:48Z",
            "updated_at": "2017-12-09T17:15:48Z",
            "resource": "payment_method",
            "resource_path": "/v2/payment-methods/1cc4f84e-dee5-54e3-a226-c3f5ad1aae11",
            "limits": {
                "type": "fiat_account",
                "name": "Coinbase Account"
            },
            "allow_buy": true,
            "allow_sell": true,
            "allow_deposit": false,
            "allow_withdraw": false,
            "fiat_account": {
                "id": "60dee830-641f-5be2-be00-4a0755badf1c",
                "resource": "account",
                "resource_path": "/v2/accounts/60dee830-641f-5be2-be00-4a0755badf1c"
            },
            "verified": true
        }
    ]
}

That is the wallet with all the bux in it, so -- I do not get it...why won't it let me buy bitcoin with the money in my USD wallet?

bchavez commented 5 years ago

Thanks for the reply. Now we're finally narrowing down the root cause of the issue.

I don't know why you don't have full access to your USD wallet, but what I find suspicious is the $0.00 / $3,002.99 balance. Did you recently transfer money from a bank account? If so, maybe you need to wait for $$ to clear through ACH. I've noticed it takes about 3 to 4 business days for my transfers to complete from Bank to Coinbase (in some cases I've experienced longer delays).

Out an abundance of caution or if it's already been 3-4 days, your next best option is to contact Coinbase support https://support.coinbase.com and ask why you don't have full access. They usually respond very quickly, even on weekends; call or email. I don't work for Coinbase, so I can't help you much with this issue.

But as for now, I'd like to close the issue since this is looking like an issue with the Coinbase account.

Lastly, you might want to consider using a Coinbase Pro account for trading in the actual market at spot prices. https://pro.coinbase.com

astrohart commented 5 years ago

OMG! I actually need to utilize the "Transfer money between accounts" API, right?

Gosh, I am so confused. Basically, my thought process was, to start:

  1. Deposit $XXX bux into my "USD Wallet" from my bank.
  2. Have my bot wait until the price of BTC falls below a certain threshold.
  3. Then have my bot utilize the funds in the BTC wallet in order to buy some BTC.

Brian

astrohart commented 5 years ago

The 0.00/3002.99 comes from the fact that the funds aren't available for withdrawal back to my bank yet, but the Coinbase website and support says that the funds can be utilized in order to purchase BTC right away -- when I click on the website to buy BTC with these funds, it shows the full amount and lets me.

astrohart commented 5 years ago

Fine, but the question remains: I am calling the API correctly, right? (i.e., I am filling up the fields of the PlaceBuy object correctly, right?

bchavez commented 5 years ago

If you called get payment-methods, and got back an id 1cc4f84e-dee5-54e3-a226-c3f5ad1aae11 of your USD Wallet as a payment method, then, it should work. As far as I know, it should work if the funds are available. However, the funds do not appear to be available in your account; yet.

The 0.00/3002.99 comes from the fact that the funds aren't available for withdrawal back to my bank yet, but the Coinbase website and support says that the funds can be utilized in order to purchase BTC right away

I do not think that is the case. You will want to contact Coinbase to clarify your statement above. Again, as far as I know, for bank ACH deposits, you MUST wait for funds to clear before using funds in your USD wallet. However, if the payment method is a credit card, then you are able to purchase BTC immediately at spot prices.

zabidin901 commented 1 year ago

So I ran into this exact issue, I had to change my currency to be USD and account for the transaction fee this is what I changed and it worked for me:

                            var buy = new PlaceBuy
                            {
                                Amount = Decimal.Multiply(cashAccount.Data.Balance.Amount, (decimal).99),
                                Currency = "USD",
                                PaymentMethod = paymentMethod?.Id
                            };

                            var response = await client.Buys.PlaceBuyOrderAsync(etherAccount.Data.Id, buy);