-
-
Notifications
You must be signed in to change notification settings - Fork 252
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
07a4f8a
450460c
2bcea68
448f6d6
ef8a4dc
03cbc88
cc72ef8
1acfab2
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 |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Abi, encodeFunctionData } from 'viem' | ||
import { frames } from '../frames' | ||
import { transaction } from 'frames.js/core' | ||
import { erc20Abi, getKeyPrice } from '../components/utils' | ||
|
||
export const POST = frames(async (ctx) => { | ||
if (!ctx?.message) { | ||
throw new Error('Invalid frame message') | ||
} | ||
|
||
const userAddress = ctx.message.address! | ||
const network = Number(ctx.state.lock!.network) | ||
const lockAddress = ctx.state.lock!.address | ||
|
||
const keyPrice = await getKeyPrice({ | ||
lockAddress, | ||
network, | ||
userAddress, | ||
}) | ||
|
||
const calldata = encodeFunctionData({ | ||
abi: erc20Abi, | ||
functionName: 'approve', | ||
args: [lockAddress, keyPrice], | ||
}) | ||
|
||
return transaction({ | ||
chainId: `eip155:${network}`, | ||
method: 'eth_sendTransaction', | ||
params: { | ||
abi: erc20Abi as Abi, | ||
to: ctx.state.lock?.tokenAddress as `0x${string}`, | ||
data: calldata, | ||
value: '0x0', | ||
}, | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,11 +1,15 @@ | ||||||||||||||
/* eslint-disable react/jsx-key */ | ||||||||||||||
import React from 'react' | ||||||||||||||
import { Button } from 'frames.js/next' | ||||||||||||||
import { State } from '../frames' | ||||||||||||||
import { DefaultImage } from './DefaultImage' | ||||||||||||||
|
||||||||||||||
export function getDefaultFrame(state: State) { | ||||||||||||||
const lock = state.lock! | ||||||||||||||
export function getDefaultFrame(ctx: any) { | ||||||||||||||
const lock = ctx.state.lock! | ||||||||||||||
const isErc20 = !!ctx.state.lock?.tokenAddress | ||||||||||||||
const approved = ctx.searchParams.approved || !isErc20 | ||||||||||||||
const buttonText = approved | ||||||||||||||
? 'Purchase a key' | ||||||||||||||
: `Approve ${lock.currencySymbol} spending` | ||||||||||||||
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.
Suggested change
|
||||||||||||||
|
||||||||||||||
return { | ||||||||||||||
image: <DefaultImage lock={lock} />, | ||||||||||||||
|
@@ -18,10 +22,14 @@ export function getDefaultFrame(state: State) { | |||||||||||||
}, | ||||||||||||||
}, | ||||||||||||||
buttons: [ | ||||||||||||||
<Button action="tx" target="/txdata" post_url="?success=true"> | ||||||||||||||
Purchase a key | ||||||||||||||
<Button | ||||||||||||||
action="tx" | ||||||||||||||
target={approved ? '/txdata' : '/approve'} | ||||||||||||||
post_url={approved ? '?success=true' : '?approved=true'} | ||||||||||||||
> | ||||||||||||||
{buttonText} | ||||||||||||||
</Button>, | ||||||||||||||
], | ||||||||||||||
state, | ||||||||||||||
state: ctx.state, | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,37 @@ import { locksmith } from '../../../../src/config/locksmith' | |
import networks from '@unlock-protocol/networks' | ||
import { config as appConfig } from '~/config/app' | ||
|
||
interface GetKeyPriceParams { | ||
lockAddress: string | ||
network: number | ||
userAddress: string | ||
} | ||
|
||
export const erc20Abi = [ | ||
{ | ||
type: 'function', | ||
name: 'approve', | ||
constant: false, | ||
inputs: [ | ||
{ | ||
name: 'spender', | ||
type: 'address', | ||
}, | ||
{ | ||
name: 'amount', | ||
type: 'uint256', | ||
}, | ||
], | ||
outputs: [ | ||
{ | ||
name: '', | ||
type: 'bool', | ||
}, | ||
], | ||
stateMutability: 'nonpayable', | ||
}, | ||
] | ||
|
||
export async function getConfig(id: string) { | ||
const { config } = await fetch( | ||
`${appConfig.locksmithHost}/v2/checkout/${id}` | ||
|
@@ -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') { | ||
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. 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 |
||
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 | ||
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. That's also not correct. just use |
||
} | ||
|
||
const redirectUri = config.redirectUri | ||
const redirectText = config.endingCallToAction | ||
|
||
|
@@ -53,9 +94,29 @@ export async function getLockDataFromCheckout(id: string) { | |
defaultImage, | ||
description, | ||
price, | ||
currencySymbol: res.currencySymbol, | ||
tokenAddress, | ||
redirectUri, | ||
redirectText, | ||
} | ||
|
||
return lock | ||
} | ||
|
||
export async function getKeyPrice({ | ||
lockAddress, | ||
network, | ||
userAddress, | ||
}: GetKeyPriceParams) { | ||
const web3Service = new Web3Service(networks) | ||
const mydata = '0x' | ||
let price = await web3Service.purchasePriceFor({ | ||
lockAddress, | ||
userAddress: userAddress, | ||
network, | ||
data: mydata, | ||
referrer: userAddress, | ||
}) | ||
price = price.toString() | ||
return price | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,7 @@ import { Abi, encodeFunctionData } from 'viem' | |
import { frames } from '../frames' | ||
import { transaction } from 'frames.js/core' | ||
import { PublicLockV14 } from '@unlock-protocol/contracts' | ||
import { Web3Service } from '@unlock-protocol/unlock-js' | ||
import networks from '@unlock-protocol/networks' | ||
import { getKeyPrice } from '../components/utils' | ||
|
||
const abi = PublicLockV14.abi | ||
|
||
|
@@ -12,25 +11,15 @@ export const POST = frames(async (ctx) => { | |
throw new Error('Invalid frame message') | ||
} | ||
|
||
const userAddress = ctx.message.connectedAddress! | ||
const userAddress = ctx.message.address! | ||
const network = Number(ctx.state.lock!.network) | ||
const lockAddress = ctx.state.lock!.address | ||
|
||
async function getKeyPrice() { | ||
const web3Service = new Web3Service(networks) | ||
const mydata = '0x' | ||
let price = await web3Service.purchasePriceFor({ | ||
lockAddress, | ||
userAddress: userAddress, | ||
network, | ||
data: mydata, | ||
referrer: userAddress, | ||
}) | ||
price = price.toString() | ||
return price | ||
} | ||
|
||
const keyPrice = await getKeyPrice() | ||
const keyPrice = await getKeyPrice({ | ||
lockAddress, | ||
network, | ||
userAddress, | ||
}) | ||
|
||
const calldata = encodeFunctionData({ | ||
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. Please use unlock'js walletService here as well so this supports any version of the lock contract! 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. I ran into errors getting the abi with |
||
abi, | ||
|
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.
You don't need to use that! unlock-js has the logic you need to use.
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.
I didn’t catch that, the encodeFunctionData part or the entire erc20 approve 🤔
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.
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.
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.
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