Add initAddress api

This commit is contained in:
2025-05-10 14:41:17 +07:00
parent b7d5a0b6a7
commit 549c4ae746
10 changed files with 170 additions and 46 deletions

View File

@@ -3,6 +3,7 @@ using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands; using Managing.Application.Trading.Commands;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Privy;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -156,4 +157,33 @@ public class TradingController : BaseController
var result = await _openTradeCommandHandler.Handle(command); var result = await _openTradeCommandHandler.Handle(command);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Initializes a Privy wallet address for the user.
/// </summary>
/// <param name="publicAddress">The public address of the Privy wallet to initialize.</param>
/// <returns>The initialization response containing success status and transaction hashes.</returns>
[HttpPost("InitPrivyWallet")]
public async Task<ActionResult<PrivyInitAddressResponse>> InitPrivyWallet([FromBody] string publicAddress)
{
if (string.IsNullOrEmpty(publicAddress))
{
return BadRequest("Public address cannot be null or empty.");
}
try
{
var result = await _tradingService.InitPrivyWallet(publicAddress);
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error initializing Privy wallet address: {Address}", publicAddress);
return StatusCode(500, new PrivyInitAddressResponse
{
Success = false,
Error = "An error occurred while initializing the Privy wallet address."
});
}
}
} }

View File

@@ -3,6 +3,7 @@ using Managing.Domain.Candles;
using Managing.Domain.Evm; using Managing.Domain.Evm;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Privy;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Repositories; namespace Managing.Application.Abstractions.Repositories;
@@ -25,7 +26,7 @@ public interface IEvmManager
decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker); decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker);
Task<List<Ticker>> GetAvailableTicker(); Task<List<Ticker>> GetAvailableTicker();
Task<Candle> GetCandle(Ticker ticker); Task<Candle> GetCandle(Ticker ticker);
Task<bool> InitAddress(string publicAddress); Task<PrivyInitAddressResponse> InitAddress(string publicAddress);
Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey, Task<bool> Send(Chain chain, Ticker ticker, decimal amount, string publicAddress, string privateKey,
string receiverAddress); string receiverAddress);

View File

@@ -3,6 +3,7 @@ using Managing.Domain.Scenarios;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Privy;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services; namespace Managing.Application.Abstractions.Services;
@@ -35,4 +36,5 @@ public interface ITradingService
void UpdateScenario(Scenario scenario); void UpdateScenario(Scenario scenario);
void UpdateStrategy(Strategy strategy); void UpdateStrategy(Strategy strategy);
Task<IEnumerable<Position>> GetBrokerPositions(Account account); Task<IEnumerable<Position>> GetBrokerPositions(Account account);
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
} }

View File

@@ -1,5 +1,4 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Scenarios; using Managing.Domain.Scenarios;
@@ -7,6 +6,7 @@ using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Privy;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -20,6 +20,7 @@ public class TradingService : ITradingService
private readonly ICacheService _cacheService; private readonly ICacheService _cacheService;
private readonly IMessengerService _messengerService; private readonly IMessengerService _messengerService;
private readonly IStatisticRepository _statisticRepository; private readonly IStatisticRepository _statisticRepository;
private readonly IEvmManager _evmManager;
private readonly ILogger<TradingService> _logger; private readonly ILogger<TradingService> _logger;
public TradingService( public TradingService(
@@ -29,7 +30,8 @@ public class TradingService : ITradingService
IAccountService accountService, IAccountService accountService,
ICacheService cacheService, ICacheService cacheService,
IMessengerService messengerService, IMessengerService messengerService,
IStatisticRepository statisticRepository) IStatisticRepository statisticRepository,
IEvmManager evmManager)
{ {
_tradingRepository = tradingRepository; _tradingRepository = tradingRepository;
_exchangeService = exchangeService; _exchangeService = exchangeService;
@@ -38,6 +40,7 @@ public class TradingService : ITradingService
_cacheService = cacheService; _cacheService = cacheService;
_messengerService = messengerService; _messengerService = messengerService;
_statisticRepository = statisticRepository; _statisticRepository = statisticRepository;
_evmManager = evmManager;
} }
public void DeleteScenario(string name) public void DeleteScenario(string name)
@@ -374,4 +377,23 @@ public class TradingService : ITradingService
public List<Trade> Trades { get; set; } public List<Trade> Trades { get; set; }
public List<string> PositionIdentifiers { get; set; } public List<string> PositionIdentifiers { get; set; }
} }
public async Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress)
{
try
{
if (string.IsNullOrEmpty(publicAddress))
{
_logger.LogWarning("Attempted to initialize Privy wallet with null or empty public address");
return new PrivyInitAddressResponse { Success = false, Error = "Public address cannot be null or empty" };
}
return await _evmManager.InitAddress(publicAddress);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error initializing Privy wallet for address {PublicAddress}", publicAddress);
return new PrivyInitAddressResponse { Success = false, Error = ex.Message };
}
}
} }

View File

@@ -210,7 +210,7 @@ public class EvmManagerTests
{ {
var accountInitilized = await _manager.InitAddress(PublicAddress); var accountInitilized = await _manager.InitAddress(PublicAddress);
Assert.True(accountInitilized); Assert.NotNull(accountInitilized);
} }
[Ignore] [Ignore]

View File

@@ -444,7 +444,7 @@ public class EvmManager : IEvmManager
return cachedCandle; return cachedCandle;
} }
public async Task<bool> InitAddress(string publicAddress) public async Task<PrivyInitAddressResponse> InitAddress(string publicAddress)
{ {
try try
{ {
@@ -452,13 +452,17 @@ public class EvmManager : IEvmManager
"/init-address", "/init-address",
new { address = publicAddress }); new { address = publicAddress });
return response.Success; return response;
} }
catch (Exception ex) catch (Exception ex)
{ {
// Log the error // Log the error
Console.Error.WriteLine($"Error initializing address: {ex.Message}"); Console.Error.WriteLine($"Error initializing address: {ex.Message}");
return false; return new PrivyInitAddressResponse
{
Success = false,
Error = ex.Message
};
} }
} }

View File

@@ -150,6 +150,9 @@ export async function getClientForAddress(
"0xdf034cd3df9a80eABFA0556232a91E03Ca67D5Cb": { "0xdf034cd3df9a80eABFA0556232a91E03Ca67D5Cb": {
isListed: false, isListed: false,
}, },
"0x9e79146b3A022Af44E0708c6794F03Ef798381A5": {
isListed: false,
},
} }
}; };

View File

@@ -2159,6 +2159,45 @@ export class TradingClient extends AuthorizedApiBase {
} }
return Promise.resolve<Position>(null as any); return Promise.resolve<Position>(null as any);
} }
trading_InitPrivyWallet(publicAddress: string): Promise<PrivyInitAddressResponse> {
let url_ = this.baseUrl + "/Trading/InitPrivyWallet";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(publicAddress);
let options_: RequestInit = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processTrading_InitPrivyWallet(_response);
});
}
protected processTrading_InitPrivyWallet(response: Response): Promise<PrivyInitAddressResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as PrivyInitAddressResponse;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<PrivyInitAddressResponse>(null as any);
}
} }
export class UserClient extends AuthorizedApiBase { export class UserClient extends AuthorizedApiBase {
@@ -3072,6 +3111,14 @@ export enum RiskLevel {
Adaptive = "Adaptive", Adaptive = "Adaptive",
} }
export interface PrivyInitAddressResponse {
success?: boolean;
usdcHash?: string | null;
orderVaultHash?: string | null;
exchangeRouterHash?: string | null;
error?: string | null;
}
export interface LoginRequest { export interface LoginRequest {
name: string; name: string;
address: string; address: string;

View File

@@ -1,21 +1,12 @@
import { import {ChevronDownIcon, ChevronRightIcon,} from '@heroicons/react/solid'
ChevronDownIcon, import React, {useEffect, useMemo, useState} from 'react'
ChevronRightIcon,
ClipboardCopyIcon,
TrashIcon,
} from '@heroicons/react/solid'
import React, { useEffect, useState, useMemo } from 'react'
import {useNavigate} from 'react-router-dom' import {useNavigate} from 'react-router-dom'
import { FiPlus, FiTrash2, FiKey, FiTrendingUp } from 'react-icons/fi' import {FiKey, FiPlay, FiTrash2, FiTrendingUp} from 'react-icons/fi'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import { import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules'
SelectColumnFilter,
Table,
Toast,
} from '../../../components/mollecules'
import type {Account} from '../../../generated/ManagingApi' import type {Account} from '../../../generated/ManagingApi'
import { AccountClient, AccountType } from '../../../generated/ManagingApi' import {AccountClient, AccountType, TradingClient} from '../../../generated/ManagingApi'
import AccountRowDetails from './accountRowDetails' import AccountRowDetails from './accountRowDetails'
import PrivyDelegationModal from './PrivyDelegationModal' import PrivyDelegationModal from './PrivyDelegationModal'
@@ -48,6 +39,22 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
}) })
} }
async function initPrivyWallet(publicAddress: string) {
const t = new Toast('Initializing Privy wallet')
const client = new TradingClient({}, apiUrl)
try {
const response = await client.trading_InitPrivyWallet(publicAddress)
if (response.success) {
t.update('success', 'Privy wallet initialized successfully')
} else {
t.update('error', `Initialization failed: ${response.error || 'Unknown error'}`)
}
} catch (err) {
t.update('error', 'Error: ' + err)
}
}
const columns = useMemo( const columns = useMemo(
() => [ () => [
{ {
@@ -114,6 +121,7 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
</button> </button>
{account.type === AccountType.Privy && ( {account.type === AccountType.Privy && (
<>
<button <button
className="btn btn-sm btn-primary btn-outline" className="btn btn-sm btn-primary btn-outline"
onClick={() => { onClick={() => {
@@ -124,9 +132,15 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
<FiKey /> <FiKey />
<span className="ml-1">Delegate</span> <span className="ml-1">Delegate</span>
</button> </button>
)}
{account.type === AccountType.Privy && ( <button
className="btn btn-sm btn-info btn-outline"
onClick={() => initPrivyWallet(account.key)}
>
<FiPlay />
<span className="ml-1">Init</span>
</button>
<button <button
className="btn btn-sm btn-success btn-outline" className="btn btn-sm btn-success btn-outline"
onClick={() => { onClick={() => {
@@ -137,6 +151,7 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
<FiTrendingUp /> <FiTrendingUp />
<span className="ml-1">GMX</span> <span className="ml-1">GMX</span>
</button> </button>
</>
)} )}
</div> </div>
) )