Skip to content

Commit

Permalink
initial erc721 extensions (read)
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsdls committed Jan 20, 2024
1 parent e06ee8b commit 6a5467f
Show file tree
Hide file tree
Showing 18 changed files with 472 additions and 3 deletions.
12 changes: 9 additions & 3 deletions packages/thirdweb/src/extensions/erc20/read/balanceOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@ import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { read } from "../../../transaction/actions/read.js";
import { decimals } from "./decimals.js";
import { formatUnits } from "viem/utils";
import { formatUnits } from "viem";
import { symbol } from "./symbol.js";

type BalanceOfParams = { address: string };

/**
* Retrieves the balance of a specific address for a given ERC20 contract.
* @param contract - The ERC20 contract instance.
* @param params - The parameters for retrieving the balance.
* @returns An object containing the balance information.
*/
export async function balanceOf(
contract: ThirdwebContract,
options: BalanceOfParams,
params: BalanceOfParams,
) {
const [balanceWei, decimals_, symbol_] = await Promise.all([
read(
transaction(contract, {
address: contract.address,
chainId: contract.chainId,
method: "function balanceOf(address) view returns (uint256)",
params: [options.address],
params: [params.address],
}),
),
decimals(contract),
Expand Down
5 changes: 5 additions & 0 deletions packages/thirdweb/src/extensions/erc20/read/decimals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { read } from "../../../transaction/actions/read.js";

/**
* Retrieves the number of decimal places used by the ERC20 token.
* @param contract - The ERC20 contract instance.
* @returns A promise that resolves to the number of decimal places.
*/
export async function decimals(contract: ThirdwebContract) {
// TODO consider caching this
return read(
Expand Down
5 changes: 5 additions & 0 deletions packages/thirdweb/src/extensions/erc20/read/symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { read } from "../../../transaction/actions/read.js";

/**
* Retrieves the symbol of the ERC20 contract.
* @param contract - The ERC20 contract instance.
* @returns A promise that resolves to the symbol of the ERC20 contract.
*/
export async function symbol(contract: ThirdwebContract) {
// TODO consider caching this
return read(
Expand Down
7 changes: 7 additions & 0 deletions packages/thirdweb/src/extensions/erc20/write/mintTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ type MintToParams = { to: string } & (
}
);

/**
* Mints a specified amount of tokens to the given address.
*
* @param contract - The ThirdwebContract instance.
* @param options - The minting options.
* @returns A promise that resolves to the transaction result.
*/
export function mintTo(contract: ThirdwebContract, options: MintToParams) {
return transaction(contract, {
address: contract.address,
Expand Down
7 changes: 7 additions & 0 deletions packages/thirdweb/src/extensions/erc20/write/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ type TransferParams = { to: string } & (
}
);

/**
* Transfers ERC20 tokens from the contract to the specified address.
*
* @param contract - The ERC20 contract instance.
* @param options - The transfer options.
* @returns A promise that resolves to the transaction result.
*/
export function transfer(contract: ThirdwebContract, options: TransferParams) {
return transaction(contract, {
address: contract.address,
Expand Down
9 changes: 9 additions & 0 deletions packages/thirdweb/src/extensions/erc721.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// READ
export { getNFT, type GetNFTParams } from "./erc721/read/getNFT.js";
export { getNFTs, type GetNFTsParams } from "./erc721/read/getNFTs.js";
export { nextTokenIdToMint } from "./erc721/read/nextTokenIdToMint.js";
export { ownerOf, type OwnerOfParams } from "./erc721/read/ownerOf.js";
export { startTokenId } from "./erc721/read/startTokenId.js";
export { tokenURI, type TokenUriParams } from "./erc721/read/tokenURI.js";

// WRITE
42 changes: 42 additions & 0 deletions packages/thirdweb/src/extensions/erc721/read/getNFT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { ThirdwebContract } from "../../../contract/index.js";
import { tokenURI, type TokenUriParams } from "./tokenURI.js";
import { fetchTokenMetadata } from "../../../utils/nft/fetchTokenMetadata.js";
import { parseNFT } from "../../../utils/nft/parseNft.js";

/**
* Parameters for getting an NFT.
*/
export type GetNFTParams = TokenUriParams & {
/**
* Whether to include the owner of the NFT.
*/
includeOwner?: boolean;
};

/**
* Retrieves the metadata of a non-fungible token (NFT) from a contract.
* @param contract - The {@link ThirdwebContract} instance representing the ERC721 contract.
* @param params - The {@link GetNFTParams} object containing the token ID and additional options.
* @returns A promise that resolves to a {@link NFT}.
*/
export async function getNFT(contract: ThirdwebContract, params: GetNFTParams) {
const [uri, owner] = await Promise.all([
tokenURI(contract, params),
params.includeOwner
? import("./ownerOf.js").then((m) => m.ownerOf(contract, params))
: null,
]);
return parseNFT(
await fetchTokenMetadata({
client: contract,
tokenId: params.tokenId,
tokenUri: uri,
}),
{
tokenId: params.tokenId,
tokenUri: uri,
type: "ERC721",
owner,
},
);
}
60 changes: 60 additions & 0 deletions packages/thirdweb/src/extensions/erc721/read/getNFTs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { ThirdwebContract } from "../../../contract/index.js";
import { startTokenId } from "./startTokenId.js";
import { nextTokenIdToMint } from "./nextTokenIdToMint.js";
import { min } from "../../../utils/bigint.js";
import { getNFT } from "./getNFT.js";
import type { NFT } from "../../../utils/nft/parseNft.js";

const DEFAULT_QUERY_ALL_COUNT = 100n;

/**
* Parameters for retrieving NFTs.
*/
export type GetNFTsParams = {
/**
* Which tokenId to start at.
*/
start?: number;
/**
* The number of NFTs to retrieve.
*/
count?: number;
/**
* Whether to include the owner of each NFT.
*/
includeOwners?: boolean;
};

/**
* Retrieves a list of NFTs from the contract.
*
* @param contract - TThe {@link ThirdwebContract} instance representing the ERC721 contract.
* @param params - The {@link GetNFTsParams} object containing the token ID and additional options.
* @returns A promise that resolves to an array of {@link NFT}s.
*/
export async function getNFTs(
contract: ThirdwebContract,
params: GetNFTsParams,
): Promise<NFT[]> {
const [startTokenId_, maxSupply] = await Promise.all([
startTokenId(contract).catch(() => {
// this method only exists on some contracts
// if it does not exist we can safely assume the start token ID is 0
return 0n;
}),
nextTokenIdToMint(contract),
]);
const start = BigInt(params.start ?? 0) + startTokenId_;
const count = BigInt(params.count ?? DEFAULT_QUERY_ALL_COUNT);

const maxId = min(maxSupply + startTokenId_, start + count);

return await Promise.all(
Array(maxId - start).map((_, i) =>
getNFT(contract, {
tokenId: BigInt(i),
includeOwner: params.includeOwners ?? false,
}),
),
);
}
18 changes: 18 additions & 0 deletions packages/thirdweb/src/extensions/erc721/read/nextTokenIdToMint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { read } from "../../../transaction/actions/read.js";

/**
* Retrieves the next token ID to be minted from the ERC721 contract.
* @param contract - The ERC721 contract instance.
* @returns A promise that resolves to the next token ID to be minted.
*/
export async function nextTokenIdToMint(contract: ThirdwebContract) {
return read(
transaction(contract, {
address: contract.address,
chainId: contract.chainId,
method: "function nextTokenIdToMint() view returns (uint256)",
}),
);
}
26 changes: 26 additions & 0 deletions packages/thirdweb/src/extensions/erc721/read/ownerOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { read } from "../../../transaction/actions/read.js";

export type OwnerOfParams = { tokenId: bigint };

/**
* Retrieves the owner of a specific ERC721 token.
* @param contract - The {@link ThirdwebContract} representing the ERC721 contract.
* @param options - The parameters for the ownerOf function.
* @returns A Promise that resolves to the address of the token owner.
*/
export async function ownerOf(
contract: ThirdwebContract,
options: OwnerOfParams,
) {
return read(
transaction(contract, {
address: contract.address,
chainId: contract.chainId,
method:
"function ownerOf(uint256 tokenId) external view returns (address owner)",
params: [BigInt(options.tokenId)],
}),
);
}
18 changes: 18 additions & 0 deletions packages/thirdweb/src/extensions/erc721/read/startTokenId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { read } from "../../../transaction/actions/read.js";

/**
* Retrieves the start token ID of the ERC721 contract.
* @param contract - The ERC721 contract.
* @returns A promise that resolves to the start token ID.
*/
export async function startTokenId(contract: ThirdwebContract) {
return read(
transaction(contract, {
address: contract.address,
chainId: contract.chainId,
method: "function startTokenId() view returns (uint256)",
}),
);
}
26 changes: 26 additions & 0 deletions packages/thirdweb/src/extensions/erc721/read/tokenURI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { transaction } from "../../../transaction/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { read } from "../../../transaction/actions/read.js";

export type TokenUriParams = { tokenId: bigint };

/**
* Retrieves the token URI for a given token ID from the ERC721 contract.
* @param contract - The {@link ThirdwebContract} instance representing the ERC721 contract.
* @param options - The token URI parameters.
* @returns A promise that resolves to the token URI string.
*/
export async function tokenURI(
contract: ThirdwebContract,
options: TokenUriParams,
) {
return read(
transaction(contract, {
address: contract.address,
chainId: contract.chainId,
method:
"function tokenURI(uint256 tokenId) external view returns (string memory)",
params: [BigInt(options.tokenId)],
}),
);
}
19 changes: 19 additions & 0 deletions packages/thirdweb/src/utils/bigint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Returns the minimum of two BigInt values.
* @param a - The first BigInt value.
* @param b - The second BigInt value.
* @returns The smaller of the two BigInt values.
*/
export function min(a: bigint, b: bigint) {
return a < b ? a : b;
}

/**
* Returns the maximum of two BigInt values.
* @param a - The first BigInt value.
* @param b - The second BigInt value.
* @returns The larger of the two BigInt values.
*/
export function max(a: bigint, b: bigint) {
return a > b ? a : b;
}
26 changes: 26 additions & 0 deletions packages/thirdweb/src/utils/nft/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { base64ToString } from "../uint8-array.js";

export type Base64Prefix = "data:application/json;base64";
export type Base64String = `${Base64Prefix},${string}`;

/**
* Checks if a given string is a base64 encoded string.
* @param input - The string to be checked.
* @returns True if the input string starts with "data:application/json;base64", false otherwise.
*/
export function isBase64String(input: string): input is Base64String {
if (input.startsWith("data:application/json;base64")) {
return true;
}
return false;
}

/**
* Parses a base64 string and returns the decoded string.
* @param input - The base64 string to parse.
* @returns The decoded string.
*/
export function parseBase64String(input: Base64String) {
const [, base64] = input.split(",") as [Base64Prefix, string];
return base64ToString(base64);
}
Loading

0 comments on commit 6a5467f

Please sign in to comment.