thirdweb 5.48.4-nightly-3d60da3717effdc7311ef5b6054f52127b5780f8-20240830000409 → 5.50.0-nightly-6432e8dc6bdd0ed985fe0d76e47b36601bfa8be3-20240831000359
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/exports/extensions/ens.js +6 -1
- package/dist/cjs/exports/extensions/ens.js.map +1 -1
- package/dist/cjs/exports/react.js +3 -1
- package/dist/cjs/exports/react.js.map +1 -1
- package/dist/cjs/extensions/ens/__generated__/L2Resolver/read/name.js +115 -0
- package/dist/cjs/extensions/ens/__generated__/L2Resolver/read/name.js.map +1 -0
- package/dist/cjs/extensions/ens/constants.js +3 -1
- package/dist/cjs/extensions/ens/constants.js.map +1 -1
- package/dist/cjs/extensions/ens/resolve-address.js +13 -1
- package/dist/cjs/extensions/ens/resolve-address.js.map +1 -1
- package/dist/cjs/extensions/ens/resolve-l2-name.js +80 -0
- package/dist/cjs/extensions/ens/resolve-l2-name.js.map +1 -0
- package/dist/cjs/extensions/ens/resolve-name.js +1 -1
- package/dist/cjs/extensions/ens/resolve-name.js.map +1 -1
- package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +11 -2
- package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
- package/dist/cjs/react/web/ui/prebuilt/NFT/NFT.js +217 -0
- package/dist/cjs/react/web/ui/prebuilt/NFT/NFT.js.map +1 -0
- package/dist/cjs/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.js +64 -19
- package/dist/cjs/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.js.map +1 -1
- package/dist/cjs/utils/nft/parseNft.js.map +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/exports/extensions/ens.js +2 -0
- package/dist/esm/exports/extensions/ens.js.map +1 -1
- package/dist/esm/exports/react.js +1 -0
- package/dist/esm/exports/react.js.map +1 -1
- package/dist/esm/extensions/ens/__generated__/L2Resolver/read/name.js +107 -0
- package/dist/esm/extensions/ens/__generated__/L2Resolver/read/name.js.map +1 -0
- package/dist/esm/extensions/ens/constants.js +2 -0
- package/dist/esm/extensions/ens/constants.js.map +1 -1
- package/dist/esm/extensions/ens/resolve-address.js +13 -1
- package/dist/esm/extensions/ens/resolve-address.js.map +1 -1
- package/dist/esm/extensions/ens/resolve-l2-name.js +75 -0
- package/dist/esm/extensions/ens/resolve-l2-name.js.map +1 -0
- package/dist/esm/extensions/ens/resolve-name.js +1 -1
- package/dist/esm/extensions/ens/resolve-name.js.map +1 -1
- package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +11 -2
- package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
- package/dist/esm/react/web/ui/prebuilt/NFT/NFT.js +212 -0
- package/dist/esm/react/web/ui/prebuilt/NFT/NFT.js.map +1 -0
- package/dist/esm/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.js +62 -17
- package/dist/esm/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.js.map +1 -1
- package/dist/esm/utils/nft/parseNft.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/types/exports/extensions/ens.d.ts +2 -0
- package/dist/types/exports/extensions/ens.d.ts.map +1 -1
- package/dist/types/exports/react.d.ts +1 -0
- package/dist/types/exports/react.d.ts.map +1 -1
- package/dist/types/extensions/ens/__generated__/L2Resolver/read/name.d.ts +84 -0
- package/dist/types/extensions/ens/__generated__/L2Resolver/read/name.d.ts.map +1 -0
- package/dist/types/extensions/ens/constants.d.ts +2 -0
- package/dist/types/extensions/ens/constants.d.ts.map +1 -1
- package/dist/types/extensions/ens/resolve-address.d.ts +12 -0
- package/dist/types/extensions/ens/resolve-address.d.ts.map +1 -1
- package/dist/types/extensions/ens/resolve-l2-name.d.ts +48 -0
- package/dist/types/extensions/ens/resolve-l2-name.d.ts.map +1 -0
- package/dist/types/react/web/ui/prebuilt/NFT/NFT.d.ts +75 -0
- package/dist/types/react/web/ui/prebuilt/NFT/NFT.d.ts.map +1 -0
- package/dist/types/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.d.ts.map +1 -1
- package/dist/types/utils/nft/parseNft.d.ts +1 -0
- package/dist/types/utils/nft/parseNft.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/exports/extensions/ens.ts +10 -0
- package/src/exports/react.ts +5 -0
- package/src/extensions/ens/__generated__/L2Resolver/read/name.ts +122 -0
- package/src/extensions/ens/constants.ts +4 -0
- package/src/extensions/ens/resolve-address.test.ts +13 -0
- package/src/extensions/ens/resolve-address.ts +13 -1
- package/src/extensions/ens/resolve-l2-name.test.ts +30 -0
- package/src/extensions/ens/resolve-l2-name.ts +107 -0
- package/src/extensions/ens/resolve-name.ts +1 -1
- package/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx +12 -2
- package/src/react/web/ui/prebuilt/NFT/NFT.test.tsx +111 -0
- package/src/react/web/ui/prebuilt/NFT/NFT.tsx +272 -0
- package/src/react/web/ui/prebuilt/thirdweb/CreateDirectListingButton/index.tsx +75 -18
- package/src/utils/nft/parseNft.ts +1 -0
- package/src/version.ts +1 -1
@@ -0,0 +1,107 @@
|
|
1
|
+
import type { Address } from "abitype";
|
2
|
+
import { type Hex, encodePacked, keccak256, namehash } from "viem";
|
3
|
+
import type { Chain } from "../../chains/types.js";
|
4
|
+
import type { ThirdwebClient } from "../../client/client.js";
|
5
|
+
import { getContract } from "../../contract/contract.js";
|
6
|
+
import { withCache } from "../../utils/promise/withCache.js";
|
7
|
+
import { name } from "./__generated__/L2Resolver/read/name.js";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* @extension ENS
|
11
|
+
*/
|
12
|
+
export type ResolveL2NameOptions = {
|
13
|
+
client: ThirdwebClient;
|
14
|
+
address: Address;
|
15
|
+
resolverAddress: string;
|
16
|
+
resolverChain: Chain;
|
17
|
+
};
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Convert an address to a reverse node for ENS resolution
|
21
|
+
*
|
22
|
+
* @internal
|
23
|
+
*/
|
24
|
+
export const convertReverseNodeToBytes = (
|
25
|
+
address: Address,
|
26
|
+
chainId: number,
|
27
|
+
) => {
|
28
|
+
const addressFormatted = address.toLocaleLowerCase() as Address;
|
29
|
+
const addressNode = keccak256(addressFormatted.substring(2) as Hex);
|
30
|
+
const cointype = (0x80000000 | chainId) >>> 0;
|
31
|
+
|
32
|
+
const chainCoinType = cointype.toString(16).toLocaleUpperCase();
|
33
|
+
const reverseNode = namehash(`${chainCoinType.toLocaleUpperCase()}.reverse`);
|
34
|
+
|
35
|
+
const addressReverseNode = keccak256(
|
36
|
+
encodePacked(["bytes32", "bytes32"], [reverseNode, addressNode]),
|
37
|
+
);
|
38
|
+
return addressReverseNode;
|
39
|
+
};
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Resolves the L2 name for a specified address.
|
43
|
+
* @param options - The options for resolving an L2 ENS address.
|
44
|
+
* @example
|
45
|
+
* ```ts
|
46
|
+
* import { resolveL2Name } from "thirdweb/extensions/ens";
|
47
|
+
* const name = await resolveL2Name({
|
48
|
+
* client,
|
49
|
+
* address: "0x1234...",
|
50
|
+
* resolverAddress: "0x...",
|
51
|
+
* resolverChain: base,
|
52
|
+
* });
|
53
|
+
* ```
|
54
|
+
*
|
55
|
+
* Resolve a Basename.
|
56
|
+
* ```ts
|
57
|
+
* import { resolveL2Name, BASENAME_RESOLVER_ADDRESS } from "thirdweb/extensions/ens";
|
58
|
+
* import { base } from "thirdweb/chains";
|
59
|
+
* const name = await resolveL2Name({
|
60
|
+
* client,
|
61
|
+
* address: "0x1234...",
|
62
|
+
* resolverAddress: BASENAME_RESOLVER_ADDRESS,
|
63
|
+
* resolverChain: base,
|
64
|
+
* });
|
65
|
+
* ```
|
66
|
+
* @extension ENS
|
67
|
+
* @returns A promise that resolves to the Ethereum address.
|
68
|
+
*/
|
69
|
+
export async function resolveL2Name(options: ResolveL2NameOptions) {
|
70
|
+
const { client, address, resolverAddress, resolverChain } = options;
|
71
|
+
|
72
|
+
return withCache(
|
73
|
+
async () => {
|
74
|
+
const contract = getContract({
|
75
|
+
client,
|
76
|
+
chain: resolverChain,
|
77
|
+
address: resolverAddress,
|
78
|
+
});
|
79
|
+
|
80
|
+
const reverseName = convertReverseNodeToBytes(
|
81
|
+
address,
|
82
|
+
resolverChain.id || 1,
|
83
|
+
);
|
84
|
+
|
85
|
+
const resolvedName = await name({
|
86
|
+
contract,
|
87
|
+
node: reverseName,
|
88
|
+
}).catch((e) => {
|
89
|
+
if ("data" in e && e.data === "0x7199966d") {
|
90
|
+
return null;
|
91
|
+
}
|
92
|
+
throw e;
|
93
|
+
});
|
94
|
+
|
95
|
+
if (resolvedName === "") {
|
96
|
+
return null;
|
97
|
+
}
|
98
|
+
|
99
|
+
return resolvedName;
|
100
|
+
},
|
101
|
+
{
|
102
|
+
cacheKey: `ens:name:${resolverChain}:${address}`,
|
103
|
+
// 1min cache
|
104
|
+
cacheTime: 60 * 1000,
|
105
|
+
},
|
106
|
+
);
|
107
|
+
}
|
@@ -185,12 +185,22 @@ function BuyScreenContent(props: BuyScreenContentProps) {
|
|
185
185
|
return undefined;
|
186
186
|
}
|
187
187
|
|
188
|
+
const supportedSources = supportedSourcesQuery.data;
|
189
|
+
if (supportedSources[0]?.chain) {
|
190
|
+
setFromChain(supportedSources[0]?.chain);
|
191
|
+
}
|
192
|
+
|
188
193
|
return createSupportedTokens(
|
189
|
-
|
194
|
+
supportedSources,
|
190
195
|
payOptions,
|
191
196
|
props.supportedTokens,
|
192
197
|
);
|
193
|
-
}, [
|
198
|
+
}, [
|
199
|
+
props.supportedTokens,
|
200
|
+
supportedSourcesQuery.data,
|
201
|
+
payOptions,
|
202
|
+
setFromChain,
|
203
|
+
]);
|
194
204
|
|
195
205
|
const enabledPaymentMethods = useEnabledPaymentMethods({
|
196
206
|
payOptions: props.payOptions,
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import { useContext } from "react";
|
2
|
+
import { describe, expect, it } from "vitest";
|
3
|
+
import { render, screen, waitFor } from "~test/react-render.js";
|
4
|
+
import { DOODLES_CONTRACT } from "~test/test-contracts.js";
|
5
|
+
import { NFT, NFTProviderContext, getNFTInfo } from "./NFT.js";
|
6
|
+
|
7
|
+
describe.runIf(process.env.TW_SECRET_KEY)("NFT prebuilt component", () => {
|
8
|
+
it("should fetch the NFT metadata", async () => {
|
9
|
+
const nft = await getNFTInfo({
|
10
|
+
contract: DOODLES_CONTRACT,
|
11
|
+
tokenId: 1n,
|
12
|
+
});
|
13
|
+
expect(nft.metadata).toStrictEqual({
|
14
|
+
attributes: [
|
15
|
+
{
|
16
|
+
trait_type: "face",
|
17
|
+
value: "holographic beard",
|
18
|
+
},
|
19
|
+
{
|
20
|
+
trait_type: "hair",
|
21
|
+
value: "white bucket cap",
|
22
|
+
},
|
23
|
+
{
|
24
|
+
trait_type: "body",
|
25
|
+
value: "purple sweater with satchel",
|
26
|
+
},
|
27
|
+
{
|
28
|
+
trait_type: "background",
|
29
|
+
value: "grey",
|
30
|
+
},
|
31
|
+
{
|
32
|
+
trait_type: "head",
|
33
|
+
value: "gradient 2",
|
34
|
+
},
|
35
|
+
],
|
36
|
+
description:
|
37
|
+
"A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
|
38
|
+
image: "ipfs://QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
|
39
|
+
name: "Doodle #1",
|
40
|
+
});
|
41
|
+
});
|
42
|
+
|
43
|
+
it("should render children correctly", () => {
|
44
|
+
render(
|
45
|
+
<NFT contract={DOODLES_CONTRACT} tokenId={0n}>
|
46
|
+
<div>Child Component</div>
|
47
|
+
</NFT>,
|
48
|
+
);
|
49
|
+
|
50
|
+
expect(screen.getByText("Child Component")).toBeInTheDocument();
|
51
|
+
});
|
52
|
+
|
53
|
+
it("should provide context values to children", () => {
|
54
|
+
function NFTConsumer() {
|
55
|
+
const context = useContext(NFTProviderContext);
|
56
|
+
if (!context) {
|
57
|
+
return <div>No context</div>;
|
58
|
+
}
|
59
|
+
return (
|
60
|
+
<div>
|
61
|
+
Contract: {String(context.contract)}, Token ID:{" "}
|
62
|
+
{context.tokenId.toString()}
|
63
|
+
</div>
|
64
|
+
);
|
65
|
+
}
|
66
|
+
render(
|
67
|
+
<NFT contract={DOODLES_CONTRACT} tokenId={0n}>
|
68
|
+
<NFTConsumer />
|
69
|
+
</NFT>,
|
70
|
+
);
|
71
|
+
|
72
|
+
expect(screen.getByText(/Contract:/)).toBeInTheDocument();
|
73
|
+
expect(screen.getByText(/Token ID: 0/)).toBeInTheDocument();
|
74
|
+
});
|
75
|
+
|
76
|
+
it("should render the NFT image", () => {
|
77
|
+
render(
|
78
|
+
<NFT contract={DOODLES_CONTRACT} tokenId={0n}>
|
79
|
+
<NFT.Media />
|
80
|
+
</NFT>,
|
81
|
+
);
|
82
|
+
|
83
|
+
waitFor(() => expect(screen.getByRole("img")).toBeInTheDocument());
|
84
|
+
});
|
85
|
+
|
86
|
+
it("should render the NFT name", () => {
|
87
|
+
render(
|
88
|
+
<NFT contract={DOODLES_CONTRACT} tokenId={1n}>
|
89
|
+
<NFT.Name />
|
90
|
+
</NFT>,
|
91
|
+
);
|
92
|
+
|
93
|
+
waitFor(() => expect(screen.getByText("Doodle #1")).toBeInTheDocument());
|
94
|
+
});
|
95
|
+
|
96
|
+
it("should render the NFT description", () => {
|
97
|
+
render(
|
98
|
+
<NFT contract={DOODLES_CONTRACT} tokenId={1n}>
|
99
|
+
<NFT.Name />
|
100
|
+
</NFT>,
|
101
|
+
);
|
102
|
+
|
103
|
+
waitFor(() =>
|
104
|
+
expect(
|
105
|
+
screen.getByText(
|
106
|
+
"A community-driven collectibles project featuring art by Burnt Toast",
|
107
|
+
),
|
108
|
+
).toBeInTheDocument(),
|
109
|
+
);
|
110
|
+
});
|
111
|
+
});
|
@@ -0,0 +1,272 @@
|
|
1
|
+
"use client";
|
2
|
+
|
3
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
4
|
+
import { createContext, useContext } from "react";
|
5
|
+
import type { ThirdwebContract } from "../../../../../contract/contract.js";
|
6
|
+
import { getNFT as getNFT721 } from "../../../../../extensions/erc721/read/getNFT.js";
|
7
|
+
import { getNFT as getNFT1155 } from "../../../../../extensions/erc1155/read/getNFT.js";
|
8
|
+
import type { NFT as NFTType } from "../../../../../utils/nft/parseNft.js";
|
9
|
+
import { MediaRenderer } from "../../MediaRenderer/MediaRenderer.js";
|
10
|
+
import type { MediaRendererProps } from "../../MediaRenderer/types.js";
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Props for the <NFT> component
|
14
|
+
* @component
|
15
|
+
*/
|
16
|
+
export type NFTProviderProps = {
|
17
|
+
/**
|
18
|
+
* The NFT contract address. Accepts both ERC721 and ERC1155 contracts
|
19
|
+
*/
|
20
|
+
contract: ThirdwebContract;
|
21
|
+
/**
|
22
|
+
* The tokenId whose info you want to display
|
23
|
+
*/
|
24
|
+
tokenId: bigint;
|
25
|
+
};
|
26
|
+
|
27
|
+
/**
|
28
|
+
* @internal
|
29
|
+
*/
|
30
|
+
export const NFTProviderContext = /* @__PURE__ */ createContext<
|
31
|
+
NFTProviderProps | undefined
|
32
|
+
>(undefined);
|
33
|
+
|
34
|
+
/**
|
35
|
+
* A React context provider component that supplies NFT-related data to its child components.
|
36
|
+
*
|
37
|
+
* This component serves as a wrapper around the `NFTProviderContext.Provider` and passes
|
38
|
+
* the provided NFT data down to all of its child components through the context API.
|
39
|
+
*
|
40
|
+
*
|
41
|
+
* @component
|
42
|
+
* @param {React.PropsWithChildren<NFTProviderProps>} props - The props for the NFT provider
|
43
|
+
*
|
44
|
+
* @example
|
45
|
+
* ```tsx
|
46
|
+
* import { getContract } from "thirdweb";
|
47
|
+
* import { NFT } from "thirdweb/react";
|
48
|
+
*
|
49
|
+
* const contract = getContract({
|
50
|
+
* address: "0x...",
|
51
|
+
* chain: ethereum,
|
52
|
+
* client: yourThirdwebClient,
|
53
|
+
* });
|
54
|
+
*
|
55
|
+
* <NFT contract={contract} tokenId={0n}>
|
56
|
+
* <Suspense fallback={"Loading media..."}>
|
57
|
+
* <NFT.Media />
|
58
|
+
* <NFT.Description />
|
59
|
+
* </Suspense>
|
60
|
+
* </NFT>
|
61
|
+
* ```
|
62
|
+
*/
|
63
|
+
export function NFT(props: React.PropsWithChildren<NFTProviderProps>) {
|
64
|
+
return (
|
65
|
+
<NFTProviderContext.Provider value={props}>
|
66
|
+
{props.children}
|
67
|
+
</NFTProviderContext.Provider>
|
68
|
+
);
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* @internal
|
73
|
+
*/
|
74
|
+
function useNFTContext() {
|
75
|
+
const ctx = useContext(NFTProviderContext);
|
76
|
+
if (!ctx) {
|
77
|
+
throw new Error(
|
78
|
+
"NFTProviderContext not found. Make sure you are using NFT.Media, NFT.Description, etc. inside a <NFT /> component",
|
79
|
+
);
|
80
|
+
}
|
81
|
+
return ctx;
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* @component
|
86
|
+
* The props for the <NFT.Media /> component
|
87
|
+
* It is similar to the [`MediaRendererProps`](https://portal.thirdweb.com/references/typescript/v5/MediaRendererProps)
|
88
|
+
* (excluding `src`, `poster` and `client`) that you can
|
89
|
+
* use to style the NFT.Media
|
90
|
+
*/
|
91
|
+
export type NFTMediaProps = Omit<
|
92
|
+
MediaRendererProps,
|
93
|
+
"src" | "poster" | "client"
|
94
|
+
>;
|
95
|
+
|
96
|
+
/**
|
97
|
+
* This component fetches and displays an NFT's media. It uses thirdweb [`MediaRenderer`](https://portal.thirdweb.com/react/v5/components/MediaRenderer) under the hood
|
98
|
+
* so you can style it just like how you would style a MediaRenderer.
|
99
|
+
* @returns A MediaRenderer component
|
100
|
+
*
|
101
|
+
* Since this component has an internal loading state (for when the NFT media is being fetched),
|
102
|
+
* you must wrap it with React.Suspense to make it work.
|
103
|
+
*
|
104
|
+
* @component
|
105
|
+
* @example
|
106
|
+
* ### Basic usage
|
107
|
+
* ```tsx
|
108
|
+
* import { getContract } from "thirdweb";
|
109
|
+
* import { NFT } from "thirdweb/react";
|
110
|
+
*
|
111
|
+
* const nftContract = getContract({
|
112
|
+
* address: "0x...",
|
113
|
+
* chain: ethereum,
|
114
|
+
* client: yourThirdwebClient,
|
115
|
+
* });
|
116
|
+
*
|
117
|
+
* <NFT contract={nftContract} tokenId={0n}>
|
118
|
+
* This will show the media for tokenId #0 from the `nftContract` collection
|
119
|
+
* <Suspense fallback={"Loading media..."}>
|
120
|
+
* <NFT.Media />
|
121
|
+
* </Suspense>
|
122
|
+
* </NFT>
|
123
|
+
* ```
|
124
|
+
*
|
125
|
+
* ### Basic stylings
|
126
|
+
* You can style NFT.Media with the `style` and `className` props.
|
127
|
+
* ```tsx
|
128
|
+
* <NFT.Media style={{ borderRadius: "8px" }} className="mx-auto" />
|
129
|
+
* ```
|
130
|
+
*/
|
131
|
+
NFT.Media = (props: NFTMediaProps) => {
|
132
|
+
const { contract, tokenId } = useNFTContext();
|
133
|
+
const nftQuery = useSuspenseQuery({
|
134
|
+
queryKey: [
|
135
|
+
"__nft_info_internal__",
|
136
|
+
contract.chain.id,
|
137
|
+
contract.address,
|
138
|
+
tokenId.toString(),
|
139
|
+
],
|
140
|
+
queryFn: () => getNFTInfo({ contract, tokenId }),
|
141
|
+
});
|
142
|
+
const animation_url = nftQuery.data?.metadata.animation_url;
|
143
|
+
const image =
|
144
|
+
nftQuery.data?.metadata.image || nftQuery.data?.metadata.image_url;
|
145
|
+
|
146
|
+
return (
|
147
|
+
<MediaRenderer
|
148
|
+
client={contract.client}
|
149
|
+
src={animation_url || image}
|
150
|
+
poster={image}
|
151
|
+
{...props}
|
152
|
+
/>
|
153
|
+
);
|
154
|
+
};
|
155
|
+
|
156
|
+
/**
|
157
|
+
* This component fetches and displays an NFT's name. It takes in a `className` and `style` props
|
158
|
+
* so you can style it just like how you would style a <span> element.
|
159
|
+
* @returns A <span> element containing the name of the NFT
|
160
|
+
*
|
161
|
+
* Since this component has an internal loading state (for when the NFT media is being fetched),
|
162
|
+
* you must wrap it with React.Suspense to make it work.
|
163
|
+
*
|
164
|
+
* @component
|
165
|
+
* @example
|
166
|
+
* ### Basic usage
|
167
|
+
* ```tsx
|
168
|
+
* import { getContract } from "thirdweb";
|
169
|
+
* import { NFT } from "thirdweb/react";
|
170
|
+
*
|
171
|
+
* const nftContract = getContract({
|
172
|
+
* address: "0x...",
|
173
|
+
* chain: ethereum,
|
174
|
+
* client: yourThirdwebClient,
|
175
|
+
* });
|
176
|
+
*
|
177
|
+
* <NFT contract={nftContract} tokenId={0n}>
|
178
|
+
* This will show the name for tokenId #0 from the `nftContract` collection
|
179
|
+
* <Suspense fallback={"Loading nft name..."}>
|
180
|
+
* <NFT.Name className="mx-auto" style={{ color: "red" }} />
|
181
|
+
* </Suspense>
|
182
|
+
* </NFT>
|
183
|
+
* ```
|
184
|
+
*/
|
185
|
+
NFT.Name = (props: { className?: string; style?: React.CSSProperties }) => {
|
186
|
+
const { contract, tokenId } = useNFTContext();
|
187
|
+
const nftQuery = useSuspenseQuery({
|
188
|
+
queryKey: [
|
189
|
+
"__nft_info_internal__",
|
190
|
+
contract.chain.id,
|
191
|
+
contract.address,
|
192
|
+
tokenId.toString(),
|
193
|
+
],
|
194
|
+
queryFn: () => getNFTInfo({ contract, tokenId }),
|
195
|
+
});
|
196
|
+
const name = nftQuery.data?.metadata.name || "";
|
197
|
+
return <span {...props}>{name}</span>;
|
198
|
+
};
|
199
|
+
|
200
|
+
/**
|
201
|
+
* This component fetches and displays an NFT's description. It takes in a `className` and `style` props
|
202
|
+
* so you can style it just like how you would style a <span> element.
|
203
|
+
* @returns A <span> element containing the description of the NFT
|
204
|
+
*
|
205
|
+
* Since this component has an internal loading state (for when the NFT media is being fetched),
|
206
|
+
* you must wrap it with React.Suspense to make it work.
|
207
|
+
*
|
208
|
+
* @component
|
209
|
+
* @example
|
210
|
+
* ### Basic usage
|
211
|
+
* ```tsx
|
212
|
+
* import { getContract } from "thirdweb";
|
213
|
+
* import { NFT } from "thirdweb/react";
|
214
|
+
*
|
215
|
+
* const nftContract = getContract({
|
216
|
+
* address: "0x...",
|
217
|
+
* chain: ethereum,
|
218
|
+
* client: yourThirdwebClient,
|
219
|
+
* });
|
220
|
+
*
|
221
|
+
* <NFT contract={nftContract} tokenId={0n}>
|
222
|
+
* This will show the description for tokenId #0 from the `nftContract` collection
|
223
|
+
* <Suspense fallback={"Loading description..."}>
|
224
|
+
* <NFT.Description className="mx-auto" style={{ color: "red" }} />
|
225
|
+
* </Suspense>
|
226
|
+
* </NFT>
|
227
|
+
* ```
|
228
|
+
*/
|
229
|
+
NFT.Description = (props: {
|
230
|
+
className: string;
|
231
|
+
style: React.CSSProperties;
|
232
|
+
}) => {
|
233
|
+
const { contract, tokenId } = useNFTContext();
|
234
|
+
const nftQuery = useSuspenseQuery({
|
235
|
+
queryKey: [
|
236
|
+
"__nft_info_internal__",
|
237
|
+
contract.chain.id,
|
238
|
+
contract.address,
|
239
|
+
tokenId.toString(),
|
240
|
+
],
|
241
|
+
queryFn: () => getNFTInfo({ contract, tokenId }),
|
242
|
+
});
|
243
|
+
const description = nftQuery.data?.metadata.description || "";
|
244
|
+
return <span {...props}>{description}</span>;
|
245
|
+
};
|
246
|
+
|
247
|
+
/**
|
248
|
+
* @internal
|
249
|
+
*/
|
250
|
+
export async function getNFTInfo(options: NFTProviderProps): Promise<NFTType> {
|
251
|
+
const nft = await Promise.allSettled([
|
252
|
+
getNFT721(options),
|
253
|
+
getNFT1155(options),
|
254
|
+
]).then(([possibleNFT721, possibleNFT1155]) => {
|
255
|
+
// getNFT extension always return an NFT object
|
256
|
+
// so we need to check if the tokenURI exists
|
257
|
+
if (
|
258
|
+
possibleNFT721.status === "fulfilled" &&
|
259
|
+
possibleNFT721.value.tokenURI
|
260
|
+
) {
|
261
|
+
return possibleNFT721.value;
|
262
|
+
}
|
263
|
+
if (
|
264
|
+
possibleNFT1155.status === "fulfilled" &&
|
265
|
+
possibleNFT1155.value.tokenURI
|
266
|
+
) {
|
267
|
+
return possibleNFT1155.value;
|
268
|
+
}
|
269
|
+
throw new Error("Failed to load NFT metadata");
|
270
|
+
});
|
271
|
+
return nft;
|
272
|
+
}
|
@@ -4,7 +4,8 @@ import { useCallback } from "react";
|
|
4
4
|
import type { Chain } from "../../../../../../chains/types.js";
|
5
5
|
import type { ThirdwebClient } from "../../../../../../client/client.js";
|
6
6
|
import { getContract } from "../../../../../../contract/contract.js";
|
7
|
-
import {
|
7
|
+
import { isERC721 } from "../../../../../../extensions/erc721/read/isERC721.js";
|
8
|
+
import { isERC1155 } from "../../../../../../extensions/erc1155/read/isERC1155.js";
|
8
9
|
import {
|
9
10
|
type CreateListingParams,
|
10
11
|
createListing,
|
@@ -74,12 +75,13 @@ export function CreateDirectListingButton(
|
|
74
75
|
});
|
75
76
|
const account = useActiveAccount();
|
76
77
|
const defaultPayModalMetadata = payModal ? payModal.metadata : undefined;
|
78
|
+
const nftContract = getContract({
|
79
|
+
address: assetContractAddress,
|
80
|
+
chain,
|
81
|
+
client,
|
82
|
+
});
|
77
83
|
const { data: payMetadata } = useReadContract(getPayMetadata, {
|
78
|
-
contract:
|
79
|
-
address: assetContractAddress,
|
80
|
-
chain,
|
81
|
-
client,
|
82
|
-
}),
|
84
|
+
contract: nftContract,
|
83
85
|
tokenId,
|
84
86
|
queryOptions: {
|
85
87
|
enabled: !defaultPayModalMetadata,
|
@@ -91,19 +93,78 @@ export function CreateDirectListingButton(
|
|
91
93
|
if (!account) {
|
92
94
|
throw new Error("No account detected");
|
93
95
|
}
|
96
|
+
const [is721, is1155] = await Promise.all([
|
97
|
+
isERC721({ contract: nftContract }),
|
98
|
+
isERC1155({ contract: nftContract }),
|
99
|
+
]);
|
100
|
+
if (!is1155 && !is721) {
|
101
|
+
throw new Error("Asset must either be ERC721 or ERC1155");
|
102
|
+
}
|
103
|
+
// Check for token approval
|
104
|
+
if (is1155) {
|
105
|
+
const [{ isApprovedForAll }, { setApprovalForAll }] = await Promise.all([
|
106
|
+
import(
|
107
|
+
"../../../../../../extensions/erc1155/__generated__/IERC1155/read/isApprovedForAll.js"
|
108
|
+
),
|
109
|
+
import(
|
110
|
+
"../../../../../../extensions/erc1155/__generated__/IERC1155/write/setApprovalForAll.js"
|
111
|
+
),
|
112
|
+
]);
|
113
|
+
const isApproved = await isApprovedForAll({
|
114
|
+
contract: nftContract,
|
115
|
+
operator: marketplaceContract.address,
|
116
|
+
owner: account.address,
|
117
|
+
});
|
118
|
+
if (!isApproved) {
|
119
|
+
const transaction = setApprovalForAll({
|
120
|
+
contract: nftContract,
|
121
|
+
operator: marketplaceContract.address,
|
122
|
+
approved: true,
|
123
|
+
});
|
124
|
+
await mutateAsync(transaction);
|
125
|
+
}
|
126
|
+
} else {
|
127
|
+
const [{ isApprovedForAll }, { setApprovalForAll }, { getApproved }] =
|
128
|
+
await Promise.all([
|
129
|
+
import(
|
130
|
+
"../../../../../../extensions/erc721/__generated__/IERC721A/read/isApprovedForAll.js"
|
131
|
+
),
|
132
|
+
import(
|
133
|
+
"../../../../../../extensions/erc721/__generated__/IERC721A/write/setApprovalForAll.js"
|
134
|
+
),
|
135
|
+
import(
|
136
|
+
"../../../../../../extensions/erc721/__generated__/IERC721A/read/getApproved.js"
|
137
|
+
),
|
138
|
+
]);
|
139
|
+
const [isApproved, tokenApproved] = await Promise.all([
|
140
|
+
isApprovedForAll({
|
141
|
+
contract: nftContract,
|
142
|
+
operator: marketplaceContract.address,
|
143
|
+
owner: account.address,
|
144
|
+
}),
|
145
|
+
getApproved({ contract: nftContract, tokenId: props.tokenId }),
|
146
|
+
]);
|
147
|
+
|
148
|
+
if (
|
149
|
+
!isApproved &&
|
150
|
+
tokenApproved.toLowerCase() !==
|
151
|
+
marketplaceContract.address.toLowerCase()
|
152
|
+
) {
|
153
|
+
const transaction = setApprovalForAll({
|
154
|
+
contract: nftContract,
|
155
|
+
operator: marketplaceContract.address,
|
156
|
+
approved: true,
|
157
|
+
});
|
158
|
+
await mutateAsync(transaction);
|
159
|
+
}
|
160
|
+
}
|
94
161
|
const listingTx = createListing({
|
95
162
|
contract: marketplaceContract,
|
96
163
|
...props,
|
97
164
|
});
|
98
|
-
|
99
|
-
transaction: listingTx,
|
100
|
-
account,
|
101
|
-
});
|
102
|
-
if (approveTx) {
|
103
|
-
await mutateAsync(approveTx);
|
104
|
-
}
|
165
|
+
|
105
166
|
return listingTx;
|
106
|
-
}, [marketplaceContract, props, account, mutateAsync]);
|
167
|
+
}, [marketplaceContract, props, account, mutateAsync, nftContract]);
|
107
168
|
|
108
169
|
return (
|
109
170
|
<TransactionButton
|
@@ -128,14 +189,10 @@ async function getPayMetadata(
|
|
128
189
|
}>,
|
129
190
|
): Promise<{ name?: string; image?: string }> {
|
130
191
|
const [
|
131
|
-
{ isERC721 },
|
132
|
-
{ isERC1155 },
|
133
192
|
{ getContractMetadata },
|
134
193
|
{ getNFT: getERC721 },
|
135
194
|
{ getNFT: getERC1155 },
|
136
195
|
] = await Promise.all([
|
137
|
-
import("../../../../../../extensions/erc721/read/isERC721.js"),
|
138
|
-
import("../../../../../../extensions/erc1155/read/isERC1155.js"),
|
139
196
|
import("../../../../../../extensions/common/read/getContractMetadata.js"),
|
140
197
|
import("../../../../../../extensions/erc721/read/getNFT.js"),
|
141
198
|
import("../../../../../../extensions/erc1155/read/getNFT.js"),
|
package/src/version.ts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export const version = "5.
|
1
|
+
export const version = "5.50.0-nightly-6432e8dc6bdd0ed985fe0d76e47b36601bfa8be3-20240831000359";
|