/// <summary> /// Accept an exchange request. /// <param name="apiKey">Your apiKey.</param> /// <param name="apiSecret">Your apiSecretKey.</param> /// <param name="address"/>The address that funds the source amount.</parm> /// <param name="privateKey">The privateKey of the sending address.</param> /// </summary> /// <returns></returns> public async Task <Result <bool, ErrorResponse> > AcceptExchangeRequestAsync( string apiKey, string apiSecret, string address, string privateKey, AcceptExchangeRequest exchangeRequest) { #region IEnumerable <ValidationResult> validation = exchangeRequest.Validate(); if (validation.Any()) { foreach (var item in validation) { throw new ArgumentNullException(item.ErrorMessage); } } if (string.IsNullOrWhiteSpace(address)) { throw new ArgumentNullException(address); } else if (string.IsNullOrWhiteSpace(privateKey)) { throw new ArgumentNullException(nameof(privateKey)); } #endregion string token = getAuthToken(apiKey, apiSecret); var result = new Result <bool, ErrorResponse>(); string requestUri = $"{mEnv.BaseUrl}/v1/ExchangeRequests/{exchangeRequest.ID}"; string reserveTxnSinatrue = null; string executeTxnSignature = null; string reclaimTxnSignature = null; BigInteger nonce = BigInteger.Parse(GluwaService.GetNonceString()); if (exchangeRequest.Conversion.Value.IsSourceCurrencyBtc()) { BtcTxnSignature txnSignature = await getBtcTxnSignaturesAsync( exchangeRequest.Conversion.Value.ToSourceCurrency(), address, exchangeRequest.SourceAmount, exchangeRequest.Fee, exchangeRequest.DestinationAddress, exchangeRequest.ReservedFundsAddress, exchangeRequest.ReservedFundsRedeemScript, privateKey); reserveTxnSinatrue = txnSignature.ReserveTxnSignature; executeTxnSignature = txnSignature.ExecuteTxnSignature; reclaimTxnSignature = txnSignature.ReclaimTxnSignature; } else { BigInteger convertExpiryBlockNumber = BigInteger.Parse(exchangeRequest.ExpiryBlockNumber.ToString()); reserveTxnSinatrue = getGluwacoinReserveTxnSignature( exchangeRequest.Conversion.Value.ToSourceCurrency(), address, exchangeRequest.SourceAmount, exchangeRequest.Fee, exchangeRequest.DestinationAddress, exchangeRequest.Executor, nonce, convertExpiryBlockNumber, privateKey); } PatchExchangeRequest bodyParams = new PatchExchangeRequest() { SendingAddress = address, ReserveTxnSignature = reserveTxnSinatrue, Nonce = nonce.ToString(), ExecuteTxnSignature = executeTxnSignature, ReclaimTxnSignature = reclaimTxnSignature }; string json = bodyParams.ToJson(); StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); HttpRequestMessage request = new HttpRequestMessage { Method = new HttpMethod("PATCH"), RequestUri = new Uri(requestUri), Content = content }; try { using (HttpClient httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Add(AUTHORIZATION, $"{BASIC} {token}"); using (HttpResponseMessage response = await httpClient.SendAsync(request)) { if (response.IsSuccessStatusCode) { result.IsSuccess = true; result.Data = true; return(result); } string contentString = await response.Content.ReadAsStringAsync(); result.Error = ResponseHandler.GetError(response.StatusCode, requestUri, contentString); } } } catch (HttpRequestException) { result.IsSuccess = false; result.Error = ResponseHandler.GetExceptionError(); } return(result); }
private async Task <BtcTxnSignature> getBtcTxnSignaturesAsync( ECurrency currency, string address, string amount, string fee, string target, string reserve, string reservedFundsRedeemScript, string privateKey ) { GluwaClient client = new GluwaClient(mEnv); Result <BalanceResponse, ErrorResponse> getUnspentOutput = await client.GetBalanceAsync(currency, address, true); List <UnspentOutput> unspentOutputs = getUnspentOutput.Data.UnspentOutputs.OrderByDescending(u => u.Amount).ToList(); Money amountValue = Money.Parse(amount); Money feeValue = Money.Parse(fee); Money reserveAmount = amountValue + feeValue; Money totalRequiredAmountMoney = reserveAmount + feeValue; BigInteger totalRequiredAmount = new BigInteger(totalRequiredAmountMoney.Satoshi); BitcoinAddress sourceAddress = BitcoinAddress.Create(address, mEnv.Network); BitcoinAddress targetAddress = BitcoinAddress.Create(target, mEnv.Network); BitcoinAddress reserveAddress = BitcoinAddress.Create(reserve, mEnv.Network); BitcoinSecret secret = new BitcoinSecret(privateKey, mEnv.Network); List <UnspentOutput> usingUnspentOutputs = new List <UnspentOutput>(); BigInteger unspentOutputTotalAmount = BigInteger.Zero; for (int i = 0; i < unspentOutputs.Count; i++) { if (unspentOutputTotalAmount < totalRequiredAmount && i >= MAX_UNSPENTOUTPUTS_COUNT) { throw new InvalidOperationException($"Could not find up to {MAX_UNSPENTOUTPUTS_COUNT} BTC unspent outputs that can cover the amount and fee."); } if (unspentOutputTotalAmount >= totalRequiredAmount) { break; } usingUnspentOutputs.Add(unspentOutputs[i]); Money sumAmount = Money.Parse(unspentOutputs[i].Amount); unspentOutputTotalAmount += new BigInteger(sumAmount.Satoshi); } List <Coin> coins = new List <Coin>(); for (int i = 0; i < usingUnspentOutputs.Count; i++) { coins.Add(new Coin( fromTxHash: new uint256(usingUnspentOutputs[i].TxHash), fromOutputIndex: (uint)usingUnspentOutputs[i].Index, amount: usingUnspentOutputs[i].Amount, scriptPubKey: Script.FromHex(sourceAddress.ScriptPubKey.ToHex()) )); } TransactionBuilder builder = mEnv.Network.CreateTransactionBuilder(); NBitcoin.Transaction reserveTxSignature = builder .AddKeys(secret) .AddCoins(coins) .Send(reserveAddress, reserveAmount) .SetChange(sourceAddress) .SendFees(fee) .BuildTransaction(true); IEnumerable <Coin> reserveTxCoins = reserveTxSignature.Outputs.AsCoins(); Coin reserveTxCoin = reserveTxCoins.First( c => c.TxOut.ScriptPubKey.GetDestinationAddress(mEnv.Network) == reserveAddress); Script reservedFundsRedeemScriptValue = new Script(reservedFundsRedeemScript); ScriptCoin reservedCoin = new ScriptCoin(reserveTxCoin, reservedFundsRedeemScriptValue); builder = mEnv.Network.CreateTransactionBuilder(); PSBT executePsbt = builder .AddKeys(secret) .AddCoins(reservedCoin) .Send(targetAddress, amount) .SendFees(feeValue) .SetChange(reserveAddress) .BuildPSBT(true); builder = mEnv.Network.CreateTransactionBuilder(); PSBT reclaimPsbt = builder .AddKeys(secret) .AddCoins(reservedCoin) .Send(sourceAddress, amount) .SendFees(feeValue) .SetChange(reserveAddress) .BuildPSBT(true); BtcTxnSignature bTCTxnSignature = new BtcTxnSignature() { ReserveTxnSignature = reserveTxSignature.ToHex(), ExecuteTxnSignature = executePsbt.ToHex(), ReclaimTxnSignature = reclaimPsbt.ToHex() }; return(bTCTxnSignature); }
/// <summary> /// Accept quote received from POST /v1/Quote endpoint. /// </summary> /// <param name="currency">The source currency of the quote.</param> /// <param name="address">The sending address of the quote.</param> /// <param name="privateKey">The privateKey of the sending address.</param> /// <param name="quoteRequest">Request body.</param> /// <response code="202">Quote is accepted.</response> /// <response code="400_InvalidUrlParameters">Invalid URL parameters.</response> /// <response code="400_MissingBody">Request body is missing.</response> /// <response code="400_InvalidBody">Request validation errors. See InnerErrors.</response> /// <response code="400_ValidationError">Request validation errors. See InnerErrors.</response> /// <response code="403">Invalid checksum. Checksum may be wrong or expired.</response> /// <response code="404">One of the matched orders are no longer available.</response> /// <response code="500">Server error.</response> /// <returns></returns> public async Task <Result <AcceptQuoteResponse, ErrorResponse> > AcceptQuoteAsync( ECurrency currency, string address, string privateKey, AcceptQuoteRequest quoteRequest) { #region if (!currency.IsGluwaExchangeCurrency()) { throw new ArgumentOutOfRangeException($"Unsupported currency: {currency}"); } foreach (var order in quoteRequest.MatchedOrders) { IEnumerable <ValidationResult> validation = quoteRequest.Validate(currency); if (validation.Any()) { foreach (var item in validation) { throw new ArgumentNullException(item.ErrorMessage); } } } if (string.IsNullOrWhiteSpace(address)) { throw new ArgumentNullException(nameof(address)); } else if (string.IsNullOrWhiteSpace(privateKey)) { throw new ArgumentNullException(nameof(privateKey)); } #endregion var result = new Result <AcceptQuoteResponse, ErrorResponse>(); string requestUri = $"{mEnv.BaseUrl}/v1/Quote"; List <MatchedOrderRequest> matchedOrders = new List <MatchedOrderRequest>(); foreach (var matchedOrder in quoteRequest.MatchedOrders) { if (currency.IsGluwaCoinCurrency()) { BigInteger nonce = BigInteger.Parse(GluwaService.GetNonceString()); BigInteger convertExpiryBlockNumber = BigInteger.Parse(matchedOrder.ExpiryBlockNumber); string signature = getGluwacoinReserveTxnSignature( currency, address, matchedOrder.SourceAmount, matchedOrder.Fee, matchedOrder.DestinationAddress, matchedOrder.Executor, nonce, convertExpiryBlockNumber, privateKey); MatchedOrderRequest matchedOrderRequest = new MatchedOrderRequest() { OrderID = matchedOrder.OrderID, ReserveTxnSignature = signature, Nonce = nonce.ToString() }; matchedOrders.Add(matchedOrderRequest); } else { BtcTxnSignature txnSignature = await getBtcTxnSignaturesAsync( currency, address, matchedOrder.SourceAmount, matchedOrder.Fee, matchedOrder.DestinationAddress, matchedOrder.ReservedFundsAddress, matchedOrder.ReservedFundsRedeemScript, privateKey); MatchedOrderRequest matchedOrderRequest = new MatchedOrderRequest() { OrderID = matchedOrder.OrderID, ReserveTxnSignature = txnSignature.ReserveTxnSignature, ExecuteTxnSignature = txnSignature.ExecuteTxnSignature, ReclaimTxnSignature = txnSignature.ReclaimTxnSignature }; matchedOrders.Add(matchedOrderRequest); } } PutQuoteRequest bodyParams = new PutQuoteRequest() { MatchedOrders = matchedOrders, Checksum = quoteRequest.Checksum }; string json = bodyParams.ToJson(); StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); try { using (HttpClient httpClient = new HttpClient()) using (HttpResponseMessage response = await httpClient.PutAsync(requestUri, content)) { if (response.IsSuccessStatusCode) { AcceptQuoteResponse quoteResponse = await response.Content.ReadAsAsync <AcceptQuoteResponse>(); result.IsSuccess = true; result.Data = quoteResponse; return(result); } string contentString = await response.Content.ReadAsStringAsync(); result.Error = ResponseHandler.GetError(response.StatusCode, requestUri, contentString); } } catch (HttpRequestException) { result.IsSuccess = false; result.Error = ResponseHandler.GetExceptionError(); } return(result); }