import { Events, type IPortalApi, generateTraceId, sdkLogger, } from '@portal-hq/utils' import type { ApproveDelegationRequest, ApproveDelegationResponse, DelegationStatusResponse, GetDelegationStatusRequest, RevokeDelegationRequest, RevokeDelegationResponse, TransferFromRequest, TransferFromResponse, } from './types' const DELEGATIONS_BASE_PATH = '/api/v3/clients/me/chains' const DelegationsEndpoint = { approve: (chain: string, token: string) => `${DELEGATIONS_BASE_PATH}/${chain}/assets/${token}/approvals`, revoke: (chain: string, token: string) => `${DELEGATIONS_BASE_PATH}/${chain}/assets/${token}/revocations`, getStatus: (chain: string, token: string, delegateAddress: string) => `${DELEGATIONS_BASE_PATH}/${chain}/assets/${token}/delegations?delegateAddress=${encodeURIComponent(delegateAddress)}`, getStatusBase: (chain: string, token: string) => `${DELEGATIONS_BASE_PATH}/${chain}/assets/${token}/delegations`, transferFrom: (chain: string, token: string) => `${DELEGATIONS_BASE_PATH}/${chain}/assets/${token}/delegations/transfers`, } as const export interface IPortalDelegationsApi { approve(params: ApproveDelegationRequest): Promise revoke(params: RevokeDelegationRequest): Promise getStatus( params: GetDelegationStatusRequest, ): Promise transferFrom(params: TransferFromRequest): Promise } export interface PortalDelegationsApiOptions { api: IPortalApi } export class PortalDelegationsApi implements IPortalDelegationsApi { private readonly api: IPortalApi constructor(options: PortalDelegationsApiOptions) { this.api = options.api } /** * Approves a delegation for a specified token on a given chain. * Returns transactions for EVM chains or encodedTransactions for Solana chains. * @param params - Delegation approval parameters * @returns Promise resolving to approval response with transaction data */ public async approve( params: ApproveDelegationRequest, ): Promise { const path = DelegationsEndpoint.approve(params.chain, params.token) const body = { delegateAddress: params.delegateAddress, amount: params.amount, } const response = await this.api.requests.post( path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body, traceId: generateTraceId(), }, ) await this.trackEvent(Events.DelegationsApprove, { path, chain: params.chain, token: params.token, }) return response } /** * Revokes a delegation for a specified token on a given chain. * Returns transactions for EVM chains or encodedTransactions for Solana chains. * @param params - Delegation revocation parameters * @returns Promise resolving to revocation response with transaction data */ public async revoke( params: RevokeDelegationRequest, ): Promise { const path = DelegationsEndpoint.revoke(params.chain, params.token) const body = { delegateAddress: params.delegateAddress, } const response = await this.api.requests.post( path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body, traceId: generateTraceId(), }, ) await this.trackEvent(Events.DelegationsRevoke, { path, chain: params.chain, token: params.token, }) return response } /** * Retrieves the delegation status for a specified token and delegate address. * @param params - Delegation status query parameters * @returns Promise resolving to delegation status information */ public async getStatus( params: GetDelegationStatusRequest, ): Promise { const path = DelegationsEndpoint.getStatus( params.chain, params.token, params.delegateAddress, ) const response = await this.api.requests.get( path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', }, traceId: generateTraceId(), }, ) await this.trackEvent(Events.DelegationsStatus, { path: DelegationsEndpoint.getStatusBase(params.chain, params.token), chain: params.chain, token: params.token, }) return response } /** * Transfers tokens from one address to another using delegated authority. * The caller must have been previously approved as a delegate. * Returns transactions for EVM chains or encodedTransactions for Solana chains. * @param params - Transfer from delegation parameters * @returns Promise resolving to transfer response with transaction data */ public async transferFrom( params: TransferFromRequest, ): Promise { const path = DelegationsEndpoint.transferFrom(params.chain, params.token) const body = { fromAddress: params.fromAddress, toAddress: params.toAddress, amount: params.amount, } const response = await this.api.requests.post(path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body, traceId: generateTraceId(), }) await this.trackEvent(Events.DelegationsTransferFrom, { path, chain: params.chain, token: params.token, }) return response } private async trackEvent( event: string, properties: Record, ): Promise { try { await this.api.track(event, properties) } catch (error) { sdkLogger.warn( `[PortalDelegationsApi] Failed to track event "${event}":`, error, ) } } } export default PortalDelegationsApi