Skip to content

Commit

Permalink
Automatic coin selection strageties
Browse files Browse the repository at this point in the history
  • Loading branch information
Rodrigo Sanchez committed Jun 16, 2023
1 parent ac6f3f3 commit f74aa37
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 40 deletions.
30 changes: 20 additions & 10 deletions src/Data/DbInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public static void Initialize(IServiceProvider serviceProvider)
{
ChannelAdminMacaroon =
"0201036c6e6402f801030a108cdfeb2614b8335c11aebb358f888d6d1201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620c999e1a30842cbae3f79bd633b19d5ec0d2b6ebdc4880f6f5d5c230ce38f26ab",
Endpoint = Constants.ALICE_HOST,
Endpoint = Constants.ALICE_HOST,
Name = "Alice",
CreationDatetime = DateTimeOffset.UtcNow,
PubKey = "02dc2ae598a02fc1e9709a23b68cd51d7fa14b1132295a4d75aa4f5acd23ee9527",
Expand All @@ -240,7 +240,7 @@ public static void Initialize(IServiceProvider serviceProvider)
};

_ = Task.Run(() => nodeRepository.AddAsync(carol)).Result;

//Bob node from Polar (BOB) LND 0.15.5 -> check devnetwork.zip polar file
var bob = new Node
{
Expand All @@ -253,7 +253,7 @@ public static void Initialize(IServiceProvider serviceProvider)
Users = new List<ApplicationUser>(),
AutosweepEnabled = false
};

_ = Task.Run(() => nodeRepository.AddAsync(bob)).Result;

//Add user to the channel
Expand All @@ -262,13 +262,13 @@ public static void Initialize(IServiceProvider serviceProvider)

var carolUpdateResult = applicationDbContext.Update(adminUser);
}

var internalWallet = applicationDbContext.InternalWallets.FirstOrDefault();
if (internalWallet == null)
{
//Default Internal Wallet
internalWallet = CreateWallet.CreateInternalWallet(logger);

applicationDbContext.Add(internalWallet);
applicationDbContext.SaveChanges();
}
Expand All @@ -280,14 +280,14 @@ public static void Initialize(IServiceProvider serviceProvider)
"social mango annual basic work brain economy one safe physical junk other toy valid load cook napkin maple runway island oil fan legend stem";
var wallet2Seed =
"solar goat auto bachelor chronic input twin depth fork scale divorce fury mushroom column image sauce car public artist announce treat spend jacket physical";

logger?.LogInformation("Wallet 1 seed: {MnemonicString}", wallet1Seed);
logger?.LogInformation("Wallet 2 seed: {MnemonicString}", wallet2Seed);

var user1Key = CreateWallet.CreateUserKey("Key 1", adminUser.Id, wallet1Seed);
var user2Key = CreateWallet.CreateUserKey("Key 2", financeUser.Id, wallet2Seed);
var testingLegacyMultisigWallet = CreateWallet.LegacyMultiSig(internalWallet, "1'", user1Key, user2Key);

var testingLegacyMultisigWallet = CreateWallet.LegacyMultiSig(internalWallet, "1'", user1Key, user2Key);
var testingMultisigWallet = CreateWallet.MultiSig(internalWallet, "0", user1Key, user2Key);
var testingSinglesigWallet = CreateWallet.SingleSig(internalWallet, "1");
var testingSingleSigBIP39Wallet = CreateWallet.BIP39Singlesig();
Expand Down Expand Up @@ -329,6 +329,16 @@ public static void Initialize(IServiceProvider serviceProvider)
minerRPC.SendToAddress(singlesigAddress, singlesigFundCoins);
minerRPC.SendToAddress(singleSigBIP39Address, singleSigBIP39FundCoins);

// Create a lot of utxos and send them to the single sig wallet
// Random r = new Random();
// for (var i = 0; i < 1000; i++)
// {
// var keypath = nbxplorerClient.GetUnused(singlesigDerivationStrategy, DerivationFeature.Deposit);
// decimal coin = r.Next(536, 10000000);
// var randomCoint = Money.Coins(coin / 100000000); //20BTC
// minerRPC.SendToAddress(keypath.Address, randomCoint);
// }

//6 blocks to confirm
minerRPC.Generate(6);

Expand Down Expand Up @@ -368,7 +378,7 @@ public static void Initialize(IServiceProvider serviceProvider)
applicationDbContext.Add(testingSingleSigBIP39Wallet);
}
}

applicationDbContext.SaveChanges();
}

Expand Down
11 changes: 7 additions & 4 deletions src/Helpers/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class Constants
public static readonly bool PUSH_NOTIFICATIONS_ONESIGNAL_ENABLED;
public static readonly bool ENABLE_HW_SUPPORT;
public static readonly bool FEE_SELECTION_ENABLED = false; // Incomplete feature, remove this line when it's ready
public static readonly bool NBXPLORER_ENABLE_CUSTOM_BACKEND = false;

// Connections
public static readonly string POSTGRES_CONNECTIONSTRING = "Host=localhost;Port=5432;Database=fundsmanager;Username=rw_dev;Password=rw_dev";
Expand Down Expand Up @@ -70,9 +71,9 @@ public class Constants
public static readonly long ANCHOR_CLOSINGS_MINIMUM_SATS;
public static readonly string DEFAULT_DERIVATION_PATH = "48'/1'";
public static readonly int SESSION_TIMEOUT_MILLISECONDS = 3_600_000;

//Sat/vb ratio
public static decimal MIN_SAT_PER_VB_RATIO = 0.9m;
public static decimal MIN_SAT_PER_VB_RATIO = 0.9m;
public static decimal MAX_SAT_PER_VB_RATIO = 2.0m;
/// <summary>
/// Max ratio of the tx total input sum that could be used as fee
Expand Down Expand Up @@ -102,6 +103,8 @@ static Constants()

ENABLE_HW_SUPPORT = Environment.GetEnvironmentVariable("ENABLE_HW_SUPPORT") != "false"; // We default to true

NBXPLORER_ENABLE_CUSTOM_BACKEND = Environment.GetEnvironmentVariable("NBXPLORER_ENABLE_CUSTOM_BACKEND") == "true";

// Connections
POSTGRES_CONNECTIONSTRING = Environment.GetEnvironmentVariable("POSTGRES_CONNECTIONSTRING") ?? POSTGRES_CONNECTIONSTRING;

Expand Down Expand Up @@ -198,11 +201,11 @@ static Constants()

var timeout = Environment.GetEnvironmentVariable("SESSION_TIMEOUT_MILLISECONDS");
if (timeout != null) SESSION_TIMEOUT_MILLISECONDS = int.Parse(timeout);

//Sat/vb ratio
var minSatPerVbRatioEnv = Environment.GetEnvironmentVariable("MIN_SAT_PER_VB_RATIO");
MIN_SAT_PER_VB_RATIO = minSatPerVbRatioEnv!= null ? decimal.Parse(minSatPerVbRatioEnv) : MIN_SAT_PER_VB_RATIO;

var maxSatPerVbRatioEnv = Environment.GetEnvironmentVariable("MAX_SAT_PER_VB_RATIO");
MAX_SAT_PER_VB_RATIO = maxSatPerVbRatioEnv!= null ? decimal.Parse(maxSatPerVbRatioEnv) : MAX_SAT_PER_VB_RATIO;

Expand Down
2 changes: 1 addition & 1 deletion src/Pages/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async=""></script>
<script>
if ("@Constants.PUSH_NOTIFICATIONS_ONESIGNAL_ENABLED")
{
{
window.OneSignal = window.OneSignal || [];
OneSignal.push(function() {
OneSignal.init({
Expand Down
1 change: 1 addition & 0 deletions src/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"POSTGRES_CONNECTIONSTRING": "Host=127.0.0.1;Port=5432;Database=fundsmanager;Username=rw_dev;Password=rw_dev",
"BITCOIN_NETWORK": "REGTEST",
"MAXIMUM_WITHDRAWAL_BTC_AMOUNT": "21000000",
"NBXPLORER_ENABLE_CUSTOM_BACKEND": "true",
"NBXPLORER_URI": "http://127.0.0.1:32838",
"NBXPLORER_BTCRPCUSER": "polaruser",
"NBXPLORER_BTCRPCPASSWORD": "polarpass",
Expand Down
34 changes: 32 additions & 2 deletions src/Services/CoinSelectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ public interface ICoinSelectionService
/// <param name="derivationStrategy"></param>
public Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase derivationStrategy);

/// <summary>
/// Gets the UTXOs for a wallet that are not locked in other transactions, but with a limit
/// </summary>
/// <param name="derivationStrategy"></param>
/// <param name="strategy"></param>
/// <param name="limit"></param>
/// <param name="amount"></param>
/// <param name="tolerance"></param>
/// <param name="closestTo"></param>
public Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase derivationStrategy, CoinSelectionStrategy strategy, int limit, long amount, long tolerance, long closestTo);

/// <summary>
/// Locks the UTXOs for using in a specific transaction
/// </summary>
Expand Down Expand Up @@ -121,10 +132,9 @@ public async Task<List<UTXO>> GetLockedUTXOsForRequest(IBitcoinRequest bitcoinRe
return utxos.Confirmed.UTXOs.Where(utxo => lockedUTXOsList.Contains(utxo.Outpoint.Hash.ToString())).ToList();
}

public async Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase derivationStrategy)
private async Task<List<UTXO>> FilterUnlockedUTXOs(UTXOChanges? utxoChanges)
{
var lockedUTXOs = await _fmutxoRepository.GetLockedUTXOs();
var utxoChanges = await _nbXplorerService.GetUTXOsAsync(derivationStrategy);
utxoChanges.RemoveDuplicateUTXOs();

var availableUTXOs = new List<UTXO>();
Expand All @@ -145,6 +155,26 @@ public async Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase deri
return availableUTXOs;
}

public async Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase derivationStrategy)
{
var utxoChanges = await _nbXplorerService.GetUTXOsAsync(derivationStrategy);
return await FilterUnlockedUTXOs(utxoChanges);
}

public async Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase derivationStrategy, CoinSelectionStrategy strategy, int limit, long amount, long tolerance, long closestTo)
{
UTXOChanges utxoChanges;
if (Constants.NBXPLORER_ENABLE_CUSTOM_BACKEND)
{
utxoChanges = await _nbXplorerService.GetUTXOsByLimitAsync(derivationStrategy, strategy, limit, amount, tolerance, closestTo);
}
else
{
utxoChanges = await _nbXplorerService.GetUTXOsAsync(derivationStrategy);
}
return await FilterUnlockedUTXOs(utxoChanges);
}

/// <summary>
/// Gets UTXOs confirmed from the wallet of the request
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Services/LightningService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,7 @@ await foreach (var response in closeChannelResult.ResponseStream.ReadAllAsync())
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting wallet balance for wallet: {WalletId}", wallet.Id);
_logger.LogError(e, "Error while getting wallet addresss for wallet: {WalletId}", wallet.Id);
}

var result = keyPathInformation?.Address ?? null;
Expand Down
54 changes: 54 additions & 0 deletions src/Services/NBXplorerService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System.Text.Json;
using FundsManager.Helpers;
using Microsoft.AspNetCore.WebUtilities;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json;

namespace FundsManager.Services;

Expand All @@ -23,6 +27,8 @@ public interface INBXplorerService

public Task<UTXOChanges> GetUTXOsAsync(DerivationStrategyBase extKey, CancellationToken cancellation = default);

public Task<UTXOChanges> GetUTXOsByLimitAsync(DerivationStrategyBase extKey, CoinSelectionStrategy strategy = CoinSelectionStrategy.SmallestFirst, int limit = 0, long amount = 0, long tolerance = 0, long closestTo = 0, CancellationToken cancellation = default);

public Task<GetFeeRateResult> GetFeeRateAsync(int blockCount, FeeRate fallbackFeeRate,
CancellationToken cancellation = default);

Expand Down Expand Up @@ -61,6 +67,14 @@ public class MempoolRecommendedFees
public int MinimumFee { get; set; }
}

[JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
public enum CoinSelectionStrategy
{
SmallestFirst,
BiggestFirst,
ClosestToTargetFirst
}

/// <summary>
/// Wrapper for the NBXplorer client to support DI
/// </summary>
Expand Down Expand Up @@ -128,6 +142,46 @@ public async Task TrackAsync(TrackedSource trackedSource, CancellationToken canc
return await client.GetUTXOsAsync(extKey, cancellation);
}

public async Task<UTXOChanges> GetUTXOsByLimitAsync(DerivationStrategyBase extKey,
CoinSelectionStrategy strategy = CoinSelectionStrategy.SmallestFirst,
int limit = 0,
long amount = 0,
long tolerance = 0,
long closestTo = 0,
CancellationToken cancellation = default)
{
try
{
var requestUri = $"{Constants.NBXPLORER_URI}/v1/cryptos/btc/derivations/{TrackedSource.Create(extKey).DerivationStrategy}/selectutxos";

IDictionary<string, string?> keyValuePairs = new Dictionary<string, string?>();
keyValuePairs.Add("strategy", strategy.ToString());
keyValuePairs.Add("limit", limit.ToString());
keyValuePairs.Add("amount", amount.ToString());
keyValuePairs.Add("tolerance", tolerance.ToString());
if (strategy == CoinSelectionStrategy.ClosestToTargetFirst)
{
keyValuePairs.Add("closestTo", closestTo.ToString());
}

var url = QueryHelpers.AddQueryString(requestUri, keyValuePairs);
var response = await _httpClient.GetAsync(url, cancellation);

if (response.IsSuccessStatusCode)
{
var client = await LightningHelper.CreateNBExplorerClient();

return client.Serializer.ToObject<UTXOChanges>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
}
catch (Exception e)
{
_logger.LogError(e.ToString());
}

return new UTXOChanges();
}

public async Task<GetFeeRateResult> GetFeeRateAsync(int blockCount, FeeRate fallbackFeeRate,
CancellationToken cancellation = default)
{
Expand Down
Loading

0 comments on commit f74aa37

Please sign in to comment.