2022-05-18 07:59:56 +02:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using BTCPayServer.Abstractions.Custodians;
|
2022-08-04 04:38:49 +02:00
|
|
|
using BTCPayServer.Abstractions.Custodians.Client;
|
Custodian Account UI: CRUD (#3923)
* WIP New APIs for dealing with custodians/exchanges
* Simplified things
* More API refinements + index.html file for quick viewing
* Finishing touches on spec
* Switched cryptoCode to paymentMethod as this allows us to differentiate between onchain and lightning
* Moved draft API docs to "/docs-draft"
* WIP baby steps
* Added DB migration for CustodianAccountData
* Rough but working POST /v1/api/custodian-account + GET /v1/api/custodian
* WIP + early Kraken API client
* Moved service registration to proper location
* Working create + list custodian accounts + permissions + WIP Kraken client
* Kraken API Balances call is working
* Added asset balances to response
* List Custodian Accounts call does not load assetBalances by default, because it can fail. Can be requested when needed.
* Call to get the details of 1 specific custodian account
* Added permissions to swagger
* Added "tradableAssetPairs" to Kraken custodian response + cache the tradable pairs in memory for 24 hours
* Removed unused file
* WIP + Moved files to better locations
* Updated docs
* Working API endpoint to get info on a trade (same response as creating a new trade)
* Working API endpoints for Deposit + Trade + untested Withdraw
* Delete custodian account
* Trading works, better error handling, cleanup
* Working withdrawals + New endpoint for getting bid/ask prices
* Completed withdrawals + new endpoint for getting info on a past withdrawal to simplify testing, Enums are output as strings,
* Better error handling when withdrawing to a wrong destination
* WithdrawalAddressName in config is now a string per currency (dictionary)
* Added TODOs
* Only show the custodian account "config" to users who are allowed
* Added the new permissions to the API Keys UI
* Renamed KrakenClient to KrakenExchange
* WIP Kraken Config Form
* Removed files for UI again, will make separate PR later
* Fixed docs + Refactored to use PaymentMethod more + Added "name" to custodian account + Using cancelationToken everywhere
* Updated withdrawal info docs
* First unit test
* Complete tests for /api/v1/custodians and /api/v1/custodian-accounts endpoints + Various improvements and fixes
* Mock custodian and more exceptions
* Many more tests + cleanup, moved files to better locations
* More tests
* WIP more tests
* Greenfield API tests complete
* Added missing "Name" column
* Cleanup, TODOs and beginning of Kraken Tests
* Added Kraken tests using public endpoints + handling of "SATS" currency
* Added 1st mocked Kraken API call: GetAssetBalancesAsync
* Added assert for bad config
* Mocked more Kraken API responses + added CreationDate to withdrawal response
* pr review club changes
* Make Kraken Custodian a plugin
* Re-added User-Agent header as it is required
* Fixed bug in market trade on Kraken using a percentage as qty
* A short delay so Kraken has the time to execute the market order and we don't fetch the details too quickly.
* Merged the draft swagger into the main swagger since it didn't work anymore
* Fixed API permissions test
* Removed 2 TODOs
* Fixed unit test
* After a utxo rescan, the cached balance should be invalidated
* Fixed Kraken plugin build issues
* Added Kraken plugin to build
* WIP UI + config form
* Create custodian account almost working - only need to add in the config form
* Working form, but lacks refinement
* Viewing balances + Editing custodian account works, but cannot change the withdrawal destination config because that is an object using a name with [] in it
* cleanup
* Minor cleanup, comments
* Working: Delete custodian account
* Moved the MockCustodian used in tests to a new plugin + linked it to the tests
* WIP viewing custodian account balances
* Split the Mock custodian into a Mock + Fake, various UI improvements and minor fixes
* Minor UI fixes
* Removed broken link
* Removed links to anchors as they cannot pass the tests since they use JavaScript
* Removed non-existing link. Even though it was commented out, the test still broke?
* Added TODOs
* Now throwing BadConfigException if API key is invalid
* UI improvements
* Commented out unfinished API endpoints. Can be finished later.
* Show fiat value for fiat assets
* Removed Kraken plugin so I can make a PR
Removed more Kraken files
* Add experimental route on UICustodianAccountsControllre
* Removed unneeded code
* Cleanup code
* Processed Nicolas' feedback
Co-authored-by: Kukks <evilkukka@gmail.com>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2022-07-07 15:42:50 +02:00
|
|
|
using BTCPayServer.Abstractions.Form;
|
2022-05-18 07:59:56 +02:00
|
|
|
using BTCPayServer.Client.Models;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
|
|
namespace BTCPayServer.Services.Custodian.Client.MockCustodian;
|
|
|
|
|
|
|
|
public class MockCustodian : ICustodian, ICanDeposit, ICanTrade, ICanWithdraw
|
|
|
|
{
|
|
|
|
public const string DepositPaymentMethod = "BTC-OnChain";
|
|
|
|
public const string DepositAddress = "bc1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
|
|
|
public const string TradeId = "TRADE-ID-001";
|
|
|
|
public const string TradeFromAsset = "EUR";
|
|
|
|
public const string TradeToAsset = "BTC";
|
|
|
|
public static readonly decimal TradeQtyBought = new decimal(1);
|
|
|
|
public static readonly decimal TradeFeeEuro = new decimal(12.5);
|
|
|
|
public static readonly decimal BtcPriceInEuro = new decimal(30000);
|
|
|
|
public const string WithdrawalPaymentMethod = "BTC-OnChain";
|
|
|
|
public const string WithdrawalAsset = "BTC";
|
|
|
|
public const string WithdrawalId = "WITHDRAWAL-ID-001";
|
|
|
|
public static readonly decimal WithdrawalAmount = new decimal(0.5);
|
|
|
|
public static readonly decimal WithdrawalFee = new decimal(0.0005);
|
|
|
|
public const string WithdrawalTransactionId = "yyy";
|
|
|
|
public const string WithdrawalTargetAddress = "bc1qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
|
|
|
|
public const WithdrawalResponseData.WithdrawalStatus WithdrawalStatus = WithdrawalResponseData.WithdrawalStatus.Queued;
|
|
|
|
public static readonly decimal BalanceBTC = new decimal(1.23456);
|
|
|
|
public static readonly decimal BalanceLTC = new decimal(50.123456);
|
|
|
|
public static readonly decimal BalanceUSD = new decimal(1500.55);
|
|
|
|
public static readonly decimal BalanceEUR = new decimal(1235.15);
|
|
|
|
|
|
|
|
public string Code
|
|
|
|
{
|
|
|
|
get => "mock";
|
|
|
|
}
|
|
|
|
|
|
|
|
public string Name
|
|
|
|
{
|
|
|
|
get => "MOCK Exchange";
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<Dictionary<string, decimal>> GetAssetBalancesAsync(JObject config, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
var r = new Dictionary<string, decimal>()
|
|
|
|
{
|
|
|
|
{ "BTC", BalanceBTC }, { "LTC", BalanceLTC }, { "USD", BalanceUSD }, { "EUR", BalanceEUR },
|
|
|
|
};
|
|
|
|
return Task.FromResult(r);
|
|
|
|
}
|
|
|
|
|
Custodian Account UI: CRUD (#3923)
* WIP New APIs for dealing with custodians/exchanges
* Simplified things
* More API refinements + index.html file for quick viewing
* Finishing touches on spec
* Switched cryptoCode to paymentMethod as this allows us to differentiate between onchain and lightning
* Moved draft API docs to "/docs-draft"
* WIP baby steps
* Added DB migration for CustodianAccountData
* Rough but working POST /v1/api/custodian-account + GET /v1/api/custodian
* WIP + early Kraken API client
* Moved service registration to proper location
* Working create + list custodian accounts + permissions + WIP Kraken client
* Kraken API Balances call is working
* Added asset balances to response
* List Custodian Accounts call does not load assetBalances by default, because it can fail. Can be requested when needed.
* Call to get the details of 1 specific custodian account
* Added permissions to swagger
* Added "tradableAssetPairs" to Kraken custodian response + cache the tradable pairs in memory for 24 hours
* Removed unused file
* WIP + Moved files to better locations
* Updated docs
* Working API endpoint to get info on a trade (same response as creating a new trade)
* Working API endpoints for Deposit + Trade + untested Withdraw
* Delete custodian account
* Trading works, better error handling, cleanup
* Working withdrawals + New endpoint for getting bid/ask prices
* Completed withdrawals + new endpoint for getting info on a past withdrawal to simplify testing, Enums are output as strings,
* Better error handling when withdrawing to a wrong destination
* WithdrawalAddressName in config is now a string per currency (dictionary)
* Added TODOs
* Only show the custodian account "config" to users who are allowed
* Added the new permissions to the API Keys UI
* Renamed KrakenClient to KrakenExchange
* WIP Kraken Config Form
* Removed files for UI again, will make separate PR later
* Fixed docs + Refactored to use PaymentMethod more + Added "name" to custodian account + Using cancelationToken everywhere
* Updated withdrawal info docs
* First unit test
* Complete tests for /api/v1/custodians and /api/v1/custodian-accounts endpoints + Various improvements and fixes
* Mock custodian and more exceptions
* Many more tests + cleanup, moved files to better locations
* More tests
* WIP more tests
* Greenfield API tests complete
* Added missing "Name" column
* Cleanup, TODOs and beginning of Kraken Tests
* Added Kraken tests using public endpoints + handling of "SATS" currency
* Added 1st mocked Kraken API call: GetAssetBalancesAsync
* Added assert for bad config
* Mocked more Kraken API responses + added CreationDate to withdrawal response
* pr review club changes
* Make Kraken Custodian a plugin
* Re-added User-Agent header as it is required
* Fixed bug in market trade on Kraken using a percentage as qty
* A short delay so Kraken has the time to execute the market order and we don't fetch the details too quickly.
* Merged the draft swagger into the main swagger since it didn't work anymore
* Fixed API permissions test
* Removed 2 TODOs
* Fixed unit test
* After a utxo rescan, the cached balance should be invalidated
* Fixed Kraken plugin build issues
* Added Kraken plugin to build
* WIP UI + config form
* Create custodian account almost working - only need to add in the config form
* Working form, but lacks refinement
* Viewing balances + Editing custodian account works, but cannot change the withdrawal destination config because that is an object using a name with [] in it
* cleanup
* Minor cleanup, comments
* Working: Delete custodian account
* Moved the MockCustodian used in tests to a new plugin + linked it to the tests
* WIP viewing custodian account balances
* Split the Mock custodian into a Mock + Fake, various UI improvements and minor fixes
* Minor UI fixes
* Removed broken link
* Removed links to anchors as they cannot pass the tests since they use JavaScript
* Removed non-existing link. Even though it was commented out, the test still broke?
* Added TODOs
* Now throwing BadConfigException if API key is invalid
* UI improvements
* Commented out unfinished API endpoints. Can be finished later.
* Show fiat value for fiat assets
* Removed Kraken plugin so I can make a PR
Removed more Kraken files
* Add experimental route on UICustodianAccountsControllre
* Removed unneeded code
* Cleanup code
* Processed Nicolas' feedback
Co-authored-by: Kukks <evilkukka@gmail.com>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2022-07-07 15:42:50 +02:00
|
|
|
public Task<Form> GetConfigForm(JObject config, string locale, CancellationToken cancellationToken = default)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-05-18 07:59:56 +02:00
|
|
|
public Task<DepositAddressData> GetDepositAddressAsync(string paymentMethod, JObject config, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (paymentMethod.Equals(DepositPaymentMethod))
|
|
|
|
{
|
|
|
|
var r = new DepositAddressData();
|
|
|
|
r.Address = DepositAddress;
|
|
|
|
return Task.FromResult(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new CustodianFeatureNotImplementedException($"Only BTC-OnChain is implemented for {this.Name}");
|
|
|
|
}
|
|
|
|
|
|
|
|
public string[] GetDepositablePaymentMethods()
|
|
|
|
{
|
|
|
|
return new[] { "BTC-OnChain" };
|
|
|
|
}
|
|
|
|
|
|
|
|
public List<AssetPairData> GetTradableAssetPairs()
|
|
|
|
{
|
|
|
|
var r = new List<AssetPairData>();
|
2022-08-04 04:38:49 +02:00
|
|
|
r.Add(new AssetPairData("BTC", "EUR", (decimal) 0.0001));
|
2022-05-18 07:59:56 +02:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
private MarketTradeResult GetMarketTradeResult()
|
|
|
|
{
|
|
|
|
var ledgerEntries = new List<LedgerEntryData>();
|
|
|
|
ledgerEntries.Add(new LedgerEntryData("BTC", TradeQtyBought, LedgerEntryData.LedgerEntryType.Trade));
|
|
|
|
ledgerEntries.Add(new LedgerEntryData("EUR", -1 * TradeQtyBought * BtcPriceInEuro, LedgerEntryData.LedgerEntryType.Trade));
|
|
|
|
ledgerEntries.Add(new LedgerEntryData("EUR", -1 * TradeFeeEuro, LedgerEntryData.LedgerEntryType.Fee));
|
|
|
|
return new MarketTradeResult(TradeFromAsset, TradeToAsset, ledgerEntries, TradeId);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<MarketTradeResult> TradeMarketAsync(string fromAsset, string toAsset, decimal qty, JObject config, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (!fromAsset.Equals("EUR") || !toAsset.Equals("BTC"))
|
|
|
|
{
|
|
|
|
throw new WrongTradingPairException(fromAsset, toAsset);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qty != TradeQtyBought)
|
|
|
|
{
|
|
|
|
throw new InsufficientFundsException($"With {Name}, you can only buy {TradeQtyBought} {TradeToAsset} with {TradeFromAsset} and nothing else.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return Task.FromResult(GetMarketTradeResult());
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<MarketTradeResult> GetTradeInfoAsync(string tradeId, JObject config, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (tradeId == TradeId)
|
|
|
|
{
|
|
|
|
return Task.FromResult(GetMarketTradeResult());
|
|
|
|
}
|
|
|
|
|
|
|
|
return Task.FromResult<MarketTradeResult>(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<AssetQuoteResult> GetQuoteForAssetAsync(string fromAsset, string toAsset, JObject config, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (fromAsset.Equals(TradeFromAsset) && toAsset.Equals(TradeToAsset))
|
|
|
|
{
|
|
|
|
return Task.FromResult(new AssetQuoteResult(TradeFromAsset, TradeToAsset, BtcPriceInEuro, BtcPriceInEuro));
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new WrongTradingPairException(fromAsset, toAsset);
|
|
|
|
//throw new AssetQuoteUnavailableException(pair);
|
|
|
|
}
|
|
|
|
|
|
|
|
private WithdrawResult CreateWithdrawResult()
|
|
|
|
{
|
|
|
|
var ledgerEntries = new List<LedgerEntryData>();
|
|
|
|
ledgerEntries.Add(new LedgerEntryData(WithdrawalAsset, WithdrawalAmount - WithdrawalFee, LedgerEntryData.LedgerEntryType.Withdrawal));
|
|
|
|
ledgerEntries.Add(new LedgerEntryData(WithdrawalAsset, WithdrawalFee, LedgerEntryData.LedgerEntryType.Fee));
|
|
|
|
DateTimeOffset createdTime = new DateTimeOffset(2021, 9, 1, 6, 45, 0, new TimeSpan(-7, 0, 0));
|
|
|
|
var r = new WithdrawResult(WithdrawalPaymentMethod, WithdrawalAsset, ledgerEntries, WithdrawalId, WithdrawalStatus, createdTime, WithdrawalTargetAddress, WithdrawalTransactionId);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<WithdrawResult> WithdrawAsync(string paymentMethod, decimal amount, JObject config, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (paymentMethod == WithdrawalPaymentMethod)
|
|
|
|
{
|
|
|
|
if (amount == WithdrawalAmount)
|
|
|
|
{
|
|
|
|
return Task.FromResult(CreateWithdrawResult());
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new InsufficientFundsException($"{Name} only supports withdrawals of {WithdrawalAmount}");
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new CannotWithdrawException(this, paymentMethod, $"Only {WithdrawalPaymentMethod} can be withdrawn from {Name}");
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task<WithdrawResult> GetWithdrawalInfoAsync(string paymentMethod, string withdrawalId, JObject config, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
if (withdrawalId == WithdrawalId && WithdrawalPaymentMethod.Equals(paymentMethod))
|
|
|
|
{
|
|
|
|
return Task.FromResult(CreateWithdrawResult());
|
|
|
|
}
|
|
|
|
|
|
|
|
return Task.FromResult<WithdrawResult>(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public string[] GetWithdrawablePaymentMethods()
|
|
|
|
{
|
|
|
|
return GetDepositablePaymentMethods();
|
|
|
|
}
|
|
|
|
}
|