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(taikoon): ipfs integration with 4everland #17119

Merged
merged 14 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion packages/taikoon-ui/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
PUBLIC_WALLETCONNECT_PROJECT_ID=fake
PUBLIC_IPFS_GATEWAY=https://taikoons-fake-ipfs-gateway.vercel.app/ipfs/
PUBLIC_IPFS_GATEWAY=https://taikoons.4everland.link/ipfs/
PUBLIC_LAUNCH_DATE=2024-05-26T00:00:00
3 changes: 3 additions & 0 deletions packages/taikoon/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ SEPOLIA_ADDRESS=
KATLA_PRIVATE_KEY=
KATLA_ADDRESS=
IPFS_BASE_URI=
OWNER=
4EVERLAND_ACCESS_KEY=
4EVERLAND_SECRET_KEY=
52 changes: 51 additions & 1 deletion packages/taikoon/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
# Taikoon NFT
# Taikoon MFT

## Setup

To run in localhost, first, start an Anvil node:

```shell
$ pnpm node
```

Copy the default `.env.example` file to `.env`, and fill in the required values

Then, deploy the contracts:

```shell
$ pnpm install # installs the workspace's dependencies
```

---

## Deploying the Taikoons

In order to deploy the token, the images for the NFTs must be placed under `data/original/`. The following script will re-size them and upload them to IPFS:

```shell
$ pnpm deploy:ipfs
```

After a lot of information about the resize and upload process, the `/data/metadata` folder will be populated with the metadata files. Upload the folder to IPFS.

4EverLand's web panel works just fine.

Copy over the root CID to the `.env` file.

```shell
$ pnpm deploy:localhost # For the local Anvil node
$ pnpm deploy:holesky # For Holesky's Devnet
$ pnpm deploy:devnet # For Taiko's Devnet
```

## Minters

Used to add minters to the whitelist. The source addresses and amounts must be added under the corresponding network CSV file in `data/whitelist/`

As with the Fetch script, there's a version available for each network:

```shell
$ pnpm minters:localhost
$ pnpm minters:holesky
$ pnpm minters:devnet
```
7 changes: 5 additions & 2 deletions packages/taikoon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
"scripts": {
"clean": "rm -rf abis cache* && forge clean",
"compile": "forge build --build-info --extra-output storage-layout",
"eslint": "pnpm exec eslint --ignore-path .eslintignore --ext .js,.ts .",
"eslint": "pnpm exec eslint --ignore-path .eslintignore --ext .js,.ts . --fix",
"fmt:sol": "forge fmt",
"lint:sol": "forge fmt && pnpm solhint 'contracts/**/*.sol'",
"test": "pnpm clean && pnpm compile && forge test --match-path 'test/*.t.sol' -vvv",
"node": "anvil",
"merkle": "node script/js/generate-merkle-tree.js",
"deploy:localhost": "forge clean && pnpm compile && forge script script/sol/Deploy.s.sol --rpc-url http://localhost:8545 --broadcast",
"deploy:holesky": "forge clean && pnpm compile && forge script script/sol/Deploy.s.sol --rpc-url https://l1rpc.hekla.taiko.xyz/ --broadcast --gas-estimate-multiplier 200",
"deploy:ipfs": "rm -rf data/metadata/* && node script/js/resize-images.js && node script/js/add-images-ipfs.js && echo 'IPFS Base URI:' && ipfs add -r ./data/metadata/ && echo 'Update your .env file with the new IPFS URI'"
"deploy:ipfs": "rm -rf data/metadata/* && node script/js/resize-images.js && node script/js/4everland.js"
},
"devDependencies": {
"@types/node": "^20.11.30",
Expand All @@ -33,10 +33,13 @@
"typescript": "^5.2.2"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.574.0",
"@aws-sdk/lib-storage": "^3.574.0",
"@openzeppelin/contracts": "5.0.2",
"@openzeppelin/contracts-upgradeable": "5.0.2",
"@openzeppelin/merkle-tree": "^1.0.6",
"convert-csv-to-json": "^2.46.0",
"dotenv": "^16.4.5",
"ds-test": "github:dapphub/ds-test#e282159d5170298eb2455a6c05280ab5a73a4ef0",
"forge-std": "github:foundry-rs/forge-std",
"ipfs-http-client": "^60.0.1",
Expand Down
168 changes: 168 additions & 0 deletions packages/taikoon/script/js/4everland.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
const { S3 } = require("@aws-sdk/client-s3");
const { Upload } = require("@aws-sdk/lib-storage");
const fs = require("fs");
const fsPromises = fs.promises;
const path = require("path");
const dotenv = require('dotenv')
dotenv.config()

async function uploadFile(s3, params) {
try {
const task = new Upload({
client: s3,
queueSize: 3, // 3 MiB
params,
});

const res = await task.done();
return res.ETag.split('"').join("");
} catch (error) {
if (error) {
console.log("task", error.message);
}
}
}

// Helper function to form the metadata JSON object
function populateNFTMetadata(name, description, CID) {
return {
name,
description,
image: CID,
};
}

async function main() {
const s3Params = {
accessKey: process.env["4EVERLAND_ACCESS_KEY"],
secretKey: process.env["4EVERLAND_SECRET_KEY"],
};
const { accessKey, secretKey } = s3Params;
const s3 = new S3({
endpoint: "https://endpoint.4everland.co",
credentials: {
accessKeyId: accessKey,
secretAccessKey: secretKey,
},
region: "4EVERLAND",
});

// Get the images to upload from the local filesystem (/images)
console.log(`Importing images from the images/ directory...`);
const imgDirPath = path.join(path.resolve(__dirname, "../../data"), "images");
const filesName = await fsPromises.readdir(imgDirPath, (err) => {
if (err) {
console.log("Import from directory failed: ", err);
}
});

// Uploading images to IPFS
console.log(`Uploading image data to IPFS...`);
const imageCIDs = [];
const imagesSummary = [];
let imageCount = 1;
const imagesName = filesName.filter((fileName) => fileName.includes(".png"));
for await (const imageName of imagesName) {
const imageFilePath = path.join(
path.resolve(__dirname, "../../data"),
"images",
imageName,
);
const params = {
Bucket: "taikoons-testbucket",
Key: imageName,
ContentType: "image/png",
Body: fs.readFileSync(imageFilePath),
};

const imageCID = await uploadFile(s3, params);

imageCIDs.push(imageCID);
imagesSummary.push({ imageCID, imageCount });
console.log(`Image ${imageCount} added to IPFS with CID of ${imageCID}`);
imageCount++;
}
console.log(` `);

// Add the metadata to IPFS
console.log(`Adding metadata to IPFS...`);
let taikoonId = 0;
for await (const imageCID of imageCIDs) {
taikoonId++;

// write into a file
fs.writeFileSync(
path.join(
path.resolve(__dirname, "../../data"),
"metadata",
`${taikoonId}.json`,
),
JSON.stringify(
populateNFTMetadata(
`Taikoon ${taikoonId}`,
"A Taikoon",
imageCID.toString(),
),
),
);

console.log(
path.join(
path.resolve(__dirname, "../../data"),
"metadata",
`${taikoonId}.json`,
),
);
/*
metadataCIDs.push(metadataCID);
for (let i = 0; i < imagesSummary.length; i++) {
if (imagesSummary[i].imageCID == imageCID) {
imagesSummary[i].metadataCID = metadataCID;
}
} */
// console.log(`Metadata with image CID ${imageCID} added to IPFS with CID of ${metadataCID}`);
}
console.log(` `);
/*
fs.writeFileSync(
path.join(
path.resolve(__dirname, "../../data"),
"metadata",
"summary.json",
),
JSON.stringify({ imagesSummary }),
);
*/
/*
const putObjectOutput = await s3.putObject({
Bucket: "bucketname",
Key: "key",
Body: "data content",
});

// multipart upload
const params = {
Bucket,
Key: file.name,
Body: file,
ContentType: file.type,
};
try {
const task = new Upload({
client: s3,
queueSize: 3, // 3 MiB
params,
});
task.on("httpUploadProgress", (e) => {
const progress = ((e.loaded / e.total) * 100) | 0;
console.log(progress, e);
});
await task.done();
} catch (error) {
if (error) {
console.log("task", error.message);
}
} */
}

main();
Loading