Skip to content

Commit

Permalink
Merge pull request #151 from VWBL/release-0.1.19
Browse files Browse the repository at this point in the history
Release 0.1.19
  • Loading branch information
Huzitatuguharu authored Jun 13, 2024
2 parents ca97292 + 4a59831 commit 1becdef
Show file tree
Hide file tree
Showing 20 changed files with 261 additions and 140 deletions.
3 changes: 2 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ PROVIDER_URL=
PRIVATE_KEY=
NFT_CONTRACT_ADDRESS=
VWBL_NETWORK_URL=
NFT_STORAGE_KEY=
PINATA_API_KEY=
PINATA_API_SECRET=
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vwbl-sdk",
"description": "VWBL SDK for TypeScript",
"version": "0.1.18",
"version": "0.1.19",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"engines": {
Expand Down
133 changes: 71 additions & 62 deletions src/storage/ipfs/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,67 @@ import FormData from "form-data";
import fs from "fs";
import { Readable } from "stream";

import { isRunningOnNode } from "../../util/envUtil";
import { isRunningOnBrowser, isRunningOnNode } from "../../util";
import { IPFSConfig } from "./types";

const pinataEndpoint = "https://api.pinata.cloud/pinning/pinFileToIPFS";

function createHeaders(ipfsConfig: IPFSConfig, formData?: FormData): { [key: string]: any } {
const headers: { [key: string]: any } = {
const createHeaders = (ipfsConfig: IPFSConfig): { [key: string]: string } => {
const headers: { [key: string]: string } = {
pinata_api_key: ipfsConfig.apiKey,
pinata_secret_api_key: ipfsConfig.apiSecret,
pinata_secret_api_key: ipfsConfig.apiSecret as string,
};
headers["Content-Type"] = "multipart/form-data";
return headers;
};

if (isRunningOnNode() && formData) {
Object.assign(headers, formData.getHeaders());
} else {
headers["Content-Type"] = "multipart/form-data";
}

const createHeadersOnNode = (ipfsConfig: IPFSConfig, formData: FormData): { [key: string]: any } => {
// eslint-disable-line
const headers: { [key: string]: any } = {
// eslint-disable-line
pinata_api_key: ipfsConfig.apiKey,
pinata_secret_api_key: ipfsConfig.apiSecret as string,
};
Object.assign(headers, formData.getHeaders());
return headers;
}
};

function createConfig(headers: { [key: string]: any }, progressType: string): any {
type ConfigType = {
headers: {
[key: string]: any; // eslint-disable-line
};
onUploadProgress: ((progressEvent: any) => void) | undefined; // eslint-disable-line
};

const createConfig = (
headers: { [key: string]: any }, // eslint-disable-line
progressType: string
): ConfigType => {
return {
headers: headers,
onUploadProgress: !isRunningOnNode()
onUploadProgress: isRunningOnBrowser()
? (progressEvent: any) => {
// eslint-disable-line
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`${progressType} Progress: ${progress}%`);
}
: undefined,
};
}
};

const uploadFile = async (formData: FormData | globalThis.FormData, config: ConfigType): Promise<string> => {
try {
const response = await axios.post(pinataEndpoint, formData, config);
return `https://gateway.pinata.cloud/ipfs/${response.data.IpfsHash}`;
} catch (err: any) {
// eslint-disable-line
throw new Error(`Pinata upload failed: ${err.message}`);
}
};

// Pinata Authentication Test Functions
export const testPinataAuthentication = async (ipfsConfig: IPFSConfig): Promise<void> => {
const headers = createHeaders(ipfsConfig);

const config = {
headers: headers,
};
Expand All @@ -48,6 +73,7 @@ export const testPinataAuthentication = async (ipfsConfig: IPFSConfig): Promise<
const response = await axios.get("https://api.pinata.cloud/data/testAuthentication", config);
console.log("Pinata authentication succeeded:", response.data);
} catch (err: any) {
// eslint-disable-line
console.error("Pinata authentication failed:", headers);
throw new Error(`Pinata authentication failed: ${err.message}`);
}
Expand All @@ -62,37 +88,30 @@ export const uploadEncryptedFileToIPFS = async (
throw new Error("Pinata API key or secret is not specified.");
}

let formData: any;

if (isRunningOnNode()) {
formData = new FormData();
let formData: FormData | globalThis.FormData;
let headers: { [key: string]: any }; // eslint-disable-line
if (typeof encryptedContent === "string" || encryptedContent instanceof Uint8Array) {
if (isRunningOnNode()) {
formData = new FormData();
const blob = Buffer.from(encryptedContent);
formData.append("file", blob, "encrypted-file");
headers = createHeadersOnNode(ipfsConfig, formData);
} else {
formData = new window.FormData();
const blob = new Blob([encryptedContent], {
type: "application/octet-stream",
});
formData.append("file", blob, "encrypted-file");
headers = createHeaders(ipfsConfig);
}
} else {
formData = new window.FormData();
}

if (typeof encryptedContent === "string") {
const blob = isRunningOnNode()
? Buffer.from(encryptedContent)
: new Blob([encryptedContent], { type: "application/octet-stream" });
formData.append("file", blob, "encrypted-file");
} else if (encryptedContent instanceof Uint8Array) {
const blob = isRunningOnNode()
? Buffer.from(encryptedContent)
: new Blob([encryptedContent], { type: "application/octet-stream" });
formData.append("file", blob, "encrypted-file");
} else if (encryptedContent instanceof Readable) {
formData = new FormData();
formData.append("file", encryptedContent, { filename: "encrypted-file" });
headers = createHeadersOnNode(ipfsConfig, formData);
}

const headers = createHeaders(ipfsConfig, formData);
const config = createConfig(headers, "uploadMetadataToIPFS");

try {
const response = await axios.post(pinataEndpoint, formData, config);
return `https://gateway.pinata.cloud/ipfs/${response.data.IpfsHash}`;
} catch (err: any) {
throw new Error(`Pinata upload failed: ${err.message}`);
}
const encryptedDataUrl = await uploadFile(formData, config);
return encryptedDataUrl;
};

// upload function for thumbnailImage
Expand All @@ -105,7 +124,7 @@ export const uploadThumbnailToIPFS = async (
}

const formData = new FormData();

let headers: { [key: string]: any }; // eslint-disable-line
if (isRunningOnNode()) {
if (typeof thumbnailImage === "string") {
const stream = fs.createReadStream(thumbnailImage);
Expand All @@ -115,6 +134,7 @@ export const uploadThumbnailToIPFS = async (
} else {
throw new Error("Invalid type for thumbnailImage in Node.js environment");
}
headers = createHeadersOnNode(ipfsConfig, formData);
} else {
if (thumbnailImage instanceof File || thumbnailImage instanceof Blob) {
formData.append("file", thumbnailImage);
Expand All @@ -125,17 +145,11 @@ export const uploadThumbnailToIPFS = async (
} else {
throw new Error("Invalid type for thumbnailImage in browser environment");
}
headers = createHeaders(ipfsConfig);
}

const headers = createHeaders(ipfsConfig, formData);
const config = createConfig(headers, "uploadThumbnailToIPFS");

try {
const response = await axios.post(pinataEndpoint, formData, config);
return `https://gateway.pinata.cloud/ipfs/${response.data.IpfsHash}`;
} catch (err: any) {
throw new Error(`Pinata upload failed: ${err.message}`);
}
const thumbnailImageUrl = await uploadFile(formData, config);
return thumbnailImageUrl;
};

// upload function for metadata
Expand Down Expand Up @@ -163,24 +177,19 @@ export const uploadMetadataToIPFS = async (

const metadataJSON = JSON.stringify(metadata);
const formData = new FormData();

let headers: { [key: string]: any }; // eslint-disable-line
if (isRunningOnNode()) {
formData.append("file", Buffer.from(metadataJSON), {
filename: "metadata.json",
contentType: "application/json",
});
headers = createHeadersOnNode(ipfsConfig, formData);
} else {
const blob = new Blob([metadataJSON], { type: "application/json" });
formData.append("file", blob, "metadata.json");
headers = createHeaders(ipfsConfig);
}

const headers = createHeaders(ipfsConfig, formData);
const config = createConfig(headers, "uploadMetadataToIPFS");

try {
const response = await axios.post(pinataEndpoint, formData, config);
return `https://gateway.pinata.cloud/ipfs/${response.data.IpfsHash}`;
} catch (err: any) {
throw new Error(`Pinata upload failed: ${err.message}`);
}
const metadataUrl = await uploadFile(formData, config);
return metadataUrl;
};
36 changes: 26 additions & 10 deletions src/util/fileHelper.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import mime from "mime-types";
import path from "path";

import { FileOrPath } from "../vwbl";
import { Base64DataUrl, FileOrPath } from "../vwbl";
import { isRunningOnBrowser } from "./envUtil";

export const toBase64FromFile = async (file: File): Promise<string> => {
export const toBase64FromFile = async (file: File): Promise<Base64DataUrl> => {
if (isRunningOnBrowser()) {
return new Promise((resolve, reject) => {
const reader = new window.FileReader();
Expand All @@ -13,21 +14,36 @@ export const toBase64FromFile = async (file: File): Promise<string> => {
if (!result || typeof result !== "string") {
reject("cannot convert to base64 string");
} else {
resolve(result);
resolve(result as Base64DataUrl);
}
};
reader.onerror = (error: any) => reject(error);
});
} else {
return new Promise<Base64DataUrl>((resolve, reject) => {
try {
const arrayBuffer = file.arrayBuffer();
arrayBuffer
.then((buffer) => {
const base64 = Buffer.from(buffer).toString("base64");
const mimetype = getMimeType(file.name);
const dataUrl: Base64DataUrl = `data:${mimetype};base64,${base64}`;
resolve(dataUrl);
})
.catch(reject);
} catch (error) {
reject(error);
}
});
}
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const base64 = buffer.toString("base64");
const mimetype = getMimeType(file);
return `data:${mimetype};base64,${base64}`;
};

export const getMimeType = (file: FileOrPath): string => {
return file instanceof File ? file.type : mime.lookup(file) || "";
if (typeof file === "string") {
const fileExtension = path.extname(file);
return mime.lookup(fileExtension) || "";
} else {
return file.type || "";
}
};

export const toArrayBuffer = async (blob: Blob): Promise<ArrayBuffer> => {
Expand Down
2 changes: 1 addition & 1 deletion src/vwbl/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class VWBLBase {
}
} else if (uploadContentType === UploadContentType.IPFS || uploadMetadataType === UploadMetadataType.IPFS) {
if (!ipfsConfig) {
throw new Error("please specify nftstorage config of IPFS.");
throw new Error("please specify pinata config of IPFS.");
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/vwbl/blockchain/erc1155/VWBLProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ export class VWBLERC1155Contract {
};
}
console.log("transaction start");
const { maxPriorityFeePerGas: _maxPriorityFeePerGas, maxFeePerGas: _maxFeePerGas } =
getFeeSettingsBasedOnEnvironment(gasSettings?.maxPriorityFeePerGas, gasSettings?.maxFeePerGas);
const receipt = await this.contract.methods
.mintBatch(decryptUrl, amount, feeNumerator, documentId)
.send(txSettings);
Expand Down
27 changes: 25 additions & 2 deletions src/vwbl/erc1155/VWBL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
EthersConstructorProps,
FileOrPath,
GasSettings,
MintTokenForIPFS,
ProgressSubscriber,
StepStatus,
UploadContentType,
Expand Down Expand Up @@ -260,11 +261,12 @@ export class VWBLERC1155 extends VWBLBase {
plainFileArray.map(async (file) => {
const plainFileBlob = file instanceof File ? file : new File([await fs.promises.readFile(file)], file);
const filePath = file instanceof File ? file.name : file;
const fileName: string = file instanceof File ? file.name : file.split("/").slice(-1)[0]; //ファイル名の取得だけのためにpathを使いたくなかった
const encryptedContent =
encryptLogic === "base64"
? encryptString(await toBase64FromFile(plainFileBlob), key)
: await encryptFile(plainFileBlob, key);
: isRunningOnBrowser()
? await encryptFile(plainFileBlob, key)
: encryptStream(fs.createReadStream(filePath), key);
return await uploadEncryptedFileCallback(encryptedContent, ipfsConfig);
})
);
Expand Down Expand Up @@ -326,6 +328,27 @@ export class VWBLERC1155 extends VWBLBase {
return await this.nft.mintToken(vwblNetworkUrl, amount, feeNumerator, documentId, gasSettings);
};

/**
* Mint new ERC1155 NFT
*
* @param amount - The amount of erc1155 tokens to be minted
* @param metadataUrl metadata url
* @param feeNumerator - This basis point of the sale price will be paid to the NFT creator every time the NFT is sold or re-sold. Ex. If feNumerator = 3.5*10^2, royalty is 3.5%
* @param maxPriorityFeePerGas - Optional: the maxPriorityFeePerGas field in EIP-1559
* @param maxFeePerGas - Optional: the maxFeePerGas field in EIP-1559
* @returns The ID of minted NFT
*/
mintTokenForIPFS = async (
amount: number,
metadataUrl: string,
feeNumerator: number,
gasSettings?: GasSettings
): Promise<number> => {
const { vwblNetworkUrl } = this.opts;
const documentId = utils.hexlify(utils.randomBytes(32));
return await this.nft.mintTokenForIPFS(metadataUrl, vwblNetworkUrl, amount, feeNumerator, documentId, gasSettings);
};

/**
* Transfer NFT
*
Expand Down
Loading

0 comments on commit 1becdef

Please sign in to comment.