UNPKG

@openocean.finance/widget-sdk

Version:

OpenOcean Any-to-Any Cross-Chain-Swap SDK

233 lines (209 loc) 6.4 kB
import type { ExtendedChain, OpenOceanStep, SignedTypedData, } from '@openocean.finance/widget-types' import type { Address, Client, Hash } from 'viem' import { signTypedData } from 'viem/actions' import { getAction } from 'viem/utils' import { MaxUint256 } from '../../constants.js' import type { StatusManager } from '../StatusManager.js' import type { ExecutionOptions, Process, ProcessType } from '../types.js' import { getAllowance } from './getAllowance.js' import { parseEVMErrors } from './parseEVMErrors.js' import { getNativePermit } from './permits/getNativePermit.js' import type { NativePermitData } from './permits/types.js' import { setAllowance } from './setAllowance.js' import { isRelayerStep } from './typeguards.js' import type { Call } from './types.js' import { waitForTransactionReceipt } from './waitForTransactionReceipt.js' export type CheckAllowanceParams = { client: Client chain: ExtendedChain step: OpenOceanStep statusManager: StatusManager executionOptions?: ExecutionOptions allowUserInteraction?: boolean batchingSupported?: boolean permit2Supported?: boolean disableMessageSigning?: boolean } export type AllowanceResult = | { status: 'ACTION_REQUIRED' | 'DONE' } | { status: 'BATCH_APPROVAL' data: Call } | { status: 'NATIVE_PERMIT' data: SignedTypedData } export const checkAllowance = async ({ client, chain, step, statusManager, executionOptions, allowUserInteraction = false, batchingSupported = false, permit2Supported = false, disableMessageSigning = false, }: CheckAllowanceParams): Promise<AllowanceResult> => { // Find existing or create new allowance process const allowanceProcess: Process = statusManager.findOrCreateProcess({ step, type: 'TOKEN_ALLOWANCE', chainId: step.action.fromChainId, }) try { // Handle existing pending transaction if (allowanceProcess.txHash && allowanceProcess.status !== 'DONE') { await waitForApprovalTransaction( client, allowanceProcess.txHash as Address, allowanceProcess.type, step, chain, statusManager ) return { status: 'DONE' } } // Start new allowance check statusManager.updateProcess(step, allowanceProcess.type, 'STARTED') const spenderAddress = permit2Supported ? chain.permit2 : step.estimate.approvalAddress const fromAmount = BigInt(step.action.fromAmount) const approved = await getAllowance( chain.id, step.action.fromToken.address as Address, client.account!.address, spenderAddress as Address ) // Return early if already approved if (fromAmount <= approved) { statusManager.updateProcess(step, allowanceProcess.type, 'DONE') return { status: 'DONE' } } const isRelayerTransaction = isRelayerStep(step) let nativePermitData: NativePermitData | undefined if (isRelayerTransaction) { nativePermitData = step.typedData.find( (p) => p.primaryType === 'Permit' ) as NativePermitData } else { nativePermitData = await getNativePermit( client, chain.id, step.action.fromToken.address as Address, chain.permit2Proxy as Address, fromAmount ) } statusManager.updateProcess(step, allowanceProcess.type, 'ACTION_REQUIRED') if (!allowUserInteraction) { return { status: 'ACTION_REQUIRED' } } // Check if proxy contract is available and token supports native permits, not available for atomic batch const nativePermitSupported = !!nativePermitData && !!chain.permit2Proxy && !batchingSupported && !disableMessageSigning if (nativePermitSupported && nativePermitData) { const signature = await getAction( client, signTypedData, 'signTypedData' )({ account: client.account!, domain: nativePermitData.domain, types: nativePermitData.types, primaryType: 'Permit', message: nativePermitData.message, }) statusManager.updateProcess(step, allowanceProcess.type, 'DONE') return { status: 'NATIVE_PERMIT', data: { ...nativePermitData, signature, }, } } // Set new allowance const approveAmount = permit2Supported ? MaxUint256 : fromAmount const approveTxHash = await setAllowance( client, step.action.fromToken.address as Address, spenderAddress as Address, approveAmount, executionOptions, batchingSupported ) if (batchingSupported) { statusManager.updateProcess(step, allowanceProcess.type, 'DONE') return { status: 'BATCH_APPROVAL', data: { to: step.action.fromToken.address as Address, data: approveTxHash, chainId: step.action.fromToken.chainId, }, } } await waitForApprovalTransaction( client, approveTxHash, allowanceProcess.type, step, chain, statusManager ) return { status: 'DONE' } } catch (e: any) { const error = await parseEVMErrors(e, step, allowanceProcess) statusManager.updateProcess(step, allowanceProcess.type, 'FAILED', { error: { message: error.cause.message, code: error.code, }, }) statusManager.updateExecution(step, 'FAILED') throw error } } const waitForApprovalTransaction = async ( client: Client, txHash: Hash, processType: ProcessType, step: OpenOceanStep, chain: ExtendedChain, statusManager: StatusManager ) => { const baseExplorerUrl = chain.metamask.blockExplorerUrls[0] const getTxLink = (hash: Hash) => `${baseExplorerUrl}tx/${hash}` statusManager.updateProcess(step, processType, 'PENDING', { txHash, txLink: getTxLink(txHash), }) const transactionReceipt = await waitForTransactionReceipt({ client, chainId: chain.id, txHash, onReplaced(response) { const newHash = response.transaction.hash statusManager.updateProcess(step, processType, 'PENDING', { txHash: newHash, txLink: getTxLink(newHash), }) }, }) const finalHash = transactionReceipt?.transactionHash || txHash statusManager.updateProcess(step, processType, 'DONE', { txHash: finalHash, txLink: getTxLink(finalHash), }) }