├── .github ├── ISSUE_TEMPLATE │ └── issue-template.md └── pull_request_template.md ├── .gitignore ├── README.md ├── components ├── active-link.tsx ├── layout.tsx ├── mint │ ├── change-controller.tsx │ ├── create-stream.tsx │ ├── mint-nft.tsx │ └── rarible-contract-artifact.ts └── use-web3.tsx ├── next-env.d.ts ├── package.json ├── pages ├── _app.tsx ├── api │ └── persist.ts ├── index.tsx ├── mint.tsx └── view.tsx ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── favicon.ico ├── logo-ceramic.svg └── vercel.svg ├── styles ├── Home.module.css ├── globals.css ├── layout.module.css └── mint.module.css ├── tailwind.config.js └── tsconfig.json /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue template 3 | about: Standard issue template. 4 | title: "[Replace me with meaningful title]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Description 11 | 12 | Provide a 2-3 sentence overview of the work to be done. 13 | 14 | # Technical Information 15 | 16 | Provide an explanation of the technical work to be done. 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # [Replace Me With Meaningful Name] - #[Issue] 2 | 3 | ## Description 4 | 5 | Include a summary of the change and which issue it addresses in the title of the PR. 6 | 7 | Include relevant motivation, context, brief description and impact of the change(s). List follow-up tasks here. 8 | 9 | ## How Has This Been Tested? 10 | 11 | Describe the tests that you ran to verify your changes. Provide instructions for reproduction. 12 | 13 | - [ ] Test A (e.g. Test A - New test that ... ran in local, docker, and dev unstable.) 14 | - [ ] Test B 15 | 16 | ## Definition of Done 17 | 18 | Before submitting this PR, please make sure: 19 | 20 | - [ ] The work addresses the description and outcomes in the issue 21 | - [ ] I have added relevant tests for new or updated functionality 22 | - [ ] My code follows conventions, is well commented, and easy to understand 23 | - [ ] My code builds and tests pass without any errors or warnings 24 | - [ ] I have tagged the relevant reviewers 25 | - [ ] I have updated the READMEs of affected packages 26 | - [ ] I have made corresponding changes to the documentation 27 | - [ ] The changes have been communicated to interested parties 28 | 29 | ## References: 30 | 31 | Please list relevant documentation (e.g. tech specs, articles, related work etc.) relevant to this change, and note if the documentation has been updated. 32 | -------------------------------------------------------------------------------- /.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 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ⚠️🛑 **DEPRECATED** 🛑⚠️ 2 | > 3 | > The project was created long time ago. While the code patterns are solid, there is no guarantee, the project can really run. 4 | 5 | This is an example application that uses [Ceramic](https://ceramic.network), based on [Next.js](https://nextjs.org/). 6 | 7 | Fork it freely. 8 | 9 | ## Getting Started 10 | 11 | 1. Install dependencies. 12 | 13 | 2. Create local ENV file with your [web3.storage](https://web3.storage) and [INFURA](https://infura.io) access tokens: 14 | 15 | ``` 16 | WEB3STORAGE_TOKEN=eyJhbGc... 17 | NEXT_PUBLIC_INFURA_TOKEN=b40... 18 | ``` 19 | 20 | 3. Run the development server: 21 | 22 | ```bash 23 | npm run dev 24 | # or 25 | yarn dev 26 | ``` 27 | 28 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 29 | 30 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 31 | 32 | Note: this app is not fully static. There is a backend part, that interacts with web3.storage. The backend parts runs automatically, 33 | when you do `npm run dev` or `npm run start`. 34 | 35 | ## Learn More 36 | 37 | To learn more about Ceramic, take a look at the following resources: 38 | 39 | - [Ceramic Web Site](https://ceramic.network) - introduction to Ceramic Network, 40 | - [Ceramic Developers Documentation](https://developers.ceramic.network/learn/welcome/) - dig deeper into Ceramic. 41 | -------------------------------------------------------------------------------- /components/active-link.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { useRouter } from "next/router"; 4 | 5 | export type Props = { 6 | href: string; 7 | activeClassName: string; 8 | }; 9 | 10 | export function ActiveLink(props: React.PropsWithChildren) { 11 | const router = useRouter(); 12 | const className = router.pathname === props.href ? props.activeClassName : ""; 13 | 14 | return ( 15 | 16 | {props.children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "../styles/layout.module.css"; 3 | import Link from "next/link"; 4 | import { ActiveLink } from "./active-link"; 5 | import { useCeramic } from "use-ceramic"; 6 | 7 | export function Layout(props: React.PropsWithChildren<{}>) { 8 | const ceramic = useCeramic(); 9 | 10 | return ( 11 |
12 | 29 | {props.children} 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /components/mint/change-controller.tsx: -------------------------------------------------------------------------------- 1 | import { TileDocument } from "@ceramicnetwork/stream-tile"; 2 | import React, { FormEvent, useEffect, useState } from "react"; 3 | import styles from "../../styles/mint.module.css"; 4 | import { createNftDidUrl } from "nft-did-resolver"; 5 | import { useWeb3 } from "../use-web3"; 6 | import BigNumber from "bignumber.js"; 7 | import { useCeramic } from "use-ceramic"; 8 | 9 | export function ChangeController(props: { 10 | tile: TileDocument; 11 | token: { contract: string; tokenId: string }; 12 | }) { 13 | const web3 = useWeb3(); 14 | 15 | const [currentController, setCurrentController] = useState( 16 | props.tile.controllers[0] 17 | ); 18 | const [progress, setProgress] = useState(false); 19 | 20 | const nextController = createNftDidUrl({ 21 | chainId: `eip155:${web3.chainId}`, 22 | namespace: `erc721`, 23 | tokenId: props.token.tokenId, 24 | contract: props.token.contract.toLowerCase(), 25 | }); 26 | 27 | useEffect(() => { 28 | const subscription = props.tile.subscribe(() => { 29 | if (props.tile.controllers[0] !== currentController) { 30 | setCurrentController(currentController); 31 | } 32 | }); 33 | 34 | return () => { 35 | subscription.unsubscribe(); 36 | }; 37 | }, [props.tile]); 38 | 39 | const handleChangeClick = () => { 40 | setProgress(true); 41 | props.tile 42 | .update(undefined, { 43 | controllers: [nextController], 44 | }) 45 | .then(async () => { 46 | console.log("updated"); 47 | await props.tile.sync(); 48 | }) 49 | .catch((error) => { 50 | console.error(error); 51 | }) 52 | .finally(() => { 53 | setProgress(false); 54 | }); 55 | }; 56 | 57 | const handleSubmit = (e: FormEvent) => { 58 | e.preventDefault(); 59 | }; 60 | 61 | const renderButton = () => { 62 | if (props.tile.controllers[0] !== nextController) { 63 | return ( 64 | 67 | ); 68 | } else { 69 | return
🪴
; 70 | } 71 | }; 72 | 73 | const formClassName = progress ? styles.disabledForm : ""; 74 | return ( 75 | <> 76 |

3. Change Controller

77 |
78 |
79 | 82 | 89 | 92 | 99 |
100 | {renderButton()} 101 |
102 | 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /components/mint/create-stream.tsx: -------------------------------------------------------------------------------- 1 | import React, { FormEvent, useState } from "react"; 2 | import { useCeramic } from "use-ceramic"; 3 | import styles from "../../styles/mint.module.css"; 4 | import { TileDocument } from "@ceramicnetwork/stream-tile"; 5 | 6 | export function CreateStream(props: { 7 | onCreate: (tile: TileDocument) => void; 8 | }) { 9 | const ceramic = useCeramic(); 10 | const [name, setName] = useState(""); 11 | const [description, setDescription] = useState(""); 12 | const [file, setFile] = useState(undefined); 13 | const [progress, setProgress] = useState(false); 14 | const [streamId, setStreamId] = useState(""); 15 | 16 | const handleSubmit = (e: FormEvent) => { 17 | e.preventDefault(); 18 | setProgress(true); 19 | if (!name) { 20 | setProgress(false); 21 | alert("Add name"); 22 | } 23 | if (!description) { 24 | setProgress(false); 25 | alert("Add description"); 26 | } 27 | if (!file) { 28 | setProgress(false); 29 | alert("Add file"); 30 | } 31 | const formData = new FormData(); 32 | formData.set("file", file!); 33 | fetch("/api/persist", { method: "POST", body: formData }) 34 | .then((r) => r.json()) 35 | .then(async (c) => { 36 | const cid = c.cid; 37 | // @ts-ignore 38 | const tile = await TileDocument.create(ceramic.client, { 39 | name: name, 40 | description: description, 41 | image: cid, 42 | }); 43 | setStreamId(tile.id.toString()); 44 | props.onCreate(tile); 45 | }) 46 | .catch((error) => { 47 | console.error(error); 48 | }) 49 | .finally(() => { 50 | setProgress(false); 51 | }); 52 | }; 53 | 54 | const renderStreamId = () => { 55 | if (!streamId) { 56 | return <>; 57 | } else { 58 | return ( 59 |
60 | 63 | 70 |
71 | ); 72 | } 73 | }; 74 | 75 | const formClassName = progress ? styles.disabledForm : ""; 76 | 77 | return ( 78 | <> 79 |

1. Create Ceramic stream

80 |
81 |
82 | 85 | setName(event.currentTarget.value)} 92 | /> 93 |
94 | 95 |
96 | 99 | setDescription(event.currentTarget.value)} 106 | /> 107 |
108 |
109 | 112 | setFile(event.currentTarget.files?.[0])} 118 | /> 119 |
120 | 121 | 124 | {renderStreamId()} 125 |
126 | 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /components/mint/mint-nft.tsx: -------------------------------------------------------------------------------- 1 | import React, { FormEvent, useState } from "react"; 2 | import { Web3Ethereum } from "@rarible/web3-ethereum"; 3 | import { createRaribleSdk } from "@rarible/protocol-ethereum-sdk"; 4 | import { useWeb3 } from "../use-web3"; 5 | import { TileDocument } from "@ceramicnetwork/stream-tile"; 6 | import styles from "../../styles/mint.module.css"; 7 | import * as ethers from "ethers"; 8 | import { RaribleContractArtifact } from "./rarible-contract-artifact"; 9 | import * as uint8arrays from "uint8arrays"; 10 | import BigNumber from "bignumber.js"; 11 | 12 | function StoreMetadata(props: { 13 | metadata: any; 14 | onMetadataCid: (cid: string) => void; 15 | }) { 16 | const [progress, setProgress] = useState(false); 17 | const [metadataCid, setMetadataCid] = useState(null); 18 | 19 | const handleStoreMetadata = (e: FormEvent) => { 20 | e.preventDefault(); 21 | setProgress(true); 22 | const blob = JSON.stringify(props.metadata); 23 | const file = new File([blob], "metadata.json"); 24 | const formData = new FormData(); 25 | formData.set("file", file); 26 | fetch("/api/persist", { method: "POST", body: formData }) 27 | .then((r) => r.json()) 28 | .then((response) => { 29 | const cid = response.cid; 30 | setMetadataCid(cid); 31 | props.onMetadataCid(cid); 32 | }) 33 | .finally(() => { 34 | setProgress(false); 35 | }); 36 | }; 37 | 38 | const renderMetadataCid = () => { 39 | if (!metadataCid) { 40 | return <>; 41 | } else { 42 | return ( 43 |
44 | 47 | 54 |
55 | ); 56 | } 57 | }; 58 | 59 | const formClassName = progress ? styles.disabledForm : ""; 60 | return ( 61 |
62 |
63 | 66 |