diff --git a/apps/playground-web/public/account-abstraction.png b/apps/playground-web/public/account-abstraction.png new file mode 100644 index 00000000000..de478e56046 Binary files /dev/null and b/apps/playground-web/public/account-abstraction.png differ diff --git a/apps/playground-web/public/connectors.png b/apps/playground-web/public/connectors.png new file mode 100644 index 00000000000..eddaac14583 Binary files /dev/null and b/apps/playground-web/public/connectors.png differ diff --git a/apps/playground-web/public/in-app-wallet.png b/apps/playground-web/public/in-app-wallet.png new file mode 100644 index 00000000000..cc8933ebb9c Binary files /dev/null and b/apps/playground-web/public/in-app-wallet.png differ diff --git a/apps/playground-web/public/pay.png b/apps/playground-web/public/pay.png new file mode 100644 index 00000000000..e0e47f9517c Binary files /dev/null and b/apps/playground-web/public/pay.png differ diff --git a/apps/playground-web/src/app/connect/account-abstraction/page.tsx b/apps/playground-web/src/app/connect/account-abstraction/page.tsx index bfdd3ec3d0a..28a85034199 100644 --- a/apps/playground-web/src/app/connect/account-abstraction/page.tsx +++ b/apps/playground-web/src/app/connect/account-abstraction/page.tsx @@ -1,11 +1,14 @@ -import { CodeExample } from "@/components/code/code-example"; -import { StyledConnectButton } from "@/components/styled-connect-button"; import { Button } from "@/components/ui/button"; import { metadataBase } from "@/lib/constants"; import type { Metadata } from "next"; +import Image from "next/image"; import Link from "next/link"; -import { chain } from "../../../components/account-abstraction/constants"; -import { SponsoredTx } from "../../../components/account-abstraction/sponsored-tx"; +import { + ConnectSmartAccountCustomPreview, + ConnectSmartAccountPreview, +} from "../../../components/account-abstraction/connect-smart-account"; +import { SponsoredTxPreview } from "../../../components/account-abstraction/sponsored-tx"; +import { CodeExample } from "../../../components/code/code-example"; export const metadata: Metadata = { metadataBase, @@ -16,13 +19,13 @@ export const metadata: Metadata = { export default function Page() { return ( -
+
-

- Account abstraction made easy +

+ Account abstraction

Let users connect to their smart accounts with any wallet and @@ -46,52 +49,110 @@ export default function Page() {

-
-
- +
+
- -

- 👆 Connect to your smart account 👆 -

-
-

- Sponsored Transactions -

-

- One simple flag to enable transactions for your users. -
- Free on testnets, billed at the end of the month on mainnets. -

-
+ +
+ +
+ +
+
+ ); +} + +function ConnectSmartAccount() { + return ( + <> +
+

+ Connect smart accounts +

+

+ Enable smart accounts on the UI components or build your own UI. +

+
+ } + code={`// Using UI components + import { ConnectButton } from "thirdweb/react"; - } - code={`import { claimTo } from "thirdweb/extensions/erc1155"; - import { ConnectButton, TransactionButton } from "thirdweb/react"; + function App(){ + return (<> + +); +};`} + lang="tsx" + /> + } + code={`// Using your own UI + import { useConnect } from "thirdweb/react"; + import { createWallet } from "thirdweb/wallets"; - function App(){ - return (<> -{/* converts any wallet to a smart account */} - + function App(){ + const { connect } = useConnect({ client, + // account abstraction options + accountAbstraction: { chain, sponsorGas: true }}); -{/* all transactions will be sponsored */} + return (<> + +); +};`} + lang="tsx" + /> + + ); +} + +function SponsoredTx() { + return ( + <> +
+

+ Sponsored transactions +

+

+ Set `sponsorGas: true` to enable gas-free transactions for your users. +
+ Free on testnets, billed at the end of the month on mainnets. +

+
+ } + code={`import { claimTo } from "thirdweb/extensions/erc1155"; + import { TransactionButton } from "thirdweb/react"; + + function App(){ + return (<> +{/* transactions will be sponsored */} claimTo({ contract, to: "0x123...", tokenId: 0n, quantity: 1n })}>Mint ); };`} - lang="tsx" - /> - -
+ lang="tsx" + /> + ); } diff --git a/apps/playground-web/src/app/connect/in-app-wallet/page.tsx b/apps/playground-web/src/app/connect/in-app-wallet/page.tsx new file mode 100644 index 00000000000..ae3d6db8f29 --- /dev/null +++ b/apps/playground-web/src/app/connect/in-app-wallet/page.tsx @@ -0,0 +1,156 @@ +import { CodeExample } from "@/components/code/code-example"; +import { Button } from "@/components/ui/button"; +import { metadataBase } from "@/lib/constants"; +import type { Metadata } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { SponsoredInAppTxPreview } from "../../../components/in-app-wallet/sponsored-tx"; +import { StyledConnectEmbed } from "../../../components/styled-connect-embed"; + +export const metadata: Metadata = { + metadataBase, + title: "Sign In, Account Abstraction and SIWE Auth | thirdweb Connect", + description: + "Let users sign up with their email, phone number, social media accounts or directly with a wallet. Seemlessly integrate account abstraction and SIWE auth.", +}; + +export default function Page() { + return ( +
+
+
+
+
+

+ Onboard users to web3 +

+

+ Onboard anyone with flexible auth options, secure account + recovery, and smart account integration. +

+
+
+ + +
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+ +
+
+ ); +} + +function AnyAuth() { + return ( + <> +
+

+ Any Auth Method +

+

+ Use any of the built-in auth methods or bring your own. +
+ Supports custom auth endpoints to integrate with your existing user + base. +

+
+ + } + code={`import { inAppWallet } from "thirdweb/wallets"; + import { ConnectEmbed } from "thirdweb/react"; + + + const wallets = [ + inAppWallet( + // built-in auth methods + { auth: { + options: ["email", "phone", "passkey", "google", "apple", "facebook"] + } + } + // or bring your own auth endpoint + ) + ]; + + function App(){ + return ( +); +};`} + lang="tsx" + /> + + ); +} + +function SponsoredInAppTx() { + return ( + <> +
+

+ Signless Sponsored Transactions +

+

+ With in-app wallets, users don't need to confirm every + transaction. +
+ Combine it with smart account flag to cover gas costs for the best UX. +

+
+ } + code={`import { inAppWallet } from "thirdweb/wallets"; + import { claimTo } from "thirdweb/extensions/erc1155"; + import { ConnectButton, TransactionButton } from "thirdweb/react"; + + + const wallets = [ + inAppWallet( + // turn on gas sponsorship for in-app wallets + { smartAccount: { chain, sponsorGas: true }} + ) + ]; + + function App(){ + return (<> + + +{/* signless, sponsored transactions */} + claimTo({ contract, to: "0x123...", tokenId: 0n, quantity: 1n })}>Mint +); +};`} + lang="tsx" + /> + + ); +} diff --git a/apps/playground-web/src/app/connect/pay/page.tsx b/apps/playground-web/src/app/connect/pay/page.tsx index 5bb922a30c1..f35a0abaa38 100644 --- a/apps/playground-web/src/app/connect/pay/page.tsx +++ b/apps/playground-web/src/app/connect/pay/page.tsx @@ -1,10 +1,11 @@ -import { StyledPayEmbed } from "@/components/styled-pay-embed"; import { Button } from "@/components/ui/button"; import { metadataBase } from "@/lib/constants"; import type { Metadata } from "next"; +import Image from "next/image"; import Link from "next/link"; import { CodeExample } from "../../../components/code/code-example"; -import { StyledPayTransaction } from "../../../components/styled-pay-transaction"; +import { StyledPayEmbedPreview } from "../../../components/pay/embed"; +import { PayTransactionButtonPreview } from "../../../components/pay/transaction-button"; export const metadata: Metadata = { metadataBase, @@ -15,88 +16,133 @@ export const metadata: Metadata = { export default function Page() { return ( -
-
-
-
-
-
-

- The easiest way for users to transact in your app -

-

- Onramp users in clicks with credit card & cross-chain - crypto payments — and generate revenue for each user - transaction. Integrate for free. -

-
-
- - -
-
-
-
- -
-

- 👆 This is live, try it out! 👆 +

+
+
+
+
+

+ The easiest way for users to transact in your app +

+

+ Onramp users with credit card & cross-chain crypto payments + — and generate revenue for each user transaction.

+
+ + +
+
+
+
+ +
-
-

- Transaction Button -

-

- Transaction Button is a handy component that handles transactions. -
- If your user doesn't have enough funds for that transaction, a - pre-filled pay modal will appear with the exact amount needed. -

-
+ +
+
+ +
+
+ ); +} - } - code={`import { TransactionButton } from "thirdweb/react"; +function StyledPayEmbed() { + return ( + <> +
+

+ Embed component +

+

+ Inline component that allows users to buy any currency. +
+ Customize theme, currency, amounts, payment methods and more. +

+
+ } + code={` + import { PayEmbed } from "thirdweb/react"; -function App() { - const account = useActiveAccount(); + function App() { + return ( + + ); + };`} + lang="tsx" + /> + + ); +} +function PayTransactionButton() { return ( - { - // any transaction works - return claimTo({ - contract, - quantity: 1n, - tokenId: 0n, - to: account?.address, - }); - }} - > - Buy for 10 MATIC - - ); -};`} - lang="tsx" - /> -
-
+ <> +
+

+ Transaction Button +

+

+ Transaction Button is a handy component that handles transactions. +
+ If your user doesn't have enough funds for that transaction, a + pre-filled pay modal will appear with the exact amount needed. +

+
+ + } + code={`import { claimTo } from "thirdweb/extensions/erc1155"; + import { TransactionButton, useActiveAccount } from "thirdweb/react"; + + + function App() { + const account = useActiveAccount(); + + return ( + { + // any transaction works + return claimTo({ + contract, + quantity: 1n, + tokenId: 0n, + to: account.address, + }); + }} + > + Buy for 10 MATIC + + ); + };`} + lang="tsx" + /> + ); } diff --git a/apps/playground-web/src/app/connect/sign-in/page.tsx b/apps/playground-web/src/app/connect/sign-in/page.tsx index a48d36e4164..7eed4bb5f28 100644 --- a/apps/playground-web/src/app/connect/sign-in/page.tsx +++ b/apps/playground-web/src/app/connect/sign-in/page.tsx @@ -1,10 +1,12 @@ -import { CodeExample } from "@/components/code/code-example"; -import { StyledConnectButton } from "@/components/styled-connect-button"; -import { StyledConnectEmbed } from "@/components/styled-connect-embed"; import { Button } from "@/components/ui/button"; import { metadataBase } from "@/lib/constants"; import type { Metadata } from "next"; +import Image from "next/image"; import Link from "next/link"; +import { CodeExample } from "../../../components/code/code-example"; +import { HooksPreview } from "../../../components/sign-in/hooks"; +import { StyledConnectButton } from "../../../components/styled-connect-button"; +import { StyledConnectEmbed } from "../../../components/styled-connect-embed"; export const metadata: Metadata = { metadataBase, @@ -15,18 +17,18 @@ export const metadata: Metadata = { export default function Page() { return ( -
+
-

- The easiest way for users to sign in to your app +

+ Sign in

- Let users sign in with their email, phone number, social media - accounts or directly with a wallet. Seemlessly integrate account - abstraction and SIWE auth. + Create a login experience tailor-made for your app. Add your + wallets of choice, enable web2 sign-in options and create a + modal that fits your brand.

@@ -45,78 +47,139 @@ export default function Page() {
-
-
- +
+
+
- -

- 👆 This is live, try it out! 👆 -

-
-

- Connect Button -

-

- When clicked opens a modal and allows users to connect to various - wallets. -
- It is extremely customizable and very easy to use. -

-
+ +
- } - code={`import { createThirdwebClient } from "thirdweb"; +
+ +
+ +
+ +
+
+ ); +} + +function ButtonComponent() { + return ( + <> +
+

+ Button Component +

+

+ When clicked opens a modal and allows users to connect to various + wallets. +
+ Extremely customizable and easy to use. +

+
+ + } + code={`import { createThirdwebClient } from "thirdweb"; import { ConnectButton } from "thirdweb/react"; const THIRDWEB_CLIENT = createThirdwebClient({ - clientId: "" +clientId: "" }); function App(){ - return ( - - ); +return ( + +); };`} - lang="tsx" - /> - + lang="tsx" + /> + + ); +} -
-
-

- Connect Embed -

-

- When clicked opens a modal and allows users to connect to various - wallets. -
- It is extremely customizable and very easy to use. -

-
- } - code={`import { createThirdwebClient } from "thirdweb"; -import { ConnectEmbed } from "thirdweb/react"; +function EmbedComponent() { + return ( + <> +
+

+ Embed Component +

+

+ Inline component to connect to various wallets. +
+ Use this to create your own full screen login page. +

+
+ + } + code={`import { createThirdwebClient } from "thirdweb"; +import { ConnectButton } from "thirdweb/react"; const THIRDWEB_CLIENT = createThirdwebClient({ - clientId: "" +clientId: "" }); function App(){ - return ( - - ); +return ( + +); };`} - lang="tsx" - /> -
-
+ lang="tsx" + /> + + ); +} + +function Hooks() { + return ( + <> +
+

+ Custom UI +

+

+ Build your own connect UI using react hooks. +
+ Wallet state management is all handled for you. +

+
+ + } + code={`// Using your own UI + import { useConnect } from "thirdweb/react"; + import { createWallet } from "thirdweb/wallets"; + + function App(){ + const { connect } = useConnect(); + + return (<> + + ); + };`} + lang="tsx" + /> + ); } diff --git a/apps/playground-web/src/app/features/Header.tsx b/apps/playground-web/src/app/features/Header.tsx index 8f96e55365e..1713e7de18b 100644 --- a/apps/playground-web/src/app/features/Header.tsx +++ b/apps/playground-web/src/app/features/Header.tsx @@ -23,7 +23,7 @@ export function Header() {
- + {/* */} + + ) : ( + + )} +
+ ); +} diff --git a/apps/playground-web/src/components/account-abstraction/constants.ts b/apps/playground-web/src/components/account-abstraction/constants.ts index 8e8ae58d160..b867512a9fc 100644 --- a/apps/playground-web/src/components/account-abstraction/constants.ts +++ b/apps/playground-web/src/components/account-abstraction/constants.ts @@ -3,7 +3,6 @@ import { zkSyncSepolia } from "thirdweb/chains"; import { THIRDWEB_CLIENT } from "../../lib/client"; export const chain = zkSyncSepolia; -export const tokenDropAddress = "0xd64A548A82c190083707CBEFD26958E5e6551D18"; export const editionDropAddress = "0xd563ACBeD80e63B257B2524562BdD7Ec666eEE77"; export const editionDropTokenId = 0n; @@ -12,9 +11,3 @@ export const editionDropContract = getContract({ chain, client: THIRDWEB_CLIENT, }); - -export const tokenDropContract = getContract({ - address: tokenDropAddress, - chain, - client: THIRDWEB_CLIENT, -}); diff --git a/apps/playground-web/src/components/account-abstraction/sponsored-tx.tsx b/apps/playground-web/src/components/account-abstraction/sponsored-tx.tsx index 0490dc1cea1..be3b82e8744 100644 --- a/apps/playground-web/src/components/account-abstraction/sponsored-tx.tsx +++ b/apps/playground-web/src/components/account-abstraction/sponsored-tx.tsx @@ -12,9 +12,10 @@ import { useReadContract, } from "thirdweb/react"; import { THIRDWEB_CLIENT } from "../../lib/client"; +import { CodeExample } from "../code/code-example"; import { editionDropContract, editionDropTokenId } from "./constants"; -export function SponsoredTx() { +export function SponsoredTxPreview() { const wallet = useActiveWallet(); const { disconnect } = useDisconnect(); useEffect(() => { diff --git a/apps/playground-web/src/components/code/code.tsx b/apps/playground-web/src/components/code/code.tsx index 75fb5f58c92..9b4c3d42ed5 100644 --- a/apps/playground-web/src/components/code/code.tsx +++ b/apps/playground-web/src/components/code/code.tsx @@ -1,8 +1,3 @@ -// enforces this only gets rendered on the server -import "server-only"; - -import { transformerTwoslash } from "@shikijs/twoslash"; -import "@shikijs/twoslash/style-rich.css"; import * as parserBabel from "prettier/plugins/babel"; import * as estree from "prettier/plugins/estree"; import { format } from "prettier/standalone"; diff --git a/apps/playground-web/src/components/in-app-wallet/connect-button.tsx b/apps/playground-web/src/components/in-app-wallet/connect-button.tsx new file mode 100644 index 00000000000..09d82cdcd75 --- /dev/null +++ b/apps/playground-web/src/components/in-app-wallet/connect-button.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { THIRDWEB_CLIENT } from "@/lib/client"; +import { useTheme } from "next-themes"; +import { ConnectButton } from "thirdweb/react"; +import type { ConnectButtonProps } from "thirdweb/react"; +import { inAppWallet } from "thirdweb/wallets/in-app"; +import { chain } from "./constants"; + +export function InAppConnectButton( + props?: Omit, +) { + const { theme } = useTheme(); + + return ( + + ); +} diff --git a/apps/playground-web/src/components/in-app-wallet/constants.ts b/apps/playground-web/src/components/in-app-wallet/constants.ts new file mode 100644 index 00000000000..fd8b8a9d86f --- /dev/null +++ b/apps/playground-web/src/components/in-app-wallet/constants.ts @@ -0,0 +1,20 @@ +import { getContract } from "thirdweb"; +import { baseSepolia } from "thirdweb/chains"; +import { THIRDWEB_CLIENT } from "../../lib/client"; + +export const chain = baseSepolia; +export const tokenDropAddress = "0xd64A548A82c190083707CBEFD26958E5e6551D18"; +export const editionDropAddress = "0x638263e3eAa3917a53630e61B1fBa685308024fa"; +export const editionDropTokenId = 0n; + +export const editionDropContract = getContract({ + address: editionDropAddress, + chain, + client: THIRDWEB_CLIENT, +}); + +export const tokenDropContract = getContract({ + address: tokenDropAddress, + chain, + client: THIRDWEB_CLIENT, +}); diff --git a/apps/playground-web/src/components/in-app-wallet/sponsored-tx.tsx b/apps/playground-web/src/components/in-app-wallet/sponsored-tx.tsx new file mode 100644 index 00000000000..2b6c5e653ae --- /dev/null +++ b/apps/playground-web/src/components/in-app-wallet/sponsored-tx.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useEffect } from "react"; +import { claimTo, getNFT, getOwnedNFTs } from "thirdweb/extensions/erc1155"; +import { + MediaRenderer, + TransactionButton, + useActiveAccount, + useActiveWallet, + useDisconnect, + useReadContract, +} from "thirdweb/react"; +import { THIRDWEB_CLIENT } from "../../lib/client"; +import { editionDropContract, editionDropTokenId } from "./constants"; + +export function SponsoredInAppTxPreview() { + const wallet = useActiveWallet(); + const { disconnect } = useDisconnect(); + useEffect(() => { + if (wallet && wallet.id !== "inApp") { + disconnect(wallet); + } + }, [wallet, disconnect]); + const { theme } = useTheme(); + const smartAccount = useActiveAccount(); + const { data: nft, isLoading: isNftLoading } = useReadContract(getNFT, { + contract: editionDropContract, + tokenId: editionDropTokenId, + }); + const { data: ownedNfts } = useReadContract(getOwnedNFTs, { + contract: editionDropContract, + // biome-ignore lint/style/noNonNullAssertion: handled by queryOptions + address: smartAccount?.address!, + queryOptions: { enabled: !!smartAccount }, + }); + + return ( +
+ {isNftLoading ? ( +
Loading...
+ ) : ( + <> + {nft ? ( + + ) : null} + {smartAccount ? ( + <> +

+ You own {ownedNfts?.[0]?.quantityOwned.toString() || "0"}{" "} + Kittens +

+ + claimTo({ + contract: editionDropContract, + tokenId: editionDropTokenId, + to: smartAccount.address, + quantity: 1n, + }) + } + onError={(error) => { + alert(`Error: ${error.message}`); + }} + onTransactionConfirmed={async () => { + alert("Minted successful!"); + }} + > + Mint + + + ) : ( +

+ Login to mint this Kitten! +

+ )} + + )} +
+ ); +} diff --git a/apps/playground-web/src/components/styled-pay-embed.tsx b/apps/playground-web/src/components/pay/embed.tsx similarity index 72% rename from apps/playground-web/src/components/styled-pay-embed.tsx rename to apps/playground-web/src/components/pay/embed.tsx index fecde9dd74f..5d94400b6bf 100644 --- a/apps/playground-web/src/components/styled-pay-embed.tsx +++ b/apps/playground-web/src/components/pay/embed.tsx @@ -4,13 +4,13 @@ import { THIRDWEB_CLIENT } from "@/lib/client"; import { useTheme } from "next-themes"; import { PayEmbed } from "thirdweb/react"; -export function StyledPayEmbed() { +export function StyledPayEmbedPreview() { const { theme } = useTheme(); return ( ); } diff --git a/apps/playground-web/src/components/pay/transaction-button.tsx b/apps/playground-web/src/components/pay/transaction-button.tsx new file mode 100644 index 00000000000..6f15c124a52 --- /dev/null +++ b/apps/playground-web/src/components/pay/transaction-button.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { getContract } from "thirdweb"; +import { polygon } from "thirdweb/chains"; +import { claimTo } from "thirdweb/extensions/erc1155"; +import { TransactionButton, useActiveAccount } from "thirdweb/react"; +import { THIRDWEB_CLIENT } from "../../lib/client"; +import { StyledConnectButton } from "../styled-connect-button"; + +const contract = getContract({ + address: "0x96B30d36f783c7BC68535De23147e2ce65788e93", + chain: polygon, + client: THIRDWEB_CLIENT, +}); + +export function PayTransactionButtonPreview() { + const account = useActiveAccount(); + const { theme } = useTheme(); + + return account ? ( + { + if (!account) throw new Error("No active account"); + return claimTo({ + contract, + quantity: 1n, + tokenId: 0n, + to: account?.address, + }); + }} + payModal={{ + theme: theme === "light" ? "light" : "dark", + }} + > + Buy for 10 MATIC + + ) : ( + + ); +} diff --git a/apps/playground-web/src/components/sign-in/hooks.tsx b/apps/playground-web/src/components/sign-in/hooks.tsx new file mode 100644 index 00000000000..991f7876e7a --- /dev/null +++ b/apps/playground-web/src/components/sign-in/hooks.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { + useActiveAccount, + useActiveWallet, + useConnect, + useDisconnect, +} from "thirdweb/react"; +import { shortenAddress } from "thirdweb/utils"; +import { createWallet } from "thirdweb/wallets"; +import { THIRDWEB_CLIENT } from "../../lib/client"; +import { Button } from "../ui/button"; + +export function HooksPreview() { + const account = useActiveAccount(); + const wallet = useActiveWallet(); + const connectMutation = useConnect(); + const { disconnect } = useDisconnect(); + + const connect = async () => { + const wallet = await connectMutation.connect(async () => { + const adminWallet = createWallet("io.metamask"); + await adminWallet.connect({ + client: THIRDWEB_CLIENT, + }); + return adminWallet; + }); + return wallet; + }; + + return ( +
+ {account && wallet ? ( + <> +

Connected as {shortenAddress(account.address)}

+ + + ) : ( + + )} +
+ ); +} diff --git a/apps/playground-web/src/components/styled-connect-embed.tsx b/apps/playground-web/src/components/styled-connect-embed.tsx index 0f0c71763c7..b3e013a4fe5 100644 --- a/apps/playground-web/src/components/styled-connect-embed.tsx +++ b/apps/playground-web/src/components/styled-connect-embed.tsx @@ -2,15 +2,28 @@ import { THIRDWEB_CLIENT } from "@/lib/client"; import { useTheme } from "next-themes"; -import { ConnectEmbed } from "thirdweb/react"; +import { + ConnectEmbed, + type ConnectEmbedProps, + useActiveAccount, +} from "thirdweb/react"; +import { StyledConnectButton } from "./styled-connect-button"; -export function StyledConnectEmbed() { +export function StyledConnectEmbed( + props?: Omit, +) { const { theme } = useTheme(); + const account = useActiveAccount(); - return ( + return account ? ( +
+ +
+ ) : ( ); }