public async ValueTask <bool> DeleteBitgoCoinEntityAsync(string coin) { if (string.IsNullOrEmpty(coin)) { throw new Exception("Cannot update coin. Coin cannot be empty"); } var entity = await _bitgoCoins.GetAsync(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(coin)); if (entity != null) { var assets = await _assetMap.GetAsync(); var existAssets = assets.Any(e => e.BitgoCoin == coin); if (existAssets) { throw new Exception("Cannot delete coin. Asset used it as coin"); } await _bitgoCoins.DeleteAsync(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(coin)); } return(true); }
public double ConvertAmountFromBitgo(string coin, long amount) { var coinSettings = _bitgoCoins.Get(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(coin)); if (coinSettings == null) { throw new System.Exception($"Do not found settings for bitgo coin {coin} in nosql table {BitgoCoinEntity.TableName}"); } return(coinSettings.AmountFromAbsoluteValue(amount)); }
public decimal ConvertAmountToBitgo(string coin, double amount) { var coinSettings = _bitgoCoins.Get(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(coin)); if (coinSettings == null) { throw new Exception( $"Do not found settings for bitgo coin {coin} in nosql table {BitgoCoinEntity.TableName}"); } return(coinSettings.AmountToAbsoluteValueDecimal(amount)); }
public int GetRequiredConfirmations(string coin) { var coinSettings = _bitgoCoins.Get(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(coin)); if (coinSettings == null) { throw new Exception( $"Do not found settings for bitgo coin {coin} in nosql table {BitgoCoinEntity.TableName}"); } return(coinSettings.RequiredConfirmations); }
public IBitGoApi GetByUser(string brokerId, string userId, string coinId, string newApiKey) { var bitGoUser = _bitgoUserReader.Get( BitGoUserNoSqlEntity.GeneratePartitionKey(brokerId), BitGoUserNoSqlEntity.GenerateRowKey(BitGoUserNoSqlEntity.TechSignerId, coinId)) ?? _bitgoUserReader.Get( BitGoUserNoSqlEntity.GeneratePartitionKey(brokerId), BitGoUserNoSqlEntity.GenerateRowKey(BitGoUserNoSqlEntity.TechSignerId, BitGoUserNoSqlEntity.DefaultCoin)); var apiKeyEnc = bitGoUser?.User?.ApiKey; if (string.IsNullOrEmpty(apiKeyEnc) && string.IsNullOrEmpty(newApiKey)) { _logger.LogError("Tech account is not configured, id = {techSignerName}", BitGoUserNoSqlEntity.TechSignerId); return(null); } lock (_clients) { if (!string.IsNullOrEmpty(apiKeyEnc) && _clients.TryGetValue(apiKeyEnc, out var api)) { return(api); } } var coin = _bitgoCointReader.Get(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(coinId)); if (coin == null) { _logger.LogError("Cannot fond bitgo coin id = {symbol}", coinId); return(null); } var apiKey = string.IsNullOrEmpty(apiKeyEnc) ? newApiKey : _encryptionService.Decrypt(apiKeyEnc); var client = new BitGoClient( apiKey, Program.Settings.BitgoExpressUrlMainNet, apiKey, Program.Settings.BitgoExpressUrlTestNet); lock (_clients) { _clients[apiKey] = coin.IsMainNet ? client.MainNet : client.TestNet; return(_clients[apiKey]); } }
private async Task <BitgoCoinEntity> GetCoin(string bitgoCoin) { var coin = _bitgoCoinReader.Get(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(bitgoCoin)); if (coin == null) { await Task.Delay(5000); coin = _bitgoCoinReader.Get(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(bitgoCoin)); } if (coin == null) { throw new Exception($"BitGo coin '{bitgoCoin}' does not exist."); } return(coin); }
public async ValueTask <bool> UpdateBitgoAssetMapEntityAsync(string brokerId, string assetSymbol, string bitgoWalletId, string enabledBitgoWalletIds, string bitgoCoin, double minBalance, string tagSeparator) { if (string.IsNullOrEmpty(brokerId)) { throw new Exception("Cannot update asset map. BrokerId cannot be empty"); } if (string.IsNullOrEmpty(assetSymbol)) { throw new Exception("Cannot update asset map. AssetSymbol cannot be empty"); } if (string.IsNullOrEmpty(bitgoWalletId)) { throw new Exception("Cannot update asset map. BitgoWalletId cannot be empty"); } if (string.IsNullOrEmpty(bitgoCoin)) { throw new Exception("Cannot update asset map. BitgoCoin cannot be empty"); } var coin = await _bitgoCoins.GetAsync(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(bitgoCoin)); if (coin == null) { throw new Exception("Cannot update asset map. Unknown BitgoCoin."); } var entity = BitgoAssetMapEntity.Create(brokerId, assetSymbol, bitgoWalletId, enabledBitgoWalletIds, bitgoCoin, minBalance, tagSeparator); var existingEntity = await _assetMap.GetAsync(entity.PartitionKey, entity.RowKey); if (existingEntity == null) { throw new Exception("Cannot update asset map. Asset map not found"); } await _assetMap.InsertOrReplaceAsync(entity); return(true); }
public async Task HandledDepositAsync(SignalBitGoTransfer transferId) { transferId.AddToActivityAsJsonTag("bitgo signal"); _logger.LogInformation("Request to handle transfer from BitGo: {transferJson}", JsonConvert.SerializeObject(transferId)); var(brokerId, assetSymbol) = _assetMapper.BitgoCoinToAsset(transferId.Coin, transferId.WalletId); if (string.IsNullOrEmpty(brokerId) || string.IsNullOrEmpty(assetSymbol)) { _logger.LogWarning("Cannot handle BitGo deposit, asset do not found {transferJson}", JsonConvert.SerializeObject(transferId)); return; } var coin = _bitgoCoinReader.Get(BitgoCoinEntity.GeneratePartitionKey(), BitgoCoinEntity.GenerateRowKey(transferId.Coin)); var transferResp = await _bitgoClient.Get(coin.IsMainNet) .GetTransferAsync(transferId.Coin, transferId.WalletId, transferId.TransferId); var transfer = transferResp.Data; if (transfer == null) { _logger.LogWarning("Cannot handle BitGo deposit, transfer do not found {transferJson}", JsonConvert.SerializeObject(transferId)); Activity.Current?.SetStatus(Status.Error); return; } transfer.AddToActivityAsJsonTag("bitgo-transfer"); _logger.LogInformation("Transfer from BitGo: {transferJson}", JsonConvert.SerializeObject(transfer)); var requirement = _assetMapper.GetRequiredConfirmations(transfer.Coin); if (transfer.Confirmations < requirement) { _logger.LogError( $"Transaction do not has enough conformations. Transaction has: {transfer.Confirmations}, requirement: {requirement}"); Activity.Current?.SetStatus(Status.Error); throw new Exception( $"Transaction do not has enough conformations. Transaction has: {transfer.Confirmations}, requirement: {requirement}"); } if (!_assetMapper.IsWalletEnabled(transfer.Coin, transfer.WalletId)) { _logger.LogError( "Transfer {transferIdString} from BitGo is skipped, Wallet do not include in enabled wallet list", transfer.TransferId); Activity.Current?.SetStatus(Status.Error); return; } foreach (var entryGroup in transfer.Entries .Where(e => e.Value > 0 && !string.IsNullOrEmpty(e.Label) && e.WalletId == transferId.WalletId && (e.Token == null || e.Token == transferId.Coin)) .GroupBy(e => e.Label)) { var label = entryGroup.Key; var wallet = _walletMapper.BitgoLabelToWallet(label); if (wallet == null) { _logger.LogWarning("Cannot found wallet for transfer entry with label={label} address={address}", label, entryGroup.First().Address); continue; } wallet.WalletId.AddToActivityAsTag("walletId"); wallet.ClientId.AddToActivityAsTag("clientId"); var bitgoAmount = entryGroup.Sum(e => e.Value); var meAmount = _assetMapper.ConvertAmountFromBitgo(transferId.Coin, bitgoAmount); var accuracy = _assetsDictionary.GetAssetById(new AssetIdentity() { Symbol = assetSymbol, BrokerId = wallet.BrokerId }).Accuracy; var roundedAmount = Math.Round(meAmount, accuracy, MidpointRounding.ToNegativeInfinity); try { await using var ctx = DatabaseContext.Create(_dbContextOptionsBuilder); await ctx.InsertAsync(new DepositEntity { BrokerId = wallet.BrokerId, WalletId = wallet.WalletId, ClientId = wallet.ClientId, TransactionId = $"{transfer.TransferId}:{wallet.WalletId}", Amount = (decimal)roundedAmount, AssetSymbol = assetSymbol, Comment = $"Bitgo transfer [{transferId.Coin}:{transferId.WalletId}] entry label='{label}', count entry={entryGroup.Count()}", Integration = "BitGo", Txid = transfer.TxId, Status = DepositStatus.New, EventDate = DateTime.UtcNow, UpdateDate = DateTime.UtcNow, FeeAmount = 0, FeeAssetSymbol = assetSymbol, CardLast4 = string.Empty }); } catch (Exception e) { Console.WriteLine(e); throw; } } _logger.LogInformation("Deposit request from BitGo {transferIdString} is handled", transfer.TransferId); }