Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- added: Add additional `approve` transaction for tokens that require allowances to be 0 before updating, like USDT on Ethereum
- changed: Support multiple pre transactions

## 2.38.0 (2025-11-03)

- changed: (Letsexchange) Disable ZEC swaps completely
Expand Down
106 changes: 87 additions & 19 deletions src/swap/defi/defiUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { asMaybe, asObject, asString } from 'cleaners'
import { EdgeCurrencyConfig, EdgeToken } from 'edge-core-js/types'
import {
EdgeCurrencyConfig,
EdgeSpendInfo,
EdgeToken,
EdgeTransaction
} from 'edge-core-js/types'
import { ethers } from 'ethers'

import { EdgeSwapRequestPlugin } from '../types'
import abi from './abi/THORCHAIN_SWAP_ABI'
import erc20Abi from './abi/UNISWAP_V2_ERC20_ABI'

Expand Down Expand Up @@ -75,28 +81,90 @@ const getEvmCheckSumAddress = (assetAddress: string): string => {
return ethers.utils.getAddress(assetAddress.toLowerCase())
}

export const getEvmApprovalData = async (params: {
export const getEvmApprovalData = ({
contractAddress,
nativeAmount
}: {
contractAddress: string
assetAddress: string
nativeAmount: string
}): Promise<string | undefined> => {
const { contractAddress, assetAddress, nativeAmount } = params
const contract = new ethers.Contract(
assetAddress,
erc20Abi,
ethers.providers.getDefaultProvider()
)

const bnNativeAmount = ethers.BigNumber.from(nativeAmount)
const approveTx = await contract.populateTransaction.approve(
}): string => {
const iface = new ethers.utils.Interface(erc20Abi)
const data = iface.encodeFunctionData('approve', [
contractAddress,
bnNativeAmount,
{
gasLimit: '500000',
gasPrice: '20'
nativeAmount
])
return data
}

const NON_STANDARD_APPROVAL_TOKENS: { [pluginId: string]: string[] } = {
ethereum: ['dac17f958d2ee523a2206206994597c13d831ec7' /* USDT */]
}

export const createEvmApprovalEdgeTransactions = async ({
request,
approvalAmount,
tokenContractAddress,
recipientAddress,
networkFeeOption,
customNetworkFee
}: {
request: EdgeSwapRequestPlugin
approvalAmount: string
tokenContractAddress: string
recipientAddress: string
networkFeeOption?: EdgeSpendInfo['networkFeeOption']
customNetworkFee?: EdgeSpendInfo['customNetworkFee']
}): Promise<EdgeTransaction[]> => {
const out: EdgeTransaction[] = []

const createApprovalTx = async (amount: string): Promise<EdgeTransaction> => {
const approvalData = getEvmApprovalData({
contractAddress: tokenContractAddress,
nativeAmount: amount
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Incorrect ERC-20 approve data leading to failures

The createApprovalTx function passes tokenContractAddress to getEvmApprovalData for the contractAddress parameter. The ERC-20 approve function expects the spender address (the recipientAddress) as its first argument. This generates incorrect approval transaction data, leading to failed approval transactions.

Fix in Cursor Fix in Web

const spendInfo: EdgeSpendInfo = {
tokenId: null,
memos: [{ type: 'hex', value: approvalData.replace(/^0x/, '') }],
spendTargets: [
{
nativeAmount: '0',
publicAddress: tokenContractAddress
}
],
networkFeeOption,
customNetworkFee,
assetAction: {
assetActionType: 'tokenApproval'
},
savedAction: {
actionType: 'tokenApproval',
tokenApproved: {
pluginId: request.fromWallet.currencyInfo.pluginId,
tokenId: request.fromTokenId,
nativeAmount: amount
},
tokenContractAddress: tokenContractAddress,
contractAddress: recipientAddress
}
}
)
return approveTx.data != null ? approveTx.data.replace(/^0x/, '') : undefined
return await request.fromWallet.makeSpend(spendInfo)
}

// If the token requires resetting allowance to 0, create a pre-tx to do so
if (
request.fromTokenId != null &&
NON_STANDARD_APPROVAL_TOKENS[
request.fromWallet.currencyInfo.pluginId
].includes(request.fromTokenId)
) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Undefined lookup leads to TypeError on non-ethereum tokens

Accessing NON_STANDARD_APPROVAL_TOKENS for a pluginId other than 'ethereum' results in a TypeError. The lookup returns undefined, and then .includes() is called on that undefined value.

Fix in Cursor Fix in Web

const preTx = await createApprovalTx('0')
out.push(preTx)
}

const preTx = await createApprovalTx(approvalAmount)
out.push(preTx)

return out
}

export const getDepositWithExpiryData = async (params: {
Expand Down
47 changes: 10 additions & 37 deletions src/swap/defi/lifi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
MakeTxParams,
StringMap
} from '../types'
import { getEvmApprovalData, WEI_MULTIPLIER } from './defiUtils'
import { createEvmApprovalEdgeTransactions, WEI_MULTIPLIER } from './defiUtils'

const pluginId = 'lifi'
const swapInfo: EdgeSwapInfo = {
Expand Down Expand Up @@ -374,7 +374,7 @@ export function makeLifiPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
const metadataNotes = `DEX Providers: ${providersStr}`
const { approvalAddress, toAmount, toAmountMin, fromAmount } = estimate

let preTx: EdgeTransaction | undefined
const preTxs: EdgeTransaction[] = []
let spendInfo: EdgeSpendInfo
switch (fromWallet.currencyInfo.pluginId) {
case 'solana': {
Expand Down Expand Up @@ -475,44 +475,17 @@ export function makeLifiPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
const gasPriceGwei = div18(gasPriceDecimal, WEI_MULTIPLIER)

if (sendingToken) {
const approvalData = await getEvmApprovalData({
contractAddress: approvalAddress,
assetAddress: fromContractAddress,
nativeAmount
})

const preTxSpendInfo: EdgeSpendInfo = {
// Token approvals only spend the parent currency
tokenId: null,
spendTargets: [
{
nativeAmount: '0',
publicAddress: fromContractAddress
}
],
memos:
approvalData != null
? [{ type: 'hex', value: approvalData }]
: undefined,
const approvalTxs = await createEvmApprovalEdgeTransactions({
request,
approvalAmount: fromAmount,
tokenContractAddress: fromContractAddress,
recipientAddress: approvalAddress,
networkFeeOption: 'custom',
customNetworkFee: {
gasPrice: gasPriceGwei
},
assetAction: {
assetActionType: 'tokenApproval'
},
savedAction: {
actionType: 'tokenApproval',
tokenApproved: {
pluginId: fromWallet.currencyInfo.pluginId,
tokenId: fromTokenId,
nativeAmount
},
tokenContractAddress: fromContractAddress,
contractAddress: approvalAddress
}
}
preTx = await request.fromWallet.makeSpend(preTxSpendInfo)
})
preTxs.push(...approvalTxs)
}

spendInfo = {
Expand Down Expand Up @@ -560,7 +533,7 @@ export function makeLifiPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
fromNativeAmount: nativeAmount,
metadataNotes,
minReceiveAmount: toAmountMin,
preTx,
preTxs,
request,
spendInfo,
swapInfo
Expand Down
41 changes: 11 additions & 30 deletions src/swap/defi/rango.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
MakeTxParams,
StringMap
} from '../types'
import { WEI_MULTIPLIER } from './defiUtils'
import { createEvmApprovalEdgeTransactions, WEI_MULTIPLIER } from './defiUtils'

const swapInfo: EdgeSwapInfo = {
pluginId: 'rango',
Expand Down Expand Up @@ -570,7 +570,7 @@ export function makeRangoPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {

const providers = route.path.map(p => p.swapper.title)

let preTx: EdgeTransaction | undefined
const preTxs: EdgeTransaction[] = []
let spendInfo: EdgeSpendInfo

switch (tx.type) {
Expand Down Expand Up @@ -685,7 +685,7 @@ export function makeRangoPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
fromNativeAmount: nativeAmount,
metadataNotes,
minReceiveAmount: route.outputAmountMin,
preTx,
preTxs,
request,
makeTxParams,
swapInfo
Expand All @@ -699,32 +699,13 @@ export function makeRangoPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
}
const { approveData, approveTo } = evmTransaction
if (approveData != null && approveTo != null) {
const approvalData = approveData.replace('0x', '')

spendInfo = {
tokenId: null,
memos: [{ type: 'hex', value: approvalData }],
spendTargets: [
{
nativeAmount: '0',
publicAddress: fromContractAddress
}
],
assetAction: {
assetActionType: 'tokenApproval'
},
savedAction: {
actionType: 'tokenApproval',
tokenApproved: {
pluginId: fromWallet.currencyInfo.pluginId,
tokenId: fromTokenId,
nativeAmount
},
tokenContractAddress: fromContractAddress,
contractAddress: approveTo
}
}
preTx = await request.fromWallet.makeSpend(spendInfo)
const approvalTxs = await createEvmApprovalEdgeTransactions({
request,
approvalAmount: nativeAmount,
tokenContractAddress: fromContractAddress,
recipientAddress: approveTo
})
preTxs.push(...approvalTxs)
}
const customNetworkFee = {
gasLimit:
Expand Down Expand Up @@ -791,7 +772,7 @@ export function makeRangoPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
fromNativeAmount: nativeAmount,
metadataNotes,
minReceiveAmount: route.outputAmountMin,
preTx,
preTxs,
request,
spendInfo,
swapInfo
Expand Down
47 changes: 10 additions & 37 deletions src/swap/defi/thorchain/swapkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
promiseWithTimeout
} from '../../../util/utils'
import { EdgeSwapRequestPlugin } from '../../types'
import { getEvmApprovalData } from '../defiUtils'
import { createEvmApprovalEdgeTransactions } from '../defiUtils'
import { MAINNET_CODE_TRANSCRIPTION } from './thorchain'
import {
AFFILIATE_FEE_BASIS_DEFAULT,
Expand Down Expand Up @@ -331,8 +331,7 @@ export function makeSwapKitPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
let memo = ''

const publicAddress = targetAddress
let approvalData
let preTx: EdgeTransaction | undefined
const preTxs: EdgeTransaction[] = []

const evmTransaction = asMaybe(asEvmCleaner)(thorSwap.tx)
const cosmosTransaction = asMaybe(asCosmosCleaner)(thorSwap.tx)
Expand All @@ -347,40 +346,14 @@ export function makeSwapKitPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {

const dexContractAddress = asString(thorSwap.meta.approvalAddress)

// Check if token approval is required and return necessary data field
approvalData = await getEvmApprovalData({
contractAddress: dexContractAddress,
assetAddress: sourceTokenContractAddress,
nativeAmount
const approvalTxs = await createEvmApprovalEdgeTransactions({
request,
approvalAmount: nativeAmount,
tokenContractAddress: sourceTokenContractAddress,
recipientAddress: dexContractAddress,
networkFeeOption: 'high'
})
if (approvalData != null) {
const spendInfo: EdgeSpendInfo = {
// Token approvals only spend the parent currency
tokenId: null,
memos: [{ type: 'hex', value: approvalData }],
spendTargets: [
{
nativeAmount: '0',
publicAddress: sourceTokenContractAddress
}
],
networkFeeOption: 'high',
assetAction: {
assetActionType: 'tokenApproval'
},
savedAction: {
actionType: 'tokenApproval',
tokenApproved: {
pluginId: fromWallet.currencyInfo.pluginId,
tokenId: fromTokenId,
nativeAmount
},
tokenContractAddress: sourceTokenContractAddress,
contractAddress: dexContractAddress
}
}
preTx = await request.fromWallet.makeSpend(spendInfo)
}
preTxs.push(...approvalTxs)
}
memo = evmTransaction.data.replace(/^0x/, '')
} else if (cosmosTransaction != null) {
Expand Down Expand Up @@ -465,7 +438,7 @@ export function makeSwapKitPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
swapInfo,
fromNativeAmount: nativeAmount,
expirationDate: new Date(expirationMs),
preTx,
preTxs,
metadataNotes: notes
}
}
Expand Down
Loading
Loading