From abb352dd5af0d20eab763c2fb5dd5ca8cfc7bea4 Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Fri, 12 Apr 2024 23:10:02 +0300 Subject: [PATCH] Waas integration (#9) * WIP * Fix disconnect * Fixes and demo improvements * Refactor * Fix duplicate connector issue, add proper session name * Network switching, updated provider * Show current chain in demo * Reset prev demo state of sent txn and signed message on chain change * Remove unnecessary call * Add default return to request for unhandled cases * Update package.json * pnpm version * version update to 2.0.5-beta.1 * react example updated package * Temp prod key for clients * Fix address view for mobile * Update demo content height and padding for mobile * version 2.0.5-beta.2 * Update kit versions in example app * Waas request confirmation (WIP) * WIP * Confirmation request type for hook, better signature confirmation view * Fix dup values added for testing * Email connector (WIP) * Refactor config * Add id for confirmation requests * Email connector selection * Refactor, hook for email auth * Waas email auth * Error handling * Minor fixes * Apple login (WIP) and other fixes * 2.0.5-beta.3 * Use kit v beta.3 * Fix script loading, publish 2.0.5-beta.4 * Apple login missing parts * 2.0.5-beta.5 * Apple response undefined check * Use 2.0.5-beta.5 * Regenerate hash on disconnect * 2.0.5-beta.6 * Use 2.0.5-beta.6 * Update sequence to v1.9.0 * 2.0.5-beta.7 * Use 2.0.5-beta.7 * Update styles * 2.0.5-beta.8 * Use 2.0.5-beta.8 * Bump sequence.js version in example * Fix initial jsonRpcProvider network issue * 2.0.5-beta.9 * Bump sequence version to 1.9.6 * Improvements * 2.0.5-beta.10 * appleRedirectURI from host * Use 2.0.5-beta.10 * Check for wallet deployment only for waas connectors * Update example * Remove console log * Improved WaaS confirmation pop up * Fix type warnings * 2.0.5-beta.11 * NFT demo, other fixes * Ignore warning * New beta version * Add confirmation toggle * Wallet package improvements * 2.0.5-beta.13 * Fix wallet issues * 2.0.5-beta.14 * Use 2.0.5-beta.14 in examples/react * Improve getting default connectors * Merge branch 'master' into waas-integration * Add option for fetching balance with 'verifiedOnly: false' * Update sequence.js deps * Make enableConfirmationModal param optional * Add useWaasFeeOptions hook * 2.0.8-beta * Fix overflow issue * Add simpler example for waas * 2.0.8-beta.1 * Use universal wallet config for main demo * Handle txnData as hash in case it's not an object * Hide use confirmation toggle if not using waas in examples/react * Fix confirmation handler order, add missing type * Improve waas demo * Update comment * Remove unnecessary imports * 2.0.8-beta.2 * Add missing import * Remove pre.json, update package.json --------- Co-authored-by: samuelea --- examples/react-waas/.eslintrc.cjs | 18 + examples/react-waas/README.md | 30 + examples/react-waas/index.html | 13 + examples/react-waas/package.json | 34 + examples/react-waas/src/App.css | 59 + examples/react-waas/src/App.tsx | 205 ++ examples/react-waas/src/index.css | 68 + examples/react-waas/src/main.tsx | 71 + examples/react-waas/src/vite-env.d.ts | 1 + examples/react-waas/tsconfig.json | 25 + examples/react-waas/tsconfig.node.json | 11 + examples/react-waas/vite.config.ts | 7 + examples/react/package.json | 11 +- examples/react/src/App.tsx | 69 +- examples/react/src/components/Homepage.tsx | 295 +- examples/react/src/constants/index.ts | 26 +- examples/react/src/constants/nft-abi.ts | 196 ++ examples/react/vite.config.js | 14 +- package.json | 5 +- packages/checkout/package.json | 12 +- packages/checkout/src/api/data.tsx | 27 +- packages/connectors/package.json | 14 +- .../connectors/src/connectors/apple/apple.ts | 2 +- .../src/connectors/apple/appleWaas.ts | 23 + .../src/connectors/email/emailWaas.ts | 21 + .../src/connectors/google/google.ts | 3 +- .../src/connectors/google/googleWaas.ts | 23 + .../src/connectors/wagmiConnectors/index.ts | 1 + .../wagmiConnectors/sequenceWaasConnector.ts | 395 +++ packages/connectors/src/defaultConnectors.tsx | 63 + packages/kit/package.json | 27 +- .../ConnectWalletContent/PINCodeInput.tsx | 116 + .../ConnectWalletContent/index.tsx | 240 +- .../kit/src/components/KitProvider/index.tsx | 259 +- .../kit/src/components/TxnDetails/index.tsx | 262 ++ packages/kit/src/components/styles.css.ts | 44 + .../kit/src/constants/defaultSignInOptions.ts | 4 +- packages/kit/src/constants/localStorage.ts | 38 +- packages/kit/src/hooks/index.ts | 1 + .../src/hooks/useWaasConfirmationHandler.ts | 68 + packages/kit/src/hooks/useWaasEmailAuth.ts | 57 + packages/kit/src/hooks/useWaasFeeOptions.tsx | 69 + packages/kit/src/utils/deferred.ts | 21 + packages/kit/src/utils/tokens.ts | 2 +- packages/kit/src/utils/txnDecoding.ts | 555 ++++ packages/wallet/package.json | 12 +- packages/wallet/src/api/data.tsx | 23 +- packages/wallet/src/contexts/Navigation.tsx | 2 +- packages/wallet/src/contexts/WalletModal.tsx | 2 +- packages/wallet/src/hooks/data.ts | 27 +- packages/wallet/src/hooks/useNavigation.tsx | 4 +- .../src/shared/CollectibleTileImage.tsx | 2 +- .../src/shared/KitWalletProvider/index.tsx | 4 +- .../shared/KitWalletProvider/utils/index.tsx | 2 +- packages/wallet/src/shared/NetworkBadge.tsx | 4 +- packages/wallet/src/shared/index.tsx | 6 + packages/wallet/src/shared/styles.css.ts | 12 - packages/wallet/src/utils/genericContext.tsx | 19 + packages/wallet/src/utils/tokens.ts | 10 +- .../wallet/src/views/CoinDetails/Skeleton.tsx | 9 +- .../wallet/src/views/CoinDetails/index.tsx | 9 +- .../src/views/CollectibleDetails/Skeleton.tsx | 5 +- .../src/views/CollectibleDetails/index.tsx | 8 +- .../src/views/CollectionDetails/Skeleton.tsx | 5 +- .../src/views/CollectionDetails/index.tsx | 11 +- packages/wallet/src/views/History.tsx | 9 +- .../Home/components/AssetSummary/index.tsx | 27 +- packages/wallet/src/views/Home/index.tsx | 10 +- .../wallet/src/views/Search/SearchWallet.tsx | 11 +- .../src/views/Search/SearchWalletViewAll.tsx | 21 +- packages/wallet/src/views/SendCoin.tsx | 112 +- packages/wallet/src/views/SendCollectible.tsx | 143 +- .../wallet/src/views/Settings/Currency.tsx | 9 +- pnpm-lock.yaml | 2745 ++++++++++++++--- 74 files changed, 5886 insertions(+), 882 deletions(-) create mode 100644 examples/react-waas/.eslintrc.cjs create mode 100644 examples/react-waas/README.md create mode 100644 examples/react-waas/index.html create mode 100644 examples/react-waas/package.json create mode 100644 examples/react-waas/src/App.css create mode 100644 examples/react-waas/src/App.tsx create mode 100644 examples/react-waas/src/index.css create mode 100644 examples/react-waas/src/main.tsx create mode 100644 examples/react-waas/src/vite-env.d.ts create mode 100644 examples/react-waas/tsconfig.json create mode 100644 examples/react-waas/tsconfig.node.json create mode 100644 examples/react-waas/vite.config.ts create mode 100644 examples/react/src/constants/nft-abi.ts create mode 100644 packages/connectors/src/connectors/apple/appleWaas.ts create mode 100644 packages/connectors/src/connectors/email/emailWaas.ts create mode 100644 packages/connectors/src/connectors/google/googleWaas.ts create mode 100644 packages/connectors/src/connectors/wagmiConnectors/sequenceWaasConnector.ts create mode 100644 packages/kit/src/components/KitProvider/ConnectWalletContent/PINCodeInput.tsx create mode 100644 packages/kit/src/components/TxnDetails/index.tsx create mode 100644 packages/kit/src/hooks/useWaasConfirmationHandler.ts create mode 100644 packages/kit/src/hooks/useWaasEmailAuth.ts create mode 100644 packages/kit/src/hooks/useWaasFeeOptions.tsx create mode 100644 packages/kit/src/utils/deferred.ts create mode 100644 packages/kit/src/utils/txnDecoding.ts create mode 100644 packages/wallet/src/utils/genericContext.tsx diff --git a/examples/react-waas/.eslintrc.cjs b/examples/react-waas/.eslintrc.cjs new file mode 100644 index 00000000..d6c95379 --- /dev/null +++ b/examples/react-waas/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/examples/react-waas/README.md b/examples/react-waas/README.md new file mode 100644 index 00000000..0d6babed --- /dev/null +++ b/examples/react-waas/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/examples/react-waas/index.html b/examples/react-waas/index.html new file mode 100644 index 00000000..e4b78eae --- /dev/null +++ b/examples/react-waas/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/examples/react-waas/package.json b/examples/react-waas/package.json new file mode 100644 index 00000000..7480a772 --- /dev/null +++ b/examples/react-waas/package.json @@ -0,0 +1,34 @@ +{ + "name": "@0xsequence/kit-example-react-waas", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "start": "vite --port 4444", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "0xsequence": "^1.9.11", + "@0xsequence/kit": "workspace:*", + "@0xsequence/kit-checkout": "workspace:*", + "@tanstack/react-query": "^4.36.1", + "viem": "^2.5.7", + "wagmi": "^2.5.7" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/examples/react-waas/src/App.css b/examples/react-waas/src/App.css new file mode 100644 index 00000000..f701aa09 --- /dev/null +++ b/examples/react-waas/src/App.css @@ -0,0 +1,59 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 10px; + margin: 6px; + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + justify-content: center; + background: rgba(29, 32, 35, 0.5); + border-radius: 6px; + width: 100%; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 440px; +} + +.read-the-docs { + color: #888; +} diff --git a/examples/react-waas/src/App.tsx b/examples/react-waas/src/App.tsx new file mode 100644 index 00000000..a0e5e02b --- /dev/null +++ b/examples/react-waas/src/App.tsx @@ -0,0 +1,205 @@ +import './App.css' +import { useOpenConnectModal, useWaasFeeOptions } from '@0xsequence/kit' +import { + useAccount, + useChainId, + useDisconnect, + usePublicClient, + useSendTransaction, + useSwitchChain, + useWalletClient +} from 'wagmi' +import { sequence } from '0xsequence' +import { formatUnits } from 'viem' +import { useEffect, useState } from 'react' + +function App() { + const { setOpenConnectModal } = useOpenConnectModal() + const { disconnect } = useDisconnect() + const { data: walletClient } = useWalletClient() + const { address, isConnected } = useAccount() + + // Switching chain + const { switchChain } = useSwitchChain() + const chainId = useChainId() + const networkForCurrentChainId = sequence.network.allNetworks.find(n => n.chainId === chainId) + + const switchNetwork = () => { + if (chainId === 421614) { + switchChain({ chainId: 42170 }) + } else { + switchChain({ chainId: 421614 }) + } + + setLastTxnDataHash(undefined) + setMessageSig(undefined) + setIsMessageValid(undefined) + } + + // Signing and verifying message + const messageToSign = 'Two roads diverged in a yellow wood' + const publicClient = usePublicClient({ chainId }) + + const [isSigningMessage, setIsSigningMessage] = useState(false) + const [messageSig, setMessageSig] = useState() + const [isMessageValid, setIsMessageValid] = useState() + + const signMessage = async () => { + if (!walletClient) { + return + } + + setMessageSig(undefined) + setIsMessageValid(undefined) + + 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) + } + } + + // Sending txn + const [lastTxnDataHash, setLastTxnDataHash] = useState() + + const { data: txnData, sendTransaction, isLoading } = useSendTransaction() + const runSendTransaction = async () => { + if (!walletClient) { + return + } + + const [account] = await walletClient.getAddresses() + + sendTransaction({ to: account, value: '0', gas: null }) + } + + useEffect(() => { + if (txnData) { + setLastTxnDataHash(txnData.hash ?? txnData) + } + }, [txnData]) + + // Fee options are required when txn is not gas sponsored (not needed on testnets) + const [pendingFeeOptionConfirmation, confirmPendingFeeOption, rejectPendingFeeOption] = useWaasFeeOptions() + + const [selectedFeeTokenAddress, setSelectedFeeTokenAddress] = useState() // option is null for native token, string for erc20 token + useEffect(() => { + if (pendingFeeOptionConfirmation) { + setSelectedFeeTokenAddress(pendingFeeOptionConfirmation.options[0].token.contractAddress) // preselect first option + } + }, [pendingFeeOptionConfirmation]) + + const confirmFeeOption = () => { + if (pendingFeeOptionConfirmation && selectedFeeTokenAddress !== undefined) { + confirmPendingFeeOption(pendingFeeOptionConfirmation.id, selectedFeeTokenAddress) + } + } + + return ( +
+
+

Vite + React + Sequence Kit with WaaS

+ {!isConnected && ( +
+ +
+ )} + + {isConnected && ( + <> +
{isConnected &&

Wallet address: ({address})

}
+ +
+

Network: {networkForCurrentChainId?.name}

+ +
+
+ + + {isLoading &&

Transaction is pending...

} + + {lastTxnDataHash && ( +
+

Transaction hash: {lastTxnDataHash}

+ + + View on {networkForCurrentChainId?.name} explorer + +
+ )} + + {pendingFeeOptionConfirmation && ( +
+

Select fee option

+ + + +
+ )} +
+ +
+

Sign message

+

Message: {messageToSign}

+ + + {isSigningMessage &&

Signing message...

} + + {messageSig &&

Signature: {messageSig}

} + + {isMessageValid &&

isValid: {isMessageValid.toString()}

} +
+ +
+ +
+ + )} +
+
+ ) +} + +export default App diff --git a/examples/react-waas/src/index.css b/examples/react-waas/src/index.css new file mode 100644 index 00000000..6119ad9a --- /dev/null +++ b/examples/react-waas/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/examples/react-waas/src/main.tsx b/examples/react-waas/src/main.tsx new file mode 100644 index 00000000..8a5e1df6 --- /dev/null +++ b/examples/react-waas/src/main.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +import { WagmiProvider, createConfig, http } from 'wagmi' +import { Chain, arbitrumNova, arbitrumSepolia, mainnet, polygon } from 'wagmi/chains' +import { sequence } from '0xsequence' +import { getDefaultWaasConnectors } from '@0xsequence/kit-connectors' +import { KitConfig, KitProvider } from '@0xsequence/kit' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +const queryClient = new QueryClient() + +const chains: readonly [Chain, ...Chain[]] = [arbitrumNova as Chain, arbitrumSepolia as Chain, mainnet as Chain, polygon as Chain] + +// replace with your keys, and better to use env vars +const projectAccessKey = 'T3czhtWsTONJpbjFgAdLAuEAAAAAAAAA' +const waasConfigKey = 'eyJwcm9qZWN0SWQiOjc1LCJycGNTZXJ2ZXIiOiJodHRwczovL3dhYXMuc2VxdWVuY2UuYXBwIn0=' +const googleClientId = '603294233249-6h5saeg2uiu8akpcbar3r2aqjp6j7oem.apps.googleusercontent.com' +// const appleClientId = +// const appleRedirectURI = 'https://' + window.location.host + +const connectors = [ + ...getDefaultWaasConnectors({ + walletConnectProjectId: 'c65a6cb1aa83c4e24500130f23a437d8', + defaultChainId: 42170, + waasConfigKey, + googleClientId, + // appleClientId, + // appleRedirectURI, + appName: 'Kit Demo', + projectAccessKey, + enableConfirmationModal: false + }) +] + +/* @ts-expect-error-next-line */ +const transports: Record = {} + +chains.forEach(chain => { + const network = sequence.network.findNetworkConfig(sequence.network.allNetworks, chain.id) + if (!network) return + + transports[chain.id] = http(network.rpcUrl) +}) + +const config = createConfig({ + transports, + chains, + connectors +}) + +const kitConfig: KitConfig = { + defaultTheme: 'dark', + signIn: { + projectName: 'Kit Demo' + } +} + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + + + +) diff --git a/examples/react-waas/src/vite-env.d.ts b/examples/react-waas/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/react-waas/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react-waas/tsconfig.json b/examples/react-waas/tsconfig.json new file mode 100644 index 00000000..a7fc6fbf --- /dev/null +++ b/examples/react-waas/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/react-waas/tsconfig.node.json b/examples/react-waas/tsconfig.node.json new file mode 100644 index 00000000..97ede7ee --- /dev/null +++ b/examples/react-waas/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/react-waas/vite.config.ts b/examples/react-waas/vite.config.ts new file mode 100644 index 00000000..5a33944a --- /dev/null +++ b/examples/react-waas/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/examples/react/package.json b/examples/react/package.json index 6bf6e253..988694ce 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -5,12 +5,13 @@ "homepage": "kit", "type": "module", "dependencies": { - "0xsequence": "^1.9.11", + "0xsequence": "^1.9.17", + "@0xsequence/core": "^1.9.17", "@0xsequence/design-system": "^1.4.1", - "@0xsequence/kit": "^2.0.0", - "@0xsequence/kit-checkout": "^2.0.0", - "@0xsequence/kit-connectors": "^2.0.0", - "@0xsequence/kit-wallet": "^2.0.0", + "@0xsequence/kit": "workspace:*", + "@0xsequence/kit-checkout": "workspace:*", + "@0xsequence/kit-connectors": "workspace:*", + "@0xsequence/kit-wallet": "workspace:*", "@tanstack/react-query": "^4.36.1", "@vanilla-extract/css": "^1.9.3", "ethers": "^5.7.2", diff --git a/examples/react/src/App.tsx b/examples/react/src/App.tsx index caa6f741..7f4129f1 100644 --- a/examples/react/src/App.tsx +++ b/examples/react/src/App.tsx @@ -3,14 +3,14 @@ import { ethers } from 'ethers' import qs from 'query-string' import { ThemeProvider } from '@0xsequence/design-system' import { KitProvider, KitConfig, getKitConnectWallets } from '@0xsequence/kit' -import { getDefaultConnectors, mock } from '@0xsequence/kit-connectors' +import { getDefaultConnectors, getDefaultWaasConnectors, mock } from '@0xsequence/kit-connectors' import { KitWalletProvider } from '@0xsequence/kit-wallet' import { KitCheckoutProvider } from '@0xsequence/kit-checkout' import Homepage from './components/Homepage' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { type Chain } from 'viem' import { createConfig, http, WagmiProvider } from 'wagmi' -import { mainnet, polygon } from 'wagmi/chains' +import { arbitrumNova, arbitrumSepolia, mainnet, polygon } from 'wagmi/chains' import '@0xsequence/design-system/styles.css' @@ -22,14 +22,49 @@ function App() { const isDebugMode = debug === 'true' /* typing error from wagmi? */ - const chains: readonly [Chain, ...Chain[]] = [mainnet as Chain, polygon as Chain] + const chains: readonly [Chain, ...Chain[]] = [ + arbitrumNova as Chain, + arbitrumSepolia as Chain, + mainnet as Chain, + polygon as Chain + ] + + const projectAccessKey = 'T3czhtWsTONJpbjFgAdLAuEAAAAAAAAA' + + /// Use this to test the waas connectors - const projectAccessKey = 'iK0DPkHRt0IFo8o4M3fZIIOAAAAAAAAAA' + // WaaS config + // const waasConfigKey = 'eyJwcm9qZWN0SWQiOjc1LCJycGNTZXJ2ZXIiOiJodHRwczovL3dhYXMuc2VxdWVuY2UuYXBwIn0=' + // const googleClientId = '603294233249-6h5saeg2uiu8akpcbar3r2aqjp6j7oem.apps.googleusercontent.com' + // const appleClientId = 'com.horizon.sequence.waas' + // const appleRedirectURI = 'https://' + window.location.host + // const connectors = [ + // ...getDefaultWaasConnectors({ + // walletConnectProjectId: 'c65a6cb1aa83c4e24500130f23a437d8', + // defaultChainId: 42170, + // waasConfigKey, + // googleClientId, + // appleClientId, + // appleRedirectURI, + // appName: 'Kit Demo', + // projectAccessKey, + // enableConfirmationModal: localStorage.getItem('confirmationEnabled') === 'true' + // }), + // ...(isDebugMode + // ? getKitConnectWallets(projectAccessKey, [ + // mock({ + // accounts: ['0xCb88b6315507e9d8c35D81AFB7F190aB6c3227C9'] + // }) + // ]) + // : []) + // ] + + /// Use this to test the universal connectors const connectors = [ ...getDefaultConnectors({ walletConnectProjectId: 'c65a6cb1aa83c4e24500130f23a437d8', - defaultChainId: 137, + defaultChainId: 42170, appName: 'demo app', projectAccessKey }), @@ -61,22 +96,32 @@ function App() { const kitConfig: KitConfig = { defaultTheme: 'dark', signIn: { - projectName: 'Skyweaver', + projectName: 'Kit Demo', // logoUrl: 'sw-logo-white.svg', useMock: isDebugMode }, displayedAssets: [ - // Matic token + // Native token { contractAddress: ethers.constants.AddressZero, - chainId: 137 + chainId: 42170 }, - // USDC token + // Native token { - contractAddress: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', - chainId: 137 + contractAddress: ethers.constants.AddressZero, + chainId: 421614 + }, + // Waas demo NFT + { + contractAddress: '0x0d402c63cae0200f0723b3e6fa0914627a48462e', + chainId: 42170 + }, + // Waas demo NFT + { + contractAddress: '0x0d402c63cae0200f0723b3e6fa0914627a48462e', + chainId: 421614 }, - // skyweaver collectibles + // Skyweaver assets { contractAddress: '0x631998e91476da5b870d741192fc5cbc55f5a52e', chainId: 137 diff --git a/examples/react/src/components/Homepage.tsx b/examples/react/src/components/Homepage.tsx index 9d4cb586..9b33fa8e 100644 --- a/examples/react/src/components/Homepage.tsx +++ b/examples/react/src/components/Homepage.tsx @@ -1,9 +1,26 @@ -import React from 'react' +import React, { useEffect } from 'react' import qs from 'query-string' -import { useOpenConnectModal, signEthAuthProof, validateEthProof, useTheme as useKitTheme } from '@0xsequence/kit' +import { + useOpenConnectModal, + signEthAuthProof, + validateEthProof, + useTheme as useKitTheme, + useWaasFeeOptions +} from '@0xsequence/kit' import { useOpenWalletModal } from '@0xsequence/kit-wallet' import { useCheckoutModal } from '@0xsequence/kit-checkout' -import { useDisconnect, useAccount, useWalletClient, usePublicClient, useChainId, useSwitchChain } from 'wagmi' + +import { + useDisconnect, + useAccount, + useWalletClient, + usePublicClient, + useChainId, + useSwitchChain, + useSendTransaction, + useWriteContract, + useConnections +} from 'wagmi' import { Box, Button, @@ -15,12 +32,19 @@ import { SignoutIcon, useTheme, vars, + Spinner, + useMediaQuery, + Switch, + Select, IconButton } from '@0xsequence/design-system' import { Footer } from './Footer' import { messageToSign } from '../constants' import { formatAddress, getCheckoutSettings } from '../utils' +import { sequence } from '0xsequence' +import abi from '../constants/nft-abi' +import { ethers } from 'ethers' function Homepage() { const { theme, setTheme } = useTheme() @@ -33,9 +57,41 @@ function Homepage() { const { data: walletClient } = useWalletClient() const { switchChain } = useSwitchChain() + const connections = useConnections() + + const isWaasConnection = connections.find(c => c.connector.id.includes('waas')) !== undefined + + const isMobile = useMediaQuery('isMobile') + + const { data: txnData, sendTransaction, isLoading: isSendTxnLoading } = useSendTransaction() + const { data: txnData2, isLoading: isMintTxnLoading, writeContract } = useWriteContract() + + const [isSigningMessage, setIsSigningMessage] = React.useState(false) + const [isMessageValid, setIsMessageValid] = React.useState() + const [messageSig, setMessageSig] = React.useState() + + const [lastTxnDataHash, setLastTxnDataHash] = React.useState() + const [lastTxnDataHash2, setLastTxnDataHash2] = React.useState() + + const [confirmationEnabled, setConfirmationEnabled] = React.useState( + localStorage.getItem('confirmationEnabled') === 'true' + ) + + const [pendingFeeOptionConfirmation, confirmPendingFeeOption, rejectPendingFeeOption] = useWaasFeeOptions() + + const [selectedFeeOptionTokenName, setSelectedFeeOptionTokenName] = React.useState() + + useEffect(() => { + if (pendingFeeOptionConfirmation) { + setSelectedFeeOptionTokenName(pendingFeeOptionConfirmation.options[0].token.name) + } + }, [pendingFeeOptionConfirmation]) + const chainId = useChainId() - const publicClient = usePublicClient() + const networkForCurrentChainId = sequence.network.allNetworks.find(n => n.chainId === chainId) + + const publicClient = usePublicClient({ chainId }) // append ?debug=true to url to enable debug mode const { debug } = qs.parse(location.search) @@ -57,11 +113,22 @@ function Homepage() { } } + useEffect(() => { + if (txnData) { + setLastTxnDataHash(txnData.hash ?? txnData) + } + if (txnData2) { + setLastTxnDataHash2(txnData2.hash ?? txnData) + } + }, [txnData, txnData2]) + const signMessage = async () => { if (!walletClient) { return } + setIsSigningMessage(true) + try { const message = messageToSign @@ -70,7 +137,9 @@ function Homepage() { 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() @@ -80,12 +149,42 @@ function Homepage() { 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: '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 onClickChangeTheme = () => { // Change theme at the app level setTheme(theme === 'dark' ? 'light' : 'dark') @@ -119,7 +218,7 @@ function Homepage() { - {formatAddress(address || '')} + {isMobile ? formatAddress(address || '') : address} @@ -136,12 +235,19 @@ function Homepage() { interface ClickableCardProps { title: string description: string + disabled?: boolean + isLoading?: boolean onClick: () => void } - const ClickableCard = ({ title, description, onClick }: ClickableCardProps) => { + const ClickableCard = ({ title, description, disabled, isLoading, onClick }: ClickableCardProps) => { return ( - + {} : onClick} + opacity={disabled ? '50' : '100'} + > {title} @@ -150,6 +256,7 @@ function Homepage() { {description} + {isLoading && } ) } @@ -167,11 +274,13 @@ function Homepage() { } const onSwitchNetwork = () => { - if (chainId === 1) { - switchChain({ chainId: 137 }) + if (chainId === 421614) { + switchChain({ chainId: 42170 }) } else { - switchChain({ chainId: 1 }) + switchChain({ chainId: 421614 }) } + + setIsMessageValid(undefined) } return ( @@ -184,7 +293,12 @@ function Homepage() { - + {isConnected ? ( @@ -196,12 +310,71 @@ function Homepage() { description="Connect a Sequence wallet to view, swap, send, and receive collections" onClick={() => setOpenWalletModal(true)} /> - */} + + + {lastTxnDataHash && (txnData?.chainId === chainId || txnData) && ( + + View on {networkForCurrentChainId.blockExplorer.name} + + )} + + {isMessageValid && ( + + Signed message: + {messageToSign} + Signature: + + {messageSig} + + + isValid: {isMessageValid.toString()} + + + )} + + - + {lastTxnDataHash2 && (txnData2?.chainId === chainId || txnData2) && ( + + View on {networkForCurrentChainId.blockExplorer.name} + + )} + {isDebugMode && ( )} - {isDebugMode && } + + + {pendingFeeOptionConfirmation && ( + +