Skip to content

Commit

Permalink
feat(erc721): add mintTo function
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsdls committed Jan 20, 2024
1 parent 6a5467f commit 6483b0b
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 11 deletions.
12 changes: 6 additions & 6 deletions packages/thirdweb/src/extensions/erc20/write/mintTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,27 @@ type MintToParams = { to: string } & (
* Mints a specified amount of tokens to the given address.
*
* @param contract - The ThirdwebContract instance.
* @param options - The minting options.
* @param params - The minting options.
* @returns A promise that resolves to the transaction result.
*/
export function mintTo(contract: ThirdwebContract, options: MintToParams) {
export function mintTo(contract: ThirdwebContract, params: MintToParams) {
return transaction(contract, {
address: contract.address,
chainId: contract.chainId,
method: "function mintTo(address to, uint256 amount)",
params: async () => {
let amount: bigint;
if ("amount" in options) {
if ("amount" in params) {
// if we need to parse the amount from ether to gwei then we pull in the decimals extension
const { decimals } = await import("../read/decimals.js");
// if this fails we fall back to `18` decimals
const d = await decimals(contract).catch(() => 18);
// turn ether into gwei
amount = parseUnits(options.amount.toString(), d);
amount = parseUnits(params.amount.toString(), d);
} else {
amount = options.amountGwei;
amount = params.amountGwei;
}
return [options.to, amount] as const;
return [params.to, amount] as const;
},
});
}
1 change: 1 addition & 0 deletions packages/thirdweb/src/extensions/erc721.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { startTokenId } from "./erc721/read/startTokenId.js";
export { tokenURI, type TokenUriParams } from "./erc721/read/tokenURI.js";

// WRITE
export { mintTo, type MintToParams } from "./erc721/write/mintTo.js";
44 changes: 44 additions & 0 deletions packages/thirdweb/src/extensions/erc721/write/mintTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import type { FileOrBufferOrString } from "../../../storage/upload/types.js";

export type NFTInput = {
name?: string;
description?: string;
image?: FileOrBufferOrString;
animation_url?: FileOrBufferOrString;
external_url?: FileOrBufferOrString;
background_color?: string;
// TODO check if we truly need both of these?
properties?: Record<string, unknown> | Array<Record<string, unknown>>;
} & Record<string, unknown>;

export type MintToParams = {
to: string;
nft: NFTInput | string;
};

export function mintTo(contract: ThirdwebContract, params: MintToParams) {
return transaction(contract, {
address: contract.address,
chainId: contract.chainId,
method: "function mintTo(address _to, string memory _tokenURI)",
params: async () => {
let tokenUri: string;

if (typeof params.nft === "string") {
// if the input is already a string then we just use that
tokenUri = params.nft;
} else {
// otherwise we need to upload the file to the storage server

tokenUri = (
await contract.storage.upload({
files: [params.nft],
})
)[0] as string;
}
return [params.to, tokenUri] as const;
},
});
}
116 changes: 116 additions & 0 deletions packages/thirdweb/src/storage/upload/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { areUint8ArraysEqual, isUint8Array } from "../../utils/uint8-array.js";
import type {
BufferOrStringWithName,
BuildFormDataOptions,
FileOrBuffer,
FileOrBufferOrString,
} from "./types.js";

Expand Down Expand Up @@ -157,3 +158,118 @@ export function buildFormData(
fileNames: fileNames.map((fName) => encodeURIComponent(fName)),
};
}

export function isFileOrUint8Array(
data: any,
): data is File | Uint8Array | BufferOrStringWithName {
return (
isFileInstance(data) || isUint8Array(data) || isBufferOrStringWithName(data)
);
}

/**
* @internal
*/
export function extractObjectFiles(
data: unknown,
files: FileOrBuffer[] = [],
): FileOrBuffer[] {
// If item is a FileOrBuffer add it to our list of files
if (isFileOrUint8Array(data)) {
files.push(data);
return files;
}

if (typeof data === "object") {
if (!data) {
return files;
}

if (Array.isArray(data)) {
data.forEach((entry) => extractObjectFiles(entry, files));
} else {
Object.keys(data).map((key) =>
extractObjectFiles(data[key as keyof typeof data], files),
);
}
}

return files;
}

/**
* @internal
*/
export function replaceObjectFilesWithUris(
data: unknown,
uris: string[],
): unknown {
if (isFileOrUint8Array(data)) {
if (uris.length) {
data = uris.shift() as string;
return data;
} else {
console.warn("Not enough URIs to replace all files in object.");
}
}

if (typeof data === "object") {
if (!data) {
return data;
}

if (Array.isArray(data)) {
return data.map((entry) => replaceObjectFilesWithUris(entry, uris));
} else {
return Object.fromEntries(
Object.entries(data).map(([key, value]) => [
key,
replaceObjectFilesWithUris(value, uris),
]),
);
}
}

return data;
}

function replaceGatewayUrlWithScheme(url: string): string {
if (url.includes("/ipfs/")) {
const hash = url.split("/ipfs/")[1];
return `ipfs://${hash}`;
}
return url;
}

/**
* @internal
*/
export function replaceObjectGatewayUrlsWithSchemes<TData>(data: TData): TData {
if (typeof data === "string") {
return replaceGatewayUrlWithScheme(data) as TData;
}
if (typeof data === "object") {
if (!data) {
return data;
}

if (isFileOrUint8Array(data)) {
return data;
}

if (Array.isArray(data)) {
return data.map((entry) =>
replaceObjectGatewayUrlsWithSchemes(entry),
) as TData;
}

return Object.fromEntries(
Object.entries(data).map(([key, value]) => [
key,
replaceObjectGatewayUrlsWithSchemes(value),
]),
) as TData;
}

return data;
}
59 changes: 56 additions & 3 deletions packages/thirdweb/src/storage/upload/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,64 @@
import { stringify } from "../../utils/json.js";
import type { RawClient } from "../../client/client.js";
import { buildFormData, isBrowser } from "./helpers.js";
import type { UploadOptions } from "./types.js";
import {
buildFormData,
extractObjectFiles,
isBrowser,
isFileOrUint8Array,
replaceObjectFilesWithUris,
replaceObjectGatewayUrlsWithSchemes,
} from "./helpers.js";
import type { FileOrBufferOrString, UploadOptions } from "./types.js";

export async function upload(client: RawClient, options: UploadOptions) {
// deal with the differnt file types

// if there are no files, return an empty array immediately
if (!options.files.length) {
return [];
}
// handle file arrays
const isFileArray = options.files
.map((item) => isFileOrUint8Array(item) || typeof item === "string")
.every((item) => !!item);

let uris: FileOrBufferOrString[];

if (isFileArray) {
// if we already have an array of files, we can just pass it through
uris = options.files as FileOrBufferOrString[];
} else {
// otherwise we have to process them first
let cleaned = options.files as unknown[];

// Replace any gateway URLs with their hashes
cleaned = replaceObjectGatewayUrlsWithSchemes(cleaned);

// Recurse through data and extract files to upload
const files = extractObjectFiles(cleaned);
if (files.length) {
// Upload all files that came from the object
const uris_ = await upload(client, { ...options, files });

// Recurse through data and replace files with hashes
cleaned = replaceObjectFilesWithUris(cleaned, uris_) as unknown[];
}

uris = cleaned.map((item) => {
if (typeof item === "string") {
return item;
}
return stringify(item);
});
}

// end deal with the differnt file types
const form_ = new FormData();

const { fileNames, form } = buildFormData(form_, options.files, options);
const { fileNames, form } = buildFormData(form_, uris, options);
// default uploadWithoutDirectory to true if there is only one file and the option is not set explicitly
options.uploadWithoutDirectory =
options.uploadWithoutDirectory ?? fileNames.length === 1;
if (isBrowser()) {
const { uploadBatchBrowser } = await import("./browser.js");
return await uploadBatchBrowser(client, form, fileNames, options);
Expand Down
4 changes: 2 additions & 2 deletions packages/thirdweb/src/storage/upload/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @internal
*/
type FileOrBuffer = File | Uint8Array | BufferOrStringWithName;
export type FileOrBuffer = File | Uint8Array | BufferOrStringWithName;

/**
* @internal
Expand All @@ -25,5 +25,5 @@ export type BuildFormDataOptions = {
};

export type UploadOptions = {
files: FileOrBufferOrString[];
files: (FileOrBufferOrString | Record<string, unknown>)[];
} & BuildFormDataOptions;

0 comments on commit 6483b0b

Please sign in to comment.