Add Privy type wallet

This commit is contained in:
2025-03-05 10:36:54 +07:00
parent 30bf9d26f6
commit 988cc9eb61
21 changed files with 287 additions and 53 deletions

View File

@@ -7,6 +7,7 @@ using Managing.Common;
using Managing.Core.Middleawares;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.OpenApi.Models;
using NSwag;
using NSwag.Generation.Processors.Security;
@@ -49,6 +50,7 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
builder.Services.AddOptions();
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
builder.Services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>

View File

@@ -9,6 +9,7 @@ using Managing.Bootstrap;
using Managing.Common;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
@@ -58,6 +59,7 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
builder.Services.AddOptions();
builder.Services.Configure<ManagingDatabaseSettings>(builder.Configuration.GetSection(Constants.Databases.MongoDb));
builder.Services.Configure<InfluxDbSettings>(builder.Configuration.GetSection(Constants.Databases.InfluxDb));
builder.Services.Configure<PrivySettings>(builder.Configuration.GetSection(Constants.ThirdParty.Privy));
builder.Services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));

View File

@@ -8,6 +8,10 @@
"Organization": "managing-org",
"Token": "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA=="
},
"Privy": {
"AppId": "cm6f47n1l003jx7mjwaembhup",
"AppSecret": "63Chz2z5M8TgR5qc8dznSLRAGTHTyPU4cjdQobrBF1Cx5tszZpTuFgyrRd7hZ2k6HpwDz3GEwQZzsCqHb8Z311bF"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",

View File

@@ -18,21 +18,37 @@ public interface IEvmManager
string VerifySignature(string signature, string message);
Task<List<EvmBalance>> GetBalances(Chain chain, int page, int pageSize, string publicAddress);
Task<List<EvmBalance>> GetAllBalancesOnAllChain(string publicAddress);
Task<List<Candle>> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate, Timeframe interval);
Task<List<Candle>> GetCandles(SubgraphProvider subgraphProvider, Ticker ticker, DateTime startDate,
Timeframe interval);
decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker);
Task<List<Ticker>> GetAvailableTicker();
Task<Candle> GetCandle(SubgraphProvider subgraphProvider, Ticker ticker);
Task<bool> InitAddress(string chainName, string publicAddress, string privateKey);
Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey, string receiverAddress);
Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey,
string receiverAddress);
Task<EvmBalance> GetTokenBalance(string chainName, Ticker ticker, string publicAddress);
Task<bool> CancelOrders(Account account, Ticker ticker);
Task<Trade> IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage = 1);
Task<Trade> IncreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price,
decimal quantity, decimal? leverage = 1);
Task<Trade> GetTrade(Account account, string chainName, Ticker ticker);
Task<Trade> DecreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage);
Task<Trade> DecreasePosition(Account account, Ticker ticker, TradeDirection direction, decimal price,
decimal quantity, decimal? leverage);
Task<decimal> QuantityInPosition(string chainName, string publicAddress, Ticker ticker);
Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage);
Task<Trade> DecreaseOrder(Account account, TradeType tradeType, Ticker ticker, TradeDirection direction,
decimal price, decimal quantity, decimal? leverage);
Task<decimal> GetFee(string chainName);
Task<List<Trade>> GetOrders(Account account, Ticker ticker);
Task<Trade> GetTrade(string reference, string arbitrum, Ticker ticker);
Task<List<FundingRate>> GetFundingRates();
}
Task<(string Id, string Address)> CreatePrivyWallet();
}

View File

@@ -7,6 +7,7 @@ using Managing.Domain.Users;
using Microsoft.Extensions.Logging;
namespace Managing.Application.Accounts;
public class AccountService : IAccountService
{
private readonly IAccountRepository _accountRepository;
@@ -48,6 +49,21 @@ public class AccountService : IAccountService
request.Key = keys.Key;
request.Secret = keys.Secret;
}
else if (request.Exchange == Enums.TradingExchanges.Evm
&& request.Type == Enums.AccountType.Privy)
{
if (string.IsNullOrEmpty(request.Key) || string.IsNullOrEmpty(request.Secret))
{
var privyClient = await _evmManager.CreatePrivyWallet();
request.Key = privyClient.Address;
request.Secret = privyClient.Id;
}
else
{
request.Key = request.Key; // Address
request.Secret = request.Secret; // Privy wallet id
}
}
else
{
request.Key = request.Key;
@@ -116,10 +132,8 @@ public class AccountService : IAccountService
{
var cacheKey = $"user-account-{user.Name}";
return _cacheService.GetOrSave(cacheKey, () =>
{
return GetAccounts(user, hideSecrets, false);
}, TimeSpan.FromMinutes(5));
return _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, hideSecrets, false); },
TimeSpan.FromMinutes(5));
}
private IEnumerable<Account> GetAccounts(User user, bool hideSecrets, bool getBalance)
@@ -139,10 +153,8 @@ public class AccountService : IAccountService
public IEnumerable<Account> GetAccountsBalancesByUser(User user, bool hideSecrets)
{
var cacheKey = $"user-account-balance-{user.Name}";
var accounts = _cacheService.GetOrSave(cacheKey, () =>
{
return GetAccounts(user, true, true);
}, TimeSpan.FromHours(3));
var accounts = _cacheService.GetOrSave(cacheKey, () => { return GetAccounts(user, true, true); },
TimeSpan.FromHours(3));
return accounts;
}

View File

@@ -31,6 +31,8 @@ using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Evm;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models.Privy;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Subgraphs;
using Managing.Infrastructure.Exchanges;
@@ -90,6 +92,9 @@ public static class ApiBootstrap
services.AddSingleton<IInfluxDbSettings>(sp =>
sp.GetRequiredService<IOptions<InfluxDbSettings>>().Value);
services.AddSingleton<IPrivySettings>(sp =>
sp.GetRequiredService<IOptions<PrivySettings>>().Value);
// Evm
services.AddGbcFeed();
services.AddUniswapV2();
@@ -129,6 +134,7 @@ public static class ApiBootstrap
services.AddSingleton<IDiscordService, DiscordService>();
services.AddSingleton<IBotService, BotService>();
services.AddSingleton<IWorkerService, WorkerService>();
services.AddTransient<IPrivyService, PrivyService>();
// Stream
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();

View File

@@ -25,6 +25,8 @@ using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Evm;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models.Privy;
using Managing.Infrastructure.Evm.Services;
using Managing.Infrastructure.Evm.Subgraphs;
using Managing.Infrastructure.Exchanges;
@@ -81,6 +83,9 @@ public static class WorkersBootstrap
services.AddTransient<IInfluxDbRepository, InfluxDbRepository>();
services.AddSingleton<IPrivySettings>(sp =>
sp.GetRequiredService<IOptions<PrivySettings>>().Value);
// Evm
services.AddUniswapV2();
services.AddGbcFeed();
@@ -112,6 +117,7 @@ public static class WorkersBootstrap
services.AddTransient<IExchangeService, ExchangeService>();
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();
services.AddSingleton<IKrakenSocketClient, KrakenSocketClient>();
services.AddSingleton<IPrivyService, PrivyService>();
// Messengers
services.AddSingleton<IMessengerService, MessengerService>();

View File

@@ -23,6 +23,11 @@
public const string MongoDb = "ManagingDatabase";
}
public class ThirdParty
{
public const string Privy = "Privy";
}
public class Chains
{
public const string Ethereum = "Ethereum";

View File

@@ -37,7 +37,8 @@ public static class Enums
Cex,
Trader,
Watch,
Auth
Auth,
Privy
}
public enum BotType
@@ -190,10 +191,10 @@ public static class Enums
Cancelled = 2,
Filled = 3,
}
public static bool IsActive(this TradeStatus status) =>
status == TradeStatus.Requested ||
status == TradeStatus.Cancelled ||
public static bool IsActive(this TradeStatus status) =>
status == TradeStatus.Requested ||
status == TradeStatus.Cancelled ||
status == TradeStatus.Filled;

View File

@@ -0,0 +1,11 @@
using Managing.Infrastructure.Evm.Models;
namespace Managing.Infrastructure.Evm.Abstractions;
public interface IPrivyService
{
Task<PrivyWallet> CreateWalletAsync(string chainType = "ethereum");
Task<HttpResponseMessage> SendTransactionAsync(string walletId, string recipientAddress, long value,
string caip2 = "eip155:84532");
}

View File

@@ -0,0 +1,7 @@
namespace Managing.Infrastructure.Evm.Abstractions;
public interface IPrivySettings
{
string AppId { get; set; }
string AppSecret { get; set; }
}

View File

@@ -37,6 +37,7 @@ public class EvmManager : IEvmManager
private readonly IEnumerable<ISubgraphPrices> _subgraphs;
private Dictionary<string, Dictionary<string, decimal>> _geckoPrices;
private readonly GmxV2Service _gmxV2Service;
private readonly IPrivyService _privyService;
private readonly List<Ticker> _eligibleTickers = new List<Ticker>()
{
@@ -44,12 +45,13 @@ public class EvmManager : IEvmManager
Ticker.PEPE, Ticker.DOGE, Ticker.UNI
};
public EvmManager(IEnumerable<ISubgraphPrices> subgraphs)
public EvmManager(IEnumerable<ISubgraphPrices> subgraphs, IPrivyService privyService)
{
var defaultChain = ChainService.GetEthereum();
_web3 = new Web3(defaultChain.RpcUrl);
_httpClient = new HttpClient();
_subgraphs = subgraphs;
_privyService = privyService;
_geckoPrices = _geckoPrices != null && _geckoPrices.Any()
? _geckoPrices
: new Dictionary<string, Dictionary<string, decimal>>();
@@ -657,4 +659,11 @@ public class EvmManager : IEvmManager
var chain = ChainService.GetChain(Constants.Chains.Arbitrum);
return new Web3(wallet, chain.RpcUrl);
}
public async Task<(string Id, string Address)> CreatePrivyWallet()
{
var privyWallet = await _privyService.CreateWalletAsync();
return (privyWallet.Id, privyWallet.Address);
}
}

View File

@@ -0,0 +1,9 @@
using Managing.Infrastructure.Evm.Abstractions;
namespace Managing.Infrastructure.Evm.Models.Privy;
public class PrivySettings : IPrivySettings
{
public string AppId { get; set; }
public string AppSecret { get; set; }
}

View File

@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace Managing.Infrastructure.Evm.Models;
public class PrivyWallet
{
[JsonPropertyName("id")] public string Id { get; set; }
[JsonPropertyName("address")] public string Address { get; set; }
[JsonPropertyName("chain_type")] public string ChainType { get; set; }
}

View File

@@ -793,6 +793,7 @@ public class GmxV2Service
var exchangeRouterService = new ExchangeRouterService(web3, Arbitrum.AddressV2.ExchangeRouter);
var receipt = await exchangeRouterService.MulticallRequestAndWaitForReceiptAsync(multiCallFunction);
// Call privy api instead and send the txn
var trade = new Trade(DateTime.UtcNow,
direction,
Enums.TradeStatus.Requested,

View File

@@ -0,0 +1,85 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models;
public class PrivyService : IPrivyService
{
private readonly HttpClient _privyClient;
private readonly string _appId;
private readonly string _appSecret;
public PrivyService(IPrivySettings settings)
{
_privyClient = new HttpClient();
_appId = settings.AppId;
_appSecret = settings.AppSecret;
ConfigureHttpClient();
}
private void ConfigureHttpClient()
{
_privyClient.BaseAddress = new Uri("https://api.privy.io/");
var authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_appId}:{_appSecret}"));
// _privyClient.DefaultRequestHeaders.Authorization =
// new AuthenticationHeaderValue("Basic", $"{_appId}:{_appSecret}");
_privyClient.DefaultRequestHeaders.Add("privy-app-id", _appId);
// add custom header
_privyClient.DefaultRequestHeaders.Add("Authorization", authToken);
_privyClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<PrivyWallet> CreateWalletAsync(string chainType = "ethereum")
{
try
{
var json = JsonSerializer.Serialize(new { chain_type = chainType });
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _privyClient.PostAsJsonAsync("/v1/wallets", content);
var result = new PrivyWallet();
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadFromJsonAsync<PrivyWallet>();
}
else
{
throw new Exception(await response.Content.ReadAsStringAsync());
}
return result;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
public async Task<HttpResponseMessage> SendTransactionAsync(string walletId, string recipientAddress, long value,
string caip2 = "eip155:84532")
{
var requestBody = new
{
method = "eth_sendTransaction",
caip2,
@params = new
{
transaction = new
{
to = recipientAddress,
value
}
}
};
return await _privyClient.PostAsJsonAsync(
$"/v1/wallets/{walletId}/rpc",
requestBody
);
}
}

View File

@@ -59,14 +59,14 @@ const BacktestRowDetails: React.FC<IBotRowDetails> = ({
<CardText
title="Money Management"
content={
"SL: " +(moneyManagement.stopLoss * 100).toFixed(2) + "% TP: " +
(moneyManagement.takeProfit * 100).toFixed(2) + "%"
"SL: " +(moneyManagement?.stopLoss * 100).toFixed(2) + "% TP: " +
(moneyManagement?.takeProfit * 100).toFixed(2) + "%"
}
></CardText>
<CardText
title="Optimized Money Management"
content={
"SL: " +optimizedMoneyManagement.stopLoss.toFixed(2) + "% TP: " + optimizedMoneyManagement.takeProfit.toFixed(2) + "%"
"SL: " +optimizedMoneyManagement?.stopLoss.toFixed(2) + "% TP: " + optimizedMoneyManagement?.takeProfit.toFixed(2) + "%"
}
></CardText>

View File

@@ -275,9 +275,9 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching }) => {
// Get average optimized money management for every backtest
const optimized = list!.map((b) => b.optimizedMoneyManagement)
const stopLoss = optimized.reduce((acc, curr) => acc + curr.stopLoss, 0)
const stopLoss = optimized.reduce((acc, curr) => acc + (curr?.stopLoss ?? 0), 0)
const takeProfit = optimized.reduce(
(acc, curr) => acc + curr.takeProfit,
(acc, curr) => acc + (curr?.takeProfit ?? 0),
0
)

View File

@@ -1631,7 +1631,7 @@ export class TradingClient extends AuthorizedApiBase {
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
method: "POST",
headers: {
"Accept": "application/json"
}
@@ -1960,6 +1960,7 @@ export enum AccountType {
Trader = "Trader",
Watch = "Watch",
Auth = "Auth",
Privy = "Privy",
}
export interface User {

View File

@@ -116,8 +116,8 @@ const AccountModal: React.FC<IModalProps> = ({ showModal, toggleModal }) => {
</div>
</div>
{selectedExchange != TradingExchanges.Evm &&
selectedType != AccountType.Trader ? (
{(selectedExchange != TradingExchanges.Evm && selectedType != AccountType.Trader ) ||
(selectedExchange == TradingExchanges.Evm && selectedType == AccountType.Privy )? (
<>
<div className="form-control">
<div className="input-group">