Skip to content

Commit

Permalink
fix(bridge-ui): fix USDT approvals (#17539)
Browse files Browse the repository at this point in the history
  • Loading branch information
KorbinianK authored Jun 10, 2024
1 parent d5965bb commit 37cb7af
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { Alert } from '$components/Alert';
import {
allApproved,
computingBalance,
Expand All @@ -10,6 +11,7 @@
errorComputingBalance,
insufficientAllowance,
insufficientBalance,
needsApprovalReset,
recipientAddress,
selectedToken,
tokenBalance,
Expand All @@ -24,9 +26,11 @@
export let approve: () => Promise<void>;
export let bridge: () => Promise<void>;
export let resetApproval: () => Promise<void>;
export let approving = false;
export let bridging = false;
export let resetting = false;
export let disabled = false;
Expand All @@ -47,6 +51,12 @@
bridge();
}
const onResetApproveClick = async () => {
resetting = true;
await resetApproval();
resetting = false;
};
onMount(async () => {
if ($selectedToken) {
$allApproved = false;
Expand Down Expand Up @@ -85,6 +95,11 @@
$: validApprovalStatus = $allApproved;
// USDT specific, L1 address of USDT contract
$: resetRequired =
$selectedToken?.addresses[$connectedSourceChain.id] === '0xdAC17F958D2ee523a2206206994597C13D831ec7' &&
$needsApprovalReset;
$: commonConditions =
validApprovalStatus &&
!bridging &&
Expand All @@ -106,6 +121,8 @@
$: ethConditionsSatisfied = commonConditions && $enteredAmount && $enteredAmount > 0;
$: disableReset = !resetRequired || resetting;
$: disableBridge = isERC20
? !erc20ConditionsSatisfied
: isERC721
Expand All @@ -119,24 +136,42 @@

<div class="f-col w-full gap-4">
{#if $selectedToken && !isETH}
<ActionButton
priority="primary"
disabled={disableApprove}
loading={approving || $validatingAmount || checking}
on:click={onApproveClick}>
{#if approving}
<span class="body-bold">{$t('bridge.button.approving')}</span>
{:else if $allApproved}
<div class="f-items-center">
<Icon type="check" />
<span class="body-bold">{$t('bridge.button.approved')}</span>
</div>
{:else if checking}
<span class="body-bold">{$t('bridge.button.validating')}</span>
{:else}
<span class="body-bold">{$t('bridge.button.approve')}</span>
{/if}
</ActionButton>
{#if resetRequired}
<Alert type="info">{$t('bridge.usdt_approval.info')}</Alert>
<ActionButton priority="primary" disabled={disableReset} loading={resetting} on:click={onResetApproveClick}>
{#if resetting}
<span class="body-bold">{$t('bridge.button.resetting')}</span>
{:else if $allApproved}
<div class="f-items-center">
<Icon type="check" />
<span class="body-bold">{$t('bridge.button.reset')}</span>
</div>
{:else if checking}
<span class="body-bold">{$t('bridge.button.validating')}</span>
{:else}
<span class="body-bold">{$t('bridge.button.reset_approval')}</span>
{/if}
</ActionButton>
{:else}
<ActionButton
priority="primary"
disabled={disableApprove}
loading={approving || $validatingAmount || checking}
on:click={onApproveClick}>
{#if approving}
<span class="body-bold">{$t('bridge.button.approving')}</span>
{:else if $allApproved}
<div class="f-items-center">
<Icon type="check" />
<span class="body-bold">{$t('bridge.button.approved')}</span>
</div>
{:else if checking}
<span class="body-bold">{$t('bridge.button.validating')}</span>
{:else}
<span class="body-bold">{$t('bridge.button.approve')}</span>
{/if}
</ActionButton>
{/if}
{/if}
<ActionButton priority="primary" disabled={disableBridge} loading={bridging} on:click={onBridgeClick}>
{#if bridging}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
let bridging: boolean;
let approving: boolean;
let checking: boolean;
let resetting: boolean;
let icon: IconType;
Expand Down Expand Up @@ -173,6 +174,25 @@
}
};
async function resetApproval() {
if (!$selectedToken || !$connectedSourceChain || !$destNetwork?.id) return;
try {
let tokenAddress = $selectedToken.addresses[$connectedSourceChain.id];
const type: TokenType = $selectedToken.type;
const spenderAddress = routingContractsMap[$connectedSourceChain.id][$destNetwork?.id].erc20VaultAddress;
const walletClient = await getConnectedWallet($connectedSourceChain.id);
const args: ApproveArgs = { tokenAddress, spenderAddress, wallet: walletClient, amount: 0n };
approveTxHash = await (bridges[type] as ERC20Bridge).approve(args, true);
if (approveTxHash) await handleApproveTxHash(approveTxHash);
} catch (err) {
console.error(err);
handleBridgeError(err as Error);
}
}
async function approve() {
isBridgePaused().then((paused) => {
if (paused) throw new BridgePausedError('Bridge is paused');
Expand Down Expand Up @@ -301,7 +321,7 @@
{#if bridgingStatus === BridgingStatus.PENDING}
<section id="actions" class="f-col w-full">
<div class="h-sep mb-[30px]" />
<Actions {approve} {bridge} bind:bridging bind:approving bind:checking />
<Actions {approve} {bridge} bind:bridging bind:approving bind:checking bind:resetting {resetApproval} />
</section>
{/if}
</div>
1 change: 1 addition & 0 deletions packages/bridge-ui/src/components/Bridge/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const insufficientBalance = writable<boolean>(false);
export const insufficientAllowance = writable<boolean>(false);

export const allApproved = writable(<boolean>false);
export const needsApprovalReset = writable<boolean>(false);

// Derived state
export const bridgeService = derived(selectedToken, (token) => (token ? bridges[token.type] : null));
Expand Down
6 changes: 6 additions & 0 deletions packages/bridge-ui/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
"bridging": "Bridging",
"fetch": "Fetch NFT data",
"import": "Import",
"reset": "Reset",
"reset_approval": "Reset Approval",
"resetting": "Resetting",
"validating": "Validating..."
},
"description": {
Expand Down Expand Up @@ -205,6 +208,9 @@
"recipient": "Recipient",
"review": "Review"
}
},
"usdt_approval": {
"info": "You have previously approved a lower amount of USDT. To adjust the approval, you must first reset the previous amount to 0. This requirement is unique to USDT."
}
},
"chain_selector": {
Expand Down
50 changes: 41 additions & 9 deletions packages/bridge-ui/src/libs/bridge/ERC20Bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class ERC20Bridge extends Bridge {
return estimatedGas;
}

async requireAllowance({ amount, tokenAddress, ownerAddress, spenderAddress }: RequireAllowanceArgs) {
async getAllowance({ amount, tokenAddress, ownerAddress, spenderAddress }: RequireAllowanceArgs) {
isBridgePaused().then((paused) => {
if (paused) throw new BridgePausedError('Bridge is paused');
});
Expand All @@ -125,33 +125,65 @@ export class ERC20Bridge extends Bridge {
chainId: (await getConnectedWallet()).chain.id,
});

return allowance;
}
async requireAllowance({ amount, tokenAddress, ownerAddress, spenderAddress }: RequireAllowanceArgs, reset = false) {
const allowance = await this.getAllowance({ amount, tokenAddress, ownerAddress, spenderAddress });

if (reset) {
return true;
}
const requiresAllowance = allowance < amount;

log('Allowance is', allowance, 'requires allowance?', requiresAllowance);

return requiresAllowance;
}

async approve(args: ApproveArgs) {
async approve(args: ApproveArgs, reset = false) {
const { amount, tokenAddress, spenderAddress, wallet } = args;
if (!wallet || !wallet.account) throw new Error('No wallet found');
const requireAllowance = await this.requireAllowance({
amount,
tokenAddress,
ownerAddress: wallet.account.address,
spenderAddress,
});
const requireAllowance = await this.requireAllowance(
{
amount,
tokenAddress,
ownerAddress: wallet.account.address,
spenderAddress,
},
reset,
);

if (!requireAllowance) {
throw new NoAllowanceRequiredError(`no allowance required for the amount ${amount}`);
}

try {
log(`Calling approve for spender "${spenderAddress}" for token "${tokenAddress}" with amount`, amount);
// USDT does not play nice with the default ERC20 ABI, this works for both
const approvalABI = [
{
constant: false,
inputs: [
{
name: '_spender',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
],
name: 'approve',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
];

const { request } = await simulateContract(config, {
address: tokenAddress,
abi: erc20Abi,
abi: approvalABI,
functionName: 'approve',
args: [spenderAddress, amount],
});
Expand Down
16 changes: 16 additions & 0 deletions packages/bridge-ui/src/libs/token/getTokenApprovalStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
destNetwork,
enteredAmount,
insufficientAllowance,
needsApprovalReset,
selectedToken,
} from '$components/Bridge/state';
import { bridges, ContractType, type RequireApprovalArgs } from '$libs/bridge';
Expand All @@ -27,6 +28,7 @@ export enum ApprovalStatus {
ETH_NO_APPROVAL_REQUIRED,
APPROVAL_REQUIRED,
NO_APPROVAL_REQUIRED,
RESET_REQUIRED,
}

export const getTokenApprovalStatus = async (token: Maybe<Token | NFT>): Promise<ApprovalStatus> => {
Expand Down Expand Up @@ -57,6 +59,7 @@ export const getTokenApprovalStatus = async (token: Maybe<Token | NFT>): Promise
}
if (token.type === TokenType.ERC20) {
log('checking approval status for ERC20');
needsApprovalReset.set(false);

const tokenVaultAddress = routingContractsMap[currentChainId][destinationChainId].erc20VaultAddress;
const bridge = bridges[TokenType.ERC20] as ERC20Bridge;
Expand All @@ -72,6 +75,19 @@ export const getTokenApprovalStatus = async (token: Maybe<Token | NFT>): Promise
insufficientAllowance.set(requireAllowance);
allApproved.set(!requireAllowance);
if (requireAllowance) {
// specific check for USDT
if (get(selectedToken)?.symbol === 'tUSDT') {
const allowance = await bridge.getAllowance({
amount: get(enteredAmount),
tokenAddress,
ownerAddress,
spenderAddress: tokenVaultAddress,
});
if (allowance > 0n) {
needsApprovalReset.set(true);
return ApprovalStatus.RESET_REQUIRED;
}
}
return ApprovalStatus.APPROVAL_REQUIRED;
}
return ApprovalStatus.NO_APPROVAL_REQUIRED;
Expand Down

0 comments on commit 37cb7af

Please sign in to comment.