import { Address, Hex } from 'viem'; import { UserOperation } from '@prex0/prex-structs'; import { retryFetch } from '../utils/retry-fetch'; import { PublicKeyCredentialCreationOptionsJSON, RegistrationResponseJSON, } from '@simplewebauthn/typescript-types'; interface LoginResponse { data: { id: string; eth_address?: Address; sub?: string; public_id: string; }; } interface PrepareResponse { options: PublicKeyCredentialCreationOptionsJSON; } interface RegistrationResponse { eth_address: Address; } interface CreateAccountResponse { eth_address: Address; } interface GetWallet2Response { id: string; public_id: string; eth_address: Address; wallet_nickname: string; sub: string; created_at: string; wallet_passkeys: { id: string; user_handle: string; passkey_name: string; public_key?: Hex; owner_index: number; is_registered: boolean; backup_status: boolean; created_at: string; }[]; wallet_eoas: { id: number; owner_index: number; public_key: Address; is_registered: boolean; created_at: string; }[]; } interface PrexApiServiceOptions { maxRetries: number; } interface RecipientData { recipient: Address; sig: Hex; metadata: Hex; } interface GetHistoryRequest { history_type: 'allTransfers' | 'transfers' | 'onetimeLocks' | 'swap'; variables: { token?: Address; eth_address?: Address; limit: number; offset: number; }; } interface TransferHistoryItemResponse { id: string; movingType: string; token: { id: string; }; sender: { id: string; }; recipient: { id: string; }; amount: string; metadata: any; txHash: string; createdAt: string; } interface OnetimeLockHistoryItemResponse { id: string; token: { id: string; }; sender: { id: string; }; recipient?: { id: string; }; expiry: string; amount: string; metadata: any; txHash: string; createdAt: string; updatedAt: string; status: 'LIVE' | 'COMPLETED' | 'CANCELLED'; } interface SwapHistoryItemResponse { id: string; reactor: string; token: { id: string; }; swapper: { id: string; }; outputs: { id: string; token: { id: string; }; amount: string; recipient: { id: string; }; }[]; createdAt: string; txHash: string; } interface FeeTier { address: Address; feeTiers: { fee: string; minAmount: string; }[]; } export class PrexApiService { public options: PrexApiServiceOptions; public apiKey: string | null = null; public ethAddress: Address | null = null; constructor( public chainId: number, public ruleId: string, public endpoint: string, options?: PrexApiServiceOptions ) { if (options) { this.options = options; } else { this.options = { maxRetries: 2, }; } } setApiKey(apiKey: string) { this.apiKey = apiKey; } setEthAddress(ethAddress: Address) { this.ethAddress = ethAddress; } async login(recapture_response?: string) { return (await this._post(`prepare/login`, { chain_id: this.chainId, recapture_response, })) as LoginResponse; } async prepare(wallet_id: string, user_name?: string) { return (await this._post(`prepare/prepare`, { chain_id: this.chainId, wallet_id, user_name, })) as PrepareResponse; } async register( wallet_id: string, response: RegistrationResponseJSON, subkey?: string ) { return (await this._post(`prepare/register`, { chain_id: this.chainId, wallet_id, response, subkey, })) as RegistrationResponse; } async createAccount(wallet_id: string) { return (await this._post(`api/wallet/create-account`, { chain_id: this.chainId, wallet_id, })) as CreateAccountResponse; } async addKey(eth_address: string, user_handle?: string) { return await this._post(`api/wallet/add-key`, { chain_id: this.chainId, eth_address, user_handle, }); } async removeKey(eth_address: string, owner_index: number) { return await this._post(`api/wallet/remove-key`, { chain_id: this.chainId, eth_address, owner_index, }); } async signPaymasterAndData(userOperation: UserOperation) { const result = await this._post(`api/transaction/get-paymaster-sig`, { chain_id: this.chainId, encoded_user_op: userOperation.serialize(), }); return result.paymasterAndData as Hex; } async execute(userOperation: UserOperation, signature: Hex) { return (await this._post(`api/transaction/execute-op`, { chain_id: this.chainId, encoded_user_op: userOperation.serialize(), signature, })) as { gas_used: number; hash: Hex; }; } async mint(token: Address, recipient: Address, amount: bigint) { return await this._post(`api/transfer/mint`, { chain_id: this.chainId, token, recipient, amount: amount.toString(), }); } async transfer( encodedRequest: string, sig: string ): Promise<{ hash: Hex; }> { return await this._post(`api/transfer/transfer`, { chain_id: this.chainId, encoded_request: encodedRequest, sig: sig, ver: '2', }); } async submitLinkTransfer( encodedRequest: string, sig: string ): Promise<{ hash: Hex; }> { return await this._post(`api/transfer/submit-link-transfer`, { chain_id: this.chainId, encoded_request: encodedRequest, sig: sig, ver: '2', }); } async confirmLinkTransfer( id: string, recipientData: RecipientData ): Promise<{ hash: Hex; }> { return await this._post(`api/transfer/confirm-link-transfer`, { chain_id: this.chainId, id: id, recipient_data: recipientData, ver: '2', }); } async confirmSecretTransfer( request: string, sig: string, recipientData: RecipientData ) { return await this._post(`api/transfer/confirm-secret-transfer`, { chain_id: this.chainId, encoded_request: request, sig, recipient_data: recipientData, }); } async permit(permitRequest: { address: Address; owner: Address; spender: Address; value: string; deadline: string; signature: Hex; }) { return await this._post(`api/permit`, { chain_id: this.chainId, permit_request: permitRequest, }); } async swap(encoded_order: any, sig: string, swap_route: Hex) { return (await this._post(`swap/execute`, { chain_id: this.chainId, encoded_order, sig, swap_route, })) as { hash: Hex; }; } async distributeSubmit(encoded_request: string, sig: string) { return await this._post(`distribute/submit`, { chain_id: this.chainId, encoded_request, sig, }); } async distributeDeposit(encoded_request: string, sig: string) { return (await this._post(`distribute/deposit`, { chain_id: this.chainId, encoded_request, sig, })) as { hash: Hex; }; } async distributeWithdraw(request: { requestId: Hex; recipient: Address; nonce: string; deadline: string; sig: Hex; subPublicKey: Hex; subSig: Hex; }) { return (await this._post(`distribute/distribute`, { chain_id: this.chainId, request, })) as { hash: Hex; }; } async getConfig(chainId: number) { const result = await this._get(`api/init?chain_id=${chainId}`); return result as { fee: FeeTier[]; max_fee_per_gas: string; max_priority_fee_per_gas: string; }; } async getFeeTiers(chainId: number) { const result = await this._get(`swap/fee?chain_id=${chainId}`); return result as { fee: FeeTier[]; }; } async getWallet2(walletId?: string) { if (walletId) { const result = await this._get(`get-wallet2?public_id=${walletId}`); return result as { wallet: GetWallet2Response | null; }; } else { const result = await this._get(`get-wallet2`); return result as { wallet: GetWallet2Response | null; }; } } async getWalletByPasskeyID(passkeyId: string) { const result = await this._get( `api/wallet/get-wallet?passkey_id=${passkeyId}` ); return result as { id: string; eth_address: string; owner_index: number; public_id: string; }; } async getWalletByEOA(public_key: Address) { const result = await this._get( `api/wallet/get-wallet?public_key=${public_key}` ); return result as { id: string; eth_address: string; owner_index: number; public_id: string; }; } async getAddress(owner: Hex) { return this._get( `api/wallet/get-address?owner=${owner}&chain_id=${this.chainId}` ); } async getHistory(request: GetHistoryRequest) { return (await this._post(`api-query/transfer/history`, { ...request, chain_id: this.chainId, })) as TransferHistoryItemResponse[]; } async postGraph(body: any) { return await this._post(`api-query/graph`, { chain_id: this.chainId, body, }); } async postExecutePumSwap(order: string, sig: string) { return (await this._post(`pumpum/swap`, { chain_id: this.chainId, encoded_order: order, sig, })) as { hash: Hex; }; } async postIssuePumToken(order: string, sig: string) { return (await this._post(`pumpum/issue`, { chain_id: this.chainId, encoded_order: order, sig, })) as { result: Address; hash: Hex; }; } async postBuyPoint(order: string, sig: string) { return await this._post(`pumpum/buy`, { chain_id: this.chainId, encoded_order: order, sig, }); } async getLinkTransferHistory(request: GetHistoryRequest) { return (await this._post(`api-query/transfer/history`, { ...request, chain_id: this.chainId, })) as OnetimeLockHistoryItemResponse[]; } async getSwapHistory(request: GetHistoryRequest) { return (await this._post(`api-query/transfer/history`, { ...request, chain_id: this.chainId, })) as SwapHistoryItemResponse[]; } async uploadAvatar({ file, eth_address, signature, timestamp, }: { file: File; timestamp: number; eth_address: string; signature: Hex; }) { const formData = new FormData(); formData.append('chain_id', this.chainId.toString()); formData.append('file', file); formData.append('eth_address', eth_address); formData.append('signature', signature); formData.append('timestamp', timestamp.toString()); return (await this._postFormData(`avatar/upload`, formData)) as { path: string; fullPath: string; url: string; }; } async uploadAvatarV2({ file, eth_address, }: { file: File; eth_address: string; }) { const formData = new FormData(); formData.append('chain_id', this.chainId.toString()); formData.append('file', file); formData.append('eth_address', eth_address); return (await this._postFormData(`avatar/upload2`, formData)) as { path: string; fullPath: string; url: string; }; } async copyAvatarV2({ picture_url, eth_address, }: { picture_url: string; eth_address: string; }) { return (await this._post(`avatar/copy2`, { chain_id: this.chainId, picture_url, eth_address, })) as { path: string; fullPath: string; url: string; }; } async generateAuthOptions({ rp_id, sub }: { rp_id: string; sub: string }) { return await this._post(`/auth/options`, { chain_id: this.chainId, rp_id, sub, }); } async _post(path: string, data: any) { return await this._postRaw(path, JSON.stringify(data)); } async _postFormData(path: string, data: FormData) { return await this._postRaw(path, data, undefined); } async _postRaw(path: string, data: any, contentType?: string) { const headers: Record = { 'x-rule': this.ruleId.toString(), 'x-app-sig': this.apiKey || '', 'x-wallet': this.ethAddress || '', }; if (contentType) { headers['Content-Type'] = contentType; } const res = await retryFetch( `${this.endpoint}/${path}`, { method: 'POST', mode: 'cors', headers, body: data, }, { retries: this.options.maxRetries, } ); if (res.status >= 500 && res.status < 600) { throw new Error('Server Error'); } return await res.json(); } async _put(path: string, data: any) { const res = await retryFetch( `${this.endpoint}/${path}`, { method: 'PUT', mode: 'cors', headers: { 'Content-Type': 'application/json', 'x-rule': this.ruleId.toString(), 'x-app-sig': this.apiKey || '', 'x-wallet': this.ethAddress || '', }, body: JSON.stringify(data), }, { retries: this.options.maxRetries, } ); return await res.json(); } async _get(url: string) { const res = await retryFetch( `${this.endpoint}/${url}`, { method: 'GET', mode: 'cors', headers: { 'Content-Type': 'application/json', 'x-rule': this.ruleId.toString(), 'x-app-sig': this.apiKey || '', 'x-wallet': this.ethAddress || '', }, }, { retries: this.options.maxRetries, } ); return await res.json(); } }