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

[feat] Wire up create font project submit button to the backend and smart contract apis #11

Merged
merged 5 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/clientApi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ export * from './lens/getPublications';
export * from './lens/mirror';
export * from './lens/refresh';

export * from './ipfonts/createIPFontsUser';
export * from './ipfonts/createIPFontsUser';
export * from './ipfonts/createIPFontProject';
86 changes: 86 additions & 0 deletions frontend/clientApi/ipfonts/createIPFontProject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import connectContract from '../../utils/connectContract';


export async function createIPFontProject({
files,
name,
description,
perCharacterMintPrice,
mintLimit
}) {
const ipfontsContract = connectContract();

if (!ipfontsContract) {
console.log('Cound not connect to contract');
return;
}

const formData = new FormData();

for (let i = 0; i < files.length; i++) {
formData.append('fonts', files[i]);
};

try {
const uploadFontResponse = await fetch('/api/upload-font', {
method: 'POST',
body: formData
});

const {
ok: fontUploadOk,
cid: fontFilesCID,
error: fontUploadError
} = await uploadFontResponse.json();

console.log({
fontUploadOk,
fontFilesCID,
fontUploadError
});

const uploadMetadataResponse = await fetch('/api/upload-metadata', {
method: 'POST',
body: JSON.stringify({
name,
description
})
});

const {
ok: metadataUploadOk,
cid: fontMetadataCID,
error: metadataUploadError
} = await uploadMetadataResponse.json();

console.log({
metadataUploadOk,
fontMetadataCID,
metadataUploadError
});

if (fontUploadOk && metadataUploadOk) {
const createdAt = Date.now();

const txn = await ipfontsContract.createFontProject(
createdAt,
createdAt,
perCharacterMintPrice,
mintLimit,
process.env.NEXT_PUBLIC_SUPERFLUID_MATICX_TOKEN_ADDRESS,
fontMetadataCID,
fontFilesCID,
{ gasLimit: 900000 }
);
console.log("IPFonts : Creating font project entity", txn.hash);

await txn.wait();
console.log("IPFonts : Font project entity created", txn.hash);
} else {
console.log(fontUploadError);
console.log(metadataUploadError);
}
} catch(err) {
console.log(err);
}
}
Copy link
Member

@eduairet eduairet Jan 6, 2023

Choose a reason for hiding this comment

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

I'll take this as example for the user :) Does make sense to have these files:

  • createIPFontsUser.js
  • editIPFontsUser.js
  • createLensUser.js (just in testnet)
  • editLensUser.js
    And condition them according to how the user has been authenticated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice yeah makes sense 😃

12 changes: 3 additions & 9 deletions frontend/clientApi/ipfonts/createIPFontsUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import { readContract } from '@wagmi/core'
import connectContract from '../../utils/connectContract';
import abiJSON from "../../utils/FontProject.json";
import { ethers } from 'ethers';

const contractAddress = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS;
import getIPFontsUser from './getIPFontsUser';

export async function createIPFontsUser({
lensAddress,
address,
lensHandle,
email,
name,
Expand All @@ -27,12 +26,7 @@ export async function createIPFontsUser({
return;
}

const { createdAt} = await readContract({
address: contractAddress,
abi: abiJSON.abi,
functionName: 'addressToUser',
args: [lensAddress]
});
const { createdAt } = await getIPFontsUser(address);

const isRegistered = !ethers.BigNumber.from(createdAt).isZero;

Copy link
Member

Choose a reason for hiding this comment

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

This file is related to the part I'm testing, didn't modify it (but I used parts of it) to avoid merging stashing, but my plan is to refactor some of the logic I'm using in the form here, does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cool yeah makes sense to me 👍

Expand Down
23 changes: 23 additions & 0 deletions frontend/clientApi/ipfonts/getIPFontsUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { readContract } from '@wagmi/core'
import connectContract from '../../utils/connectContract';
import abiJSON from "../../utils/FontProject.json";

const contractAddress = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS;

export async function getIPFontsUser({
address
}) {
const ipfontsContract = connectContract();

if (!ipfontsContract) {
console.log('Cound not connect to contract');
return;
}

return await readContract({
address: contractAddress,
abi: abiJSON.abi,
functionName: 'addressToUser',
args: [lensAddress]
});
}
35 changes: 30 additions & 5 deletions frontend/components/Forms/Submit/Submit.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import React, { useContext } from "react";
import { useAccount } from 'wagmi';
import { FormContext } from "../../Overlay/CreateProject.js";
import classes from "../../../styles/Forms.module.css";
import {
client as lensClient,
createIPFontsUser,
createIPFontProject,
getProfileByAddress
} from '../../../clientApi';

export default function Submit() {
const { activeStepIndex, setActiveStepIndex, formData, setFormData } =
useContext(FormContext);
const { address, isConnected } = useAccount();
Copy link
Member

Choose a reason for hiding this comment

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

Haven't seen this before, looks smart!

const { activeStepIndex, setActiveStepIndex, formData } = useContext(FormContext);

const submit = async () => {
// Check to see if user is logged in
if (isConnected) {
const lensProfile = await lensClient.query({
query: getProfileByAddress,
variables: {
owner: address,
},
});

// TODO - create user in smart contract if it does not exist already

await createIPFontProject({
files: formData.files,
name : formData.projectName,
description : formData.description,
perCharacterMintPrice: formData.setPrice,
mintLimit: formData.minLimit
});
}
Copy link
Member

Choose a reason for hiding this comment

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

This file really got cleaned 💯 sweet!


const submit = () => {
const data = { ...formData };
setFormData(data);
setActiveStepIndex(activeStepIndex + 1);
console.table(formData);
};
Expand Down
2 changes: 0 additions & 2 deletions frontend/components/UI/NavBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { useRouter } from 'next/router';
import logo from '../../public/logoHeader.svg';
import classes from '../../styles/NavBar.module.css';
import ConnectButton from '../UI/ConnectButton';
import { client as lensClient, challenge, authenticate, getProfileByAddress, createIPFontsUser } from '../../clientApi';
import { ethers } from 'ethers';

export default function NavBar(props) {
const router = useRouter();
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"clsx": "^1.2.1",
"ethers": "^5.7.2",
"graphql": "^15.8.0",
"ipfs-http-client": "^59.0.0",
"iron-session": "^6.3.1",
"multer": "1.4.5-lts.1",
"next": "12.2.5",
Expand Down
45 changes: 0 additions & 45 deletions frontend/pages/api/store-font-data.js

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/pages/api/upload-font.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const handler = async (req, res) => {
cid
});
} catch (_error) {
res.json({ ok: false });
res.status(500).json({ error: "Error uploading font files", ok: false });
}
break;
default:
Expand Down
41 changes: 41 additions & 0 deletions frontend/pages/api/upload-metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { withIronSessionApiRoute } from 'iron-session/next';
import ironOptions from '../../config/ironOptions';
import { storeMetadataFileIPFS } from '../../utils/storeMetadataFileIPFS';

async function handler(req, res) {
if (req.method === "POST") {
// Check that user has signed in and is authorized to uplaod files
if (!req.session.siwe) {
return res.status(401).json({ message: 'You have to sign-in first' });
}

return await storeFontMetadata(req, res);
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about making a generic storeMetadata method instead storeFontMetadata and using it for both, lens and user, maybe add a string parameter that makes custom messages "Error uploading ${type} metadata"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah yeah good call 👍 Let me update in a follow up PR. The type variable could be a param passed in the request body

} else {
return res
.setHeader('Allow', ['POST'])
.status(405)
.json({ message: "Method not allowed", success: false });
}
}

async function storeFontMetadata(req, res) {
const body = req.body;

try {
const file = {
path : '/tmp/data.json',
content: Buffer.from(body)
};

const cid = await storeMetadataFileIPFS(file);

return res.status(200).json({ ok: true, cid: cid });
} catch (err) {
console.log(err);
return res
.status(500)
.json({ error: "Error uploading font metadata", ok: false });
}
}

export default withIronSessionApiRoute(handler, ironOptions)
4 changes: 2 additions & 2 deletions frontend/utils/storeFilesIPFS.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Web3Storage, File } from "web3.storage";

function makeStorageClient() {
function makeWeb3StorageClient() {
return new Web3Storage({ token: process.env.WEB3STORAGE_TOKEN });
}

Expand All @@ -9,7 +9,7 @@ export async function storeFilesIPFS(files) {
return new File([buffer],name);
});

const client = makeStorageClient();
const client = makeWeb3StorageClient();
const cid = await client.put(web3StorageFiles);

return cid;
Expand Down
26 changes: 26 additions & 0 deletions frontend/utils/storeMetadataFileIPFS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { create } from 'ipfs-http-client'

function makeInfuraStorageClient() {
const idKeySecretPair = `${process.env.INFURA_IPFS_PROJECT_ID}:${process.env.INFURA_IPFS_KEY_SECRET}`;
const auth = 'Basic ' + Buffer.from(idKeySecretPair).toString('base64');

return create({
host: process.env.INFURA_IPFS_HOST,
port: process.env.INFURA_IPFS_PORT,
protocol: 'https',
headers: {
authorization: auth,
},
});
}

export async function storeMetadataFileIPFS(file) {
const client = makeInfuraStorageClient();

const result = await client.add({
path : file.path,
content : file.content
});

return result.cid.toString();
}
Copy link
Member

Choose a reason for hiding this comment

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

Already used this file for the user :) thanks a lot JP

Loading