Implement revoke all approvals functionality in TradingController and related services

- Added a new endpoint in TradingController to revoke all token approvals for a specified Privy wallet address, with permission checks for user access.
- Implemented the revokeAllApprovals method in TradingService to handle the revocation logic, including error handling and logging.
- Updated IWeb3ProxyService and Web3ProxyService to support revocation requests to the Privy service.
- Introduced a new PrivyRevokeAllApprovalsResponse type for structured responses from the revocation process.
- Enhanced the UI in account tables to allow users to revoke approvals directly from the interface, providing feedback on success or failure.
- Updated appsettings.json to change the default model for Gemini integration.
This commit is contained in:
2026-01-06 22:40:37 +07:00
parent afd9ddaad5
commit 2814d67c58
14 changed files with 656 additions and 120 deletions

View File

@@ -200,6 +200,49 @@ public class TradingController : BaseController
} }
} }
/// <summary>
/// Revokes all token approvals for a Privy wallet address.
/// Only admins can revoke approvals for any address, regular users can only revoke approvals for their own addresses.
/// </summary>
/// <param name="publicAddress">The public address of the Privy wallet to revoke approvals for.</param>
/// <returns>The revocation response containing success status and transaction hashes.</returns>
[HttpPost("RevokeAllApprovals")]
public async Task<ActionResult<Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse>> RevokeAllApprovals([FromBody] string publicAddress)
{
if (string.IsNullOrEmpty(publicAddress))
{
return BadRequest("Public address cannot be null or empty.");
}
try
{
var user = await GetUser();
if (user == null)
{
return Unauthorized("User not found");
}
// Check if user has permission to revoke approvals for this address
if (!await CanUserInitializeAddress(user.Name, publicAddress))
{
return Forbid(
"You don't have permission to revoke approvals for this wallet address. You can only revoke approvals for your own wallet addresses.");
}
var result = await _tradingService.RevokeAllApprovals(publicAddress);
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error revoking all approvals for Privy wallet address: {Address}", publicAddress);
return StatusCode(500, new Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse
{
Success = false,
Error = "An error occurred while revoking all approvals for the Privy wallet address."
});
}
}
/// <summary> /// <summary>
/// Checks if the user can initialize the given public address. /// Checks if the user can initialize the given public address.
/// Admins can initialize any address, regular users can only initialize their own addresses. /// Admins can initialize any address, regular users can only initialize their own addresses.

View File

@@ -28,7 +28,7 @@
}, },
"Llm": { "Llm": {
"Gemini": { "Gemini": {
"DefaultModel": "gemini-2.0-flash" "DefaultModel": "gemini/gemini-3-flash-preview"
}, },
"OpenAI": { "OpenAI": {
"DefaultModel": "gpt-4o" "DefaultModel": "gpt-4o"

View File

@@ -69,6 +69,8 @@ public interface ITradingService
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
List<IndicatorRequest> indicators); List<IndicatorRequest> indicators);
Task<Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse> RevokeAllApprovals(string publicAddress);
} }
/// <summary> /// <summary>

View File

@@ -41,5 +41,7 @@ namespace Managing.Application.Abstractions.Services
string? ticker = null, string? ticker = null,
DateTime? fromDate = null, DateTime? fromDate = null,
DateTime? toDate = null); DateTime? toDate = null);
Task<Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse> RevokeAllApprovalsAsync(string address);
} }
} }

View File

@@ -542,12 +542,39 @@ public class TradingService : ITradingService
// Generate signals for the date range using rolling window approach // Generate signals for the date range using rolling window approach
var signals = GenerateSignalsForDateRange(candlesList, scenario, indicatorsValues); var signals = GenerateSignalsForDateRange(candlesList, scenario, indicatorsValues);
return new RefineIndicatorsResult return new RefineIndicatorsResult
{
IndicatorsValues = indicatorsValues,
Signals = signals
};
}
public async Task<Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse> RevokeAllApprovals(string publicAddress)
{ {
IndicatorsValues = indicatorsValues, try
Signals = signals {
}; if (string.IsNullOrEmpty(publicAddress))
} {
_logger.LogWarning("Attempted to revoke approvals with null or empty public address");
return new Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse
{ Success = false, Error = "Public address cannot be null or empty" };
}
// Call the Web3ProxyService to revoke all approvals
var result = await _web3ProxyService.RevokeAllApprovalsAsync(publicAddress);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error revoking all approvals for address {PublicAddress}", publicAddress);
return new Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse
{
Success = false,
Error = ex.Message
};
}
}
/// <summary> /// <summary>
/// Maps IndicatorRequest list to a domain Scenario object. /// Maps IndicatorRequest list to a domain Scenario object.

View File

@@ -0,0 +1,18 @@
namespace Managing.Infrastructure.Evm.Models.Privy;
public class PrivyRevokeAllApprovalsResponse
{
public bool Success { get; set; }
public string? OrderVaultUsdcHash { get; set; }
public string? OrderVaultWethHash { get; set; }
public string? ExchangeRouterUsdcHash { get; set; }
public string? ExchangeRouterWethHash { get; set; }
public string? SyntheticsRouterUsdcHash { get; set; }
public string? SyntheticsRouterWethHash { get; set; }
public string? DataStoreUsdcHash { get; set; }
public string? DataStoreWethHash { get; set; }
public string? DepositVaultUsdcHash { get; set; }
public string? DepositVaultWethHash { get; set; }
public string? Error { get; set; }
}

View File

@@ -819,5 +819,19 @@ namespace Managing.Infrastructure.Evm.Services
return positions; return positions;
} }
public async Task<Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse> RevokeAllApprovalsAsync(string address)
{
var payload = new { address };
var response = await CallPrivyServiceAsync<Managing.Infrastructure.Evm.Models.Privy.PrivyRevokeAllApprovalsResponse>("/revoke-all-approvals", payload);
if (response == null)
{
throw new Web3ProxyException("Revoke all approvals response is null");
}
return response;
}
} }
} }

View File

@@ -1444,40 +1444,6 @@ export interface JobStatusTypeSummary {
count?: number; count?: number;
} }
export interface LlmProgressUpdate {
type?: string;
message?: string;
iteration?: number | null;
maxIterations?: number | null;
toolName?: string | null;
toolArguments?: { [key: string]: any; } | null;
content?: string | null;
response?: LlmChatResponse | null;
error?: string | null;
timestamp?: Date;
}
export interface LlmChatResponse {
content?: string;
provider?: string;
model?: string;
toolCalls?: LlmToolCall[] | null;
usage?: LlmUsage | null;
requiresToolExecution?: boolean;
}
export interface LlmToolCall {
id?: string;
name?: string;
arguments?: { [key: string]: any; };
}
export interface LlmUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}
export interface LlmChatRequest { export interface LlmChatRequest {
messages?: LlmMessage[]; messages?: LlmMessage[];
provider?: string | null; provider?: string | null;
@@ -1488,6 +1454,10 @@ export interface LlmChatRequest {
tools?: McpToolDefinition[] | null; tools?: McpToolDefinition[] | null;
} }
export interface LlmChatStreamRequest extends LlmChatRequest {
connectionId?: string | null;
}
export interface LlmMessage { export interface LlmMessage {
role?: string; role?: string;
content?: string; content?: string;
@@ -1495,6 +1465,12 @@ export interface LlmMessage {
toolCallId?: string | null; toolCallId?: string | null;
} }
export interface LlmToolCall {
id?: string;
name?: string;
arguments?: { [key: string]: any; };
}
export interface McpToolDefinition { export interface McpToolDefinition {
name?: string; name?: string;
description?: string; description?: string;
@@ -1508,6 +1484,21 @@ export interface McpParameterDefinition {
defaultValue?: any | null; defaultValue?: any | null;
} }
export interface LlmChatResponse {
content?: string;
provider?: string;
model?: string;
toolCalls?: LlmToolCall[] | null;
usage?: LlmUsage | null;
requiresToolExecution?: boolean;
}
export interface LlmUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}
export interface ScenarioViewModel { export interface ScenarioViewModel {
name: string; name: string;
indicators: IndicatorViewModel[]; indicators: IndicatorViewModel[];
@@ -1548,6 +1539,21 @@ export interface PrivyInitAddressResponse {
isAlreadyInitialized?: boolean; isAlreadyInitialized?: boolean;
} }
export interface PrivyRevokeAllApprovalsResponse {
success?: boolean;
orderVaultUsdcHash?: string | null;
orderVaultWethHash?: string | null;
exchangeRouterUsdcHash?: string | null;
exchangeRouterWethHash?: string | null;
syntheticsRouterUsdcHash?: string | null;
syntheticsRouterWethHash?: string | null;
dataStoreUsdcHash?: string | null;
dataStoreWethHash?: string | null;
depositVaultUsdcHash?: string | null;
depositVaultWethHash?: string | null;
error?: string | null;
}
export interface IndicatorRequestDto { export interface IndicatorRequestDto {
indicatorName: string; indicatorName: string;
strategyDescription: string; strategyDescription: string;

View File

@@ -45,6 +45,7 @@ declare module 'fastify' {
signPrivyMessage: typeof signPrivyMessage; signPrivyMessage: typeof signPrivyMessage;
approveToken: typeof approveToken; approveToken: typeof approveToken;
initAddress: typeof initAddress; initAddress: typeof initAddress;
revokeAllApprovals: typeof revokeAllApprovals;
sendToken: typeof sendToken; sendToken: typeof sendToken;
getWalletBalance: typeof getWalletBalance; getWalletBalance: typeof getWalletBalance;
getTokenBalanceOnChain: typeof getTokenBalanceOnChain; getTokenBalanceOnChain: typeof getTokenBalanceOnChain;
@@ -975,6 +976,277 @@ export async function initAddress(
} }
} }
/**
* Revokes all token approvals for an address by setting approvals to 0 for all GMX contracts
* @param address The wallet address to revoke approvals for
* @returns Object containing transaction hashes for all revoked approvals
*/
export const revokeAllApprovalsImpl = async (
address: string,
fastify?: FastifyInstance
): Promise<{
orderVaultUsdcHash: string,
orderVaultWethHash: string,
exchangeRouterUsdcHash: string,
exchangeRouterWethHash: string,
syntheticsRouterUsdcHash: string,
syntheticsRouterWethHash: string,
dataStoreUsdcHash: string,
dataStoreWethHash: string,
depositVaultUsdcHash: string,
depositVaultWethHash: string
}> => {
try {
console.log('🚫 Starting revocation of all approvals for address:', address);
const sdk = await getClientForAddress(address);
const {tokensData} = await sdk.tokens.getTokensData();
const usdcTokenData = getTokenDataFromTicker(Ticker.USDC, tokensData);
const wrapperEtherData = getTokenDataFromTicker("WETH", tokensData);
// Revoke by setting approval to 0
const revokeAmount = BigInt(0);
console.log('📊 Revocation amount set to:', revokeAmount.toString());
// Get USDC and WETH token addresses
const usdcToken = GetToken('USDC');
console.log('💱 USDC Token Address:', usdcToken.address);
console.log('💱 WETH Token Address:', wrapperEtherData.address);
const results: any = {};
// ============================================================================
// Revoke OrderVault Approvals
// ============================================================================
console.log('\n🔐 Step 1: Revoking OrderVault approvals...');
// USDC approval for OrderVault
console.log(' Revoking USDC -> OrderVault approval...');
results.orderVaultUsdcHash = await approveContractImpl(
address,
usdcToken.address,
CONTRACTS[ARBITRUM].OrderVault,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ USDC OrderVault revocation hash:', results.orderVaultUsdcHash);
// WETH approval for OrderVault
console.log(' Revoking WETH -> OrderVault approval...');
results.orderVaultWethHash = await approveContractImpl(
address,
wrapperEtherData.address,
CONTRACTS[ARBITRUM].OrderVault,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ WETH OrderVault revocation hash:', results.orderVaultWethHash);
// ============================================================================
// Revoke ExchangeRouter Approvals
// ============================================================================
console.log('\n🔐 Step 2: Revoking ExchangeRouter approvals...');
// USDC approval for ExchangeRouter
console.log(' Revoking USDC -> ExchangeRouter approval...');
results.exchangeRouterUsdcHash = await approveContractImpl(
address,
usdcToken.address,
CONTRACTS[ARBITRUM].ExchangeRouter,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ USDC ExchangeRouter revocation hash:', results.exchangeRouterUsdcHash);
// WETH approval for ExchangeRouter
console.log(' Revoking WETH -> ExchangeRouter approval...');
results.exchangeRouterWethHash = await approveContractImpl(
address,
wrapperEtherData.address,
CONTRACTS[ARBITRUM].ExchangeRouter,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ WETH ExchangeRouter revocation hash:', results.exchangeRouterWethHash);
// ============================================================================
// Revoke SyntheticsRouter Approvals
// ============================================================================
console.log('\n🔐 Step 3: Revoking SyntheticsRouter approvals...');
// USDC approval for SyntheticsRouter
console.log(' Revoking USDC -> SyntheticsRouter approval...');
results.syntheticsRouterUsdcHash = await approveContractImpl(
address,
usdcToken.address,
CONTRACTS[ARBITRUM].SyntheticsRouter,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ USDC SyntheticsRouter revocation hash:', results.syntheticsRouterUsdcHash);
// WETH approval for SyntheticsRouter
console.log(' Revoking WETH -> SyntheticsRouter approval...');
results.syntheticsRouterWethHash = await approveContractImpl(
address,
wrapperEtherData.address,
CONTRACTS[ARBITRUM].SyntheticsRouter,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ WETH SyntheticsRouter revocation hash:', results.syntheticsRouterWethHash);
// ============================================================================
// Revoke DataStore Approvals
// ============================================================================
console.log('\n🔐 Step 4: Revoking DataStore approvals...');
// USDC approval for DataStore
console.log(' Revoking USDC -> DataStore approval...');
results.dataStoreUsdcHash = await approveContractImpl(
address,
usdcToken.address,
CONTRACTS[ARBITRUM].DataStore,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ USDC DataStore revocation hash:', results.dataStoreUsdcHash);
// WETH approval for DataStore
console.log(' Revoking WETH -> DataStore approval...');
results.dataStoreWethHash = await approveContractImpl(
address,
wrapperEtherData.address,
CONTRACTS[ARBITRUM].DataStore,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ WETH DataStore revocation hash:', results.dataStoreWethHash);
// ============================================================================
// Revoke DepositVault Approvals
// ============================================================================
console.log('\n🔐 Step 5: Revoking DepositVault approvals...');
// USDC approval for DepositVault
console.log(' Revoking USDC -> DepositVault approval...');
results.depositVaultUsdcHash = await approveContractImpl(
address,
usdcToken.address,
CONTRACTS[ARBITRUM].DepositVault,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ USDC DepositVault revocation hash:', results.depositVaultUsdcHash);
// WETH approval for DepositVault
console.log(' Revoking WETH -> DepositVault approval...');
results.depositVaultWethHash = await approveContractImpl(
address,
wrapperEtherData.address,
CONTRACTS[ARBITRUM].DepositVault,
ARBITRUM,
revokeAmount,
true,
fastify
);
console.log(' ✅ WETH DepositVault revocation hash:', results.depositVaultWethHash);
// ============================================================================
// SUMMARY: Final revocation status
// ============================================================================
console.log('\n📋 Final Revocation Summary:');
console.log(' OrderVault USDC:', results.orderVaultUsdcHash);
console.log(' OrderVault WETH:', results.orderVaultWethHash);
console.log(' ExchangeRouter USDC:', results.exchangeRouterUsdcHash);
console.log(' ExchangeRouter WETH:', results.exchangeRouterWethHash);
console.log(' SyntheticsRouter USDC:', results.syntheticsRouterUsdcHash);
console.log(' SyntheticsRouter WETH:', results.syntheticsRouterWethHash);
console.log(' DataStore USDC:', results.dataStoreUsdcHash);
console.log(' DataStore WETH:', results.dataStoreWethHash);
console.log(' DepositVault USDC:', results.depositVaultUsdcHash);
console.log(' DepositVault WETH:', results.depositVaultWethHash);
// Clear approval cache for this address since all approvals have been revoked
try {
const {clearApprovalCacheForAccount} = await import('../../utils/approvalCache.js');
await clearApprovalCacheForAccount(ARBITRUM, address);
console.log(' ✅ Approval cache cleared for address');
} catch (cacheError) {
console.warn(' ⚠️ Failed to clear approval cache:', cacheError);
// Don't fail the entire operation if cache clearing fails
}
console.log('\n✅ All approvals revoked successfully!');
return results;
} catch (error) {
console.error('❌ Error revoking approvals:', error);
throw new Error(`Failed to revoke approvals: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
};
/**
* Revokes all token approvals for a wallet address
* @param this The FastifyRequest instance
* @param reply The FastifyReply instance
* @param address The wallet address to revoke approvals for
* @returns The response object with success status and transaction hashes
*/
export async function revokeAllApprovals(
this: FastifyRequest,
reply: FastifyReply,
address: string
) {
try {
if (!address) {
throw new Error('Wallet address is required for revocation');
}
const results = await revokeAllApprovalsImpl(address, this.server);
return {
success: true,
orderVaultUsdcHash: results.orderVaultUsdcHash,
orderVaultWethHash: results.orderVaultWethHash,
exchangeRouterUsdcHash: results.exchangeRouterUsdcHash,
exchangeRouterWethHash: results.exchangeRouterWethHash,
syntheticsRouterUsdcHash: results.syntheticsRouterUsdcHash,
syntheticsRouterWethHash: results.syntheticsRouterWethHash,
dataStoreUsdcHash: results.dataStoreUsdcHash,
dataStoreWethHash: results.dataStoreWethHash,
depositVaultUsdcHash: results.depositVaultUsdcHash,
depositVaultWethHash: results.depositVaultWethHash
};
} catch (error) {
this.log.error(error);
reply.status(500);
return {
success: false,
error: error instanceof Error ? error.message : 'An unknown error occurred'
};
}
}
/** /**
* Sends tokens from one address to another using Privy wallet (implementation) * Sends tokens from one address to another using Privy wallet (implementation)
* @param senderAddress The sender's wallet address * @param senderAddress The sender's wallet address
@@ -1756,6 +2028,10 @@ export default fp(async (fastify) => {
return initAddress.call(this, reply, address); return initAddress.call(this, reply, address);
}); });
fastify.decorateRequest('revokeAllApprovals', async function(this: FastifyRequest, reply: FastifyReply, address: string) {
return revokeAllApprovals.call(this, reply, address);
});
fastify.decorateRequest('sendToken', async function(this: FastifyRequest, reply: FastifyReply, senderAddress: string, recipientAddress: string, ticker: string, amount: string, chainId?: number) { fastify.decorateRequest('sendToken', async function(this: FastifyRequest, reply: FastifyReply, senderAddress: string, recipientAddress: string, ticker: string, amount: string, chainId?: number) {
return sendToken.call(this, reply, senderAddress, recipientAddress, ticker, amount, chainId); return sendToken.call(this, reply, senderAddress, recipientAddress, ticker, amount, chainId);
}); });

View File

@@ -77,6 +77,50 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
} }
) )
fastify.post(
'/revoke-all-approvals',
{
schema: {
body: Type.Object({
address: Type.String()
}),
response: {
200: Type.Object({
success: Type.Boolean(),
orderVaultUsdcHash: Type.Optional(Type.String()),
orderVaultWethHash: Type.Optional(Type.String()),
exchangeRouterUsdcHash: Type.Optional(Type.String()),
exchangeRouterWethHash: Type.Optional(Type.String()),
syntheticsRouterUsdcHash: Type.Optional(Type.String()),
syntheticsRouterWethHash: Type.Optional(Type.String()),
dataStoreUsdcHash: Type.Optional(Type.String()),
dataStoreWethHash: Type.Optional(Type.String()),
depositVaultUsdcHash: Type.Optional(Type.String()),
depositVaultWethHash: Type.Optional(Type.String()),
error: Type.Optional(Type.String())
}),
400: Type.Object({
success: Type.Boolean(),
error: Type.String()
}),
500: Type.Object({
success: Type.Boolean(),
error: Type.String()
})
},
tags: ['Privy']
}
},
async function (request, reply) {
try {
const { address } = request.body;
return await request.revokeAllApprovals(reply, address);
} catch (error) {
return handleError(request, reply, error, 'privy/revoke-all-approvals');
}
}
)
fastify.post( fastify.post(
'/send-token', '/send-token',
{ {

View File

@@ -2910,7 +2910,7 @@ export class LlmClient extends AuthorizedApiBase {
this.baseUrl = baseUrl ?? "http://localhost:5000"; this.baseUrl = baseUrl ?? "http://localhost:5000";
} }
llm_ChatStream(request: LlmChatRequest): Promise<LlmProgressUpdate[]> { llm_ChatStream(request: LlmChatStreamRequest): Promise<FileResponse> {
let url_ = this.baseUrl + "/Llm/ChatStream"; let url_ = this.baseUrl + "/Llm/ChatStream";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@@ -2921,7 +2921,7 @@ export class LlmClient extends AuthorizedApiBase {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Accept": "application/json" "Accept": "application/octet-stream"
} }
}; };
@@ -2932,21 +2932,26 @@ export class LlmClient extends AuthorizedApiBase {
}); });
} }
protected processLlm_ChatStream(response: Response): Promise<LlmProgressUpdate[]> { protected processLlm_ChatStream(response: Response): Promise<FileResponse> {
const status = response.status; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) { if (status === 200 || status === 206) {
return response.text().then((_responseText) => { const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let result200: any = null; let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as LlmProgressUpdate[]; let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
return result200; if (fileName) {
}); fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) { } else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => { return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers); return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}); });
} }
return Promise.resolve<LlmProgressUpdate[]>(null as any); return Promise.resolve<FileResponse>(null as any);
} }
llm_Chat(request: LlmChatRequest): Promise<LlmChatResponse> { llm_Chat(request: LlmChatRequest): Promise<LlmChatResponse> {
@@ -4262,6 +4267,45 @@ export class TradingClient extends AuthorizedApiBase {
return Promise.resolve<PrivyInitAddressResponse>(null as any); return Promise.resolve<PrivyInitAddressResponse>(null as any);
} }
trading_RevokeAllApprovals(publicAddress: string): Promise<PrivyRevokeAllApprovalsResponse> {
let url_ = this.baseUrl + "/Trading/RevokeAllApprovals";
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_RevokeAllApprovals(_response);
});
}
protected processTrading_RevokeAllApprovals(response: Response): Promise<PrivyRevokeAllApprovalsResponse> {
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 PrivyRevokeAllApprovalsResponse;
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<PrivyRevokeAllApprovalsResponse>(null as any);
}
trading_RequestIndicator(request: IndicatorRequestDto): Promise<FileResponse> { trading_RequestIndicator(request: IndicatorRequestDto): Promise<FileResponse> {
let url_ = this.baseUrl + "/Trading/RequestIndicator"; let url_ = this.baseUrl + "/Trading/RequestIndicator";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@@ -6272,40 +6316,6 @@ export interface JobStatusTypeSummary {
count?: number; count?: number;
} }
export interface LlmProgressUpdate {
type?: string;
message?: string;
iteration?: number | null;
maxIterations?: number | null;
toolName?: string | null;
toolArguments?: { [key: string]: any; } | null;
content?: string | null;
response?: LlmChatResponse | null;
error?: string | null;
timestamp?: Date;
}
export interface LlmChatResponse {
content?: string;
provider?: string;
model?: string;
toolCalls?: LlmToolCall[] | null;
usage?: LlmUsage | null;
requiresToolExecution?: boolean;
}
export interface LlmToolCall {
id?: string;
name?: string;
arguments?: { [key: string]: any; };
}
export interface LlmUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}
export interface LlmChatRequest { export interface LlmChatRequest {
messages?: LlmMessage[]; messages?: LlmMessage[];
provider?: string | null; provider?: string | null;
@@ -6316,6 +6326,10 @@ export interface LlmChatRequest {
tools?: McpToolDefinition[] | null; tools?: McpToolDefinition[] | null;
} }
export interface LlmChatStreamRequest extends LlmChatRequest {
connectionId?: string | null;
}
export interface LlmMessage { export interface LlmMessage {
role?: string; role?: string;
content?: string; content?: string;
@@ -6323,6 +6337,12 @@ export interface LlmMessage {
toolCallId?: string | null; toolCallId?: string | null;
} }
export interface LlmToolCall {
id?: string;
name?: string;
arguments?: { [key: string]: any; };
}
export interface McpToolDefinition { export interface McpToolDefinition {
name?: string; name?: string;
description?: string; description?: string;
@@ -6336,6 +6356,21 @@ export interface McpParameterDefinition {
defaultValue?: any | null; defaultValue?: any | null;
} }
export interface LlmChatResponse {
content?: string;
provider?: string;
model?: string;
toolCalls?: LlmToolCall[] | null;
usage?: LlmUsage | null;
requiresToolExecution?: boolean;
}
export interface LlmUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}
export interface ScenarioViewModel { export interface ScenarioViewModel {
name: string; name: string;
indicators: IndicatorViewModel[]; indicators: IndicatorViewModel[];
@@ -6376,6 +6411,21 @@ export interface PrivyInitAddressResponse {
isAlreadyInitialized?: boolean; isAlreadyInitialized?: boolean;
} }
export interface PrivyRevokeAllApprovalsResponse {
success?: boolean;
orderVaultUsdcHash?: string | null;
orderVaultWethHash?: string | null;
exchangeRouterUsdcHash?: string | null;
exchangeRouterWethHash?: string | null;
syntheticsRouterUsdcHash?: string | null;
syntheticsRouterWethHash?: string | null;
dataStoreUsdcHash?: string | null;
dataStoreWethHash?: string | null;
depositVaultUsdcHash?: string | null;
depositVaultWethHash?: string | null;
error?: string | null;
}
export interface IndicatorRequestDto { export interface IndicatorRequestDto {
indicatorName: string; indicatorName: string;
strategyDescription: string; strategyDescription: string;

View File

@@ -1444,40 +1444,6 @@ export interface JobStatusTypeSummary {
count?: number; count?: number;
} }
export interface LlmProgressUpdate {
type?: string;
message?: string;
iteration?: number | null;
maxIterations?: number | null;
toolName?: string | null;
toolArguments?: { [key: string]: any; } | null;
content?: string | null;
response?: LlmChatResponse | null;
error?: string | null;
timestamp?: Date;
}
export interface LlmChatResponse {
content?: string;
provider?: string;
model?: string;
toolCalls?: LlmToolCall[] | null;
usage?: LlmUsage | null;
requiresToolExecution?: boolean;
}
export interface LlmToolCall {
id?: string;
name?: string;
arguments?: { [key: string]: any; };
}
export interface LlmUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}
export interface LlmChatRequest { export interface LlmChatRequest {
messages?: LlmMessage[]; messages?: LlmMessage[];
provider?: string | null; provider?: string | null;
@@ -1488,6 +1454,10 @@ export interface LlmChatRequest {
tools?: McpToolDefinition[] | null; tools?: McpToolDefinition[] | null;
} }
export interface LlmChatStreamRequest extends LlmChatRequest {
connectionId?: string | null;
}
export interface LlmMessage { export interface LlmMessage {
role?: string; role?: string;
content?: string; content?: string;
@@ -1495,6 +1465,12 @@ export interface LlmMessage {
toolCallId?: string | null; toolCallId?: string | null;
} }
export interface LlmToolCall {
id?: string;
name?: string;
arguments?: { [key: string]: any; };
}
export interface McpToolDefinition { export interface McpToolDefinition {
name?: string; name?: string;
description?: string; description?: string;
@@ -1508,6 +1484,21 @@ export interface McpParameterDefinition {
defaultValue?: any | null; defaultValue?: any | null;
} }
export interface LlmChatResponse {
content?: string;
provider?: string;
model?: string;
toolCalls?: LlmToolCall[] | null;
usage?: LlmUsage | null;
requiresToolExecution?: boolean;
}
export interface LlmUsage {
promptTokens?: number;
completionTokens?: number;
totalTokens?: number;
}
export interface ScenarioViewModel { export interface ScenarioViewModel {
name: string; name: string;
indicators: IndicatorViewModel[]; indicators: IndicatorViewModel[];
@@ -1548,6 +1539,21 @@ export interface PrivyInitAddressResponse {
isAlreadyInitialized?: boolean; isAlreadyInitialized?: boolean;
} }
export interface PrivyRevokeAllApprovalsResponse {
success?: boolean;
orderVaultUsdcHash?: string | null;
orderVaultWethHash?: string | null;
exchangeRouterUsdcHash?: string | null;
exchangeRouterWethHash?: string | null;
syntheticsRouterUsdcHash?: string | null;
syntheticsRouterWethHash?: string | null;
dataStoreUsdcHash?: string | null;
dataStoreWethHash?: string | null;
depositVaultUsdcHash?: string | null;
depositVaultWethHash?: string | null;
error?: string | null;
}
export interface IndicatorRequestDto { export interface IndicatorRequestDto {
indicatorName: string; indicatorName: string;
strategyDescription: string; strategyDescription: string;

View File

@@ -1,7 +1,7 @@
import {ChevronDownIcon, ChevronRightIcon,} from '@heroicons/react/solid' import {ChevronDownIcon, ChevronRightIcon,} from '@heroicons/react/solid'
import React, {useEffect, useMemo, useState} from 'react' import React, {useEffect, useMemo, useState} from 'react'
import {useNavigate} from 'react-router-dom' import {useNavigate} from 'react-router-dom'
import {FiCopy, FiKey, FiPlay, FiTrash2, FiTrendingUp} from 'react-icons/fi' import {FiCopy, FiKey, FiPlay, FiTrash2, FiTrendingUp, FiX} from 'react-icons/fi'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules' import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules'
@@ -55,6 +55,22 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
} }
} }
async function revokeAllApprovals(publicAddress: string) {
const t = new Toast('Revoking all approvals')
const client = new TradingClient({}, apiUrl)
try {
const response = await client.trading_RevokeAllApprovals(publicAddress)
if (response.success) {
t.update('success', 'All approvals revoked successfully')
} else {
t.update('error', `Revocation failed: ${response.error || 'Unknown error'}`)
}
} catch (err) {
t.update('error', 'Error: ' + err)
}
}
async function copyToClipboard(text: string) { async function copyToClipboard(text: string) {
const t = new Toast('Copying to clipboard...') const t = new Toast('Copying to clipboard...')
try { try {
@@ -160,6 +176,14 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
<span className="ml-1">Init</span> <span className="ml-1">Init</span>
</button> </button>
<button
className="btn btn-sm btn-warning btn-outline"
onClick={() => revokeAllApprovals(account.key)}
>
<FiX />
<span className="ml-1">Revoke</span>
</button>
<button <button
className="btn btn-sm btn-success btn-outline" className="btn btn-sm btn-success btn-outline"
onClick={() => { onClick={() => {

View File

@@ -1,7 +1,7 @@
import {ChevronDownIcon, ChevronRightIcon,} from '@heroicons/react/solid' import {ChevronDownIcon, ChevronRightIcon,} from '@heroicons/react/solid'
import React, {useEffect, useMemo, useState} from 'react' import React, {useEffect, useMemo, useState} from 'react'
import {useNavigate} from 'react-router-dom' import {useNavigate} from 'react-router-dom'
import {FiCopy, FiKey, FiPlay, FiTrash2, FiTrendingUp} from 'react-icons/fi' import {FiCopy, FiKey, FiPlay, FiTrash2, FiTrendingUp, FiX} from 'react-icons/fi'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules' import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules'
@@ -55,6 +55,22 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
} }
} }
async function revokeAllApprovals(publicAddress: string) {
const t = new Toast('Revoking all approvals')
const client = new TradingClient({}, apiUrl)
try {
const response = await client.trading_RevokeAllApprovals(publicAddress)
if (response.success) {
t.update('success', 'All approvals revoked successfully')
} else {
t.update('error', `Revocation failed: ${response.error || 'Unknown error'}`)
}
} catch (err) {
t.update('error', 'Error: ' + err)
}
}
async function copyToClipboard(text: string) { async function copyToClipboard(text: string) {
const t = new Toast('Copying to clipboard...') const t = new Toast('Copying to clipboard...')
try { try {
@@ -160,6 +176,14 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
<span className="ml-1">Init</span> <span className="ml-1">Init</span>
</button> </button>
<button
className="btn btn-sm btn-warning btn-outline"
onClick={() => revokeAllApprovals(account.key)}
>
<FiX />
<span className="ml-1">Revoke</span>
</button>
<button <button
className="btn btn-sm btn-success btn-outline" className="btn btn-sm btn-success btn-outline"
onClick={() => { onClick={() => {