├── .eslintrc.json ├── context.js ├── public ├── favicon.ico └── vercel.svg ├── postcss.config.js ├── tailwind.config.js ├── pages ├── api │ └── hello.ts ├── _app.tsx └── index.tsx ├── next.config.js ├── styles ├── globals.css └── Home.module.css ├── utils └── index.js ├── contract ├── contract.js └── deploy.mjs ├── .gitignore ├── tsconfig.json ├── LICENSE ├── tests └── index.mjs ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /context.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | export const MainContext = createContext(); -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PBillingsby/arweave-nextjs-template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [require("daisyui")], 11 | }; 12 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: false, 4 | trailingSlash: true, 5 | assetPrefix: './', 6 | images: { 7 | domains: ['arweave.net'], 8 | }, 9 | webpack5: true, 10 | webpack: (config) => { 11 | config.resolve.fallback = { fs: false }; 12 | return config; 13 | }, 14 | } 15 | 16 | module.exports = nextConfig -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | import { MainContext } from '../context' 4 | import { arweave, warp } from '../utils/index' 5 | 6 | function MyApp({ Component, pageProps }: AppProps) { 7 | return ( 11 | 12 | ) 13 | } 14 | 15 | export default MyApp 16 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | html, 6 | body { 7 | padding: 0; 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 10 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 11 | } 12 | 13 | a { 14 | color: inherit; 15 | text-decoration: none; 16 | } 17 | 18 | * { 19 | box-sizing: border-box; 20 | } -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | import Arweave from 'arweave'; 2 | import { WarpFactory, defaultCacheOptions } from 'warp-contracts' 3 | 4 | export const arweave = Arweave.init({ 5 | host: 'arweave.net', 6 | port: 443, 7 | protocol: 'https' 8 | }) 9 | 10 | // Creating local || mainnet Warp instance 11 | // Remove { ...defaultCacheOptions, inMemory: true } for logs and cache files 12 | export const warp = process.env.WARP === 'local' ? WarpFactory.forTestnet() : WarpFactory.forMainnet({ ...defaultCacheOptions, inMemory: true }) -------------------------------------------------------------------------------- /contract/contract.js: -------------------------------------------------------------------------------- 1 | const functions = { click } 2 | 3 | export function handle(state, action) { 4 | // Checks if action.input function is defined 5 | if (Object.keys(functions).includes(action.input.function)) { 6 | // Calls input function 7 | return functions[action.input.function](state, action) 8 | } 9 | throw new ContractError('function not defined!') 10 | } 11 | 12 | function click(state, action) { 13 | // Updates and returns new state 14 | state.clicks = state.clicks + 1; 15 | return { state } 16 | } -------------------------------------------------------------------------------- /.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*.local 30 | .env 31 | wallet.json 32 | logs 33 | .vscode 34 | cache 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx", 26 | "utils/index.js", 27 | "contract/deploy.mjs", 28 | "tests/index.mjs", 29 | "contract/contract.js" 30 | ], 31 | "exclude": [ 32 | "node_modules" 33 | ] 34 | } -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import { useContext } from 'react' 4 | import { MainContext } from '../context' 5 | import styles from '../styles/Home.module.css' 6 | 7 | const Home: NextPage = () => { 8 | const { arweave, warp } = useContext(MainContext) 9 | 10 | return ( 11 |
12 | 13 | Create Next App 14 | 15 | 16 | 17 | 18 |
19 |

20 | Hello world! 21 |

22 |
23 |
24 | ) 25 | } 26 | 27 | export default Home 28 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 pbillingsby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/index.mjs: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu' 2 | import * as assert from 'uvu/assert' 3 | import { WarpFactory } from 'warp-contracts' 4 | 5 | import ArLocal from 'arlocal' 6 | import { deploy } from '../contract/deploy.mjs' 7 | 8 | const arlocal = new ArLocal.default() 9 | test('create contract and test state update', async () => { 10 | // set up local testing 11 | await arlocal.start() 12 | 13 | // validate 14 | try { 15 | const res = await deploy() 16 | 17 | // assert initial state 18 | assert.is(res.cachedValue.state.clicks, 1) 19 | 20 | // update and assert second state 21 | const warp = WarpFactory.forLocal() 22 | const wallet = await warp.testing.generateWallet() 23 | const contract = warp.contract(res.contractTxId).connect(wallet.jwk) 24 | 25 | await contract.writeInteraction({ 26 | function: 'click' 27 | }) 28 | 29 | const secondCall = await contract.readState() 30 | assert.is(secondCall.cachedValue.state.clicks, 2) 31 | } catch (err) { 32 | console.error(err) 33 | } 34 | 35 | // tear down local testing 36 | await arlocal.stop() 37 | }) 38 | 39 | test.run(); 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arweave-nextjs-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "browser": { 6 | "fs": false 7 | }, 8 | "scripts": { 9 | "dev": "next dev", 10 | "build": "next build && next export", 11 | "start": "next start", 12 | "lint": "next lint", 13 | "test": "uvu ./tests", 14 | "fund": "arkb fund-bundler --wallet ./wallet.json --use-bundler https://node2.bundlr.network", 15 | "deploy": "arkb deploy out --use-bundler https://node2.bundlr.network --wallet ./wallet.json", 16 | "deploy-contract": "ts-node contract/deploy.mjs" 17 | }, 18 | "dependencies": { 19 | "arweave": "^1.11.6", 20 | "next": "12.3.0", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0" 23 | }, 24 | "devDependencies": { 25 | "@types/dotenv": "^8.2.0", 26 | "@types/node": "18.7.18", 27 | "@types/react": "18.0.20", 28 | "@types/react-dom": "18.0.6", 29 | "arkb": "^1.1.61", 30 | "arlocal": "^1.1.58", 31 | "arweave-wallet-connector": "^0.0.31", 32 | "autoprefixer": "^10.4.12", 33 | "daisyui": "^2.33.0", 34 | "eslint": "8.23.1", 35 | "eslint-config-next": "12.3.0", 36 | "fs": "^0.0.1-security", 37 | "postcss": "^8.4.18", 38 | "tailwindcss": "^3.2.1", 39 | "typescript": "4.8.3", 40 | "uvu": "^0.5.6", 41 | "warp-contracts": "^1.2.11" 42 | } 43 | } -------------------------------------------------------------------------------- /contract/deploy.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { WarpFactory, defaultCacheOptions } from 'warp-contracts' 3 | import * as dotenv from 'dotenv' 4 | 5 | dotenv.config() 6 | 7 | export async function deploy() { 8 | const isLocal = process.env.WARP === "local" 9 | let warp 10 | // Creating test wallet || importing Arweave wallet from root 11 | // Creating local || mainnet Warp instance 12 | let wallet 13 | if (isLocal) { 14 | warp = WarpFactory.forLocal() 15 | 16 | const testWallet = await warp.testing.generateWallet() 17 | wallet = testWallet.jwk 18 | } 19 | else { 20 | // Remove { ...defaultCacheOptions, inMemory: true } for logs and cache files 21 | warp = WarpFactory.forMainnet({ ...defaultCacheOptions, inMemory: true }); 22 | 23 | wallet = JSON.parse(fs.readFileSync('./wallet.json', 'utf-8')) 24 | } 25 | // Importing the smart contract 26 | const contractSource = fs.readFileSync('contract/contract.js', 'utf-8') 27 | 28 | // Setting contract initState & Deploying first contract 29 | const { contractTxId } = await warp.createContract.deploy({ 30 | wallet, 31 | initState: JSON.stringify({ 32 | "clicks": 0 33 | }), 34 | src: contractSource 35 | }) 36 | 37 | const contract = warp.contract(contractTxId).connect(wallet) 38 | // Interacting with smart contract by calling the click function 39 | await contract.writeInteraction({ 40 | function: 'click' 41 | }) 42 | // Reading and abstracting contract state 43 | const { cachedValue } = await contract.readState() 44 | 45 | return { contractTxId, cachedValue } 46 | } 47 | 48 | deploy() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arweave/NextJS template 2 | Create Permaweb apps using Arweave and NextJS 3 | 4 | ## Contents 5 | * [Usage](#usage) 6 | * [Stack](#stack) 7 | * [Contributions](#contributions) 8 | 9 | ## Usage 10 | For Bundlr, this template uses `node2`. To use `node1`, change references in `package.json` to `node1`. 11 | 12 | Create a `.env` and add `WARP=local` | `WARP=mainnet` depending on use. 13 | ### Wallet 14 | Add wallet.json to root of directory.

Make sure it is acknowledged by .gitignore!

15 | ### Contract deployment test 16 | ```yarn test``` 17 | ### Deploy app 18 | ```yarn build && yarn deploy``` 19 | ### Deploy smart contract 20 | - For Testnet: 21 | - In `.env` add `WARP=local` 22 | - Run ArLocal `npx arlocal` 23 | - Deploy `yarn deploy-contract` 24 | - For Mainnet 25 | - In `.env` add `WARP=mainnet` 26 | - Deploy `yarn deploy-contract` 27 | ### Fund Bundlr 28 | To fund a Bundlr instance. 29 | ```yarn fund ``` 30 | 31 | * Note - Funding can take up to 30 minutes to register funds. 32 | 33 | ## Stack 34 | 41 | 42 | ## Contributing 43 |
    44 |
  1. Create a fork
  2. 45 |
  3. Create your feature branch: git checkout -b my-feature
  4. 46 |
  5. Commit your changes: git commit -am 'Add some feature'
  6. 47 |
  7. Push to the branch: git push origin my-new-feature
  8. 48 |
  9. Submit a pull request 🚀
  10. 49 |
50 | 51 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | 118 | @media (prefers-color-scheme: dark) { 119 | 120 | .card, 121 | .footer { 122 | border-color: #222; 123 | } 124 | 125 | .code { 126 | background: #111; 127 | } 128 | 129 | .logo img { 130 | filter: invert(1); 131 | } 132 | } --------------------------------------------------------------------------------