-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add an approval/funding UI #30
base: development
Are you sure you want to change the base?
Changes from 25 commits
bc37549
6719d29
dc3aaaa
68e1715
a220ca3
f6a4e17
4571b5e
287505d
a3caa79
9634f81
edd0772
3befb07
03b9dcf
6fd952e
9d1ffb8
19f3bbf
5b93446
5f48675
18adb4e
1337208
3c77a03
6478209
4605da3
e2149ad
89b0f04
ba36a2c
6bec149
1f81afa
8ea7037
7878d48
fa44c95
f421786
c526d60
b5c844a
f801b95
3ffcf15
bded1b3
8f8fa38
c22af00
c0ca66d
228f7d1
834b8dd
1bd7ca8
8105aab
9299ea3
23f3344
f04f3d1
656e862
58edb53
c906697
78fb771
b525724
326dbc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +0,0 @@ | ||
ALCHEMY_KEY="" | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { erc20Abi } from "./abis"; | ||
import { renderErrorInModal } from "./display-popup-modal"; | ||
import { renderErrorInModal, renderSuccessModal } from "./display-popup-modal"; | ||
import { appState, provider, userSigner } from "./main"; | ||
import { getPermit2Address } from "./permit2-addresses"; | ||
import { ethers } from "ethers"; | ||
|
@@ -11,53 +11,54 @@ const approveButton = document.querySelector(".approve-button") as HTMLButtonEle | |
const revokeButton = document.querySelector(".revoke-button") as HTMLButtonElement; | ||
|
||
function isValidAddress(): boolean { | ||
// Check if the address is 20 bytes long (40 characters) excluding the '0x' prefix | ||
const result = /^0x[a-fA-F0-9]{40}$/.test(addressInput.value); | ||
const isValid = /^0x[a-fA-F0-9]{40}$/.test(addressInput.value); | ||
|
||
console.log("a"); | ||
|
||
if (result) { | ||
if (isValid) { | ||
addressInput.style.border = "1px solid #5af55a"; | ||
} else if (addressInput.value === "") { | ||
addressInput.style.border = "1px solid rgba(255, 255, 255, 0.1)"; | ||
} else { | ||
addressInput.style.border = "1px solid red"; | ||
} | ||
|
||
return result; | ||
return isValid; | ||
} | ||
|
||
function isValidAmount(): boolean { | ||
// Check if the amount is a positive number | ||
const result = !isNaN(Number(amountInput.value)) && Number(amountInput.value) > 0; | ||
const isValid = !isNaN(Number(amountInput.value)) && Number(amountInput.value) > 0; | ||
|
||
if (result) { | ||
if (isValid) { | ||
amountInput.style.border = "1px solid #5af55a"; | ||
} else if (amountInput.value === "") { | ||
amountInput.style.border = "1px solid rgba(255, 255, 255, 0.1)"; | ||
} else { | ||
amountInput.style.border = "1px solid red"; | ||
} | ||
|
||
return result; | ||
return isValid; | ||
} | ||
|
||
function isApprovalValid() { | ||
const addressValidity = isValidAddress(); | ||
const amountValidity = isValidAmount(); | ||
const isValid = addressValidity && amountValidity; | ||
export function isApprovalButtonsValid() { | ||
const isConnected = appState.getIsConnectedState(); | ||
const isAddressValid = isValidAddress(); | ||
const isAmountValid = isValidAmount(); | ||
|
||
approveButton.disabled = !isValid; | ||
revokeButton.disabled = !addressValidity; | ||
approveButton.disabled = !(isConnected && isAddressValid && isAmountValid); | ||
revokeButton.disabled = !(isConnected && isAddressValid); | ||
|
||
if (addressValidity && appState.getIsConnectedState()) { | ||
if (isAddressValid && isConnected) { | ||
void getCurrentAllowance(); | ||
} | ||
} | ||
|
||
async function getCurrentAllowance() { | ||
if (!provider) { | ||
console.error("Provider is not initialized"); | ||
return; | ||
} | ||
|
||
const tokenAddress = addressInput.value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you might wanna check if the address is ERC20 because if put a random address then it throws a revert error and it can be confusing to the users There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. managed that, thanks |
||
const permit2Address = getPermit2Address(appState.getCaipNetworkId() as number); | ||
const permit2Address = getPermit2Address(appState.getChainId() as number); | ||
const userAddress = appState.getAddress(); | ||
const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, provider); | ||
|
||
|
@@ -75,42 +76,80 @@ async function getCurrentAllowance() { | |
} | ||
|
||
export function setupApproveButton() { | ||
approveButton.addEventListener("click", async () => { | ||
approveButton.removeEventListener("click", onApproveClick); // ensure no duplicate listeners | ||
approveButton.addEventListener("click", onApproveClick); | ||
} | ||
|
||
async function onApproveClick() { | ||
if (!userSigner) { | ||
console.error("No signer available. Cannot send transaction."); | ||
return; | ||
} | ||
|
||
const originalText = approveButton.textContent; | ||
try { | ||
approveButton.textContent = "Loading..."; | ||
approveButton.disabled = true; | ||
revokeButton.disabled = true; // disable revoke as well to prevent conflicting actions | ||
|
||
const tokenAddress = addressInput.value; | ||
const permit2Address = getPermit2Address(appState.getCaipNetworkId() as number); | ||
const permit2Address = getPermit2Address(appState.getChainId() as number); | ||
const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, userSigner); | ||
const decimals = await tokenContract.decimals(); | ||
const amount = ethers.utils.parseUnits(amountInput.value, decimals); | ||
|
||
try { | ||
const decimals = await tokenContract.decimals(); | ||
const amount = ethers.utils.parseUnits(amountInput.value, decimals); | ||
const tx = await tokenContract.connect(userSigner).approve(permit2Address, amount); | ||
await tx.wait(); | ||
await getCurrentAllowance(); | ||
} catch (error) { | ||
console.error("Error approving allowance:", error); | ||
renderErrorInModal(error as Error); | ||
} | ||
}); | ||
const tx = await tokenContract.approve(permit2Address, amount); | ||
await tx.wait(); | ||
|
||
renderSuccessModal(tx.hash); | ||
|
||
await getCurrentAllowance(); | ||
} catch (error) { | ||
console.error("Error approving allowance:", error); | ||
renderErrorInModal(error as Error); | ||
} finally { | ||
approveButton.textContent = originalText; | ||
isApprovalButtonsValid(); // re-check the state to restore buttons correctly | ||
} | ||
} | ||
|
||
export function setupRevokeButton() { | ||
revokeButton.addEventListener("click", async () => { | ||
revokeButton.removeEventListener("click", onRevokeClick); // ensure no duplicate listeners | ||
revokeButton.addEventListener("click", onRevokeClick); | ||
} | ||
|
||
async function onRevokeClick() { | ||
if (!userSigner) { | ||
console.error("No signer available. Cannot send transaction."); | ||
return; | ||
} | ||
|
||
const originalText = revokeButton.textContent; | ||
try { | ||
revokeButton.textContent = "Loading..."; | ||
revokeButton.disabled = true; | ||
approveButton.disabled = true; // disable approve as well | ||
|
||
const tokenAddress = addressInput.value; | ||
const permit2Address = getPermit2Address(appState.getCaipNetworkId() as number); | ||
const permit2Address = getPermit2Address(appState.getChainId() as number); | ||
const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, userSigner); | ||
|
||
try { | ||
const tx = await tokenContract.connect(userSigner).approve(permit2Address, 0); | ||
await tx.wait(); | ||
await getCurrentAllowance(); | ||
} catch (error) { | ||
console.error("Error revoking allowance:", error); | ||
renderErrorInModal(error as Error); | ||
} | ||
}); | ||
const tx = await tokenContract.approve(permit2Address, 0); | ||
await tx.wait(); | ||
|
||
renderSuccessModal(tx.hash); | ||
|
||
await getCurrentAllowance(); | ||
} catch (error) { | ||
console.error("Error revoking allowance:", error); | ||
renderErrorInModal(error as Error); | ||
} finally { | ||
revokeButton.textContent = originalText; | ||
isApprovalButtonsValid(); // re-check state to restore buttons correctly | ||
} | ||
} | ||
|
||
export function setupValidityListener() { | ||
amountInput.addEventListener("change", isApprovalValid); | ||
addressInput.addEventListener("change", isApprovalValid); | ||
export function setupButtonValidityListener() { | ||
amountInput.addEventListener("change", isApprovalButtonsValid); | ||
addressInput.addEventListener("change", isApprovalButtonsValid); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for common errors like
ACTION_REJECTED
you can display a more user-friendly error messageThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already display action rejected in a pretty way, also we try to avoid all errors instead of better displaying them. insufficient funds, missing new, numeric fault .. are handled. we could handle nonce id if user waits tooooo long to sign tho
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be pretty enough for a developer but it's not really pretty a normal user though