import { Address, ContractFunctionParameters, Hex, PublicActions, encodeAbiParameters, encodeFunctionData, fromHex, isAddressEqual, maxUint256, toHex, } from 'viem'; import { PrexSigner } from './core/sign'; import { PagingOptions, PrexUser, TransferByLinkResponse, CreateWalletResult, LinkTransferHistoryItem, PrexClientOptions, TransferHistoryQuery, TokenActivity, } from './types'; import { DutchOrder } from '@prex0/prex-structs'; import { PrexApiService } from './api'; import { getEvmChainClient } from './evm-client'; import { decodePublicKey, getCredentialID, registerByWebAuthn, } from './core/web-authn'; import { SmartWalletAbi } from './abis/SmartWallet'; import { browserSupportsWebAuthn, platformAuthenticatorIsAvailable, startAuthentication, } from '@simplewebauthn/browser'; import { Buffer } from 'buffer'; import { privateKeyToAddress } from 'viem/accounts'; import { Logger } from './utils/logger'; import { RegistrationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, } from '@simplewebauthn/typescript-types'; import { PrexSDKError, normalizeErrorFn } from './errors'; import { ProviderInterface } from './providers/base-provider'; import { PrexStorage } from './storage/PrexStorage'; import { LocalForageStorage } from './storage/LocalForageStorage'; import { SwapAction } from './actions/swap'; import { ApproveAction } from './actions/approve'; import { ExecuteOperationAction } from './actions/execute-operation'; import { TransferAction, TransferOptions } from './actions/transfer'; import { NicknameAction } from './actions/nickname'; import { TransferByLinkAction } from './actions/transfer-by-link'; import { DistributeAction } from './actions/distribute'; import { fetchBalanceCall, fetchBalanceBatch } from './evm-api/fetch-balance'; import { FacilitatorConfig, PrexClientInterface, } from './interfaces/prex-client-interface'; import { getTokenDetails } from './evm-api/token-detail'; import { NonceManager } from './utils/nonce-manager'; import { queryLinkTransferHistory } from './graph/link-transfers'; import { queryTransferHistory } from './graph/transfers'; import { querySharedWallets } from './graph/wallets'; import { getSmartWalletAddress } from './evm-api/get-address'; import { getIsSmartWallet } from './evm-api/is-smart-wallet'; import { getProfile } from './evm-api/get-profile'; import { getAddressByName } from './evm-api/get-address-by-name'; import { queryTokenHolders } from './graph/holders'; import { queryTokenHolder } from './graph/holder'; import { queryTokenActivity } from './graph/token-activity'; import { PumpumAction } from './actions/pumpum'; import { generateSecret } from './utils/tmp-secret'; import { ProfileActionV2 } from './actions/nickname-v2'; if (typeof window !== 'undefined') { window.Buffer = Buffer; } // storage interface CredentialStorage { walletId: string; userHandle: string; displayName: string; credential: RegistrationResponseJSON; } export class PrexClient extends NonceManager implements PrexClientInterface { user?: PrexUser; balances: Record = {}; allowances: Record = {}; apiService: PrexApiService; evmChainClient: PublicActions; logger: Logger; signer: PrexSigner | null = null; provider?: ProviderInterface | null = null; useExternalWallet: boolean = false; private storage: PrexStorage; private swapAction: SwapAction; /** * Creates a new instance of the PrexClient. * @param chainId - The ID of the blockchain network. * @param policyId - The ID of the policy set to be used. * @param options - Configuration options for the client. * @param options.apiKey - Optional API key for authentication. * @param options.endpoint - Optional custom endpoint URL for the API. * @param options.debugMode - Optional flag to enable debug log mode. */ constructor( chainId: number, policyId: string, { apiKey, endpoint = 'https://api-v0.prex0.com/functions/v1', debugMode = false, provider, storage, }: PrexClientOptions ) { const evmChainClient = getEvmChainClient(chainId); super(evmChainClient); this.apiService = new PrexApiService(chainId, policyId, endpoint); this.swapAction = new SwapAction(this, evmChainClient, this.apiService); if (provider) { this.provider = provider; } this.storage = storage || new LocalForageStorage(); if (apiKey) { this.apiService.setApiKey(apiKey); } this.evmChainClient = evmChainClient; this.logger = new Logger(debugMode); } getSigner() { return this.signer; } getPublicClient() { return this.evmChainClient; } /** * Sets the API key for authentication. * @param apiKey - The API key to be used for requests. */ setApiKey(apiKey: string) { this.apiService.setApiKey(apiKey); } setProvider(provider: ProviderInterface) { this.provider = provider; } startHandler(): Promise { return Promise.resolve(); } /** * Checks if the passkey (WebAuthn) feature is available in the current environment. * @returns A promise that resolves to a boolean indicating passkey availability. */ async isPasskeyAvailable() { return ( browserSupportsWebAuthn() && (await platformAuthenticatorIsAvailable()) ); } /** * Get the chain ID from the provider or the API service. * @returns A promise that resolves to the chain ID. */ async getChainId() { if (!this.provider) { return this.apiService.chainId; } const hexChainId = await this.provider.request({ method: 'eth_chainId', params: [], }); return fromHex(hexChainId as `0x${string}`, 'number'); } /** * Switches the chain ID. * @param chainId - The ID of the blockchain network to switch to. * @returns A promise that resolves to the chain ID. */ async switchChain(chainId: number) { if (!this.provider) { throw new Error('Provider not set'); } return await this.provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: toHex(chainId) }], }); } /** * Creates a new wallet for the user. * @throws Will throw an error if the wallet creation process fails. */ async createWallet(options?: { userName?: string; withDeploy?: boolean; }): Promise { const address = await this.storage.getItem('address'); if (address !== null) { this.logger.debug( `Wallet ${address} already exists. if you already have passkey, please use recoverWallet` ); throw new PrexSDKError('already_created', 'Wallet already exists'); } this.logger.debug(`start registration procedure`); const credential = await this.createOrGetCredential(options?.userName); const subPublicKey = await this.generateSubKey(); const result = await this.apiService.register( credential.walletId, credential.credential, subPublicKey.subKey ); if (!result.eth_address) { this.logger.warn(`fail to register ${JSON.stringify(result)}`); throw new PrexSDKError('unknown', 'Failed to register'); } this.logger.debug(`registration succeeded ${result.eth_address}`); this.apiService.setEthAddress(result.eth_address as Address); await this.storage.setItem('address', result.eth_address); await this.storage.setItem('owner_index', 0); await this.storage.setItem('wallet_id', credential.walletId); await this.storage.removeItem('registration_result'); const withDeploy = options?.withDeploy === undefined ? false : options.withDeploy; if (withDeploy) { await this.apiService.createAccount(credential.walletId); } this.logger.debug(`wallet creation succeeded ${result.eth_address}`); this.user = { id: credential.userHandle, name: credential.displayName, backupMode: false, address: result.eth_address, ownerIndex: 0, walletId: credential.walletId, isPasskeyPresentInDevice: true, passkeys: [], eoas: [], }; this.setAccountAddress(this.user.address); this.signer = PrexSigner.fromPrexWallet(this.apiService, this.user); return { wallet: this.user, }; } async getExistSubKey() { const subKey = await this.storage.getItem('subkey_public'); const subSecret = await this.storage.getItem('subkey_secret'); if (subKey && subSecret) { return { subKey, subSecret }; } return null; } private async generateSubKey() { const { secret, publicKey } = generateSecret(); await this.storage.setItem('subkey_public', publicKey.toLowerCase()); await this.storage.setItem('subkey_secret', secret); return { subKey: publicKey.toLowerCase(), subSecret: secret, }; } async existsSubKey() { const subKey = await this.getExistSubKey(); return subKey !== null; } /** * Deletes the subkey from the storage. */ async deleteSubKey() { await this.storage.removeItem('subkey_public'); await this.storage.removeItem('subkey_secret'); } async generateOrGetSubKey() { const subKey = await this.getExistSubKey(); if (subKey) { return { ...subKey, isNew: false }; } const newSubKey = await this.generateSubKey(); return { ...newSubKey, isNew: true }; } async deployWallet() { const walletId = await this.storage.getItem('wallet_id'); if (walletId === null) { throw new Error('Wallet not found'); } if (!this.user) { throw new Error('User not found'); } const isSmartWallet = await getIsSmartWallet( this.evmChainClient, this.user.address ); if (isSmartWallet) { this.logger.debug('Smart wallet already deployed'); return; } await this.apiService.createAccount(walletId); } private async createOrGetCredential(userName?: string) { const login = async () => { try { return await this.apiService.login(); } catch (e) { throw new PrexSDKError( 'unknown', normalizeErrorFn('Failed to login')(e).message ); } }; const registerPasskey = async ( options: PublicKeyCredentialCreationOptionsJSON ) => { try { return await registerByWebAuthn(options); } catch (e) { throw new PrexSDKError( 'passkey_not_allowed', normalizeErrorFn('Failed to create passkey')(e).message ); } }; const cachedCredential = await this.storage.getItem( 'registration_result' ); if (cachedCredential) { return cachedCredential; } const loginResponse = await login(); if (loginResponse.data.eth_address !== null) { throw new PrexSDKError('already_created', 'Wallet already exists'); } const prepareResponse = await this.apiService.prepare( loginResponse.data.public_id, userName ); const creationResult = await registerPasskey(prepareResponse.options); const newCredential = { walletId: loginResponse.data.public_id, userHandle: prepareResponse.options.user.id, displayName: prepareResponse.options.user.displayName, credential: creationResult.response, }; await this.storage.setItem( 'registration_result', newCredential ); return newCredential; } /** * Attempts to restore a wallet using stored credentials. * @throws Will throw an error if the restore process fails. */ async restoreWallet() { const getUserHandleWithError = async () => { try { const passkeyId = await getCredentialID(); if (!passkeyId) { throw new PrexSDKError( 'passkey_not_allowed', 'Failed to get user handle' ); } return passkeyId; } catch (e) { throw new PrexSDKError( 'passkey_not_allowed', normalizeErrorFn('Failed to get user handle')(e).message ); } }; const passkeyId = await getUserHandleWithError(); await this.restoreWalletInner(passkeyId); } private async restoreWalletInner(passkeyId: string) { const getWalletByPasskeyID = async (passkeyId: string) => { try { return await this.apiService.getWalletByPasskeyID(passkeyId); } catch (e) { const normalizedError = normalizeErrorFn('Failed to get wallet'); const error = normalizedError(e); if (error.message === 'Not Found') { throw new PrexSDKError('wallet_not_found', error.message); } else { throw new PrexSDKError('unknown', error.message); } } }; const result = await getWalletByPasskeyID(passkeyId); if (!result.eth_address) { throw new PrexSDKError('unknown', 'Failed to get eth address'); } await this.storage.setItem('address', result.eth_address); await this.storage.setItem('owner_index', result.owner_index); await this.storage.setItem('wallet_id', result.public_id); this.user = { id: passkeyId, name: '', backupMode: false, address: result.eth_address as Address, ownerIndex: result.owner_index, walletId: result.public_id, isPasskeyPresentInDevice: true, passkeys: [], eoas: [], }; this.signer = PrexSigner.fromPrexWallet(this.apiService, this.user); this.setAccountAddress(this.user.address); } /** * Logs out the user. */ async logout() { await this.storage.removeItem('address'); await this.storage.removeItem('owner_index'); await this.storage.removeItem('wallet_id'); this.user = undefined; } getProfileAction() { return new ProfileActionV2(this); } async updateNickName({ nickName, from, }: { nickName: string; from?: Address; }) { if (!this.user || !this.signer) { throw new Error('User not initialized'); } const nicknameAction = new NicknameAction(this); await nicknameAction.updateNickName({ nickName, from: from || this.user.address, }); } async uploadAvatar({ image, from }: { image: File; from?: Address }) { if (!this.user || !this.signer) { throw new Error('User not initialized'); } const nicknameAction = new NicknameAction(this); return await nicknameAction.saveProfilePicture({ image, from: from || this.user.address, }); } async load() { this.logger.debug( 'This app is built using the Prex API. The Prex API is a service that allows for easy development of apps using smart wallets. The service is planned to launch at https://www.prex0.com, so please stay tuned.' ); const getWallet = async () => { const address = await this.storage.getItem('address'); const walletId = await this.storage.getItem('wallet_id'); if (address === null || walletId === null) { const wallet = await this.apiService.getWallet2(); if (wallet === null || wallet.wallet === null) { return { wallet: null, isPasskeyPresentInDevice: false }; } if (wallet.wallet.wallet_passkeys.length === 0) { return { wallet: null, isPasskeyPresentInDevice: false }; } this.apiService.setEthAddress(wallet.wallet.eth_address); const ownerIndex = wallet.wallet.wallet_passkeys[0].owner_index; await this.storage.setItem('address', wallet.wallet.eth_address); await this.storage.setItem('owner_index', ownerIndex); await this.storage.setItem('wallet_id', wallet.wallet.public_id); return { wallet: wallet.wallet, isPasskeyPresentInDevice: false }; } else { this.apiService.setEthAddress(address as Address); const wallet = await this.apiService.getWallet2(walletId); if (wallet === null || wallet.wallet === null) { return { wallet: null, isPasskeyPresentInDevice: false }; } return { wallet: wallet.wallet, isPasskeyPresentInDevice: true }; } }; const getWalletWithError = async () => { try { return await getWallet(); } catch (e) { const normalizedError = normalizeErrorFn('Failed to get wallet'); const error = normalizedError(e); throw PrexSDKError.fromError(error); } }; // if custom provider is set if (this.provider) { const accounts = (await this.provider.request({ method: 'eth_accounts', params: [], })) as Address[]; this.user = { id: '', name: '', backupMode: false, address: accounts[0], ownerIndex: 0, walletId: '', isPasskeyPresentInDevice: false, passkeys: [], eoas: [], }; this.setAccountAddress(this.user.address); // inject by provider this.signer = PrexSigner.fromPrexWallet( this.apiService, this.user, this.provider ); return; } if (this.useExternalWallet) { return; } const { wallet, isPasskeyPresentInDevice } = await getWalletWithError(); if (wallet === null) { return; } const ownerIndex = await this.storage.getItem('owner_index'); this.user = { id: '', name: '', backupMode: false, address: wallet.eth_address, ownerIndex: ownerIndex || 0, walletId: wallet.public_id, isPasskeyPresentInDevice, passkeys: wallet.wallet_passkeys.map((item) => ({ id: item.id, userHandle: item.user_handle, passkeyName: item.passkey_name, ownerIndex: item.owner_index, publicKey: item.public_key, isRegistered: item.is_registered, backupStatus: item.backup_status, createdAt: item.created_at, })), eoas: wallet.wallet_eoas.map((item) => ({ id: item.id, ownerIndex: item.owner_index, publicKey: item.public_key, createdAt: item.created_at, })), }; this.setAccountAddress(this.user.address); // inject by provider this.signer = PrexSigner.fromPrexWallet( this.apiService, this.user, this.provider || undefined ); } /** * Initiates a token transfer. * @param token - The address of the token to be transferred. * @param recipient - The recipient's address. * @param amount - The amount of tokens to transfer. * @param metadata - Optional metadata to include with the transfer. * @throws Will throw an error if the user is not initialized or if the transfer fails. */ async transfer( params: { token: Address; recipient: Address; amount: bigint; metadata?: Record; sender?: Address; }, options?: TransferOptions ) { if (!this.user || !this.signer) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const transferAction = new TransferAction( this, this.user, this.signer, this.apiService ); return await transferAction.transfer(params, options); } async mint(options: { token?: Address; recipient: Address; amount: bigint }) { if (!this.user) { throw new Error('User not initialized'); } const token = options.token || '0xAa0ebd8c37f4E00425cC82b2E19fee54a097e769'; await this.apiService.mint(token, options.recipient, options.amount); // Demo Coin Address await this.fetchBalance(token); } /** * Initiates a transfer by creating a secret. * @param token - The address of the token to be transferred. * @param amount - The amount of tokens to transfer. * @param expiration - The expiration time for the transfer link. * @param metadata - Additional metadata for the transfer. * @param isRequiredLock - Whether the transfer requires locking. * @returns A promise that resolves to the transfer link response. * @throws Will throw an error if the user is not initialized or if the transfer creation fails. */ async transferByLink({ token, amount, expiration, metadata, sender, }: { token: Address; amount: bigint; expiration: number; metadata?: Record; sender?: Address; }): Promise { if (!this.user || !this.signer) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const transferByLinkAction = new TransferByLinkAction( this, this.apiService, this.storage, this.logger, this.user, this.signer ); return transferByLinkAction.transferByLink({ token, amount, expiration, metadata, sender, }); } async getLinkTransfer(id: string) { const transferByLinkAction = new TransferByLinkAction( this, this.apiService, this.storage, this.logger, this.user, this.signer || undefined ); return transferByLinkAction.getLinkTransfer(id); } async getLinkTransferBySecret(secret: string) { const transferByLinkAction = new TransferByLinkAction( this, this.apiService, this.storage, this.logger, this.user, this.signer || undefined ); return transferByLinkAction.getLinkTransferBySecret(secret); } async receiveLinkTransfer(params: { secret: Hex; recipient?: Address }) { if (!this.user || !this.signer) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const transferByLinkAction = new TransferByLinkAction( this, this.apiService, this.storage, this.logger, this.user, this.signer ); return transferByLinkAction.receiveLinkTransfer(params); } async approve({ token, amount = maxUint256, from, }: { token: Address; amount?: bigint; from?: Address; }) { if (!this.user || !this.signer) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const approveAction = new ApproveAction( this, this.user, this.signer, this.apiService ); return await approveAction.approve({ token, amount, from }); } async backupByEOA(backupAddress: Address) { if (!this.user) { throw new PrexSDKError('not_initialized', 'User not initialized'); } await this.addOwnerAddress({ owner: backupAddress }); this.logger.debug(`backup address ${backupAddress} added in on-chain`); await this.apiService.addKey(this.user.address); this.logger.debug( `backup address ${backupAddress} registered in prex server` ); } async recoverByEOA(backupPrivateKey: Hex) { const backupAddress = privateKeyToAddress(backupPrivateKey); const wallet = await this.apiService.getWalletByEOA(backupAddress); this.user = { id: '', name: '', backupMode: true, privateKey: backupPrivateKey, address: wallet.eth_address as Address, ownerIndex: wallet.owner_index, walletId: wallet.public_id, isPasskeyPresentInDevice: true, passkeys: [], eoas: [], }; this.signer = PrexSigner.fromPrexWallet(this.apiService, this.user); const prepareResponse = await this.apiService.prepare(wallet.public_id); const creationResult = await registerByWebAuthn(prepareResponse.options); await this.apiService.register(wallet.public_id, creationResult.response); await this.addOwnerPublicKey( this.user.address, toHex(creationResult.decodedCred.publicKey[0], { size: 32 }), toHex(creationResult.decodedCred.publicKey[1], { size: 32 }) ); await this.apiService.addKey( wallet.eth_address, prepareResponse.options.user.id ); await this.restoreWalletInner(creationResult.response.id); } /** * @description Adds new passkey to Smart Wallet. * User must register the passkey to Prex service before calling this method. */ async backupByPasskey(ownerIndex?: number) { if (!this.user) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const wallet = await this.apiService.getWallet2(); if (wallet === null || wallet.wallet === null) { throw new PrexSDKError('wallet_not_found', 'Wallet not found'); } const nonRegisteredPasskeys = wallet.wallet?.wallet_passkeys .filter((item) => !item.is_registered) .filter((item) => (ownerIndex ? item.owner_index === ownerIndex : true)); if (nonRegisteredPasskeys.length == 0) { throw new PrexSDKError('passkey_not_found', 'Passkey not found'); } const passkey = nonRegisteredPasskeys[0]; if (!passkey.public_key) { throw new PrexSDKError('unknown', 'you need to login to get public_key'); } const xy = decodePublicKey(passkey.public_key); await this.addOwnerPublicKey( this.user.address, toHex(xy[0], { size: 32 }), toHex(xy[1], { size: 32 }) ); await this.apiService.addKey(this.user.address, passkey.user_handle); } /** * @description Registers new passkey to Prex service */ async registerNewPasskey() { if (!this.user) { throw new PrexSDKError('not_initialized', 'User not initialized'); } // prepare webauthn registration const prepareResponse = await this.apiService.prepare(this.user.walletId); // register new passkey in the device const creationResult = await registerByWebAuthn(prepareResponse.options); // complete webauthn registration await this.apiService.register(this.user.walletId, creationResult.response); } public async getSharedWalletAddress(owners: Address[], nonce: number) { const sharedWalletAddress = await getSmartWalletAddress( this.evmChainClient, owners.map((owner) => encodeAbiParameters([{ type: 'address' }], [owner]) ), nonce ); return sharedWalletAddress; } public async createSharedWallet({ name, owners, nonce, }: { name: string; owners: Address[]; nonce: number; }) { const sharedWalletAddress = await this.getSharedWalletAddress( owners, nonce ); const isSmartWallet = await getIsSmartWallet( this.evmChainClient, sharedWalletAddress ); if (isSmartWallet) { return sharedWalletAddress; } // deploy parent smart wallet first await this.deployWallet(); const nicknameAction = new ProfileActionV2(this); await nicknameAction.updateNickNameWithSharedWallet({ nickName: name, owners, nonce, sharedWalletAddress: sharedWalletAddress, }); return sharedWalletAddress; } public async executeWithCreateSharedWallet( contracts: ContractFunctionParameters, owners: Address[], nonce: number, sharedWalletAddress: Address ) { const executeOpAction = this.getExecuteAction(); return await executeOpAction.executeWithCreateSharedWallet( contracts.address, encodeFunctionData(contracts), owners, nonce, sharedWalletAddress ); } public async addOwnerAddress({ owner, from, }: { owner: Address; from?: Address; }) { await this._executeOperationWithoutChainIdValidation( encodeFunctionData({ abi: SmartWalletAbi, functionName: 'addOwnerAddress', args: [owner], }), from ); } public async removeOwnerAtIndex({ index, owner, from, }: { index: number; owner: Address; from?: Address; }) { await this._executeOperationWithoutChainIdValidation( encodeFunctionData({ abi: SmartWalletAbi, functionName: 'removeOwnerAtIndex', args: [ BigInt(index), encodeAbiParameters([{ type: 'address' }], [owner]), ], }), from ); } public async getSharedWallets() { if (!this.user) { throw new PrexSDKError('not_initialized', 'User not initialized'); } return await querySharedWallets(this.apiService, this.user.address); } private async addOwnerPublicKey( smartWalletAddress: Address, publicKeyX: Hex, publicKeyY: Hex ) { await this.executeOperation({ address: smartWalletAddress, abi: SmartWalletAbi, functionName: 'addOwnerPublicKey', args: [publicKeyX, publicKeyY], }); } async fetchBalance(token: Address, owner?: Address) { const targetOwner = owner || this.user?.address; if (!targetOwner) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const { balance, allowance } = await fetchBalanceCall( this.evmChainClient, this.apiService.chainId, token, targetOwner ); if (this.user && isAddressEqual(targetOwner, this.user.address)) { if (balance !== undefined) { this.balances[token] = balance; } if (allowance !== undefined) { this.allowances[token] = allowance; } } return { balance, allowance, }; } async fetchBalanceBatch(tokens: Address[], user?: Address) { const targetUser = user || this.user?.address; if (!targetUser) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const results = await fetchBalanceBatch(this.evmChainClient, tokens, targetUser); return results; } /** * @description Fetches transfer history * @param token token address * @param pagingOptions paging options * @returns transfer history */ async getHistory( _query?: TransferHistoryQuery, pagingOptions?: PagingOptions ) { if (!this.user?.address) { throw new PrexSDKError('not_initialized', 'User not initialized'); } const history = await queryTransferHistory( this.apiService, _query || { user: this.user.address, }, pagingOptions?.offset || 0, pagingOptions?.limit || 10 ); return history; } async getOnetimeLockHistory( _query?: | { user: Address; } | { token: Address; }, pagingOptions?: PagingOptions ) { const history = await queryLinkTransferHistory( this.apiService, _query || { user: this.user?.address, }, pagingOptions?.offset || 0, pagingOptions?.limit || 10 ); const historyItems = history.map(async (item) => { // store secret const secret = await this.storage.getItem( 'secret-' + item.id.toLowerCase() ); return { ...item, secret: secret, } as LinkTransferHistoryItem; }); return Promise.all(historyItems); } async getTokenHolder(query: { token: Address; address?: Address }) { const token = query.token; const address = query.address || this.user?.address; if (!token || !address) { throw new PrexSDKError('not_initialized', 'User not initialized'); } return await queryTokenHolder(this.apiService, { token, address, }); } async getTokenHolders( _query: { token: Address }, pagingOptions?: PagingOptions ) { return await queryTokenHolders( this.apiService, _query, pagingOptions?.offset || 0, pagingOptions?.limit || 10 ); } async executeOperation( contracts: ContractFunctionParameters, from?: Address ) { return await this._executeOperation( contracts.address, encodeFunctionData(contracts), from ); } private getExecuteAction() { if (!this.user || !this.signer) { throw new PrexSDKError('not_initialized', 'User not initialized'); } return new ExecuteOperationAction( this, this.user, this.signer, this.apiService ); } async _executeOperation(target: Address, callData: Hex, from?: Address) { const executeOpAction = this.getExecuteAction(); return await executeOpAction.executeOperation(target, callData, { from, }); } async _executeOperationWithoutChainIdValidation( callData: Hex, from?: Address ) { const executeOpAction = this.getExecuteAction(); return await executeOpAction.executeOperationWithoutChainIdValidation( callData, { from, } ); } async authenticate(options: PublicKeyCredentialRequestOptionsJSON) { return await startAuthentication(options); } async quoteSwap(params: { tokenIn: Address; tokenOut: Address; amount: bigint; tradeType: 'EXACT_INPUT' | 'EXACT_OUTPUT'; swapper?: Address; recipient?: Address; slippageTolerance?: bigint; }) { const swapper = params.swapper || this.user?.address; if (!swapper) { throw new PrexSDKError('not_initialized', 'User not initialized'); } return await this.swapAction.quoteSwap({ ...params, swapper, recipient: params.recipient || swapper, }); } async swap(order: DutchOrder, route: Hex) { if (!this.user || !this.signer) { throw new PrexSDKError('not_initialized', 'User not initialized'); } return await this.swapAction.swap(order, route); } async getSwapHistory( query?: { user: Address }, pagingOptions?: PagingOptions ) { return await this.swapAction.getSwapHistory(query, pagingOptions); } distribute() { const distributeAction = new DistributeAction( this, this.apiService, this.storage, this.user, this.signer || undefined ); return distributeAction; } getPumAction() { return new PumpumAction(this, this.apiService); } async getTokenDetails(token: Address) { return await getTokenDetails(this.evmChainClient, token); } async getTokenActivity(token: Address): Promise { return await queryTokenActivity(this.apiService, { token, }); } getUser() { return this.user; } private config: Record = {}; async loadConfig(chainId: number) { if (this.config[chainId]) { return this.config[chainId]; } const result = await this.apiService.getConfig(chainId); this.config[chainId] = { feeTiers: result.fee.map((fee) => ({ address: fee.address, feeTiers: fee.feeTiers.map((tier) => ({ fee: BigInt(tier.fee), minAmount: BigInt(tier.minAmount), })), })), maxFeePerGas: result.max_fee_per_gas, maxPriorityFeePerGas: result.max_priority_fee_per_gas, }; return this.config[chainId]; } async getProfile(address: Address) { try { return await getProfile(this.evmChainClient, address); } catch (error) { return {}; } } async getAddressByName({ baseName = 'default', name, }: { baseName?: string; name: string; }) { return await getAddressByName(this.evmChainClient, baseName, name); } async signWithSubKey({ hash, from }: { hash: Hex; from?: Address }) { if (!this.signer) { throw new Error('Signer not found'); } const subKey = await this.generateOrGetSubKey(); return await this.signer.signWithSubKey({ extraHash: hash, subKey, from, }); } }