diff --git a/.eslintrc.js b/.eslintrc.js index d4366e42..421c5477 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,3 @@ -const { off } = require("process") - module.exports = { parser: '@typescript-eslint/parser', parserOptions: { @@ -28,11 +26,10 @@ module.exports = { '@typescript-eslint/no-this-alias': 'off', 'import/no-unresolved': 'off', - 'import/no-default-export': 2, + 'import/no-default-export': 1, 'import/no-named-as-default-member': 'off', 'import/export': 'off' - // 'import/order': [ // 'warn', // { @@ -43,6 +40,5 @@ module.exports = { // } // }, // ] - } } diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index 228531d4..d78d39cb 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -12,7 +12,7 @@ runs: - name: Setup PNPM uses: pnpm/action-setup@v3 with: - version: 8 + version: 9 run_install: false - name: Get pnpm store directory diff --git a/examples/next/.eslintrc.json b/examples/next/.eslintrc.json new file mode 100644 index 00000000..bffb357a --- /dev/null +++ b/examples/next/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/examples/next/.gitignore b/examples/next/.gitignore new file mode 100644 index 00000000..fd3dbb57 --- /dev/null +++ b/examples/next/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/next/README.md b/examples/next/README.md new file mode 100644 index 00000000..c4033664 --- /dev/null +++ b/examples/next/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/examples/next/next.config.mjs b/examples/next/next.config.mjs new file mode 100644 index 00000000..c1c329b1 --- /dev/null +++ b/examples/next/next.config.mjs @@ -0,0 +1,14 @@ +import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin' + +const withVanillaExtract = createVanillaExtractPlugin() + +/** @type {import('next').NextConfig} */ +const nextConfig = { + webpack: config => { + config.externals.push('pino-pretty', 'encoding') + return config + } + // transpilePackages: ['@0xsequence/kit', '@0xsequence/kit-wallet', '@0xsequence/kit-connectors', '@0xsequence/checkout'] +} + +export default withVanillaExtract(nextConfig) diff --git a/examples/next/package.json b/examples/next/package.json new file mode 100644 index 00000000..947786b0 --- /dev/null +++ b/examples/next/package.json @@ -0,0 +1,36 @@ +{ + "name": "@0xsequence/kit-example-next", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev -p 4444", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@0xsequence/design-system": "^1.4.7", + "@0xsequence/kit": "workspace:*", + "@0xsequence/kit-checkout": "workspace:*", + "@0xsequence/kit-connectors": "workspace:*", + "@0xsequence/kit-wallet": "workspace:*", + "@0xsequence/network": "^1.9.26", + "@tanstack/react-query": "^5.29.2", + "next": "14.2.3", + "react": "^18", + "react-dom": "^18", + "viem": "^2.5.7", + "wagmi": "^2.5.7" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "@vanilla-extract/next-plugin": "^2.4.0", + "eslint": "^8", + "eslint-config-next": "14.2.3", + "postcss": "^8", + "tailwindcss": "^3.4.3", + "typescript": "^5" + } +} diff --git a/examples/next/public/discord.svg b/examples/next/public/discord.svg new file mode 100644 index 00000000..b4ca26f4 --- /dev/null +++ b/examples/next/public/discord.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/examples/next/public/github.svg b/examples/next/public/github.svg new file mode 100644 index 00000000..5a9a1f46 --- /dev/null +++ b/examples/next/public/github.svg @@ -0,0 +1,21 @@ + + + + + diff --git a/examples/next/public/kit-logo-text.svg b/examples/next/public/kit-logo-text.svg new file mode 100644 index 00000000..2d6def52 --- /dev/null +++ b/examples/next/public/kit-logo-text.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/next/public/kit-logo.svg b/examples/next/public/kit-logo.svg new file mode 100644 index 00000000..cc49f75d --- /dev/null +++ b/examples/next/public/kit-logo.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/next/public/next.svg b/examples/next/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/examples/next/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/next/public/twitter.svg b/examples/next/public/twitter.svg new file mode 100644 index 00000000..46a33e5f --- /dev/null +++ b/examples/next/public/twitter.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/examples/next/public/youtube.svg b/examples/next/public/youtube.svg new file mode 100644 index 00000000..4f8014f9 --- /dev/null +++ b/examples/next/public/youtube.svg @@ -0,0 +1,11 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/examples/next/src/app/WalletOptions.tsx b/examples/next/src/app/WalletOptions.tsx new file mode 100644 index 00000000..693db88f --- /dev/null +++ b/examples/next/src/app/WalletOptions.tsx @@ -0,0 +1,17 @@ +'use client' + +import { Connector, useConnect } from 'wagmi' + +export const WalletOptions = () => { + const { connectors, connect } = useConnect() + + return ( +
+ {connectors.map(connector => ( + + ))} +
+ ) +} diff --git a/examples/next/src/app/Web3Provider.tsx b/examples/next/src/app/Web3Provider.tsx new file mode 100644 index 00000000..5729deb4 --- /dev/null +++ b/examples/next/src/app/Web3Provider.tsx @@ -0,0 +1,31 @@ +'use client' + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { WagmiProvider } from 'wagmi' + +import { wagmiConfig, kitConfig } from './config' +import { KitProvider } from '@0xsequence/kit' +import { KitWalletProvider } from '@0xsequence/kit-wallet' +import { KitCheckoutProvider } from '@0xsequence/kit-checkout' + +const queryClient = new QueryClient() + +export interface Web3ProviderProps { + children: React.ReactNode +} + +export const Web3Provider = (props: Web3ProviderProps) => { + const { children } = props + + return ( + + + + + {children} + + + + + ) +} diff --git a/examples/next/src/app/components/Connected.tsx b/examples/next/src/app/components/Connected.tsx new file mode 100644 index 00000000..735df42e --- /dev/null +++ b/examples/next/src/app/components/Connected.tsx @@ -0,0 +1,520 @@ +import { ComponentProps, useEffect, useState } from 'react' +import { Box, Text, Card, Button, Select, Switch, SignoutIcon } from '@0xsequence/design-system' +import { + useAccount, + useChainId, + useConnections, + useDisconnect, + usePublicClient, + useSendTransaction, + useSwitchChain, + useWalletClient, + useWriteContract +} from 'wagmi' +import { Header } from './Header' +import { delay } from '@/utils' +import { formatUnits, parseUnits } from 'viem' +import { signEthAuthProof, useIndexerClient, useOpenConnectModal, useWaasFeeOptions, validateEthProof } from '@0xsequence/kit' +import { useOpenWalletModal } from '@0xsequence/kit-wallet' +import { useCheckoutModal, CheckoutSettings } from '@0xsequence/kit-checkout' +import { ConnectionMode, isDebugMode } from '../config' +import { allNetworks } from '@0xsequence/network' +import { abi, messageToSign } from '@/constants' + +export const Connected = () => { + const { address } = useAccount() + const { disconnect } = useDisconnect() + const { setOpenConnectModal } = useOpenConnectModal() + const { setOpenWalletModal } = useOpenWalletModal() + const connections = useConnections() + const { triggerCheckout } = useCheckoutModal() + const { data: walletClient } = useWalletClient() + const { switchChain } = useSwitchChain() + + const isWaasConnection = connections.find(c => c.connector.id.includes('waas')) !== undefined + const { data: txnData, sendTransaction, isPending: isPendingSendTxn, error } = useSendTransaction() + const { data: txnData2, isPending: isPendingMintTxn, writeContract } = useWriteContract() + + const [isSigningMessage, setIsSigningMessage] = useState(false) + const [isMessageValid, setIsMessageValid] = useState() + const [messageSig, setMessageSig] = useState() + + const [lastTxnDataHash, setLastTxnDataHash] = useState() + const [lastTxnDataHash2, setLastTxnDataHash2] = useState() + + const [pendingFeeOptionConfirmation, confirmPendingFeeOption, rejectPendingFeeOption] = useWaasFeeOptions() + + const [selectedFeeOptionTokenName, setSelectedFeeOptionTokenName] = useState() + + useEffect(() => { + if (pendingFeeOptionConfirmation) { + setSelectedFeeOptionTokenName(pendingFeeOptionConfirmation.options[0].token.name) + } + }, [pendingFeeOptionConfirmation]) + + useEffect(() => { + console.log(error?.message) + }, [error]) + + const chainId = useChainId() + + const indexerClient = useIndexerClient(chainId) + + const [feeOptionBalances, setFeeOptionBalances] = useState<{ tokenName: string; decimals: number; balance: string }[]>([]) + + const [feeOptionAlert, setFeeOptionAlert] = useState(undefined) + + useEffect(() => { + if (pendingFeeOptionConfirmation) { + checkTokenBalancesForFeeOptions() + } + }, [pendingFeeOptionConfirmation]) + + const handleSwitchConnectionMode = (mode: ConnectionMode) => { + const searchParams = new URLSearchParams() + + searchParams.set('mode', mode) + window.location.search = searchParams.toString() + } + + const checkTokenBalancesForFeeOptions = async () => { + if (pendingFeeOptionConfirmation) { + const [account] = await walletClient!.getAddresses() + const nativeTokenBalance = await indexerClient.getEtherBalance({ accountAddress: account }) + + const tokenBalances = await indexerClient.getTokenBalances({ + accountAddress: account + }) + + console.log('feeOptions', pendingFeeOptionConfirmation.options) + console.log('nativeTokenBalance', nativeTokenBalance) + console.log('tokenBalances', tokenBalances) + + const balances = pendingFeeOptionConfirmation.options.map(option => { + if (option.token.contractAddress === null) { + return { + tokenName: option.token.name, + decimals: option.token.decimals || 0, + balance: nativeTokenBalance.balance.balanceWei + } + } else { + return { + tokenName: option.token.name, + decimals: option.token.decimals || 0, + balance: + tokenBalances.balances.find(b => b.contractAddress.toLowerCase() === option.token.contractAddress!.toLowerCase()) + ?.balance || '0' + } + } + }) + + setFeeOptionBalances(balances) + } + } + + const networkForCurrentChainId = allNetworks.find(n => n.chainId === chainId)! + + const publicClient = usePublicClient({ chainId }) + + const generateEthAuthProof = async () => { + if (!walletClient || !publicClient) { + return + } + + try { + const proof = await signEthAuthProof(walletClient) + console.log('proof:', proof) + + const isValid = await validateEthProof(walletClient, publicClient, proof) + console.log('isValid?:', isValid) + } catch (e) { + console.error(e) + } + } + + useEffect(() => { + if (txnData) { + setLastTxnDataHash((txnData as any).hash ?? txnData) + } + if (txnData2) { + setLastTxnDataHash2((txnData2 as any).hash ?? txnData) + } + }, [txnData, txnData2]) + + const signMessage = async () => { + if (!walletClient || !publicClient) { + return + } + + setIsSigningMessage(true) + + try { + const message = messageToSign + + // sign + const sig = await walletClient.signMessage({ + account: address || ('' as `0x${string}`), + message + }) + console.log('address', address) + console.log('signature:', sig) + console.log('chainId in homepage', chainId) + + const [account] = await walletClient.getAddresses() + + const isValid = await publicClient.verifyMessage({ + address: account, + message, + signature: sig + }) + + setIsSigningMessage(false) + setIsMessageValid(isValid) + setMessageSig(sig) + + console.log('isValid?', isValid) + } catch (e) { + setIsSigningMessage(false) + console.error(e) + } + } + + const runSendTransaction = async () => { + if (!walletClient) { + return + } + + const [account] = await walletClient.getAddresses() + + sendTransaction({ to: account, value: BigInt(0), gas: null }) + } + + const runMintNFT = async () => { + if (!walletClient) { + return + } + + const [account] = await walletClient.getAddresses() + + writeContract({ + address: '0x0d402C63cAe0200F0723B3e6fa0914627a48462E', + abi, + functionName: 'awardItem', + args: [account, 'https://dev-metadata.sequence.app/projects/277/collections/62/tokens/0.json'] + }) + } + + const onClickConnect = () => { + setOpenConnectModal(true) + } + + const onClickCheckout = () => { + triggerCheckout(getCheckoutSettings(address)) + } + + const onSwitchNetwork = () => { + if (chainId === 421614) { + switchChain({ chainId: 42170 }) + } else { + switchChain({ chainId: 421614 }) + } + + setLastTxnDataHash(undefined) + setLastTxnDataHash2(undefined) + setIsMessageValid(undefined) + } + + return ( + <> +
+ + + + + + Demos + + setOpenWalletModal(true)} + /> + {/* */} + + + {networkForCurrentChainId.blockExplorer && lastTxnDataHash && ((txnData as any)?.chainId === chainId || txnData) && ( + + View on {networkForCurrentChainId.blockExplorer.name} + + )} + + + + {isMessageValid && ( + + Signed message: + {messageToSign} + Signature: + + {messageSig} + + + isValid: {isMessageValid.toString()} + + + )} + + + + {networkForCurrentChainId.blockExplorer && + lastTxnDataHash2 && + ((txnData2 as any)?.chainId === chainId || txnData2) && ( + + View on {networkForCurrentChainId.blockExplorer.name} + + )} + + {isDebugMode && ( + + )} + + + + + {pendingFeeOptionConfirmation && feeOptionBalances.length > 0 && ( + +