Enhance user authentication by adding optional OwnerWalletAddress parameter in LoginRequest and UserService. Update UserController and related components to support the new wallet address functionality, ensuring better user profile management and validation in trading operations.

This commit is contained in:
2025-11-17 13:48:05 +07:00
parent 8697f1598d
commit 06ef33b7ab
15 changed files with 1802 additions and 28 deletions

View File

@@ -44,7 +44,7 @@ public class UserController : BaseController
[HttpPost("create-token")]
public async Task<ActionResult<string>> CreateToken([FromBody] LoginRequest login)
{
var user = await _userService.Authenticate(login.Name, login.Address, login.Message, login.Signature);
var user = await _userService.Authenticate(login.Name, login.Address, login.Message, login.Signature, login.OwnerWalletAddress);
if (user != null)
{

View File

@@ -12,4 +12,5 @@ public class LoginRequest
public string Signature { get; set; }
[Required]
public string Message { get; set; }
public string? OwnerWalletAddress { get; set; }
}

View File

@@ -4,7 +4,7 @@ namespace Managing.Application.Abstractions.Services;
public interface IUserService
{
Task<User> Authenticate(string name, string address, string message, string signature);
Task<User> Authenticate(string name, string address, string message, string signature, string? ownerWalletAddress = null);
Task<User> GetUserByAddressAsync(string address, bool useCache = true);
Task<User> UpdateAgentName(User user, string agentName);
Task<User> UpdateAvatarUrl(User user, string avatarUrl);

View File

@@ -163,25 +163,20 @@ namespace Managing.Application.ManageBot
}
}
private async Task ValidateKudaiStakingRequirements(User user)
private async Task ValidateKudaiStakingRequirements(User user)
{
// Use the user's wallet address directly
if (string.IsNullOrEmpty(user.OwnerWalletAddress))
{
// Get user's accounts to find their wallet address
var userAccounts = await _accountService.GetAccountsByUserAsync(user, true, true);
var evmAccount = userAccounts.FirstOrDefault(acc =>
acc.Exchange == TradingExchanges.Evm ||
acc.Exchange == TradingExchanges.GmxV2);
throw new InvalidOperationException(
"To copy trade the Kudai strategy, you must have a wallet address configured in your profile.");
}
if (evmAccount == null)
{
throw new InvalidOperationException(
"To copy trade the Kudai strategy, you must have an EVM-compatible account (GMX V2 or EVM exchange).");
}
// Check KUDAI staked balance
var kudaiStakedBalance = await _evmManager.GetKudaiStakedBalance(user.OwnerWalletAddress);
// Check KUDAI staked balance
var kudaiStakedBalance = await _evmManager.GetKudaiStakedBalance(evmAccount.Key);
// Check GBC staked NFT count
var gbcStakedCount = await _evmManager.GetGbcStakedCount(evmAccount.Key);
// Check GBC staked NFT count
var gbcStakedCount = await _evmManager.GetGbcStakedCount(user.OwnerWalletAddress);
// Requirements: 100 million KUDAI OR 10 GBC NFTs
const decimal requiredKudaiAmount = 100_000_000m; // 100 million

View File

@@ -45,7 +45,7 @@ public class UserService : IUserService
: authorizedAddressesString.Split(';', StringSplitOptions.RemoveEmptyEntries);
}
public async Task<User> Authenticate(string name, string address, string message, string signature)
public async Task<User> Authenticate(string name, string address, string message, string signature, string? ownerWalletAddress = null)
{
var recoveredAddress = _evmManager.VerifySignature(signature, message);
@@ -106,6 +106,13 @@ public class UserService : IUserService
throw new Exception("Account not found for address " + recoveredAddress);
}
// Update owner wallet address if provided and user doesn't have one set yet
if (!string.IsNullOrEmpty(ownerWalletAddress) && string.IsNullOrEmpty(user.OwnerWalletAddress))
{
user.OwnerWalletAddress = ownerWalletAddress;
await _userRepository.SaveOrUpdateUserAsync(user);
}
return user;
}
else
@@ -113,7 +120,8 @@ public class UserService : IUserService
// First login - create user first
user = new User
{
Name = name
Name = name,
OwnerWalletAddress = ownerWalletAddress ?? string.Empty
};
// Save the user first

View File

@@ -18,5 +18,7 @@ public class User
[Id(5)] public string TelegramChannel { get; set; } = string.Empty;
[Id(6)] public bool IsAdmin { get; set; } = false;
[Id(6)] public string OwnerWalletAddress { get; set; } = string.Empty;
[Id(7)] public bool IsAdmin { get; set; } = false;
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UpdateKudaiStakingValidation : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "OwnerWalletAddress",
table: "Users",
type: "text",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OwnerWalletAddress",
table: "Users");
}
}
}

View File

@@ -1418,6 +1418,9 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<string>("OwnerWalletAddress")
.HasColumnType("text");
b.Property<string>("TelegramChannel")
.HasMaxLength(255)
.HasColumnType("character varying(255)");

View File

@@ -13,6 +13,7 @@ public class UserEntity
[MaxLength(255)] public string? AgentName { get; set; }
public string? AvatarUrl { get; set; }
public string? TelegramChannel { get; set; }
public string? OwnerWalletAddress { get; set; }
public bool IsAdmin { get; set; }
// Navigation properties

View File

@@ -130,6 +130,7 @@ public static class PostgreSqlMappers
AgentName = entity.AgentName,
AvatarUrl = entity.AvatarUrl,
TelegramChannel = entity.TelegramChannel,
OwnerWalletAddress = entity.OwnerWalletAddress,
Id = entity.Id, // Assuming Id is the primary key for UserEntity
IsAdmin = entity.IsAdmin,
Accounts = entity.Accounts?.Select(MapAccountWithoutUser).ToList() ?? new List<Account>()
@@ -165,6 +166,7 @@ public static class PostgreSqlMappers
AgentName = user.AgentName,
AvatarUrl = user.AvatarUrl,
TelegramChannel = user.TelegramChannel,
OwnerWalletAddress = user.OwnerWalletAddress,
IsAdmin = user.IsAdmin
};
}

View File

@@ -45,7 +45,8 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
Name = u.Name,
AgentName = u.AgentName,
AvatarUrl = u.AvatarUrl,
TelegramChannel = u.TelegramChannel
TelegramChannel = u.TelegramChannel,
OwnerWalletAddress = u.OwnerWalletAddress
})
.FirstOrDefaultAsync()
.ConfigureAwait(false);
@@ -93,7 +94,8 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
Name = u.Name,
AgentName = u.AgentName,
AvatarUrl = u.AvatarUrl,
TelegramChannel = u.TelegramChannel
TelegramChannel = u.TelegramChannel,
OwnerWalletAddress = u.OwnerWalletAddress
})
.FirstOrDefaultAsync()
.ConfigureAwait(false);
@@ -208,7 +210,8 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
Name = u.Name,
AgentName = u.AgentName,
AvatarUrl = u.AvatarUrl,
TelegramChannel = u.TelegramChannel
TelegramChannel = u.TelegramChannel,
OwnerWalletAddress = u.OwnerWalletAddress
})
.ToListAsync()
.ConfigureAwait(false);
@@ -249,6 +252,7 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
existingUser.AgentName = user.AgentName;
existingUser.AvatarUrl = user.AvatarUrl;
existingUser.TelegramChannel = user.TelegramChannel;
existingUser.OwnerWalletAddress = user.OwnerWalletAddress;
existingUser.IsAdmin = user.IsAdmin;
_context.Users.Update(existingUser);

View File

@@ -37,18 +37,21 @@ const LogIn = () => {
t.update('info', 'Creating token...')
// Use the Privy embedded wallet address
const walletAddress = user.linkedAccounts[1]?.address
// Use the Privy embedded wallet address for authentication
const embeddedWalletAddress = user.linkedAccounts[1]?.address
// Use the connected user wallet address as ownerWalletAddress
const connectedWalletAddress = user.wallet?.address
if (signature && walletAddress) {
if (signature && embeddedWalletAddress) {
const userClient = new UserClient({}, apiUrl)
await userClient
.user_CreateToken({
address: walletAddress,
address: embeddedWalletAddress,
message: message,
name: user?.id,
signature: signature,
ownerWalletAddress: connectedWalletAddress,
})
.then((data) => {
setCookie('token', data, 1)

View File

@@ -4523,6 +4523,7 @@ export interface User {
agentName?: string | null;
avatarUrl?: string | null;
telegramChannel?: string | null;
ownerWalletAddress?: string | null;
isAdmin?: boolean;
}
@@ -5863,6 +5864,7 @@ export interface LoginRequest {
address: string;
signature: string;
message: string;
ownerWalletAddress?: string | null;
}
export interface PaginatedWhitelistAccountsResponse {

View File

@@ -46,6 +46,7 @@ export interface User {
agentName?: string | null;
avatarUrl?: string | null;
telegramChannel?: string | null;
ownerWalletAddress?: string | null;
isAdmin?: boolean;
}
@@ -1386,6 +1387,7 @@ export interface LoginRequest {
address: string;
signature: string;
message: string;
ownerWalletAddress?: string | null;
}
export interface PaginatedWhitelistAccountsResponse {