Skip to content
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

fix(unlock-app): checkout frame errors #14912

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

iMac7
Copy link
Contributor

@iMac7 iMac7 commented Oct 31, 2024

Description

Issues

Fixes #14888
Refs #

Checklist:

  • 1 PR, 1 purpose: my Pull Request applies to a single purpose
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the docs to reflect my changes if applicable
  • I have added tests (and stories for frontend components) that prove my fix is effective or that my feature works
  • I have performed a self-review of my own code
  • If my code involves visual changes, I am adding applicable screenshots to this thread

Indeed an erc20 approval is needed first. This handles that by having the first button click as the approval and the second as the actual purchase, lmk if that's confusing or a more elegant way, I don't see a way to 'batch' those 2 together in a single transaction so far 🤔 .

As for checking if a user has a membership before the purchase, this can happen by cancelling a transaction midway, because the user's wallet details are not accessible elsewhere. Doing that would throw an error anyway so I'm assuming a failed purchase (because you already have a key) does the same and it's sort of an 'acceptable' error

Frame.Debugger.mp4

Release Note Draft Snippet

image

@cla-bot cla-bot bot added the cla-signed label Oct 31, 2024
Comment on lines 10 to 12
const buttonText = approved
? 'Purchase a key'
: `Approve ${lock.currencySymbol} spending`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const buttonText = approved
? 'Purchase a key'
: `Approve ${lock.currencySymbol} spending`
const buttonText = approved
? 'Mint'
: `Approve ${lock.currencySymbol} spending`

@@ -42,6 +73,16 @@ export async function getLockDataFromCheckout(id: string) {
const res = await web3Service.getLock(lockAddress, network)
const price = `${res.keyPrice} ${res.currencySymbol}`

let tokenAddress
if (res.currencySymbol !== 'ETH') {
Copy link
Member

Choose a reason for hiding this comment

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

That's not the right way to check if the lock is priced in the native currency. Thus would fail on Polygon for example. Just check that the lock.tokenAddress is the Zero address.

Comment on lines 78 to 83
const tokens = networks[network].tokens
const matches = tokens!.filter(
(token) => token.symbol === res.currencySymbol
)
tokenAddress = matches.find((token) => token.featured) || matches[0] || null
tokenAddress = tokenAddress.address
Copy link
Member

Choose a reason for hiding this comment

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

That's also not correct. just use lock.tokenAddress here.

Copy link
Member

@julien51 julien51 left a comment

Choose a reason for hiding this comment

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

Some changes needed.

@julien51
Copy link
Member

julien51 commented Nov 1, 2024

Indeed an erc20 approval is needed first. This handles that by having the first button click as the approval and the second as the actual purchase, lmk if that's confusing or a more elegant way, I don't see a way to 'batch' those 2 together in a single transaction so far 🤔 .

In the case of an ERC20 lock, you first need to check if there is an existing approval and if that approval is "enough" ( > price). I don't think you can "batch" the 2 tx. The user would first need to approve and 2nd need to send the purchase.

@julien51
Copy link
Member

julien51 commented Nov 1, 2024

As for checking if a user has a membership before the purchase, this can happen by cancelling a transaction midway, because the user's wallet details are not accessible elsewhere. Doing that would throw an error anyway so I'm assuming a failed purchase (because you already have a key) does the same and it's sort of an 'acceptable' error

No, you need to actually perform a check and can't rely on the fact that there is an error, because the purchase could fail for other reasons (sold out.. etc).
So this means we can't show a Mint button right away but start with an intermediary action like Continue. This button would then trigger an action where you can verify if the user does indeed own a specific key to the lock. If they do, you can show a message saying "you already have a key"... and if they don't you can check if an approval is needed. If it is, trigger the approval, and if they don't (or once they have done it), just trigger the actual purchase tx.

What do you think?

@iMac7
Copy link
Contributor Author

iMac7 commented Nov 4, 2024

to get the user address, they are first required to sign a dummy message to prompt the wallet connect. The user's address is only available in a transaction context so this hacky way is the only one I see to get that without an error or an actual transaction.
It shows below that I'm a member even though I can still buy another key, just for testing but it's implemented in the code.

https://www.loom.com/share/722c2376444a4b6393963e43a8398669?sid=a407e3b8-a2d5-483a-a2a9-e5971bc7754d

@iMac7 iMac7 requested a review from julien51 November 4, 2024 16:03
@julien51
Copy link
Member

julien51 commented Nov 5, 2024

to get the user address, they are first required to sign a dummy message to prompt the wallet connect.

You can get it from the frame as long as the user has interracted with it, which is why the "Continue" button is useful :)

@iMac7
Copy link
Contributor Author

iMac7 commented Nov 6, 2024

to get the user address, they are first required to sign a dummy message to prompt the wallet connect.

You can get it from the frame as long as the user has interracted with it, which is why the "Continue" button is useful :)

This might be specific to the current version of framesjs or only on the local debugger not sure, but only a button with action 'mint' or 'tx' is able to give a prompt to connect a wallet. the continue button would have a 'post' or 'post_redirect' property which doesn't connect a wallet but only navigates to another page (I just confirmed locally that it doesn't).

Maybe in prod it might behave differently but in the docs I don't see any other way to get the address with no tx.
Connecting a wallet is better handled with the useFrame() hook which is used inside react, but it's not handled as gracefully when dealing with the server side of framesjs like in this case.

https://framesjs.org/guides/transactions#using-the-connected-wallet-address
This doesn't help much for an unauthenticated user, maybe I don't get it 🤔

Which kind of action button ( https://framesjs.org/reference/core/Button#button ) do you suggest for 'continue'? I seem to hit a wall on that one 😮‍💨

const lock = ctx.state.lock!
const { address: lockAddress, network, priceForUser } = lock

const calldata = encodeFunctionData({
Copy link
Member

Choose a reason for hiding this comment

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

You don't need to use that! unlock-js has the logic you need to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn’t catch that, the encodeFunctionData part or the entire erc20 approve 🤔

Copy link
Member

Choose a reason for hiding this comment

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

So I think you should first check if the user has already approved enough (and you can compute the amount that needs to be approved based on the ehckout config (especially if it supports renewals)

Sorry my initial comment was unclear. Check unlock-js where there is already logic around how to compute the amount to be approved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

seen the logic in unlock-js/src/PublicLock/v10/getPurchaseKeysArguments to account for renewals and done it slightly differently.
my approach now checks if a lock is renewable to decide whether to render the approve button, so you can always approve a different amount before the purchase to replace any previous approval

@julien51
Copy link
Member

julien51 commented Nov 6, 2024

This might be specific to the current version of framesjs or only on the local debugger not sure, but only a button with action 'mint' or 'tx' is able to give a prompt to connect a wallet. the continue button would have a 'post' or 'post_redirect' property which doesn't connect a wallet but only navigates to another page (I just confirmed locally that it doesn't).

You have the fid, and then you can get the wallet address as soon as the user has interracted wuth the frame https://framesjs.org/reference/js/getAddressesForFid#usage

@iMac7 iMac7 requested a review from julien51 November 11, 2024 13:07
userAddress as string,
tokenAddress!
)
if (Number(allowance) >= Number(keyPrice)) {
Copy link
Member

Choose a reason for hiding this comment

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

That's not enough, the user may need to approve MORE than the keyprice to allow for renewals.

return price
}

const keyPrice = await getKeyPrice()

const calldata = encodeFunctionData({
Copy link
Member

Choose a reason for hiding this comment

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

Please use unlock'js walletService here as well so this supports any version of the lock contract!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ran into errors getting the abi with walletService.lockContractAbiVersion() which seems the closest method in functionality imo.
Would it work for all versions just importing the generic PublicLock without a version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Debug errors on frame checkout
2 participants