@openocean.finance/widget-sdk
Version:
OpenOcean Any-to-Any Cross-Chain-Swap SDK
141 lines (126 loc) • 3.81 kB
text/typescript
import type { BaseToken, ChainId } from '@openocean.finance/widget-types'
import type { Address } from 'viem'
import { multicall, readContract } from 'viem/actions'
import { isNativeTokenAddress } from '../../utils/isZeroAddress.js'
import { allowanceAbi } from './abi.js'
import { getPublicClient } from './publicClient.js'
import type {
TokenAllowance,
TokenSpender,
TokenSpenderAllowance,
} from './types.js'
import { getMulticallAddress } from './utils.js'
export const getAllowance = async (
chainId: ChainId,
tokenAddress: Address,
ownerAddress: Address,
spenderAddress: Address
): Promise<bigint> => {
const client = await getPublicClient(chainId)
try {
const approved = (await readContract(client, {
address: tokenAddress as Address,
abi: allowanceAbi,
functionName: 'allowance',
args: [ownerAddress, spenderAddress],
})) as bigint
return approved
} catch (_e) {
return 0n
}
}
export const getAllowanceMulticall = async (
chainId: ChainId,
tokens: TokenSpender[],
ownerAddress: Address
): Promise<TokenSpenderAllowance[]> => {
if (!tokens.length) {
return []
}
const multicallAddress = await getMulticallAddress(chainId)
if (!multicallAddress) {
throw new Error(`No multicall address configured for chainId ${chainId}.`)
}
const client = await getPublicClient(chainId)
const contracts = tokens.map((token) => ({
address: token.token.address as Address,
abi: allowanceAbi,
functionName: 'allowance',
args: [ownerAddress, token.spenderAddress],
}))
const results = await multicall(client, {
contracts,
multicallAddress: multicallAddress as Address,
})
if (!results.length) {
throw new Error(
`Couldn't load allowance from chainId ${chainId} using multicall.`
)
}
return tokens.map(({ token, spenderAddress }, i: number) => ({
token,
spenderAddress,
allowance: results[i].result as bigint,
}))
}
/**
* Get the current allowance for a certain token.
* @param token - The token that should be checked
* @param ownerAddress - The owner of the token
* @param spenderAddress - The spender address that has to be approved
* @returns Returns allowance
*/
export const getTokenAllowance = async (
token: BaseToken,
ownerAddress: Address,
spenderAddress: Address
): Promise<bigint | undefined> => {
// native token don't need approval
if (isNativeTokenAddress(token.address)) {
return
}
const approved = await getAllowance(
token.chainId,
token.address as Address,
ownerAddress,
spenderAddress
)
return approved
}
/**
* Get the current allowance for a list of token/spender address pairs.
* @param ownerAddress - The owner of the tokens
* @param tokens - A list of token and spender address pairs
* @returns Returns array of tokens and their allowance
*/
export const getTokenAllowanceMulticall = async (
ownerAddress: Address,
tokens: TokenSpender[]
): Promise<TokenAllowance[]> => {
// filter out native tokens
const filteredTokens = tokens.filter(
({ token }) => !isNativeTokenAddress(token.address)
)
// group by chain
const tokenDataByChain: { [chainId: number]: TokenSpender[] } = {}
for (const data of filteredTokens) {
if (!tokenDataByChain[data.token.chainId]) {
tokenDataByChain[data.token.chainId] = []
}
tokenDataByChain[data.token.chainId].push(data)
}
const chainKeys = Object.keys(tokenDataByChain).map(Number.parseInt)
const allowances = (
await Promise.all(
chainKeys.map(async (chainId) => {
// get allowances for current chain and token list
return getAllowanceMulticall(
chainId,
tokenDataByChain[chainId],
ownerAddress
)
})
)
).flat()
return allowances
}