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,
});
}
}