Skip to content

Commit 2d96f10

Browse files
committed
chore: erc permits first pass
1 parent f307223 commit 2d96f10

File tree

7 files changed

+109
-44
lines changed

7 files changed

+109
-44
lines changed

scripts/typescript/generate-permit2-url.ts

+67
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,72 @@ async function generate() {
1717
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_PROVIDER_URL);
1818
const myWallet = new ethers.Wallet(process.env.UBIQUIBOT_PRIVATE_KEY || "", provider);
1919

20+
const erc721TransferFromData: PermitTransferFrom = {
21+
permitted: {
22+
token: process.env.NFT_TOKEN_ADDRESS || "0x5FbDB2315678afecb367f032d93F642f64180aa3", // anvil no salt first acc NFT deployment
23+
amount: 1, // this could be the tokenId if the permit is identified as an NFT via permitType
24+
},
25+
spender: process.env.BENEFICIARY_ADDRESS || "",
26+
nonce: BigNumber.from(`0x${randomBytes(32).toString("hex")}`),
27+
deadline: MaxUint256,
28+
};
29+
30+
const { domain: domain721, types: types721, values: values721 } = SignatureTransfer.getPermitData(
31+
erc721TransferFromData,
32+
PERMIT2_ADDRESS,
33+
process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : 1
34+
);
35+
36+
const signature721 = await myWallet._signTypedData(domain721, types721, values721);
37+
38+
const GITHUB_CONTRIBUTION_TYPE = process.env.GITHUB_CONTRIBUTION_TYPE || "issue";
39+
const GITHUB_ISSUE_ID = process.env.GITHUB_ISSUE_ID || "1";
40+
const GITHUB_ORGANIZATION_NAME = process.env.GITHUB_ORGANIZATION_NAME || "ubiquity";
41+
const GITHUB_REPOSITORY_NAME = process.env.GITHUB_REPOSITORY_NAME || "pay.ubq.fi";
42+
const GITHUB_USERNAME = process.env.GITHUB_USERNAME || "keyrxng";
43+
44+
const txData721 = [
45+
{
46+
type: "erc721-permit",
47+
permit: {
48+
permitted: {
49+
token: erc721TransferFromData.permitted.token,
50+
amount: erc721TransferFromData.permitted.amount.toString(),
51+
},
52+
nonce: erc721TransferFromData.nonce.toString(),
53+
deadline: erc721TransferFromData.deadline.toString(),
54+
},
55+
transferDetails: {
56+
to: erc721TransferFromData.spender,
57+
requestedAmount: erc721TransferFromData.permitted.amount.toString(),
58+
},
59+
owner: myWallet.address,
60+
signature: signature721,
61+
networkId: Number(process.env.CHAIN_ID),
62+
nftMetadata: {
63+
GITHUB_ORGANIZATION_NAME,
64+
GITHUB_REPOSITORY_NAME,
65+
GITHUB_ISSUE_ID,
66+
GITHUB_USERNAME,
67+
GITHUB_CONTRIBUTION_TYPE
68+
},
69+
request: {
70+
beneficiary: process.env.BENEFICIARY_ADDRESS ?? "",
71+
deadline: erc721TransferFromData.deadline.toString(),
72+
keys: ["GITHUB_ORGANIZATION_NAME", "GITHUB_REPOSITORY_NAME", "GITHUB_ISSUE_ID", "GITHUB_USERNAME", "GITHUB_CONTRIBUTION_TYPE"],
73+
nonce: erc721TransferFromData.nonce.toString(),
74+
values: [GITHUB_ORGANIZATION_NAME, GITHUB_REPOSITORY_NAME, GITHUB_ISSUE_ID, GITHUB_USERNAME, GITHUB_CONTRIBUTION_TYPE],
75+
},
76+
},
77+
];
78+
79+
const base64encodedTxData721 = Buffer.from(JSON.stringify(txData721)).toString("base64");
80+
log.ok("Testing URL:");
81+
console.log(`${process.env.FRONTEND_URL}?claim=${base64encodedTxData721}`);
82+
log.ok("Public URL:");
83+
console.log(`https://pay.ubq.fi?claim=${base64encodedTxData721}`);
84+
console.log();
85+
2086
const permitTransferFromData: PermitTransferFrom = {
2187
permitted: {
2288
// token we are permitting to be transferred
@@ -37,6 +103,7 @@ async function generate() {
37103
process.env.CHAIN_ID ? Number(process.env.CHAIN_ID) : 1
38104
);
39105
const signature = await myWallet._signTypedData(domain, types, values);
106+
40107
const txData = [
41108
{
42109
type: "erc20-permit",

static/scripts/rewards/render-transaction/insert-table-data.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ export function insertErc20PermitTableData(
3333

3434
export function insertErc721PermitTableData(permit: Erc721Permit, table: Element): Element {
3535
const requestedAmountElement = document.getElementById("rewardAmount") as Element;
36-
renderToFields(permit.request.beneficiary, app.currentExplorerUrl);
37-
renderTokenFields(permit.nftAddress, app.currentExplorerUrl);
36+
renderToFields(permit.transferDetails.to, app.currentExplorerUrl);
37+
renderTokenFields(permit.permit.permitted.token, app.currentExplorerUrl);
3838
const { GITHUB_REPOSITORY_NAME, GITHUB_CONTRIBUTION_TYPE, GITHUB_ISSUE_ID, GITHUB_ORGANIZATION_NAME, GITHUB_USERNAME } = permit.nftMetadata;
3939
renderDetailsFields([
4040
{
4141
name: "NFT address",
42-
value: `<a target="_blank" rel="noopener noreferrer" href="${app.currentExplorerUrl}/address/${permit.nftAddress}">${permit.nftAddress}</a>`,
42+
value: `<a target="_blank" rel="noopener noreferrer" href="${app.currentExplorerUrl}/address/${permit.permit.permitted.token}">${permit.permit.permitted.token}</a>`,
4343
},
4444
{
4545
name: "Expiry",
46-
value: permit.request.deadline.lte(Number.MAX_SAFE_INTEGER.toString()) ? new Date(permit.request.deadline.toNumber()).toLocaleString() : undefined,
46+
value: permit.permit.deadline.lte(Number.MAX_SAFE_INTEGER.toString()) ? new Date(permit.permit.deadline.toNumber()).toLocaleString() : undefined,
4747
},
4848
{
4949
name: "GitHub Organization",

static/scripts/rewards/render-transaction/read-claim-data-from-url.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ export async function readClaimDataFromUrl(app: AppState) {
2424
app.claims = decodeClaimData(base64encodedTxData);
2525
app.provider = await useFastestRpc(app);
2626
const networkId = app.permit?.networkId || app.networkId;
27-
app.signer = await connectWallet(networkId).catch(console.error);
27+
app.signer = await connectWallet().catch(console.error);
2828
displayRewardDetails();
2929
displayRewardPagination();
3030

3131
renderTransaction(true)
32-
.then(() => verifyCurrentNetwork(networkId))
32+
.then(() => verifyCurrentNetwork(networkId as number))
3333
.catch(console.error);
3434
}
3535

static/scripts/rewards/render-transaction/render-transaction.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ export async function renderTransaction(nextTx?: boolean): Promise<Success> {
6262
table.setAttribute(`data-claim`, "ok");
6363

6464
renderNftSymbol({
65-
tokenAddress: app.permit.nftAddress,
65+
tokenAddress: app.permit.permit.permitted.token,
6666
explorerUrl: networkExplorers[app.permit.networkId],
6767
table,
6868
requestedAmountElement,
6969
provider: app.provider,
7070
}).catch(console.error);
7171

7272
const toElement = document.getElementById(`rewardRecipient`) as Element;
73-
renderEnsName({ element: toElement, address: app.permit.request.beneficiary }).catch(console.error);
73+
renderEnsName({ element: toElement, address: app.permit.transferDetails.to }).catch(console.error);
7474

7575
claimButton.element.addEventListener("click", claimErc721PermitHandler(app.permit));
7676
}

static/scripts/rewards/render-transaction/tx-type.ts

+27-28
Original file line numberDiff line numberDiff line change
@@ -23,55 +23,54 @@ const erc20PermitT = T.Object({
2323
type: T.Literal("erc20-permit"),
2424
permit: T.Object({
2525
permitted: T.Object({
26-
token: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
27-
amount: T.Union([T.RegExp(/^\d+$/), T.Number()]),
26+
token: addressT,
27+
amount: bigNumberT,
2828
}),
29-
nonce: T.Union([T.RegExp(/^\d+$/), T.Number()]),
30-
deadline: T.Union([T.RegExp(/^\d+$/), T.Number()]),
29+
nonce: bigNumberT,
30+
deadline: bigNumberT,
3131
}),
3232
transferDetails: T.Object({
33-
to: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
34-
requestedAmount: T.Union([T.RegExp(/^\d+$/), T.Number()]),
33+
to: addressT,
34+
requestedAmount: bigNumberT,
3535
}),
36-
owner: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
37-
signature: T.RegExp(/^0x[a-fA-F0-9]+$/),
36+
owner: addressT,
37+
signature: signatureT,
3838
networkId: T.Number(),
3939
});
4040

4141
export type Erc20Permit = StaticDecode<typeof erc20PermitT>;
4242

4343
const erc721Permit = T.Object({
4444
type: T.Literal("erc721-permit"),
45-
request: T.Object({
46-
beneficiary: addressT,
47-
deadline: bigNumberT,
48-
keys: T.Array(T.String()),
45+
permit: T.Object({
46+
permitted: T.Object({
47+
token: addressT,
48+
// explicitly state tokenId or keep as "amount" but pass in the tokenId anyway? I'm passing amount in test case
49+
amount: bigNumberT,
50+
}),
4951
nonce: bigNumberT,
50-
values: T.Array(T.String()),
52+
deadline: bigNumberT,
53+
}),
54+
transferDetails: T.Object({
55+
to: addressT,
56+
requestedAmount: bigNumberT,
5157
}),
58+
owner: addressT,
59+
signature: signatureT,
60+
networkId: networkIdT,
5261
nftMetadata: T.Object({
5362
GITHUB_ORGANIZATION_NAME: T.String(),
5463
GITHUB_REPOSITORY_NAME: T.String(),
5564
GITHUB_ISSUE_ID: T.String(),
5665
GITHUB_USERNAME: T.String(),
5766
GITHUB_CONTRIBUTION_TYPE: T.String(),
5867
}),
59-
nftAddress: addressT,
60-
networkId: networkIdT,
61-
signature: signatureT,
62-
// @whilefoo: they should have matching key names.
63-
owner: addressT,
64-
permit: T.Object({
65-
permitted: T.Object({
66-
token: addressT,
67-
amount: bigNumberT,
68-
}),
69-
nonce: bigNumberT,
68+
request: T.Object({
69+
beneficiary: addressT,
7070
deadline: bigNumberT,
71-
}),
72-
transferDetails: T.Object({
73-
to: T.RegExp(/^0x[a-fA-F0-9]{40}$/),
74-
requestedAmount: T.Union([T.RegExp(/^\d+$/), T.Number()]),
71+
keys: T.Array(T.String()),
72+
nonce: bigNumberT,
73+
values: T.Array(T.String()),
7574
}),
7675
});
7776

static/scripts/rewards/web3/erc20-permit.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ export async function checkPermitClaimable(app: AppState): Promise<boolean> {
153153
return false;
154154
}
155155

156-
const permit = app.permit.permit;
156+
const permit = app.permit;
157157

158-
if (permit.deadline.lt(Math.floor(Date.now() / 1000))) {
158+
if (permit.permit.deadline.lt(Math.floor(Date.now() / 1000))) {
159159
toaster.create("error", `This reward has expired.`);
160160
return false;
161161
}
@@ -169,7 +169,7 @@ export async function checkPermitClaimable(app: AppState): Promise<boolean> {
169169
}
170170

171171
const { balance, allowance } = treasury;
172-
const permitted = BigNumber.from(permit.permitted.amount);
172+
const permitted = BigNumber.from(permit.permit.permitted.amount);
173173
const isSolvent = balance.gte(permitted);
174174
const isAllowed = allowance.gte(permitted);
175175

static/scripts/rewards/web3/erc721-permit.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { renderTransaction } from "../render-transaction/render-transaction";
66
import { Erc721Permit } from "../render-transaction/tx-type";
77
import { claimButton, errorToast, showLoader, toaster } from "../toaster";
88
import { connectWallet } from "./connect-wallet";
9-
109
export function claimErc721PermitHandler(permit: Erc721Permit) {
1110
return async function claimButtonHandler() {
1211
const signer = await connectWallet();
@@ -19,7 +18,7 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {
1918
return;
2019
}
2120

22-
if (permit.request.deadline.lt(Math.floor(Date.now() / 1000))) {
21+
if (permit.permit.deadline.lt(Math.floor(Date.now() / 1000))) {
2322
toaster.create("error", `This NFT has expired.`);
2423
return;
2524
}
@@ -32,7 +31,7 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {
3231

3332
showLoader();
3433
try {
35-
const nftContract = new ethers.Contract(permit.nftAddress, nftRewardAbi, signer);
34+
const nftContract = new ethers.Contract(permit.permit.permitted.token, nftRewardAbi, signer);
3635

3736
const tx: TransactionResponse = await nftContract.safeMint(permit.request, permit.signature);
3837
toaster.create("info", `Transaction sent. Waiting for confirmation...`);
@@ -47,7 +46,7 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {
4746
toaster.create("error", `Error rendering transaction: ${error.message}`);
4847
});
4948
} catch (error: unknown) {
50-
if (error instanceof Error) {
49+
if (error instanceof MetaMaskError) {
5150
console.error(error);
5251
errorToast(error, error.message ?? error);
5352
}
@@ -56,6 +55,6 @@ export function claimErc721PermitHandler(permit: Erc721Permit) {
5655
}
5756

5857
export async function isNonceRedeemed(nftMint: Erc721Permit, provider: JsonRpcProvider): Promise<boolean> {
59-
const nftContract = new ethers.Contract(nftMint.nftAddress, nftRewardAbi, provider);
58+
const nftContract = new ethers.Contract(nftMint.permit.permitted.token, nftRewardAbi, provider);
6059
return nftContract.nonceRedeemed(nftMint.request.nonce);
6160
}

0 commit comments

Comments
 (0)