├── .eslintrc.json ├── public ├── logo.png ├── discord.png ├── favicon.ico ├── github.png ├── twitter.png └── hero-fade.png ├── .prettierignore ├── postcss.config.js ├── next.config.js ├── lib └── utils.ts ├── next-env.d.ts ├── README.md ├── components └── ui │ ├── skeleton.tsx │ ├── separator.tsx │ ├── toaster.tsx │ ├── input.tsx │ ├── button.tsx │ ├── card.tsx │ ├── use-toast.ts │ └── toast.tsx ├── components.json ├── .env.example ├── .vscode ├── settings.json └── extensions.json ├── .gitignore ├── pages ├── api │ ├── secret.ts │ └── auth │ │ └── [...thirdweb].ts ├── _app.tsx └── index.tsx ├── const ├── chains.ts └── contracts.ts ├── tsconfig.json ├── package.json ├── postinstall.mjs ├── styles └── globals.css ├── tailwind.config.js └── LICENSE.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/babel", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarrodwatts/zkevm-cashapp/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarrodwatts/zkevm-cashapp/HEAD/public/discord.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarrodwatts/zkevm-cashapp/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarrodwatts/zkevm-cashapp/HEAD/public/github.png -------------------------------------------------------------------------------- /public/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarrodwatts/zkevm-cashapp/HEAD/public/twitter.png -------------------------------------------------------------------------------- /public/hero-fade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarrodwatts/zkevm-cashapp/HEAD/public/hero-fade.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Don't auto-format the generated file 2 | template/application/const/contractAddresses.ts -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | basePath: "", 5 | }; 6 | 7 | module.exports = nextConfig; 8 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Demo CashApp style application built on Polygon zkEVM. 2 | 3 | This is an [EVM Kit](https://evmkit.com/) project bootstrapped with `npx evmkit create`. To learn more about EVM Kit, take a look at the [documentation](https://docs.evmkit.com/). 4 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # thirdweb API Keys (https://thirdweb.com/dashboard/settings/api-keys) 2 | NEXT_PUBLIC_THIRDWEB_API_KEY= 3 | THIRDWEB_SECRET_KEY= 4 | 5 | # Magic Link (Email & Phone Number Wallets) 6 | NEXT_PUBLIC_MAGIC_LINK_API_KEY= 7 | 8 | # Paper Wallet (Email Wallets) 9 | NEXT_PUBLIC_PAPER_CLIENT_ID= 10 | 11 | #ERC-4337 Smart Wallets 12 | NEXT_PUBLIC_WALLET_FACTORY= 13 | 14 | # User Authentication 15 | AUTH_PRIVATE_KEY= 16 | NEXT_PUBLIC_AUTH_DOMAIN= -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | // Set formatter for solidity 4 | "[solidity]": { 5 | // hardhat solidity extension 6 | "editor.defaultFormatter": "NomicFoundation.hardhat-solidity" 7 | }, 8 | "editor.formatOnPaste": true, 9 | "editor.defaultFormatter": "esbenp.prettier-vscode", 10 | "files.associations": { 11 | "*.css": "tailwindcss" 12 | }, 13 | "editor.quickSuggestions": { 14 | "strings": true 15 | }, 16 | "tailwindCSS.includeLanguages": { 17 | "plaintext": "html" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | -------------------------------------------------------------------------------- /pages/api/secret.ts: -------------------------------------------------------------------------------- 1 | import { getUser } from "./auth/[...thirdweb]"; 2 | import { NextApiRequest, NextApiResponse } from "next"; 3 | 4 | const handler = async (req: NextApiRequest, res: NextApiResponse) => { 5 | // Get the user of the request 6 | const user = await getUser(req); 7 | 8 | // Check if the user is authenticated 9 | if (!user) { 10 | return res.status(401).json({ 11 | message: "No user is signed in.", 12 | }); 13 | } 14 | 15 | // Return the secret message to the authenticated user 16 | return res.status(200).json({ 17 | message: `${user.address}`, 18 | }); 19 | }; 20 | 21 | export default handler; 22 | -------------------------------------------------------------------------------- /const/chains.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Define the blockchains you want to use in your application. 3 | * - DEVELOPMENT_CHAIN: The blockchain you want to use while on localhost and testing. 4 | * - PRODUCTION_CHAIN: The blockchain you want to use when you deploy your application. 5 | * The CHAIN export changes depending on what environment you are in. 6 | */ 7 | 8 | import { PolygonZkevm, PolygonZkevmTestnet } from "@thirdweb-dev/chains"; 9 | 10 | export const IS_DEV_ENV = process.env.NODE_ENV === "development"; 11 | 12 | const DEVELOPMENT_CHAIN = PolygonZkevmTestnet; 13 | const PRODUCTION_CHAIN = PolygonZkevmTestnet; 14 | 15 | export const CHAIN = IS_DEV_ENV ? DEVELOPMENT_CHAIN : PRODUCTION_CHAIN; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "paths": { 18 | "@/components/*": ["./components/*"], 19 | "@/lib/*": ["./lib/*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /pages/api/auth/[...thirdweb].ts: -------------------------------------------------------------------------------- 1 | import { ThirdwebAuth } from "@thirdweb-dev/auth/next"; 2 | import { EthersWallet, PrivateKeyWallet } from "@thirdweb-dev/wallets"; 3 | import { ethers } from "ethers"; 4 | 5 | export const { ThirdwebAuthHandler, getUser } = ThirdwebAuth({ 6 | // Use the domain from the environment or default to evmkit.com 7 | domain: process.env.NEXT_PUBLIC_AUTH_DOMAIN || "evmkit.com", 8 | 9 | // Use the private key from the environment or generate a random one 10 | wallet: process.env.THIRDWEB_AUTH_PRIVATE_KEY 11 | ? new PrivateKeyWallet(process.env.THIRDWEB_AUTH_PRIVATE_KEY) 12 | : new EthersWallet(ethers.Wallet.createRandom()), 13 | }); 14 | 15 | export default ThirdwebAuthHandler(); 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bradlc.vscode-tailwindcss", // Tailwind CSS IntelliSense 4 | "naumovs.color-highlight", // Highlight CSS color codes in your editor 5 | "irongeek.vscode-env", // Syntax highlighting for .env files 6 | "dsznajder.es7-react-js-snippets", // Generate boilerplate code for React 7 | "dbaeumer.vscode-eslint", // ESLint statically analyzes your code to quickly find problems 8 | "wix.vscode-import-cost", // Display import/require package size in the editor 9 | "visualstudioexptteam.vscodeintellicode", // Intelligent autocomplete suggestions 10 | "esbenp.prettier-vscode", // Autoamtic consistent code formatting 11 | "nomicfoundation.hardhat-solidity", // Solidity syntax highlighting 12 | "github.copilot" // Paid Extension: AI Code Completions 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Separator = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >( 10 | ( 11 | { className, orientation = "horizontal", decorative = true, ...props }, 12 | ref 13 | ) => ( 14 | 25 | ) 26 | ) 27 | Separator.displayName = SeparatorPrimitive.Root.displayName 28 | 29 | export { Separator } 30 | -------------------------------------------------------------------------------- /components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Toast, 3 | ToastClose, 4 | ToastDescription, 5 | ToastProvider, 6 | ToastTitle, 7 | ToastViewport, 8 | } from "@/components/ui/toast"; 9 | import { useToast } from "@/components/ui/use-toast"; 10 | 11 | export function Toaster() { 12 | const { toasts } = useToast(); 13 | 14 | return ( 15 | 16 | {toasts.map(function ({ id, title, description, action, ...props }) { 17 | return ( 18 | 19 |
20 | {title && {title}} 21 | {description && ( 22 | {description} 23 | )} 24 |
25 | {action} 26 | 27 |
28 | ); 29 | })} 30 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | } 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /const/contracts.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Configure your smart contract addresses here. 3 | * Each smart contract should have two addresses defined: 4 | * 1. Development address - used for testing purposes 5 | * 2. Production address - the mainnet smart contract address for when you deploy your application 6 | */ 7 | 8 | import { IS_DEV_ENV } from "./chains"; 9 | 10 | // For example, below, we have a Greeter smart contract that has two addresses defined. 11 | // Then, we use the IS_DEV_ENV variable to determine which address to use in the current environment. 12 | const greeter_dev = "0x2E74A1664dA1066FFa4aF8b85856cb474D189029"; 13 | const greeter_prod = ""; 14 | 15 | // Below, we force the typescript type to be of the dev address type. 16 | // This is to ensure thirdweb generate knows what the ABI is when using useContract 17 | // So that we get type-safety when interacting with it's functions. 18 | export const greeter = IS_DEV_ENV 19 | ? greeter_dev 20 | : (greeter_prod as typeof greeter_dev); // Here's the type assertion, since we assume the ABIs are the same in dev and prod. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application", 3 | "license": "MIT", 4 | "version": "0.1.0", 5 | "private": true, 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint", 11 | "export": "next export", 12 | "generate": "npx thirdweb@latest generate", 13 | "add-component": "npx --yes shadcn-ui@latest add" 14 | }, 15 | "dependencies": { 16 | "@lens-protocol/react-web": "^1.2.2", 17 | "@radix-ui/react-checkbox": "1.0.3", 18 | "@radix-ui/react-separator": "^1.0.3", 19 | "@radix-ui/react-slot": "^1.0.2", 20 | "@radix-ui/react-toast": "^1.1.4", 21 | "@tanstack/react-query": "^4.32.0", 22 | "@thirdweb-dev/auth": "3.0.21", 23 | "@thirdweb-dev/chains": "0.1.15", 24 | "@thirdweb-dev/react": "^3", 25 | "@thirdweb-dev/sdk": "^3", 26 | "@thirdweb-dev/wallets": "^0.2.22", 27 | "class-variance-authority": "^0.7.0", 28 | "clsx": "^2.0.0", 29 | "ethers": "5.7.2", 30 | "lucide-react": "^0.263.1", 31 | "next": "^13", 32 | "react": "^18.2", 33 | "react-dom": "^18.2", 34 | "tailwind-merge": "^1.14.0", 35 | "tailwindcss-animate": "^1.0.6", 36 | "use-debounce": "^9.0.4" 37 | }, 38 | "devDependencies": { 39 | "@tanstack/eslint-plugin-query": "^4.29.25", 40 | "@types/node": "^18.11.11", 41 | "@types/react": "^18", 42 | "autoprefixer": "^10.4.14", 43 | "dotenv": "^16.3.1", 44 | "env-cmd": "^10.1.0", 45 | "eslint": "^8.29.0", 46 | "eslint-config-next": "^13", 47 | "postcss": "^8.4.23", 48 | "tailwindcss": "^3.3.2", 49 | "typescript": "^4.9.4" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /postinstall.mjs: -------------------------------------------------------------------------------- 1 | import { promisify } from "util"; 2 | import { exec } from "child_process"; 3 | import { readFileSync, writeFileSync } from "fs"; 4 | import { config } from "dotenv"; 5 | config(); 6 | 7 | const execPromise = promisify(exec); 8 | 9 | const packageJsonPath = "./package.json"; 10 | let packageJsonOriginalContents; 11 | 12 | async function runThirdwebGenerate() { 13 | try { 14 | // Read the existing package.json content 15 | const originalPackageJsonContent = readFileSync(packageJsonPath, "utf8"); 16 | packageJsonOriginalContents = JSON.parse(originalPackageJsonContent); 17 | 18 | console.log("Running thirdweb generate..."); 19 | const cmd = `npx --yes thirdweb@latest generate --key "${process.env.THIRDWEB_SECRET_KEY}"`; 20 | const { stdout, stderr } = await execPromise(cmd); 21 | 22 | if (stdout) { 23 | console.log(stdout); 24 | console.log("thirdweb generate completed successfully."); 25 | } 26 | if (stderr) { 27 | console.error(stderr); 28 | } 29 | } catch (error) { 30 | console.error("Failed to execute thirdweb generate. Error below:"); 31 | console.error(error); 32 | } finally { 33 | try { 34 | // Restore the original postinstall script in the package.json 35 | packageJsonOriginalContents.scripts.postinstall = "node postinstall.mjs"; 36 | writeFileSync( 37 | packageJsonPath, 38 | JSON.stringify(packageJsonOriginalContents, null, 2) 39 | ); 40 | console.log("Overwrote package.json postinstall script successfully."); 41 | } catch (error) { 42 | console.error( 43 | "Failed to update package.json postinstall script. Error below:" 44 | ); 45 | console.error(error); 46 | } 47 | } 48 | } 49 | 50 | runThirdwebGenerate(); 51 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { VariantProps, cva } from "class-variance-authority"; 4 | import { cn } from "@/lib/utils"; 5 | 6 | const buttonVariants = cva( 7 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 12 | destructive: 13 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 14 | outline: 15 | "border border-input hover:bg-accent hover:text-accent-foreground", 16 | secondary: 17 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 18 | ghost: "hover:bg-accent hover:text-accent-foreground", 19 | link: "underline-offset-4 hover:underline text-primary", 20 | }, 21 | size: { 22 | default: "h-10 py-2 px-4", 23 | sm: "h-9 px-3 rounded-md", 24 | lg: "h-11 px-8 rounded-md", 25 | }, 26 | }, 27 | defaultVariants: { 28 | variant: "default", 29 | size: "default", 30 | }, 31 | } 32 | ); 33 | 34 | export interface ButtonProps 35 | extends React.ButtonHTMLAttributes, 36 | VariantProps { 37 | asChild?: boolean; 38 | } 39 | 40 | const Button = React.forwardRef( 41 | ({ className, variant, size, asChild = false, ...props }, ref) => { 42 | const Comp = asChild ? Slot : "button"; 43 | return ( 44 | 49 | ); 50 | } 51 | ); 52 | Button.displayName = "Button"; 53 | 54 | export { Button, buttonVariants }; 55 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 224 71% 4%; 8 | --foreground: 213 31% 91%; 9 | 10 | --muted: 223 47% 11%; 11 | --muted-foreground: 215.4 16.3% 56.9%; 12 | 13 | --popover: 224 71% 4%; 14 | --popover-foreground: 215 20.2% 65.1%; 15 | 16 | --card: 224 71% 4%; 17 | --card-foreground: 213 31% 91%; 18 | 19 | --border: 216 34% 17%; 20 | --input: 216 34% 17%; 21 | 22 | --primary: 210 40% 98%; 23 | --primary-foreground: 222.2 47.4% 1.2%; 24 | 25 | --secondary: 222.2 47.4% 11.2%; 26 | --secondary-foreground: 210 40% 98%; 27 | 28 | --accent: 216 34% 17%; 29 | --accent-foreground: 210 40% 98%; 30 | 31 | --destructive: 0 63% 31%; 32 | --destructive-foreground: 210 40% 98%; 33 | 34 | --ring: 216 34% 17%; 35 | 36 | --radius: 0.5rem; 37 | 38 | font-family: "Inter", sans-serif; 39 | } 40 | 41 | .light { 42 | --background: 0 0% 100%; 43 | --foreground: 222.2 47.4% 11.2%; 44 | 45 | --muted: 210 40% 96.1%; 46 | --muted-foreground: 215.4 16.3% 46.9%; 47 | 48 | --popover: 0 0% 100%; 49 | --popover-foreground: 222.2 47.4% 11.2%; 50 | 51 | --card: 0 0% 100%; 52 | --card-foreground: 222.2 47.4% 11.2%; 53 | 54 | --border: 214.3 31.8% 91.4%; 55 | --input: 214.3 31.8% 91.4%; 56 | 57 | --primary: 222.2 47.4% 11.2%; 58 | --primary-foreground: 210 40% 98%; 59 | 60 | --secondary: 210 40% 96.1%; 61 | --secondary-foreground: 222.2 47.4% 11.2%; 62 | 63 | --accent: 210 40% 96.1%; 64 | --accent-foreground: 222.2 47.4% 11.2%; 65 | 66 | --destructive: 0 100% 50%; 67 | --destructive-foreground: 210 40% 98%; 68 | 69 | --ring: 215 20.2% 65.1%; 70 | 71 | --radius: 0.5rem; 72 | } 73 | } 74 | 75 | @layer base { 76 | * { 77 | @apply border-border; 78 | } 79 | body { 80 | @apply bg-background text-foreground; 81 | font-feature-settings: "rlig" 1, "calt" 1; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )); 18 | Card.displayName = "Card"; 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )); 30 | CardHeader.displayName = "CardHeader"; 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )); 45 | CardTitle.displayName = "CardTitle"; 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )); 57 | CardDescription.displayName = "CardDescription"; 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )); 65 | CardContent.displayName = "CardContent"; 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )); 77 | CardFooter.displayName = "CardFooter"; 78 | 79 | export { 80 | Card, 81 | CardHeader, 82 | CardFooter, 83 | CardTitle, 84 | CardDescription, 85 | CardContent, 86 | }; 87 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { ThirdwebProvider } from "@thirdweb-dev/react"; 2 | import { CHAIN } from "../const/chains"; 3 | import { Inter } from "next/font/google"; 4 | import Head from "next/head"; 5 | import "../styles/globals.css"; 6 | import type { AppProps } from "next/app"; 7 | import { 8 | LensProvider, 9 | development, 10 | production, 11 | } from "@lens-protocol/react-web"; 12 | import { JsonRpcProvider } from "@ethersproject/providers"; 13 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 14 | 15 | const inter = Inter({ subsets: ["latin"] }); 16 | 17 | function MyApp({ Component, pageProps }: AppProps) { 18 | const queryClient = new QueryClient(); 19 | 20 | return ( 21 |
22 | 23 | Lens Pay 24 | 25 | 26 | 27 | 28 |
29 | 30 | 41 | undefined, 47 | getProvider: async () => 48 | new JsonRpcProvider("https://mumbai.rpc.thirdweb.com"), 49 | }, 50 | }} 51 | > 52 | 53 | 54 | 55 | 56 |
57 |
58 | ); 59 | } 60 | 61 | export default MyApp; 62 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | "./pages/**/*.{ts,tsx}", 6 | "./components/**/*.{ts,tsx}", 7 | "./app/**/*.{ts,tsx}", 8 | "./src/**/*.{ts,tsx}", 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | colors: { 20 | border: "hsl(var(--border))", 21 | input: "hsl(var(--input))", 22 | ring: "hsl(var(--ring))", 23 | background: "hsl(var(--background))", 24 | foreground: "hsl(var(--foreground))", 25 | primary: { 26 | DEFAULT: "hsl(var(--primary))", 27 | foreground: "hsl(var(--primary-foreground))", 28 | }, 29 | secondary: { 30 | DEFAULT: "hsl(var(--secondary))", 31 | foreground: "hsl(var(--secondary-foreground))", 32 | }, 33 | destructive: { 34 | DEFAULT: "hsl(var(--destructive))", 35 | foreground: "hsl(var(--destructive-foreground))", 36 | }, 37 | muted: { 38 | DEFAULT: "hsl(var(--muted))", 39 | foreground: "hsl(var(--muted-foreground))", 40 | }, 41 | accent: { 42 | DEFAULT: "hsl(var(--accent))", 43 | foreground: "hsl(var(--accent-foreground))", 44 | }, 45 | popover: { 46 | DEFAULT: "hsl(var(--popover))", 47 | foreground: "hsl(var(--popover-foreground))", 48 | }, 49 | card: { 50 | DEFAULT: "hsl(var(--card))", 51 | foreground: "hsl(var(--card-foreground))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | keyframes: { 60 | "accordion-down": { 61 | from: { height: 0 }, 62 | to: { height: "var(--radix-accordion-content-height)" }, 63 | }, 64 | "accordion-up": { 65 | from: { height: "var(--radix-accordion-content-height)" }, 66 | to: { height: 0 }, 67 | }, 68 | }, 69 | animation: { 70 | "accordion-down": "accordion-down 0.2s ease-out", 71 | "accordion-up": "accordion-up 0.2s ease-out", 72 | }, 73 | }, 74 | }, 75 | plugins: [require("tailwindcss-animate")], 76 | }; 77 | -------------------------------------------------------------------------------- /components/ui/use-toast.ts: -------------------------------------------------------------------------------- 1 | // Inspired by react-hot-toast library 2 | import * as React from "react"; 3 | 4 | import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; 5 | 6 | const TOAST_LIMIT = 1; 7 | const TOAST_REMOVE_DELAY = 1000000; 8 | 9 | type ToasterToast = ToastProps & { 10 | id: string; 11 | title?: React.ReactNode; 12 | description?: React.ReactNode; 13 | action?: ToastActionElement; 14 | }; 15 | 16 | const actionTypes = { 17 | ADD_TOAST: "ADD_TOAST", 18 | UPDATE_TOAST: "UPDATE_TOAST", 19 | DISMISS_TOAST: "DISMISS_TOAST", 20 | REMOVE_TOAST: "REMOVE_TOAST", 21 | } as const; 22 | 23 | let count = 0; 24 | 25 | function genId() { 26 | count = (count + 1) % Number.MAX_VALUE; 27 | return count.toString(); 28 | } 29 | 30 | type ActionType = typeof actionTypes; 31 | 32 | type Action = 33 | | { 34 | type: ActionType["ADD_TOAST"]; 35 | toast: ToasterToast; 36 | } 37 | | { 38 | type: ActionType["UPDATE_TOAST"]; 39 | toast: Partial; 40 | } 41 | | { 42 | type: ActionType["DISMISS_TOAST"]; 43 | toastId?: ToasterToast["id"]; 44 | } 45 | | { 46 | type: ActionType["REMOVE_TOAST"]; 47 | toastId?: ToasterToast["id"]; 48 | }; 49 | 50 | interface State { 51 | toasts: ToasterToast[]; 52 | } 53 | 54 | const toastTimeouts = new Map>(); 55 | 56 | const addToRemoveQueue = (toastId: string) => { 57 | if (toastTimeouts.has(toastId)) { 58 | return; 59 | } 60 | 61 | const timeout = setTimeout(() => { 62 | toastTimeouts.delete(toastId); 63 | dispatch({ 64 | type: "REMOVE_TOAST", 65 | toastId: toastId, 66 | }); 67 | }, TOAST_REMOVE_DELAY); 68 | 69 | toastTimeouts.set(toastId, timeout); 70 | }; 71 | 72 | export const reducer = (state: State, action: Action): State => { 73 | switch (action.type) { 74 | case "ADD_TOAST": 75 | return { 76 | ...state, 77 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 78 | }; 79 | 80 | case "UPDATE_TOAST": 81 | return { 82 | ...state, 83 | toasts: state.toasts.map((t) => 84 | t.id === action.toast.id ? { ...t, ...action.toast } : t 85 | ), 86 | }; 87 | 88 | case "DISMISS_TOAST": { 89 | const { toastId } = action; 90 | 91 | // ! Side effects ! - This could be extracted into a dismissToast() action, 92 | // but I'll keep it here for simplicity 93 | if (toastId) { 94 | addToRemoveQueue(toastId); 95 | } else { 96 | state.toasts.forEach((toast) => { 97 | addToRemoveQueue(toast.id); 98 | }); 99 | } 100 | 101 | return { 102 | ...state, 103 | toasts: state.toasts.map((t) => 104 | t.id === toastId || toastId === undefined 105 | ? { 106 | ...t, 107 | open: false, 108 | } 109 | : t 110 | ), 111 | }; 112 | } 113 | case "REMOVE_TOAST": 114 | if (action.toastId === undefined) { 115 | return { 116 | ...state, 117 | toasts: [], 118 | }; 119 | } 120 | return { 121 | ...state, 122 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 123 | }; 124 | } 125 | }; 126 | 127 | const listeners: Array<(state: State) => void> = []; 128 | 129 | let memoryState: State = { toasts: [] }; 130 | 131 | function dispatch(action: Action) { 132 | memoryState = reducer(memoryState, action); 133 | listeners.forEach((listener) => { 134 | listener(memoryState); 135 | }); 136 | } 137 | 138 | type Toast = Omit; 139 | 140 | function toast({ ...props }: Toast) { 141 | const id = genId(); 142 | 143 | const update = (props: ToasterToast) => 144 | dispatch({ 145 | type: "UPDATE_TOAST", 146 | toast: { ...props, id }, 147 | }); 148 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); 149 | 150 | dispatch({ 151 | type: "ADD_TOAST", 152 | toast: { 153 | ...props, 154 | id, 155 | open: true, 156 | onOpenChange: (open) => { 157 | if (!open) dismiss(); 158 | }, 159 | }, 160 | }); 161 | 162 | return { 163 | id: id, 164 | dismiss, 165 | update, 166 | }; 167 | } 168 | 169 | function useToast() { 170 | const [state, setState] = React.useState(memoryState); 171 | 172 | React.useEffect(() => { 173 | listeners.push(setState); 174 | return () => { 175 | const index = listeners.indexOf(setState); 176 | if (index > -1) { 177 | listeners.splice(index, 1); 178 | } 179 | }; 180 | }, [state]); 181 | 182 | return { 183 | ...state, 184 | toast, 185 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 186 | }; 187 | } 188 | 189 | export { useToast, toast }; 190 | -------------------------------------------------------------------------------- /components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ToastPrimitives from "@radix-ui/react-toast" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | import { X } from "lucide-react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ToastProvider = ToastPrimitives.Provider 9 | 10 | const ToastViewport = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )) 23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName 24 | 25 | const toastVariants = cva( 26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", 27 | { 28 | variants: { 29 | variant: { 30 | default: "border bg-background", 31 | destructive: 32 | "destructive group border-destructive bg-destructive text-destructive-foreground", 33 | }, 34 | }, 35 | defaultVariants: { 36 | variant: "default", 37 | }, 38 | } 39 | ) 40 | 41 | const Toast = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef & 44 | VariantProps 45 | >(({ className, variant, ...props }, ref) => { 46 | return ( 47 | 52 | ) 53 | }) 54 | Toast.displayName = ToastPrimitives.Root.displayName 55 | 56 | const ToastAction = React.forwardRef< 57 | React.ElementRef, 58 | React.ComponentPropsWithoutRef 59 | >(({ className, ...props }, ref) => ( 60 | 68 | )) 69 | ToastAction.displayName = ToastPrimitives.Action.displayName 70 | 71 | const ToastClose = React.forwardRef< 72 | React.ElementRef, 73 | React.ComponentPropsWithoutRef 74 | >(({ className, ...props }, ref) => ( 75 | 84 | 85 | 86 | )) 87 | ToastClose.displayName = ToastPrimitives.Close.displayName 88 | 89 | const ToastTitle = React.forwardRef< 90 | React.ElementRef, 91 | React.ComponentPropsWithoutRef 92 | >(({ className, ...props }, ref) => ( 93 | 98 | )) 99 | ToastTitle.displayName = ToastPrimitives.Title.displayName 100 | 101 | const ToastDescription = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | ToastDescription.displayName = ToastPrimitives.Description.displayName 112 | 113 | type ToastProps = React.ComponentPropsWithoutRef 114 | 115 | type ToastActionElement = React.ReactElement 116 | 117 | export { 118 | type ToastProps, 119 | type ToastActionElement, 120 | ToastProvider, 121 | ToastViewport, 122 | Toast, 123 | ToastTitle, 124 | ToastDescription, 125 | ToastClose, 126 | ToastAction, 127 | } 128 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { Skeleton } from "@/components/ui/skeleton"; 3 | import { 4 | ConnectWallet, 5 | MediaRenderer, 6 | useAddress, 7 | useBalance, 8 | useNetworkMismatch, 9 | useSDK, 10 | useSwitchChain, 11 | } from "@thirdweb-dev/react"; 12 | import type { NextPage } from "next"; 13 | import { CHAIN } from "../const/chains"; 14 | import { Separator } from "@/components/ui/separator"; 15 | import { Input } from "@/components/ui/input"; 16 | import { useState } from "react"; 17 | import { useDebounce } from "use-debounce"; 18 | import { Profile, useSearchProfiles } from "@lens-protocol/react-web"; 19 | import { useToast } from "@/components/ui/use-toast"; 20 | import { ethers } from "ethers"; 21 | import { ChevronLeft } from "lucide-react"; 22 | 23 | const Home: NextPage = () => { 24 | // Allows us to show messages to user 25 | const { toast } = useToast(); 26 | 27 | // Get connected wallet address 28 | const address = useAddress(); 29 | // Check if they're on Polygon zkEVM 30 | const wrongNetwork = useNetworkMismatch(); 31 | // Function to switch chains to Polygon zkEVM 32 | const switchChain = useSwitchChain(); 33 | // Read the balance of ETH of the connected wallet on Polygon zkEVM 34 | const { data: balance, isLoading: loadingBalance } = useBalance(); 35 | 36 | // Access connected wallet using thirdweb SDK 37 | const sdk = useSDK(); 38 | 39 | // Manage search state with a debounce to hit API when user stops typing 40 | const [search, setSearch] = useState(""); 41 | const [debouncedSearch] = useDebounce(search, 500); 42 | 43 | // Search for the Lens profiles that match the search query 44 | const { data: profiles, loading: loadingProfiles } = useSearchProfiles({ 45 | query: debouncedSearch, 46 | }); 47 | 48 | // Once user selects profile, we'll store it here 49 | const [selectedProfile, setSelectedProfile] = useState(null); 50 | 51 | // Set the amount to pay 52 | const [amount, setAmount] = useState(""); 53 | 54 | // Loading state while pay transaction is being sent 55 | const [loadingPay, setLoadingPay] = useState(false); 56 | 57 | async function handlePay() { 58 | setLoadingPay(true); 59 | const amountBN = ethers.utils.parseEther(amount); 60 | 61 | if (!selectedProfile) { 62 | toast({ 63 | title: "Invalid Profile selected.", 64 | description: "Select a valid profile to send payment to.", 65 | variant: "destructive", 66 | }); 67 | return; 68 | } 69 | 70 | if (!amountBN) { 71 | toast({ 72 | title: "Invalid Amount", 73 | description: "Please enter a valid amount to pay.", 74 | variant: "destructive", 75 | }); 76 | return; 77 | } 78 | 79 | // Does user have enough balance to pay? 80 | if (balance && balance.value.lt(amountBN)) { 81 | toast({ 82 | title: "Insufficient Balance", 83 | description: "You don't have enough balance to pay", 84 | variant: "destructive", 85 | }); 86 | return; 87 | } 88 | 89 | // Try send funds 90 | try { 91 | const tx = await sdk?.wallet.transfer(selectedProfile.ownedBy, amount); 92 | 93 | toast({ 94 | title: "Funds sent successfully!", 95 | description: `Transaction Hash: ${tx?.receipt.transactionHash}`, 96 | }); 97 | 98 | // Clear state 99 | setSelectedProfile(null); 100 | setAmount(""); 101 | setSearch(""); 102 | } catch (error) { 103 | toast({ 104 | title: "An Error Occurred", 105 | description: "Something went wrong. Please try again.", 106 | variant: "destructive", 107 | }); 108 | } finally { 109 | setLoadingPay(false); 110 | } 111 | } 112 | 113 | // Ensure the user connect's their wallet and is on Polygon zkEVM 114 | if (!address || wrongNetwork) { 115 | return ( 116 |
117 |

118 | Connect Your Wallet 119 |

120 |

121 | Connect your wallet so we can read your balance and ask you to sign 122 | transactions. 123 |

124 | 125 | {!address && } 126 | {wrongNetwork && ( 127 | 130 | )} 131 |
132 | ); 133 | } 134 | 135 | return ( 136 |
137 | {!selectedProfile && ( 138 | <> 139 |

Your Balance

140 | 141 | {!loadingBalance ? ( 142 |

143 | {balance?.displayValue} {balance?.symbol} 144 |

145 | ) : ( 146 | 147 | )} 148 | 149 | 150 | 151 |

152 | Pay Someone 153 |

154 | 155 | setSearch(e.target.value)} 160 | /> 161 | 162 | )} 163 | 164 | {!selectedProfile && profiles && profiles.length > 0 ? ( 165 |

166 | Results 167 |

168 | ) : ( 169 |
170 | )} 171 | 172 | {!selectedProfile && loadingProfiles 173 | ? Array.from({ length: 10 }).map((_, i) => ( 174 | 175 | )) 176 | : !selectedProfile && 177 | profiles?.map((profile) => ( 178 |
setSelectedProfile(profile)} 183 | > 184 |
185 | 193 |
194 |

{profile.name}

195 |

196 | {profile.handle} 197 |

198 |
199 |
200 |
201 | ))} 202 | 203 | {selectedProfile && ( 204 | <> 205 |
206 |
207 | setSelectedProfile(null)} 211 | /> 212 |

213 | Pay {selectedProfile?.name || selectedProfile?.handle} 214 |

215 |
216 |

217 | {selectedProfile.ownedBy} 218 |

219 |
220 | setAmount(e.target.value)} 225 | /> 226 | 227 | 234 |
235 |

236 | Your Balance: {balance?.displayValue} {balance?.symbol} 237 |

238 |
239 | 240 | )} 241 |
242 | ); 243 | }; 244 | 245 | export default Home; 246 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Non-Fungible Labs, Inc 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------