├── .eslintrc.json
├── public
├── favicon.ico
├── imageNftMetadata.sample.json
├── videoNftMetadata.sample.json
└── vercel.svg
├── styles
├── tailwind.css
├── globals.css
├── Home.module.css
└── main.css
├── postcss.config.js
├── next.config.js
├── next-env.d.ts
├── client.ts
├── lib
└── ga
│ └── index.js
├── .gitignore
├── tsconfig.json
├── types.ts
├── components
├── instructionDrawerRow.tsx
├── nftCard.tsx
├── nftRow.tsx
├── nftDetails.tsx
├── candyMachineMints.tsx
├── quickFix.tsx
├── quickMint.tsx
├── viewer.tsx
├── minthash.tsx
├── editionPrinter.tsx
├── updateUA.tsx
├── closenfts.tsx
├── airdropCannon.tsx
├── holdersnapshot.tsx
├── navbar.jsx
├── burn.tsx
├── nftedit.tsx
├── multisend.tsx
└── nftMinter.tsx
├── pages
├── _document.js
├── index.tsx
├── _app.tsx
└── [which].tsx
├── README.md
├── package.json
├── tailwind.config.js
└── LICENSE.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echohtp/btools/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
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 |
--------------------------------------------------------------------------------
/client.ts:
--------------------------------------------------------------------------------
1 | import { ApolloClient, gql, InMemoryCache } from '@apollo/client'
2 |
3 | const client = new ApolloClient({
4 | uri: "https://graph.holaplex.com/v1",
5 | cache: new InMemoryCache({resultCaching: false})
6 | })
7 |
8 | export default client
9 |
--------------------------------------------------------------------------------
/lib/ga/index.js:
--------------------------------------------------------------------------------
1 | // log the pageview with their URL
2 | export const pageview = (url) => {
3 | window.gtag('config', process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS, {
4 | page_path: url,
5 | })
6 | }
7 |
8 | // log specific events happening.
9 | export const event = ({ action, params }) => {
10 | window.gtag('event', action, params)
11 | }
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | html,
5 | body {
6 | padding: 0;
7 | margin: 0;
8 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
9 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
10 | }
11 |
12 | a {
13 | color: inherit;
14 | text-decoration: none;
15 | }
16 |
17 | * {
18 | box-sizing: border-box;
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 |
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 |
36 | # typescript
37 | *.tsbuildinfo
38 |
--------------------------------------------------------------------------------
/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 | "downlevelIteration": true
18 | },
19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/public/imageNftMetadata.sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "symbol": "",
4 | "description": "",
5 | "seller_fee_basis_points": 1000,
6 | "image": "",
7 | "attributes": [
8 | {
9 | "trait_type": "Type",
10 | "value": "Value"
11 | },
12 | {
13 | "trait_type": "Size",
14 | "value": "Small"
15 | }
16 | ],
17 | "external_url": "",
18 | "properties": {
19 | "category": "image",
20 | "files": [
21 | {
22 | "uri": "",
23 | "type": "image/png"
24 | }
25 | ],
26 | "creators": [
27 | {
28 | "address": "",
29 | "share": 100
30 | }
31 | ]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/types.ts:
--------------------------------------------------------------------------------
1 | export interface Owner {
2 | address: string
3 | associatedTokenAccountAddress: string
4 | }
5 |
6 | export interface Attribute {
7 | value: String
8 | traitType: String
9 | }
10 |
11 | export interface File {
12 | metadataAddress: String
13 | uri: String
14 | fileType: String
15 | }
16 |
17 | export interface Creator{
18 | address: String
19 | metadataAddress: String
20 | share: Number
21 | verified: Boolean
22 | position?: Number
23 | twitterHandle?: String
24 | }
25 |
26 | export interface Nft {
27 | name: string
28 | address: string
29 | description: string
30 | image: string
31 | mintAddress: string
32 | animationUrl: string
33 | externalUrl: string
34 | owner: Owner
35 | files: File[]
36 | attributes: Attribute[]
37 | creators: Creator[]
38 | }
39 |
--------------------------------------------------------------------------------
/public/videoNftMetadata.sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "symbol": "",
4 | "description": "",
5 | "seller_fee_basis_points": 1000,
6 | "image": "",
7 | "animation_url": "",
8 | "attributes": [
9 | {
10 | "trait_type": "Type",
11 | "value": "Value"
12 | },
13 | {
14 | "trait_type": "Size",
15 | "value": "Small"
16 | }
17 | ],
18 | "external_url": "",
19 | "properties": {
20 | "category": "video",
21 | "files": [
22 | {
23 | "uri": "",
24 | "type": "image/png",
25 | "cdn": "false"
26 | },
27 | {
28 | "uri": "",
29 | "type": "video/mp4",
30 | "cdn": "true"
31 | }
32 | ],
33 | "creators": [
34 | {
35 | "address": "",
36 | "share": 100
37 | }
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/components/instructionDrawerRow.tsx:
--------------------------------------------------------------------------------
1 | function showfirstandlastfour (str: string) {
2 | if (str.length > 4) {
3 | return str.substr(0, 4) + '...' + str.substr(str.length - 4, 4)
4 | }
5 | return str
6 | }
7 |
8 | interface InstructionDrawerRowProps {
9 | name: String
10 | to: string
11 | amount?: string
12 | unselect(): void
13 | }
14 |
15 | export const InstructionDrawerRow = (props: InstructionDrawerRowProps) => {
16 | return (
17 |
{
20 | props.unselect()
21 | }}
22 | >
23 |
24 |
{props.name}
25 | {props.to && (
26 |
27 | to: {showfirstandlastfour(props.to)}
28 |
29 | )}
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 | {/* Global Site Tag (gtag.js) - Google Analytics */}
9 |
13 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 | }
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/components/nftCard.tsx:
--------------------------------------------------------------------------------
1 | interface NftCardProps {
2 | image: string
3 | name: String
4 | select(): void
5 | unselect(): void
6 | selected: Boolean
7 | }
8 |
9 | export const NftCard = (props: NftCardProps) => {
10 | let classes = props.selected ? 'shadow-xl w-72 card card-compact bg-base-100 border border-white' : 'shadow-xl w-72 card card-compact bg-base-100'
11 | return (
12 |
13 |
14 |
15 |
21 |
22 |
23 |
{props.name}
24 |
25 | {props.selected ? (
26 |
27 | x
28 |
29 | ) : (
30 |
31 | +
32 |
33 | )}
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/components/nftRow.tsx:
--------------------------------------------------------------------------------
1 | function showfirstandlastfour (str: string) {
2 | if (!str) return ""
3 | if (str.length > 4) {
4 | return str.substr(0, 4) + '...' + str.substr(str.length - 4, 4)
5 | }
6 | return str
7 | }
8 |
9 | interface NftRowProps {
10 | image: string
11 | name: String
12 | select(): void
13 | unselect(): void
14 | selected: Boolean
15 | showHidden?: Boolean
16 | owner?: any
17 | }
18 |
19 | export const NftRow = (props: NftRowProps) => {
20 | let hideme = props.name == '' && !props.showHidden ? ' hidden ' : ''
21 |
22 | let amSelected = props.selected ? ' border border-secondary ' : ''
23 | return (
24 | {
27 | if (props.selected) {
28 | props.unselect()
29 | } else {
30 | props.select()
31 | }
32 | }}
33 | >
34 |
35 |
{props.name}
36 | {props.owner && (
37 |
38 | owner: {showfirstandlastfour(props.owner.address)}
39 | { props.owner.twitterHandle && @{props.owner.twitterHandle} }
40 |
41 |
42 | )}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
36 | ## License
37 |
38 | AGPLv3
--------------------------------------------------------------------------------
/components/nftDetails.tsx:
--------------------------------------------------------------------------------
1 | import { Nft } from '../types'
2 |
3 | interface nftDetailsInterface {
4 | nft: Nft | undefined
5 | }
6 |
7 | export const NftDetails = (props: nftDetailsInterface) => {
8 | const { nft } = props
9 | if (!nft) return (<>>)
10 | console.log('nft details')
11 | console.log(nft)
12 | return (
13 |
14 |
{nft.name}
15 |
{nft.description}
16 | {nft.files.map(file => {
17 | if (file.fileType.includes('video')) {
18 | return
19 | }
20 | if (file.fileType.includes('image')) {
21 | if (file.uri == nft.image) return
22 | else return
23 | }
24 | if (file.fileType.includes('html')) {
25 | return
26 | }
27 | })}
28 |
Creators
29 |
30 | {nft.creators.map((creator) => {
31 | if (creator.twitterHandle)
32 | return (
{creator.twitterHandle} ({creator.address})
)
33 | else
34 | return (
{creator.address} )})}
35 |
36 | {nft.attributes.length > 0 && (
37 | <>
38 |
Attributes
39 |
40 | {nft.attributes.map((attr,i) => (
41 |
42 |
{attr.traitType}
43 | {attr.value}
44 |
45 | ))}
46 |
47 | >
48 | )}
49 |
50 | )
51 | }
52 |
53 | export default NftDetails
54 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import Link from 'next/link'
4 | import { useRouter } from 'next/router'
5 | import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'
6 |
7 | const navbarLinks = [
8 | { title: 'INDEX', href: '' },
9 | { title: 'AIRDROP CANNON', href: 'airdropcannon' },
10 | { title: 'BURN', href: 'burn' },
11 | { title: 'CHANGE UA', href: 'updateua' },
12 | { title: 'CLOSE ACCTS', href: 'closeaccts' },
13 | { title: 'EDITION PRINTER', href: 'editionprinter' },
14 | { title: 'HOLDER SNAPSHOT', href: 'holdersnapshot' },
15 | { title: 'MASS SEND', href: 'multisend' },
16 | { title: 'MINT HASH', href: 'minthash' },
17 | { title: 'NFT EDITOR', href: 'editor' },
18 | { title: 'NFT MINTER', href: 'nftmint' },
19 | { title: 'QUICK FIX', href: 'quickfix' },
20 | { title: 'QUICK MINT', href: 'quickmint' },
21 | { title: 'VIEWER', href: 'viewer' },
22 | // { title: 'CANDY MACHINE MINTS', href: 'cmmints' }
23 | ]
24 |
25 |
26 | const Home: NextPage = () => {
27 | const { query } = useRouter()
28 |
29 | return (
30 |
31 |
32 |
🍌 Tools
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {navbarLinks.map(link => (
43 |
44 |
45 | {link.title}
46 |
47 | ))}
48 |
49 |
50 |
51 |
My little corner of the internet
52 | Pick something from the sidebar
53 |
54 |
55 | {/* grid */}
56 |
57 | {/* // container */}
58 |
59 |
60 |
61 | )
62 | }
63 |
64 | export default Home
65 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "btools",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next dev",
6 | "build": "next build",
7 | "start": "next start",
8 | "lint": "next lint",
9 | "build:css": "postcss styles/tailwind.css -o styles/main.css",
10 | "build:watch": "postcss styles/tailwind.css -o styles/main.css --watch",
11 | "prod:build": "NODE_ENV=production yarn run build:css && yarn run build"
12 | },
13 | "dependencies": {
14 | "@apollo/client": "^3.6.6",
15 | "@glasseaters/hydra-sdk": "^0.3.2",
16 | "@jsonforms/core": "^2.5.2",
17 | "@jsonforms/material-renderers": "^2.5.2",
18 | "@jsonforms/react": "^2.5.2",
19 | "@material-ui/core": "^4.12.4",
20 | "@material-ui/icons": "^4.11.3",
21 | "@metaplex-foundation/js": "^0.13.3",
22 | "@metaplex/js": "^4.12.0",
23 | "@project-serum/anchor": "^0.23.0",
24 | "@saberhq/anchor-contrib": "^1.13.1",
25 | "@saberhq/solana-contrib": "^1.13.2",
26 | "@solana/buffer-layout": "^4.0.0",
27 | "@solana/spl-token": "^0.2.0",
28 | "@solana/spl-token-swap": "^0.1.2",
29 | "@solana/wallet-adapter-base": "^0.9.3",
30 | "@solana/wallet-adapter-react": "^0.15.3",
31 | "@solana/wallet-adapter-react-ui": "^0.9.5",
32 | "@solana/wallet-adapter-wallets": "^0.19.7",
33 | "@solana/web3.js": "^1.50.0",
34 | "@strata-foundation/spl-utils": "^3.9.7",
35 | "@types/react": "18.0.0",
36 | "antd": "^4.20.7",
37 | "autoprefixer": "^10.4.4",
38 | "daisyui": "^2.15.1",
39 | "feather-icons": "^4.29.0",
40 | "formidable": "^2.0.1",
41 | "graphql": "^16.4.0",
42 | "next": "12.1.6",
43 | "nextjs-cors": "^2.1.1",
44 | "nft.storage": "^7.0.0",
45 | "postcss": "^8.4.12",
46 | "postcss-cli": "^9.1.0",
47 | "ramda": "^0.28.0",
48 | "react": "18.1.0",
49 | "react-confetti-explosion": "^1.0.3",
50 | "react-dom": "18.1.0",
51 | "react-feather": "^2.0.10",
52 | "react-hook-form": "^7.27.0",
53 | "react-router-dom": "^6.3.0",
54 | "react-select": "^5.4.0",
55 | "react-toastify": "^9.0.1",
56 | "tailwindcss": "^3.0.23"
57 | },
58 | "devDependencies": {
59 | "@types/formidable": "^2.0.5",
60 | "@types/node": "17.0.18",
61 | "@types/ramda": "^0.28.14",
62 | "@types/react": "18.0.0",
63 | "@types/react-dom": "17.0.15",
64 | "eslint": "8.9.0",
65 | "eslint-config-next": "12.0.10",
66 | "typescript": "4.6.4"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/components/candyMachineMints.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { useWallet } from '@solana/wallet-adapter-react'
3 | import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'
4 | import {
5 | Metaplex,
6 | walletAdapterIdentity,
7 | Nft,
8 | guestIdentity
9 | } from '@metaplex-foundation/js'
10 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
11 | import { Button } from 'antd'
12 | import axios from 'axios'
13 | import { JsonForms } from '@jsonforms/react'
14 | import { toast } from 'react-toastify'
15 |
16 | const schema = {
17 | type: 'object',
18 | properties: {
19 | address: {
20 | type: 'string'
21 | }
22 | },
23 | required: ['address']
24 | }
25 |
26 | export const CandyMachineMints = () => {
27 | const initData = { address: '' }
28 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
29 | const wallet = useWallet()
30 | const [loading, setLoading] = useState(false)
31 | const [data, setData] = useState(initData)
32 | const [foundMints, setFoundMints] = useState()
33 |
34 | const mintIt = async () => {
35 | console.log(data)
36 | if (data.address == '') {
37 | alert('Please enter a candymachine address')
38 | return
39 | }
40 |
41 | setLoading(true)
42 |
43 | const metaplex = Metaplex.make(connection).use(guestIdentity())
44 |
45 | let nfts: any
46 | try {
47 | nfts = await metaplex
48 | .candyMachines()
49 | .findMintedNfts(new PublicKey(data.address))
50 | .run()
51 | setFoundMints(nfts)
52 | toast('Mints found')
53 | } catch (e) {
54 | toast('couldnt load nfts from CM')
55 | setLoading(false)
56 | }
57 | toast('Creating File')
58 | const element = document.createElement('a')
59 |
60 | let file
61 |
62 | file = new Blob(
63 | [JSON.stringify(nfts.map((n: any) => n.mintAddress.toBase58()))],
64 | {
65 | type: 'text/json'
66 | }
67 | )
68 |
69 | element.href = URL.createObjectURL(file)
70 | element.download = data.address.toBase58() + '_mints.json'
71 | document.body.appendChild(element)
72 | element.click()
73 |
74 | setLoading(false)
75 | }
76 |
77 | return (
78 | <>
79 |
80 |
Candy Machine Mints - supply candy machine address
81 | setData(data)}
87 | />
88 |
89 |
94 | Get Mints
95 |
96 |
97 | >
98 | )
99 | }
100 |
101 | export default CandyMachineMints
102 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { ApolloProvider } from '@apollo/client'
2 | import { ApolloClient, gql, InMemoryCache } from '@apollo/client'
3 |
4 | import '../styles/globals.css'
5 | // import '../styles/tailwind.css'
6 | import '../styles/main.css'
7 | import type { AppProps } from 'next/app'
8 | import React, { FC, useMemo } from 'react';
9 | import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react'
10 | import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
11 | import {
12 | LedgerWalletAdapter,
13 | PhantomWalletAdapter,
14 | SlopeWalletAdapter,
15 | SolflareWalletAdapter,
16 | SolletExtensionWalletAdapter,
17 | SolletWalletAdapter,
18 | TorusWalletAdapter,
19 | BackpackWalletAdapter
20 | } from '@solana/wallet-adapter-wallets';
21 | import {
22 | WalletModalProvider
23 | } from '@solana/wallet-adapter-react-ui';
24 | import { clusterApiUrl } from '@solana/web3.js';
25 | import client from '../client'
26 | import { useEffect } from 'react'
27 | import { useRouter } from 'next/router'
28 |
29 | import * as ga from '../lib/ga'
30 | import { ToastContainer } from 'react-toastify'
31 |
32 | import 'antd/dist/antd.css'
33 | // Default styles that can be overridden by your app
34 | require('@solana/wallet-adapter-react-ui/styles.css');
35 |
36 | function MyApp({ Component, pageProps }: AppProps) {
37 |
38 | const router = useRouter()
39 | // The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
40 | const network = WalletAdapterNetwork.Mainnet;
41 |
42 | // You can also provide a custom RPC endpoint.
43 | const endpoint = useMemo(() => clusterApiUrl(network), [network]);
44 |
45 | // @solana/wallet-adapter-wallets includes all the adapters but supports tree shaking and lazy loading --
46 | // Only the wallets you configure here will be compiled into your application, and only the dependencies
47 | // of wallets that your users connect to will be loaded.
48 | const wallets = useMemo(
49 | () => [
50 | new BackpackWalletAdapter(),
51 | new PhantomWalletAdapter(),
52 | new SlopeWalletAdapter(),
53 | new SolflareWalletAdapter({ network }),
54 | new TorusWalletAdapter(),
55 | new LedgerWalletAdapter(),
56 | new SolletWalletAdapter({ network }),
57 | new SolletExtensionWalletAdapter({ network }),
58 | ],
59 | [network]
60 | );
61 |
62 | useEffect(() => {
63 | const handleRouteChange = (url: string) => {
64 | ga.pageview(url)
65 | }
66 | //When the component is mounted, subscribe to router changes
67 | //and log those page views
68 | router.events.on('routeChangeComplete', handleRouteChange)
69 |
70 | // If the component is unmounted, unsubscribe
71 | // from the event with the `off` method
72 | return () => {
73 | router.events.off('routeChangeComplete', handleRouteChange)
74 | }
75 | }, [router.events])
76 |
77 | return (
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | );
89 |
90 | }
91 |
92 | export default MyApp
93 |
--------------------------------------------------------------------------------
/components/quickFix.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { useWallet } from '@solana/wallet-adapter-react'
3 | import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'
4 | import { Metaplex, walletAdapterIdentity, Nft } from '@metaplex-foundation/js'
5 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
6 | import { Button } from 'antd'
7 | import axios from 'axios'
8 | import { JsonForms } from '@jsonforms/react'
9 | import { toast } from 'react-toastify'
10 |
11 | const schema = {
12 | type: 'object',
13 | properties: {
14 | mint: {
15 | type: 'string'
16 | },
17 | url: {
18 | type: 'string'
19 | }
20 | },
21 | required: ['uri']
22 | }
23 |
24 | export const QuickFix = () => {
25 | const initData = { url: '', mint: '' }
26 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
27 | const wallet = useWallet()
28 | const [loading, setLoading] = useState(false)
29 | const [data, setData] = useState(initData)
30 |
31 | const mintIt = async () => {
32 | console.log(data)
33 | if (data.url.length === 0 || !data.mint || data.mint == '') {
34 | alert('Please enter a metadata URL')
35 | return
36 | }
37 |
38 | if (!data.mint || data.mint == '') {
39 | alert('Please enter a mint hash')
40 | return
41 | }
42 |
43 | setLoading(true)
44 | const mints = data.mint.replace(/(^,)|(,$)/g, '').split(',')
45 | toast(mints.length)
46 | try {
47 | const metaplex = Metaplex.make(connection).use(
48 | walletAdapterIdentity(wallet)
49 | )
50 |
51 | let nft: Nft
52 |
53 | for (var i = 0; i < mints.length; i++) {
54 | try {
55 | nft = await metaplex
56 | .nfts()
57 | .findByMint(new PublicKey(mints[i]))
58 | .run()
59 | } catch (e) {
60 | alert('couldnt load nft from mint')
61 | return
62 | }
63 | console.log('gonna try and fix this!')
64 | console.log(data.url)
65 | toast("Updating: ", mints[i])
66 | // need logic to detect of metadata is the same and skip
67 | try {
68 | const updatedNft = await metaplex
69 | .nfts()
70 | .update(nft, {
71 | uri: data.url
72 | })
73 | .run()
74 | console.log('updated!')
75 | console.log(updatedNft.nft.mintAddress.toBase58())
76 | } catch (e) {
77 | console.log(e)
78 | }
79 | }
80 | } catch (e) {
81 | alert('couldnt load url')
82 | console.error(e)
83 | setLoading(false)
84 | }
85 | setLoading(false)
86 | }
87 |
88 | return (
89 | <>
90 |
91 |
Quick Fix - Supply Mint & URL to metaplex json to fix NFT
92 | setData(data)}
98 | />
99 |
100 |
105 | fix it
106 |
107 |
108 | >
109 | )
110 | }
111 |
112 | export default QuickFix
113 |
--------------------------------------------------------------------------------
/components/quickMint.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { useWallet } from '@solana/wallet-adapter-react'
3 | import { Connection, clusterApiUrl } from '@solana/web3.js'
4 | import { Metaplex, walletAdapterIdentity, toMetaplexFileFromBrowser } from '@metaplex-foundation/js'
5 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
6 | import { Button } from 'antd'
7 | import axios from 'axios'
8 | import { JsonForms } from '@jsonforms/react'
9 |
10 | const schema = {
11 | type: 'object',
12 | properties: {
13 | url: {
14 | type: 'string'
15 | }
16 | },
17 | required: ['uri']
18 | }
19 |
20 | export const QuickMint = () => {
21 | const initData = { url: '' }
22 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
23 | const wallet = useWallet()
24 | const [loading, setLoading] = useState(false)
25 | const [data, setData] = useState(initData)
26 |
27 | const mintIt = async () => {
28 | console.log(data)
29 | //@ts-ignore
30 | const selectedFile = document.getElementById('jsonFile').files[0]
31 | console.log(selectedFile)
32 | if (data.url == undefined && selectedFile == undefined) {
33 | alert('Please enter a metadata source')
34 | return
35 | }
36 |
37 | setLoading(true)
38 | try {
39 | const metaplex = Metaplex.make(connection).use(
40 | walletAdapterIdentity(wallet)
41 | )
42 | console.log(wallet.publicKey?.toBase58())
43 | if (selectedFile) {
44 | console.log('gonna try and mint this file')
45 | console.log(selectedFile)
46 | const file = await toMetaplexFileFromBrowser(selectedFile)
47 | const json = JSON.parse(file.buffer.toString())
48 | const { uri, metadata } = await metaplex
49 | .nfts()
50 | .uploadMetadata(json)
51 | .run()
52 | console.log(json)
53 | try {
54 | const nft = await metaplex
55 | .nfts()
56 | .create({
57 | name: json.name,
58 | uri: uri,
59 | sellerFeeBasisPoints: json.seller_fee_basis_points,
60 | symbol: json.symbol
61 | })
62 | .run()
63 | console.log('minted!')
64 | console.log(nft)
65 | } catch (e) {
66 | console.log(e)
67 | }
68 | setLoading(false)
69 |
70 |
71 | } else {
72 | console.log('gonna try and mint this!')
73 | console.log(data.url)
74 | axios.get(data.url).then(async d => {
75 | try {
76 | const nft = await metaplex
77 | .nfts()
78 | .create({
79 | name: d.data.name,
80 | uri: data.url,
81 | sellerFeeBasisPoints: d.data.seller_fee_basis_points
82 | })
83 | .run()
84 | console.log('minted!')
85 | console.log(nft)
86 | } catch (e) {
87 | console.log(e)
88 | }
89 | setLoading(false)
90 | })
91 | }
92 | } catch (e) {
93 | console.error(e)
94 | setLoading(false)
95 | }
96 | }
97 |
98 | return (
99 | <>
100 |
122 | >
123 | )
124 | }
125 |
126 | export default QuickMint
127 |
--------------------------------------------------------------------------------
/components/viewer.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import { Navbar } from './navbar'
4 | import { useMemo, useState } from 'react'
5 |
6 |
7 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'
8 | import { Transaction, PublicKey } from '@solana/web3.js'
9 | import { gql } from '@apollo/client'
10 | import { toast } from 'react-toastify'
11 | import 'react-toastify/dist/ReactToastify.css'
12 | import client from '../client'
13 |
14 | import { NftRow } from './nftRow'
15 | import * as ga from '../lib/ga'
16 | import { Nft } from '../types'
17 | import {NftDetails} from '../components/nftDetails'
18 |
19 | export const Viewer = () => {
20 | const { publicKey, signTransaction, connected } = useWallet()
21 | const { connection } = useConnection()
22 | const [nfts, setNfts] = useState([])
23 | const [nft, setNft] = useState()
24 | const [sending, setSending] = useState([])
25 | const [to, setTo] = useState('')
26 | const [search, setSearch] = useState('')
27 | const [viewer, setViewer] = useState('')
28 | const [loading, setLoading] = useState(false)
29 |
30 | const GET_NFTS = gql`
31 | query GetNfts($owners: [PublicKey!], $limit: Int!, $offset: Int!) {
32 | nfts(owners: $owners, limit: $limit, offset: $offset) {
33 | address
34 | mintAddress
35 | name
36 | description
37 | animationUrl
38 | image
39 | files {
40 | uri
41 | fileType
42 | }
43 | owner {
44 | address
45 | associatedTokenAccountAddress
46 | }
47 | creators {
48 | address
49 | twitterHandle
50 | }
51 | attributes{
52 | traitType
53 | value
54 | }
55 | }
56 | }
57 | `
58 |
59 | useMemo(() => {
60 | try {
61 | let searchKey = new PublicKey(viewer)
62 | client
63 | .query({
64 | query: GET_NFTS,
65 | variables: {
66 | owners: [searchKey],
67 | offset: 0,
68 | limit: 10000
69 | }
70 | })
71 | .then(res => setNfts(res.data.nfts))
72 | ga.event({
73 | action: 'viewer_load',
74 | params: { who: viewer }
75 | })
76 | } catch (e:any) {
77 | if (publicKey) {
78 | client
79 | .query({
80 | query: GET_NFTS,
81 | variables: {
82 | owners: [publicKey?.toBase58()],
83 | offset: 0,
84 | limit: 10000
85 | }
86 | })
87 | .then(res => setNfts(res.data.nfts))
88 | ga.event({
89 | action: 'viewer_load',
90 | params: { who: publicKey?.toBase58() }
91 | })
92 | } else {
93 | setNfts([])
94 | }
95 | }
96 | }, [publicKey, GET_NFTS, viewer])
97 |
98 | return (
99 |
100 |
101 |
Viewer!
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | setViewer(e.target.value)}
114 | />
115 |
116 |
117 | setSearch(e.target.value)}
122 | />
123 |
124 |
125 |
126 | {nfts
127 | .filter((n: Nft) => n.name.toLowerCase().includes(search.toLowerCase()))
128 | .map((n: Nft) => (
129 | {}}
134 | select={() => {
135 | setNft(n)
136 | //@ts-ignore
137 | document.getElementById('my-modal-3').checked = true
138 | }}
139 | selected={false}
140 | />
141 | ))}
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
153 | ✕
154 |
155 |
156 |
157 |
158 |
159 | )
160 | }
161 |
162 | export default Viewer
163 |
--------------------------------------------------------------------------------
/components/minthash.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import { Navbar } from './navbar'
4 | import { useMemo, useState } from 'react'
5 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'
6 | import { gql } from '@apollo/client'
7 | import 'react-toastify/dist/ReactToastify.css'
8 | import client from '../client'
9 | import React from 'react'
10 | import { NftRow } from './nftRow'
11 |
12 |
13 |
14 | const MintHash: NextPage = () => {
15 | interface Nft {
16 | mintAddress: string
17 | name: string
18 | image: string
19 | owner: any
20 | }
21 |
22 | const { publicKey, signTransaction, connected } = useWallet()
23 | const { connection } = useConnection()
24 | const [nfts, setNfts] = useState([])
25 | const [sending, setSending] = useState([])
26 | const [search, setSearch] = useState("")
27 | const [loading, setLoading] = useState(false)
28 |
29 | const downloadFile = (all: boolean = false) => {
30 | const element = document.createElement('a')
31 |
32 | let file
33 |
34 | if (all)
35 | file = new Blob([JSON.stringify(nfts.map(n => n.mintAddress))], {
36 | type: 'text/json'
37 | })
38 | else
39 | file = new Blob([JSON.stringify(sending.map(n => n.mintAddress))], {
40 | type: 'text/json'
41 | })
42 |
43 | element.href = URL.createObjectURL(file)
44 | element.download = publicKey?.toBase58() + '_minthash.json'
45 | document.body.appendChild(element)
46 | element.click()
47 | }
48 |
49 | const GET_NFTS = gql`
50 | query GetNfts($creators: [PublicKey!], $limit: Int!, $offset: Int!) {
51 | nfts(creators: $creators, limit: $limit, offset: $offset) {
52 | mintAddress
53 | name
54 | image
55 | owner {
56 | address
57 | }
58 | }
59 | }
60 | `
61 |
62 | useMemo(() => {
63 | setLoading(true)
64 | if (publicKey?.toBase58()) {
65 | client
66 | .query({
67 | query: GET_NFTS,
68 | variables: {
69 | creators: [publicKey?.toBase58()],
70 | offset: 0,
71 | limit: 10000
72 | }
73 | })
74 | .then(res => setNfts(res.data.nfts))
75 | setLoading(false)
76 | } else {
77 | setNfts([])
78 | setLoading(false)
79 | }
80 | }, [publicKey, GET_NFTS])
81 |
82 | return (
83 |
84 |
85 |
Mint Hash Getter
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | (setSearch(e.target.value))} />
95 | downloadFile(true)}
98 | >
99 | Get All
100 | {sending.length > 0 ? {
101 | //@ts-ignore
102 | document.getElementById("my-drawer").checked = true
103 | }} className="mx-3 btn btn-secondary">{sending.length} : {sending.length} }
104 |
105 |
106 | {/* {nfts.filter((n)=>(n.name.includes(search))).map(n => ( */}
107 | {loading && <>
LOADING.... >}
108 | {nfts.map(n => (
109 | {
115 | setSending(sending.filter(item => item !== n))
116 | }}
117 | select={() => {
118 | setSending([...sending, n])
119 | }}
120 | selected={sending.includes(n)}
121 | />
122 | ))}
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | {sending.map(n => (
131 |
132 | {
136 | setSending(sending.filter(item => item !== n))
137 | }}
138 | select={() => {
139 | setSending([...sending, n])
140 | }}
141 | selected={sending.includes(n)}
142 | />
143 |
144 | ))}
145 |
146 | { (sending.length > 0) ?
147 | <>
148 |
149 | {
153 | navigator.clipboard.writeText(
154 | JSON.stringify(sending.map(n => n.mintAddress))
155 | )
156 | }}
157 | >
158 | Copy Selected Mints
159 |
160 |
161 |
162 | downloadFile()}
165 | >
166 | Download JSON File
167 |
168 |
169 | > : <>Select some NFTs! >}
170 |
171 |
172 |
173 |
174 | )
175 | }
176 |
177 | export default MintHash
178 |
--------------------------------------------------------------------------------
/components/editionPrinter.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useMemo } from 'react'
2 | import { useWallet } from '@solana/wallet-adapter-react'
3 | import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'
4 | import { Metaplex, walletAdapterIdentity , Nft} from '@metaplex-foundation/js'
5 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
6 | import { Button } from 'antd'
7 | import { gql } from '@apollo/client'
8 | import client from '../client'
9 | import { JsonForms } from '@jsonforms/react'
10 | import * as ga from '../lib/ga'
11 |
12 | export const EditionPrinter = () => {
13 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
14 | const wallet = useWallet()
15 | const [loading, setLoading] = useState(false)
16 | const initData = { destinationAddress: wallet.publicKey?.toBase58(), nfts: '', mintAddress: '' }
17 | const [data, setData] = useState(initData)
18 | const [nfts, setNfts] = useState([])
19 | const [lists, setLists] = useState([])
20 | const [schema, setSchema] = useState({})
21 |
22 | const GET_NFTS = gql`
23 | query GetNfts($creators: [PublicKey!], $limit: Int!, $offset: Int!) {
24 | nfts(owners: $creators, limit: $limit, offset: $offset) {
25 | address
26 | mintAddress
27 | name
28 | description
29 | image
30 | owner {
31 | address
32 | associatedTokenAccountAddress
33 | }
34 | }
35 | }
36 | `
37 |
38 | useMemo(() => {
39 | if (wallet.publicKey?.toBase58()) {
40 | client
41 | .query({
42 | query: GET_NFTS,
43 | variables: {
44 | creators: [wallet.publicKey?.toBase58()],
45 | offset: 0,
46 | limit: 10000
47 | }
48 | })
49 | .then(res => {
50 | var mapResult = res.data.nfts.map((n: Nft) => {
51 | return { const: n.mintAddress, title: n.name }
52 | }, {})
53 | setNfts(mapResult)
54 | setSchema({
55 | type: 'object',
56 | properties: {
57 | destinationAddress: {
58 | type: 'string'
59 | },
60 | nfts: {
61 | type: 'string',
62 | title: 'Nft',
63 | oneOf: mapResult
64 | },
65 | mintHash: {
66 | type: 'string'
67 | }
68 | },
69 | required: ['uri']
70 | })
71 | console.log(mapResult)
72 | })
73 | } else {
74 | setNfts([])
75 | setSchema({
76 | type: 'object',
77 | properties: {
78 | destinationAddress: {
79 | type: 'string'
80 | },
81 | nfts: {
82 | type: 'string',
83 | title: 'Nft',
84 | enum: []
85 | },
86 | mintHash: {
87 | type: 'string'
88 | }
89 | },
90 | required: ['uri']
91 | })
92 | }
93 | }, [wallet, GET_NFTS])
94 |
95 | // useMemo(async()=>{
96 | // if (wallet.publicKey?.toBase58()) {
97 | // console.log("ok");
98 |
99 | // try {
100 | // const metaplex = Metaplex.make(connection).use(
101 | // walletAdapterIdentity(wallet)
102 | // )
103 | // console.log("ok 7 ");
104 |
105 | // const nfts = await metaplex.nfts().findAllByCreator(wallet.publicKey).run()
106 | // console.log("ok 8 ");
107 | // console.log(nfts);
108 |
109 | // nfts.map((n,i)=>{
110 | // console.log(n)
111 | // })
112 | // }catch(e:any){
113 | // console.log("error")
114 | // console.log(e)
115 | // }
116 | // }
117 | // },[wallet])
118 |
119 | const printIt = async () => {
120 | console.log(data)
121 | if (data.mintHash == '' && data.nfts == '') {
122 | alert('mint hash needed')
123 | return
124 | }
125 |
126 | if (data.destinationAddress == ''){
127 | alert('destination address needed')
128 | return
129 | }
130 |
131 | setLoading(true)
132 | try {
133 | const metaplex = Metaplex.make(connection).use(
134 | walletAdapterIdentity(wallet)
135 | )
136 | const nftPk = (data.mintHash && data.mintHash !== "" && data.nfts !== "") ? new PublicKey(data.mintHash) : new PublicKey(data.nfts)
137 | // (data.mintHash !== null && data.mintHash !== "" && data.nfts !== "")
138 | // ? new PublicKey(data.nfts)
139 | // : new PublicKey(data.mintHash)
140 |
141 | console.log("nft: ", nftPk.toBase58())
142 | const nft = await metaplex
143 | .nfts()
144 | .findByMint(nftPk)
145 | .run()
146 |
147 | const owners = data.destinationAddress.split(',') ? data.destinationAddress.replace(/(^,)|(,$)/g, '').split(',') : data.destinationAddress
148 | console.log(owners)
149 | for (var i =0 ; i < owners.length; i++){
150 | const newOwner = new PublicKey(owners[i])
151 | await metaplex
152 | .nfts()
153 | .printNewEdition(nft, { newOwner })
154 | .run()
155 | }
156 | alert('done!')
157 | ga.event({action: 'edition_print',
158 | params: { mint: nftPk.toBase58() }})
159 | } catch (e: any) {
160 | alert(`error: ${e.message}`)
161 | console.error(e.message)
162 | setLoading(false)
163 | }
164 | setLoading(false)
165 | }
166 |
167 | return (
168 | <>
169 |
170 |
Edition Printer - Make prints from your Master Edition NFTs
171 | setData(data)}
177 | />
178 |
179 |
184 | Print It
185 |
186 |
187 | >
188 | )
189 | }
190 |
191 | export default EditionPrinter
192 |
--------------------------------------------------------------------------------
/components/updateUA.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useMemo } from 'react'
2 | import { useWallet } from '@solana/wallet-adapter-react'
3 | import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'
4 | import { Metaplex, walletAdapterIdentity, Nft } from '@metaplex-foundation/js'
5 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
6 | import { Button } from 'antd'
7 | import { gql } from '@apollo/client'
8 | import client from '../client'
9 | import { JsonForms } from '@jsonforms/react'
10 | import * as ga from '../lib/ga'
11 | import { toast } from 'react-toastify'
12 |
13 | export const UpdateUA = () => {
14 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
15 | const wallet = useWallet()
16 | const [loading, setLoading] = useState(false)
17 | const initData = { newUpdateAuthority: wallet.publicKey?.toBase58(), nft: '', allOrOne: "Update One" }
18 | const [data, setData] = useState(initData)
19 | const [nfts, setNfts] = useState([])
20 | const [lists, setLists] = useState([])
21 | const [schema, setSchema] = useState({})
22 |
23 | const GET_NFTS = gql`
24 | query GetNfts($owners: [PublicKey!], $updateAuthorities: [PublicKey!], $limit: Int!, $offset: Int!) {
25 | nfts(owners: $owners, updateAuthorities: $updateAuthorities, limit: $limit, offset: $offset) {
26 | address
27 | mintAddress
28 | name
29 | description
30 | image
31 | owner {
32 | address
33 | associatedTokenAccountAddress
34 | }
35 | }
36 | }
37 | `
38 |
39 | useMemo(() => {
40 | if (wallet.publicKey?.toBase58()) {
41 | client
42 | .query({
43 | query: GET_NFTS,
44 | variables: {
45 | owners: [wallet.publicKey?.toBase58()],
46 | updateAuthorities: [wallet.publicKey?.toBase58()],
47 | offset: 0,
48 | limit: 10000
49 | }
50 | })
51 | .then(res => {
52 | var mapResult = res.data.nfts.map((n: Nft) => {
53 | return { const: n.mintAddress, title: n.name }
54 | }, {})
55 | setNfts(mapResult)
56 | setSchema({
57 | type: 'object',
58 | properties: {
59 | newUpdateAuthority: {
60 | type: 'string'
61 | },
62 | nft: {
63 | type: 'string',
64 | title: 'Nft',
65 | oneOf: mapResult
66 | },
67 |
68 | allOrOne: {
69 | type: "string",
70 | enum: [
71 | "Update One",
72 | "Update All",
73 | ]
74 | },
75 | },
76 | required: ['uri']
77 | })
78 | console.log(mapResult)
79 | })
80 | } else {
81 | setNfts([])
82 | setSchema({
83 | type: 'object',
84 | properties: {
85 | newUpdateAuthority: {
86 | type: 'string'
87 | },
88 | nft: {
89 | type: 'string',
90 | title: 'Nft',
91 | enum: []
92 | },
93 | allOrOne: {
94 | type: "string",
95 | enum: [
96 | "Update One",
97 | "Update All",
98 | ]
99 | },
100 | },
101 | required: ['uri']
102 | })
103 | }
104 | }, [wallet, GET_NFTS])
105 |
106 | const updateIt = async () => {
107 | console.log(data)
108 |
109 |
110 | if (data.newUpdateAuthority == ''){
111 | alert('update authority address needed')
112 | return
113 | }
114 |
115 |
116 | if (!data.allOrOne){
117 | alert('select one the box')
118 | return
119 | }
120 |
121 |
122 | if(data.allOrOne == "Update One" && data.nft == ""){
123 | alert('mint hash needed')
124 | return
125 | }
126 |
127 |
128 | setLoading(true)
129 | try {
130 | const metaplex = Metaplex.make(connection).use(
131 | walletAdapterIdentity(wallet)
132 | )
133 |
134 | console.log(data)
135 |
136 | if (data.allOrOne == "Update All"){
137 | console.log("update all")
138 | for (var i=0;i < nfts.length; i++){
139 | const nftPk = new PublicKey(nfts[i].const)
140 | console.log("nft: ", nftPk.toBase58())
141 |
142 | const nft = await metaplex.nfts().findByMint(nftPk).run()
143 | const newNft = await metaplex
144 | .nfts()
145 | .update(nft, {newUpdateAuthority: new PublicKey(data.newUpdateAuthority) })
146 | .run()
147 | toast(`updated: ${newNft.nft.mintAddress}`)
148 | }
149 | ga.event({action: 'update_ua',
150 | params: { mint: nfts[i].mintAddress }})
151 | toast(`done, updated: ${nfts[i].mintAddress}`)
152 | setLoading(false)
153 | return // Done
154 | }
155 |
156 | if (data.allOrOne == "Update One"){
157 | console.log("update one")
158 | const nftPk = new PublicKey(data.nft)
159 | console.log("nft: ", nftPk.toBase58())
160 |
161 | const nft = await metaplex.nfts().findByMint(nftPk).run()
162 | const newNft = await metaplex
163 | .nfts()
164 | .update(nft, {newUpdateAuthority: new PublicKey(data.newUpdateAuthority) })
165 | .run()
166 | toast(`updated: ${newNft.nft.mintAddress}`)
167 | ga.event({action: 'update_ua',
168 | params: { mint: data.nft }})
169 | toast(`done, updated: ${data.nft}`)
170 | setLoading(false)
171 | }
172 |
173 | } catch (e: any) {
174 | alert(`error: ${e.message}`)
175 | console.error(e.message)
176 | setLoading(false)
177 | }
178 | setLoading(false)
179 | }
180 |
181 | return (
182 | <>
183 |
184 |
Update Update Authority
185 | setData(data)}
191 | />
192 |
193 |
198 | Update It
199 |
200 |
201 | >
202 | )
203 | }
204 |
205 | export default UpdateUA
206 |
--------------------------------------------------------------------------------
/components/closenfts.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import { Navbar } from './navbar'
4 | import { useMemo, useState } from 'react'
5 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'
6 | import { Connection, PublicKey, Transaction } from '@solana/web3.js'
7 | import {
8 | TOKEN_PROGRAM_ID,
9 | createCloseAccountInstruction
10 | } from '@solana/spl-token'
11 | import { Button } from 'antd'
12 | import * as ga from '../lib/ga'
13 |
14 | const CloseNfts = () => {
15 | const { publicKey, signTransaction, connected } = useWallet()
16 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
17 |
18 | interface Token {
19 | pubkey: PublicKey
20 | balance: any
21 | decimals: any
22 | }
23 |
24 | const [loading, setLoading] = useState(false)
25 | const [txLoading, setTxLoading] = useState(false)
26 | const [tokens, setTokens] = useState([])
27 | const [solPrice, setSolPrice] = useState(0)
28 |
29 | useMemo(() => {
30 | var url =
31 | 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd'
32 | fetch(url).then(async response => {
33 | let price = await response.json()
34 | setSolPrice(price.solana.usd)
35 | })
36 | }, [])
37 |
38 | useMemo(async () => {
39 | if (publicKey?.toBase58()) {
40 | // start loading
41 | setLoading(true)
42 | try {
43 | // Get list of all token accounts
44 | const userTokens = await connection.getTokenAccountsByOwner(publicKey, {
45 | programId: TOKEN_PROGRAM_ID
46 | })
47 |
48 | // Promise array to get balance for each token account
49 | const pAll = userTokens.value.map(ut =>
50 | connection.getTokenAccountBalance(ut.pubkey)
51 | )
52 |
53 | // Get balance for each token account
54 | Promise.all(pAll).then(balances => {
55 | // Create array of all tokens and balances
56 | let allTokens = userTokens.value.map((ut, i) => ({
57 | pubkey: ut.pubkey,
58 | balance: balances[i].value.uiAmount,
59 | decimals: balances[i].value.decimals
60 | }))
61 |
62 | // Filter out tokens that have zero balance and decimals 0 (NFT)
63 | setTokens(allTokens.filter(t => t.balance == 0 && t.decimals == 0))
64 |
65 | // stop loading
66 | setLoading(false)
67 | })
68 |
69 | // catch error
70 | } catch (e: any) {
71 | // stop loading
72 | setLoading(false)
73 | }
74 | }
75 | }, [publicKey])
76 |
77 | const closeAll = async () => {
78 | if (!publicKey || !signTransaction) {
79 | return
80 | }
81 |
82 | // Set Transaction Loading to true
83 | setTxLoading(true)
84 |
85 | // list of all closed token accounts
86 | let sentTokens: Token[] = []
87 |
88 | // close 10 at a time
89 | for (var i = 0; i < Math.ceil(tokens.length / 10); i++) {
90 | const tx = new Transaction()
91 |
92 | // list of tokens accounts queued to close
93 | let queuedTokens = []
94 |
95 | // load up the 10 to close
96 | for (var j = 0; j < 10; j++) {
97 | // make sure we dont overflow the list length
98 | if (tokens[i * 10 + j]) {
99 | // add the token to the list of tokens to be closed
100 | queuedTokens.push(tokens[i * 10 + j])
101 |
102 | // add the instruction to the transaction
103 | tx.add(
104 | createCloseAccountInstruction(
105 | new PublicKey(tokens[i * 10 + j].pubkey),
106 | publicKey,
107 | publicKey,
108 | [],
109 | TOKEN_PROGRAM_ID
110 | )
111 | )
112 | }
113 | }
114 |
115 | // get recent blockhash
116 | tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
117 |
118 | // set whos paying for the tx
119 | tx.feePayer = publicKey
120 |
121 | // pop for user signature
122 | let signed: Transaction | undefined = undefined
123 |
124 | try {
125 | signed = await signTransaction(tx)
126 | } catch (e: any) {
127 | console.log(e.message)
128 | // didnt complete outer loop - if token was sent, remove it from tokens
129 | setTokens(tokens.filter(t => !sentTokens.includes(t)))
130 | setTxLoading(false)
131 | return
132 | }
133 |
134 | let signature: string | undefined = undefined
135 |
136 | // send transaction
137 | try {
138 | signature = await connection.sendRawTransaction(signed.serialize())
139 | await connection.confirmTransaction(signature, 'confirmed')
140 |
141 | console.log('Transaction successful')
142 |
143 | // tx was successful, add queued tokens to sent tokens
144 | sentTokens = [...queuedTokens, ...sentTokens]
145 |
146 | ga.event({
147 | action: 'close_nft_accounts',
148 | params: { count: queuedTokens.length }
149 | })
150 | } catch (e: any) {
151 | setTxLoading(false)
152 |
153 | // didnt complete outer loop - if token was sent, remove it from tokens
154 | setTokens(tokens.filter(t => !sentTokens.includes(t)))
155 | }
156 | }
157 |
158 | // record the event
159 | ga.event({
160 | action: 'close_nft_success',
161 | params: {
162 | count: tokens.length,
163 | value: Number(tokens.length * 0.002 * solPrice).toFixed(2)
164 | }
165 | })
166 |
167 | // tx event done
168 | setTxLoading(false)
169 |
170 | // clear tokens list
171 | setTokens([])
172 | }
173 |
174 | return (
175 |
176 |
177 | {!connected &&
Connect your wallet first }
178 | {connected && loading && (
179 | <>
180 |
181 |
182 |
189 | Loading...
190 |
191 |
192 |
193 | >
194 | )}
195 | {connected && !loading && (
196 | <>
197 |
198 |
199 |
206 | Close Accounts: {tokens.length} ($
207 | {Number(tokens.length * 0.002 * solPrice).toFixed(2)})
208 |
209 |
210 |
211 | >
212 | )}
213 |
214 |
215 |
216 |
217 | )
218 | }
219 |
220 | export default CloseNfts
221 |
--------------------------------------------------------------------------------
/components/airdropCannon.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useMemo } from 'react'
2 | import { useWallet } from '@solana/wallet-adapter-react'
3 | import {
4 | Connection,
5 | clusterApiUrl,
6 | PublicKey,
7 | Transaction
8 | } from '@solana/web3.js'
9 | import { Metaplex, walletAdapterIdentity } from '@metaplex-foundation/js'
10 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
11 | import { Button } from 'antd'
12 | import { gql } from '@apollo/client'
13 | import client from '../client'
14 | import { JsonForms } from '@jsonforms/react'
15 | import { Nft } from '../types'
16 | import * as ga from '../lib/ga'
17 | import { toast } from 'react-toastify'
18 | import 'react-toastify/dist/ReactToastify.css'
19 |
20 | import {
21 | createAssociatedTokenAccountInstruction,
22 | getAssociatedTokenAddress,
23 | TOKEN_PROGRAM_ID,
24 | ASSOCIATED_TOKEN_PROGRAM_ID,
25 | createTransferInstruction
26 | } from '@solana/spl-token'
27 |
28 | export const AirdropCannon = () => {
29 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
30 | const wallet = useWallet()
31 | const { publicKey, signTransaction } = useWallet()
32 | const [loading, setLoading] = useState(false)
33 | const initData = { destinationAddress: wallet.publicKey?.toBase58() }
34 | const [data, setData] = useState(initData)
35 | const [nfts, setNfts] = useState([])
36 | const [lists, setLists] = useState([])
37 |
38 | const schema = {
39 | type: 'object',
40 | properties: {
41 | destinationAddress: {
42 | type: 'string'
43 | }
44 | }
45 | }
46 |
47 | const GET_NFTS = gql`
48 | query GetNfts($owners: [PublicKey!], $limit: Int!, $offset: Int!) {
49 | nfts(owners: $owners, limit: $limit, offset: $offset) {
50 | address
51 | mintAddress
52 | name
53 | description
54 | image
55 | creators {
56 | address
57 | }
58 | owner {
59 | address
60 | associatedTokenAccountAddress
61 | }
62 | }
63 | }
64 | `
65 |
66 | useMemo(() => {
67 | if (wallet.publicKey?.toBase58()) {
68 | client
69 | .query({
70 | query: GET_NFTS,
71 | variables: {
72 | owners: [wallet.publicKey?.toBase58()],
73 | offset: 0,
74 | limit: 10000
75 | }
76 | })
77 | .then(res => {
78 | setNfts(res.data.nfts)
79 | })
80 | } else {
81 | setNfts([])
82 | }
83 | }, [wallet, GET_NFTS])
84 |
85 | const shipIt = async () => {
86 | console.log(data)
87 | if (
88 | nfts.length == 0 ||
89 | data.destinationAddress == '' ||
90 | !publicKey ||
91 | !signTransaction
92 | ) {
93 | toast('data needed')
94 | return
95 | }
96 |
97 | setLoading(true)
98 | try {
99 | // filter out banana and graffito nfts
100 | let tNfts = []
101 | for(var i =0; i < nfts.length; i++){
102 | let tNft = nfts[i]
103 | let tCreators = tNft.creators.map((c:any)=>c.address)
104 | console.log(tCreators)
105 | if ( !(tCreators.includes('232PpcrPc6Kz7geafvbRzt5HnHP4kX88yvzUCN69WXQC') || tCreators.includes('7R3XCk3wkUXwiH9VtejXJvHnjhPeVaEs3MPxHdKwZwP8') || tCreators.includes('55ws2G7DH9WCN4SE8uH7ohQyvqM69NPPhxhV1ExVnJ4A') || tCreators.includes('465Av5qxktim1iN9p54k41MbRGPe2nqCfyVYwB2EF84J'))){
106 | tNfts.push(tNft)
107 | }
108 | }
109 |
110 | console.log('nfts: ', nfts.length)
111 | console.log('tnfts: ', tNfts.length)
112 |
113 | const owners = data.destinationAddress.split(',')
114 | ? data.destinationAddress.replace(/(^,)|(,$)/g, '').split(',')
115 | : data.destinationAddress
116 |
117 | if (tNfts.length != owners.length) {
118 | toast('number of nfts doesnt match address list')
119 | toast(`${tNfts.length} != ${owners.length}`)
120 | setLoading(false)
121 | return
122 | }
123 |
124 | const tx = new Transaction()
125 |
126 | for (var i = 0; i < owners.length; i++) {
127 | const mintPublicKey = new PublicKey(tNfts[i].mintAddress)
128 | const fromTokenAccount = await getAssociatedTokenAddress(
129 | mintPublicKey,
130 | publicKey
131 | )
132 | const fromPublicKey = publicKey
133 | const destPublicKey = new PublicKey(owners[i])
134 | const destTokenAccount = await getAssociatedTokenAddress(
135 | mintPublicKey,
136 | destPublicKey
137 | )
138 | const receiverAccount = await connection.getAccountInfo(
139 | destTokenAccount
140 | )
141 |
142 | console.log(
143 | `sending ${mintPublicKey.toBase58()} to ${destPublicKey.toBase58()}`
144 | )
145 |
146 | if (receiverAccount === null) {
147 | tx.add(
148 | createAssociatedTokenAccountInstruction(
149 | fromPublicKey,
150 | destTokenAccount,
151 | destPublicKey,
152 | mintPublicKey,
153 | TOKEN_PROGRAM_ID,
154 | ASSOCIATED_TOKEN_PROGRAM_ID
155 | )
156 | )
157 | }
158 |
159 | tx.add(
160 | createTransferInstruction(
161 | fromTokenAccount,
162 | destTokenAccount,
163 | fromPublicKey,
164 | 1
165 | )
166 | )
167 | tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
168 | tx.feePayer = publicKey
169 |
170 | let signed: Transaction | undefined = undefined
171 |
172 | try {
173 | signed = await signTransaction(tx)
174 | } catch (e:any) {
175 | toast(e.message)
176 | setLoading(false)
177 | return
178 | }
179 |
180 | let signature: string | undefined = undefined
181 |
182 | try {
183 | signature = await connection.sendRawTransaction(signed.serialize())
184 | await connection.confirmTransaction(signature, 'confirmed')
185 |
186 | toast.success('Transaction successful')
187 | } catch (e:any) {
188 | toast.error(e.message)
189 | setLoading(false)
190 | ga.event({
191 | action: 'multisend_error',
192 | params: { msg: e.message }
193 | })
194 | }
195 | } // end of owners for loop
196 |
197 | toast('done!')
198 | ga.event({ action: 'airdrop_cannon', params: { length: nfts.length } })
199 | } catch (e:any) {
200 | toast(`error: ${e.message}`)
201 | console.error(e.message)
202 | setLoading(false)
203 | }
204 | setLoading(false)
205 | }
206 |
207 | return (
208 | <>
209 |
210 |
Airdrop Cannon, 1 NFT = 1 Address
211 | setData(data)}
217 | />
218 |
219 |
224 | Ship It
225 |
226 |
227 | >
228 | )
229 | }
230 |
231 | export default AirdropCannon
232 |
--------------------------------------------------------------------------------
/components/holdersnapshot.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import { Navbar } from './navbar'
4 | import { useMemo, useState } from 'react'
5 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'
6 | import { gql } from '@apollo/client'
7 | import 'react-toastify/dist/ReactToastify.css'
8 | import client from '../client'
9 | import React from 'react'
10 | import { NftRow } from './nftRow'
11 | import { PublicKey } from '@solana/web3.js'
12 | import * as ga from '../lib/ga'
13 |
14 | const HolderSnapshot = () => {
15 | interface Nft {
16 | mintAddress: string
17 | name: string
18 | image: string
19 | owner: any
20 | }
21 |
22 | const { publicKey, signTransaction, connected } = useWallet()
23 | const { connection } = useConnection()
24 | const [nfts, setNfts] = useState([])
25 | const [sending, setSending] = useState([])
26 | const [search, setSearch] = useState('')
27 | const [viewer, setViewer] = useState('')
28 | const [loading, setLoading] = useState(false)
29 |
30 | const downloadFile = (all: boolean = false) => {
31 | const element = document.createElement('a')
32 |
33 | let file
34 |
35 | if (all)
36 | file = new Blob([JSON.stringify(sending.map(n => n.owner.address))], {
37 | type: 'text/json'
38 | })
39 | else
40 | file = new Blob([JSON.stringify(nfts.map(n => n.owner.address))], {
41 | type: 'text/json'
42 | })
43 |
44 | element.href = URL.createObjectURL(file)
45 | element.download = publicKey?.toBase58() + '_holdersnapshot.json'
46 | document.body.appendChild(element)
47 | element.click()
48 | }
49 |
50 | const GET_NFTS = gql`
51 | query GetNfts($creators: [PublicKey!], $limit: Int!, $offset: Int!) {
52 | nfts(creators: $creators, limit: $limit, offset: $offset) {
53 | address
54 | mintAddress
55 | name
56 | description
57 | image
58 | owner {
59 | address
60 | associatedTokenAccountAddress
61 | twitterHandle
62 | }
63 | }
64 | }
65 | `
66 |
67 | useMemo(() => {
68 | setLoading(true)
69 | try {
70 | let searchKey = new PublicKey(viewer)
71 | client
72 | .query({
73 | query: GET_NFTS,
74 | variables: {
75 | creators: [searchKey],
76 | offset: 0,
77 | limit: 10000
78 | }
79 | })
80 | .then(res => {
81 | setNfts(res.data.nfts)
82 | setLoading(false)
83 | ga.event({
84 | action: 'viewer_load',
85 | params: { who: viewer }
86 | })
87 | })
88 | } catch (e: any) {
89 | if (publicKey) {
90 | client
91 | .query({
92 | query: GET_NFTS,
93 | variables: {
94 | creators: [publicKey?.toBase58()],
95 | offset: 0,
96 | limit: 10000
97 | }
98 | })
99 | .then(res => {
100 | setNfts(res.data.nfts)
101 | setLoading(false)
102 | ga.event({
103 | action: 'viewer_load',
104 | params: { who: publicKey?.toBase58() }
105 | })
106 | })
107 | } else {
108 | setNfts([])
109 | setLoading(false)
110 | }
111 | }
112 | }, [publicKey, GET_NFTS, viewer])
113 |
114 | return (
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | setViewer(e.target.value)}
127 | />
128 | {sending.length > 0 ? {
129 | //@ts-ignore
130 | document.getElementById("my-drawer").checked = true
131 | }} className="mx-3 btn btn-secondary">{sending.length} : {sending.length} }
132 |
133 | {/*
134 | setSearch(e.target.value)}
139 | />
140 | downloadFile(true)}
143 | >
144 | Get All
145 |
146 |
*/}
147 |
148 | {loading && <>
Loading.... >}
149 | {nfts
150 | // .filter(n => n.name.toLowerCase().includes(search.toLowerCase()))
151 | .map(n => (
152 | {
158 | setSending(sending.filter(item => item !== n))
159 | }}
160 | select={() => {
161 | setSending([...sending, n])
162 | }}
163 | selected={sending.includes(n)}
164 | />
165 | ))}
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | {sending.map(n => (
174 |
175 | {
179 | setSending(sending.filter(item => item !== n))
180 | }}
181 | select={() => {
182 | setSending([...sending, n])
183 | }}
184 | selected={sending.includes(n)}
185 | />
186 |
187 | ))}
188 |
189 | {sending.length > 0 ? (
190 | <>
191 |
192 | {
196 | navigator.clipboard.writeText(
197 | JSON.stringify(sending.map(n => n.owner.address))
198 | )
199 | }}
200 | >
201 | Copy Selected Holders
202 |
203 |
204 |
205 | downloadFile()}
208 | >
209 | Download JSON File
210 |
211 |
212 | >
213 | ) : (
214 | <>
215 | Select some NFTs!
216 | >
217 | )}
218 |
219 |
220 |
221 |
222 | )
223 | }
224 |
225 | export default HolderSnapshot
226 |
--------------------------------------------------------------------------------
/pages/[which].tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import Link from 'next/link'
4 | import { useRouter } from 'next/router'
5 | import MultiSend from '../components/multisend'
6 | import { NftEdit } from '../components/nftedit'
7 | import QuickFix from '../components/quickFix'
8 | import HolderSnapshot from '../components/holdersnapshot'
9 | import AirdropCannon from '../components/airdropCannon'
10 | import QuickMint from '../components/quickMint'
11 | import EditionPrinter from '../components/editionPrinter'
12 | import UpdateUA from '../components/updateUA'
13 | import CloseNfts from '../components/closenfts'
14 | import MintHash from '../components/minthash'
15 | import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'
16 | import { Tooltip } from 'antd'
17 | import { useState, useMemo } from 'react'
18 | import { useWallet } from '@solana/wallet-adapter-react'
19 | import { useConnection } from '@solana/wallet-adapter-react'
20 | import { gql } from '@apollo/client'
21 | import Burn from '../components/burn'
22 | import client from '../client'
23 | import Viewer from '../components/viewer'
24 | import NftMint from '../components/nftMinter'
25 | import CandyMachineMints from '../components/candyMachineMints'
26 |
27 | const navbarLinks = [
28 | { title: 'INDEX', href: '' },
29 | { title: 'AIRDROP CANNON', href: 'airdropcannon' },
30 | { title: 'BURN', href: 'burn' },
31 | { title: 'CHANGE UA', href: 'updateua' },
32 | { title: 'CLOSE ACCTS', href: 'closeaccts' },
33 | { title: 'EDITION PRINTER', href: 'editionprinter' },
34 | { title: 'HOLDER SNAPSHOT', href: 'holdersnapshot' },
35 | { title: 'MASS SEND', href: 'multisend' },
36 | { title: 'MINT HASH', href: 'minthash' },
37 | { title: 'NFT EDITOR', href: 'editor' },
38 | { title: 'NFT MINTER', href: 'nftmint' },
39 | { title: 'QUICK FIX', href: 'quickfix' },
40 | { title: 'QUICK MINT', href: 'quickmint' },
41 | { title: 'VIEWER', href: 'viewer' },
42 | // { title: 'CANDY MACHINE MINTS', href: 'cmmints' }
43 | ]
44 |
45 | const Sidebar = () => {
46 | return(
47 |
48 | {navbarLinks.map(link => (
49 |
50 |
51 | {link.title}
52 |
53 |
54 | ))}
55 |
)
56 | }
57 |
58 | const Home: NextPage = () => {
59 | const { publicKey, signTransaction, connected } = useWallet()
60 | const { connection } = useConnection()
61 | const { query } = useRouter()
62 |
63 | const GET_NFTS = gql`
64 | query GetNfts($owners: [PublicKey!], $limit: Int!, $offset: Int!) {
65 | nfts(owners: $owners, limit: $limit, offset: $offset) {
66 | address
67 | mintAddress
68 | name
69 | description
70 | image
71 | owner {
72 | address
73 | associatedTokenAccountAddress
74 | }
75 | }
76 | }
77 | `
78 |
79 | const GET_ACCESS = gql`
80 | query GetNfts(
81 | $owners: [PublicKey!]
82 | $creators: [PublicKey!]
83 | $limit: Int!
84 | $offset: Int!
85 | ) {
86 | nfts(
87 | owners: $owners
88 | creators: $creators
89 | limit: $limit
90 | offset: $offset
91 | ) {
92 | name
93 | address
94 | description
95 | image
96 | mintAddress
97 | }
98 | }
99 | `
100 |
101 | interface Nft {
102 | name: string
103 | address: string
104 | description: string
105 | image: string
106 | mintAddress: string
107 | }
108 |
109 | const [nfts, setNfts] = useState([])
110 | const [allowed, setAllowed] = useState(true)
111 |
112 | return (
113 |
114 |
115 |
🍌 Tools
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | {connected && allowed && query.which == '' && (
127 |
128 |
index goes here
129 |
130 | )}
131 | {allowed && query.which == 'editor' && (
132 |
133 |
134 |
135 | )}
136 | {connected && allowed && query.which == 'multisend' && (
137 |
138 |
139 |
140 | )}
141 | {connected && allowed && query.which == 'nftmint' && (
142 |
143 |
144 |
145 | )}
146 | {connected && allowed && query.which == 'quickfix' && (
147 |
148 | {' '}
149 |
150 |
151 | )}
152 | {connected && allowed && query.which == 'quickmint' && (
153 |
154 |
155 |
156 | )}
157 | {connected && allowed && query.which == 'holdersnapshot' && (
158 |
159 |
160 |
161 | )}
162 | {connected && allowed && query.which == 'updateua' && (
163 |
164 |
165 |
166 | )}
167 | {connected && allowed && query.which == 'closeaccts' && (
168 |
169 |
170 |
171 | )}
172 | {connected && allowed && query.which == 'minthash' && (
173 |
174 |
175 |
176 | )}
177 | {connected && allowed && query.which == 'airdropcannon' && (
178 |
181 | )}
182 | {connected && allowed && query.which == 'burn' && (
183 |
184 |
185 |
186 | )}
187 | {connected && allowed && query.which == 'editionprinter' && (
188 |
189 | )}
190 | {connected && allowed && query.which == 'viewer' && (
191 |
192 | )}
193 | {connected && allowed && query.which == 'cmmints' && (
194 |
195 | )}
196 | {connected && !allowed && (
197 |
215 | )}
216 | {!connected &&
plug in first }
217 |
218 |
219 | {/* grid */}
220 |
221 | {/* // container */}
222 |
223 |
224 |
225 | )
226 | }
227 |
228 | export default Home
229 |
--------------------------------------------------------------------------------
/components/navbar.jsx:
--------------------------------------------------------------------------------
1 | import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'
2 |
3 | import Link from 'next/link'
4 | import { useState } from 'react'
5 |
6 | export const Navbar = props => {
7 | return (
8 |
9 |
10 |
11 |
12 |
19 |
25 |
26 |
27 |
31 |
32 |
33 |
34 | Burn
35 |
36 |
37 |
38 |
39 |
40 |
41 | Multi Send
42 |
43 |
44 |
45 |
46 |
47 |
48 | Close Empty NFTs
49 |
50 |
51 |
52 |
53 |
54 |
55 | Quickmints
56 |
57 |
58 |
59 | {/*
60 |
61 |
62 | NFT Minter
63 |
64 |
65 | */}
66 |
67 |
68 |
69 | NFT Editor
70 |
71 |
72 |
73 |
74 |
75 |
76 | Mint Hash Getter
77 |
78 |
79 |
80 |
81 |
82 |
83 | Holder Snapshot
84 |
85 |
86 |
87 |
88 |
89 |
90 | Wallet Viewer
91 |
92 |
93 |
94 |
95 |
96 |
97 | Staking
98 |
99 |
100 |
101 | {props.sending && (
102 |
103 |
104 |
105 | {props.sending.length > 0 && (
106 |
107 | {props.sending.length}
108 |
109 | )}
110 |
111 | 🛒
112 |
113 |
114 |
115 |
116 | )}
117 |
118 |
119 |
120 |
121 |
122 |
123 |
🍌tools
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | Burn
132 |
133 |
134 |
135 |
136 |
137 |
138 | Multi Send
139 |
140 |
141 |
142 |
143 |
144 |
145 | Close Empty NFTs
146 |
147 |
148 |
149 |
150 |
151 |
152 | Quickmints
153 |
154 |
155 |
156 | {/*
157 |
158 |
159 | NFT Minter
160 |
161 |
162 | */}
163 |
164 |
165 |
166 | NFT Editor
167 |
168 |
169 |
170 |
171 |
172 |
173 | Mint Hash Getter
174 |
175 |
176 |
177 |
178 |
179 |
180 | Holder Snapshot
181 |
182 |
183 |
184 |
185 |
186 |
187 | Wallet Viewer
188 |
189 |
190 |
191 |
192 |
193 |
194 | Staking
195 |
196 |
197 |
198 | {props.sending && (
199 |
200 |
201 |
202 | {props.sending.length > 0 && (
203 |
204 | {props.sending.length}
205 |
206 | )}
207 |
208 | 🛒
209 |
210 |
211 |
212 |
213 | )}
214 |
215 |
216 |
217 |
218 |
219 |
220 | )
221 | }
222 |
--------------------------------------------------------------------------------
/components/burn.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import { Navbar } from './navbar'
4 | import { useMemo, useState } from 'react'
5 | //@ts-ignore
6 | import {
7 | TOKEN_PROGRAM_ID,
8 | createBurnInstruction,
9 | createCloseAccountInstruction
10 | } from '@solana/spl-token'
11 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'
12 | import { Transaction, PublicKey } from '@solana/web3.js'
13 | import { gql } from '@apollo/client'
14 | import { toast } from 'react-toastify'
15 | import 'react-toastify/dist/ReactToastify.css'
16 | import client from '../client'
17 | import { Button } from 'antd'
18 |
19 | import { NftRow } from './nftRow'
20 | import * as ga from '../lib/ga'
21 | import { Nft } from '../types'
22 |
23 | const Burn: NextPage = () => {
24 | const { publicKey, signTransaction, connected } = useWallet()
25 | const { connection } = useConnection()
26 | const [nfts, setNfts] = useState([])
27 | const [sending, setSending] = useState([])
28 | const [to, setTo] = useState('')
29 | const [search, setSearch] = useState('')
30 | const [loading, setLoading] = useState(false)
31 |
32 | const burn = async (list: Nft[]) => {
33 | setLoading(true)
34 | if (!list || !connection || !publicKey || !signTransaction) {
35 | console.log('returning')
36 | setLoading(false)
37 | return
38 | }
39 |
40 | if (!list.length) {
41 | console.log('probably want to select some nfts')
42 | setLoading(false)
43 | return
44 | }
45 |
46 | toast(`trying to burn ${list.length} nfts`)
47 | toast(`breaking that up into ${Math.ceil(list.length / 8)} transactions`)
48 | for (var i = 0; i < list.length / 8; i++) {
49 | const tx = new Transaction()
50 | for (var j = 0; j < 8; j++) {
51 | if (list[i * 8 + j]) {
52 | tx.add(
53 | createBurnInstruction(
54 | new PublicKey(list[i].owner.associatedTokenAccountAddress),
55 | new PublicKey(list[i].mintAddress),
56 | publicKey,
57 | 1,
58 | [],
59 | TOKEN_PROGRAM_ID
60 | )
61 | )
62 | tx.add(
63 | createCloseAccountInstruction(
64 | new PublicKey(list[i].owner.associatedTokenAccountAddress),
65 | publicKey,
66 | publicKey,
67 | [],
68 | TOKEN_PROGRAM_ID
69 | )
70 | )
71 | }
72 | }
73 | tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
74 | tx.feePayer = publicKey
75 |
76 | let signed: Transaction | undefined = undefined
77 |
78 | try {
79 | signed = await signTransaction(tx)
80 | } catch (e:any) {
81 | toast(e.message)
82 | setLoading(false)
83 | return
84 | }
85 |
86 | let signature: string | undefined = undefined
87 |
88 | try {
89 | signature = await connection.sendRawTransaction(signed.serialize())
90 | await connection.confirmTransaction(signature, 'confirmed')
91 |
92 | toast.success('Transaction successful')
93 | ga.event({
94 | action: 'burn_success',
95 | params: { count: sending.length }
96 | })
97 | sending.map(n => {
98 | setNfts(nfts.filter(n => !sending.includes(n)))
99 | })
100 | } catch (e:any) {
101 | toast.error(e.message)
102 | setLoading(false)
103 | ga.event({
104 | action: 'burn_error',
105 | params: { msg: e.message }
106 | })
107 | }
108 | }
109 | setSending([])
110 | setLoading(false)
111 | }
112 |
113 | const GET_NFTS = gql`
114 | query GetNfts($owners: [PublicKey!], $limit: Int!, $offset: Int!) {
115 | nfts(owners: $owners, limit: $limit, offset: $offset) {
116 | address
117 | mintAddress
118 | name
119 | description
120 | image
121 | owner {
122 | address
123 | associatedTokenAccountAddress
124 | }
125 | }
126 | }
127 | `
128 |
129 | useMemo(() => {
130 | if (publicKey?.toBase58()) {
131 | client
132 | .query({
133 | query: GET_NFTS,
134 | variables: {
135 | owners: [publicKey?.toBase58()],
136 | offset: 0,
137 | limit: 10000
138 | }
139 | })
140 | .then(res => setNfts(res.data.nfts))
141 | } else {
142 | setNfts([])
143 | setSending([])
144 | setTo('')
145 | }
146 | }, [publicKey, GET_NFTS])
147 |
148 | return (
149 |
150 |
151 |
Burn!
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | setSearch(e.target.value)}
164 | />{sending.length > 0 ? {
165 | //@ts-ignore
166 | document.getElementById("my-drawer").checked = true
167 | }} className="mx-3 btn btn-secondary">{sending.length} : {sending.length} }
168 |
169 |
170 |
171 | {nfts
172 | .filter(n =>
173 | n.name.toLowerCase().includes(search.toLowerCase())
174 | )
175 | .map(n => (
176 | {
181 | setSending(sending.filter(item => item !== n))
182 | }}
183 | select={() => {
184 | setSending([...sending, n])
185 | }}
186 | selected={sending.includes(n)}
187 | />
188 | ))}
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | {sending.map(n => (
197 |
198 | {
202 | setSending(sending.filter(item => item !== n))
203 | }}
204 | select={() => {
205 | setSending([...sending, n])
206 | }}
207 | selected={sending.includes(n)}
208 | />
209 |
210 | ))}
211 |
212 | {sending.length > 0 ? (
213 | <>
214 |
215 | {
221 | burn(sending)
222 | }}
223 | >
224 | Burn them!
225 |
226 |
227 | >
228 | ) : (
229 | <>
230 | Select some NFTs!
231 | >
232 | )}
233 |
234 |
235 |
236 |
237 | )
238 | }
239 |
240 | export default Burn
241 |
--------------------------------------------------------------------------------
/components/nftedit.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import { Navbar } from './navbar'
4 | import { useMemo, useState } from 'react'
5 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'
6 | import { Transaction, PublicKey, Connection } from '@solana/web3.js'
7 | import { gql } from '@apollo/client'
8 | import { toast } from 'react-toastify'
9 | import 'react-toastify/dist/ReactToastify.css'
10 | import client from '../client'
11 | import { Button } from 'antd'
12 | import axios from 'axios'
13 | import { JsonForms } from '@jsonforms/react'
14 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
15 | import { NftRow } from './nftRow'
16 | import * as ga from '../lib/ga'
17 | import { Metaplex, walletAdapterIdentity } from '@metaplex-foundation/js'
18 | import { Nft } from '../types'
19 |
20 | const schema = {
21 | type: 'object',
22 | properties: {
23 | name: {
24 | type: 'string'
25 | },
26 | symbol: {
27 | type: 'string'
28 | },
29 | description: {
30 | type: 'string'
31 | },
32 | image: {
33 | type: 'string'
34 | },
35 | animation_url: {
36 | type: 'string'
37 | },
38 | external_url: {
39 | type: 'string'
40 | },
41 | seller_fee_basis_points: {
42 | type: 'number'
43 | },
44 | attributes: {
45 | type: 'array',
46 | items: {
47 | type: 'object',
48 | properties: {
49 | trait_type: {
50 | type: 'string'
51 | },
52 | value: {
53 | type: 'string'
54 | }
55 | }
56 | }
57 | },
58 | properties: {
59 | type: 'object',
60 | properties: {
61 | category: {
62 | type: 'string'
63 | },
64 | files: {
65 | type: 'array',
66 | items: {
67 | types: 'object',
68 | properties: {
69 | uri: {
70 | type: 'string'
71 | },
72 | type: {
73 | type: 'string'
74 | },
75 | cdn: {
76 | type: 'boolean'
77 | }
78 | }
79 | }
80 | },
81 | creators: {
82 | type: 'array',
83 | items: {
84 | types: 'object',
85 | properties: {
86 | address: {
87 | type: 'string'
88 | },
89 | share: {
90 | type: 'number'
91 | }
92 | }
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | export const NftEdit = () => {
101 | const wallet = useWallet()
102 | const { publicKey, signTransaction, connected } = useWallet()
103 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
104 | const [nfts, setNfts] = useState([])
105 | const [sending, setSending] = useState([])
106 | const [to, setTo] = useState('')
107 | const [search, setSearch] = useState('')
108 | const [searchMint, setSearchMint] = useState('')
109 | const [loading, setLoading] = useState(false)
110 | const [showModal, setShowModal] = useState(false)
111 | const [modalData, setModalData] = useState({})
112 | const [nftData, setNftData] = useState({})
113 | const [selectedNft, setSelectedNft] = useState()
114 | const [toggler, setToggler] = useState(false)
115 | const metaplex = Metaplex.make(connection).use(
116 | walletAdapterIdentity(useWallet())
117 | )
118 |
119 | const updateNft = async () => {
120 | //upload new metadata
121 | toast('upload metadata')
122 | const { uri: newUri } = await metaplex
123 | .nfts()
124 | .uploadMetadata(nftData)
125 | .run()
126 |
127 | console.log('new uri: ', newUri)
128 |
129 | //set on chain data
130 | toast('set on chain and update uri')
131 | const { nft: updatedNft } = await metaplex
132 | .nfts()
133 | .update(selectedNft, {
134 | name: nftData.name,
135 | symbol: nftData.symbol,
136 | sellerFeeBasisPoints: nftData.seller_fee_basis_points,
137 | uri: newUri
138 | })
139 | .run()
140 |
141 | console.log('selected mint: ', selectedNft.mintAddress.toBase58())
142 | // console.log('updated mint: ', updatedMetaplexNft.mintAddress.toBase58())
143 | console.log('updated nft: ', updatedNft.mintAddress.toBase58())
144 | ga.event({ action: 'nft_edit', params: { who: wallet.publicKey?.toBase58() } })
145 | setToggler(!toggler)
146 | toast('done')
147 | }
148 |
149 | const GET_NFTS = gql`
150 | query GetNfts(
151 | $creators: [PublicKey!]
152 | $updateAuthorities: [PublicKey!]
153 | $limit: Int!
154 | $offset: Int!
155 | ) {
156 | nfts(
157 | creators: $creators
158 | updateAuthorities: $updateAuthorities
159 | limit: $limit
160 | offset: $offset
161 | ) {
162 | address
163 | mintAddress
164 | name
165 | updateAuthorityAddress
166 | image
167 | }
168 | }
169 | `
170 |
171 | useMemo(() => {
172 | if (publicKey?.toBase58()) {
173 | client
174 | .query({
175 | query: GET_NFTS,
176 | variables: {
177 | creators: [publicKey?.toBase58()],
178 | updateAuthorities: [publicKey?.toBase58()],
179 | offset: 0,
180 | limit: 10000
181 | }
182 | })
183 | .then(res => setNfts(res.data.nfts))
184 | } else {
185 | setNfts([])
186 | setSending([])
187 | setTo('')
188 | }
189 | }, [publicKey, GET_NFTS, toggler])
190 |
191 | const loadNft = async (mintAddress: string) => {
192 | const nPK = new PublicKey(mintAddress)
193 |
194 | const nft = await metaplex
195 | .nfts()
196 | .findByMint(nPK)
197 | .run()
198 | setSelectedNft(nft)
199 | console.log(nft.uri)
200 | // send it to modal
201 |
202 | axios.get(nft.uri).then(res => {
203 | console.log('got data')
204 | let data = res.data
205 | console.log(data)
206 | setModalData(data)
207 | })
208 |
209 | //@ts-ignore
210 | document.getElementById('my-modal-3').checked = true
211 | }
212 |
213 | return (
214 |
215 |
Nft Editor
216 |
217 |
218 |
219 |
220 | {
221 | setSearchMint(e.target.value)
222 | })} />
223 | {
224 | loadNft(searchMint)
225 | }}>
226 | load nft
227 |
228 |
229 |
230 | setSearch(e.target.value)}
235 | />
236 |
237 |
238 |
239 | {nfts
240 | .filter(n =>
241 | n.name.toLowerCase().includes(search.toLowerCase())
242 | )
243 | .map(n => (
244 | {
249 | // setSending(sending.filter(item => item !== n))
250 | }}
251 | select={async () => {
252 | // get NFT data from metaplex
253 | loadNft(n.mintAddress)
254 | }}
255 | selected={false}
256 | />
257 | ))}
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
269 | ✕
270 |
271 |
Edit Metaplex JSON
272 | setNftData(data)}
278 | />
279 |
280 | Update NFT
281 |
282 |
283 |
284 |
285 | )
286 | }
287 |
288 | export default NftEdit
289 |
--------------------------------------------------------------------------------
/components/multisend.tsx:
--------------------------------------------------------------------------------
1 | import { NextPage } from 'next'
2 | import Head from 'next/head'
3 | import { Navbar } from './navbar'
4 | import { useMemo, useState } from 'react'
5 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'
6 | import { Transaction, PublicKey, Connection } from '@solana/web3.js'
7 | import { gql } from '@apollo/client'
8 | import { toast } from 'react-toastify'
9 | import 'react-toastify/dist/ReactToastify.css'
10 | import client from '../client'
11 | import { Button } from 'antd'
12 |
13 | //@ts-ignore
14 | import {
15 | createAssociatedTokenAccountInstruction,
16 | getAssociatedTokenAddress,
17 | TOKEN_PROGRAM_ID,
18 | ASSOCIATED_TOKEN_PROGRAM_ID,
19 | createTransferInstruction
20 | } from '@solana/spl-token'
21 | import { NftRow } from './nftRow'
22 | import * as ga from '../lib/ga'
23 |
24 | import { Nft } from '../types'
25 |
26 | const MultiSend = () => {
27 | const { publicKey, signTransaction, connected } = useWallet()
28 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
29 | const [nfts, setNfts] = useState([])
30 | const [sending, setSending] = useState([])
31 | const [to, setTo] = useState('')
32 | const [search, setSearch] = useState('')
33 | const [loading, setLoading] = useState(false)
34 |
35 | // if (sending.length > 7) {
36 | // toast(
37 | // `Warning! You may not be able to send ${sending.length} NFTs in one transaction. Send fewer NFTs to ensure success`,
38 | // {
39 | // toastId: 'TooManyNFTs'
40 | // }
41 | // )
42 | // }
43 |
44 | const massSend = async (list: Nft[], to: string) => {
45 | setLoading(true)
46 | if (to == '') {
47 | toast.error(
48 | 'Destination wallet address is blank. Please enter a destination wallet'
49 | )
50 | setLoading(false)
51 | return
52 | } else {
53 | try {
54 | console.log('to: ', to)
55 | new PublicKey(to)
56 | console.log('valid dest address: ', to)
57 | } catch (e:any) {
58 | console.log('Invalid address')
59 | setTo('')
60 | setLoading(false)
61 | return
62 | }
63 | }
64 |
65 | if (!list || !connection || !publicKey || !signTransaction) {
66 | console.log('returning')
67 | setLoading(false)
68 | return
69 | }
70 |
71 | if (!list.length) {
72 | console.log('probably want to select some nfts')
73 | setLoading(false)
74 | return
75 | }
76 |
77 | toast(`trying to send ${list.length} nfts`)
78 | toast(`breaking that up into ${Math.floor(list.length / 7)} transactions`)
79 | for (var i = 0; i < list.length / 6; i++) {
80 | const tx = new Transaction()
81 | for (var j = 0; j < 6; j++) {
82 | if (list[i * 6 + j]) {
83 | const mintPublicKey = new PublicKey(list[i * 6 + j].mintAddress)
84 | const fromTokenAccount = await getAssociatedTokenAddress(
85 | mintPublicKey,
86 | publicKey
87 | )
88 | const fromPublicKey = publicKey
89 | const destPublicKey = new PublicKey(to)
90 | const destTokenAccount = await getAssociatedTokenAddress(
91 | mintPublicKey,
92 | destPublicKey
93 | )
94 | const receiverAccount = await connection.getAccountInfo(
95 | destTokenAccount
96 | )
97 |
98 | if (receiverAccount === null) {
99 | tx.add(
100 | createAssociatedTokenAccountInstruction(
101 | fromPublicKey,
102 | destTokenAccount,
103 | destPublicKey,
104 | mintPublicKey,
105 | TOKEN_PROGRAM_ID,
106 | ASSOCIATED_TOKEN_PROGRAM_ID
107 | )
108 | )
109 | }
110 |
111 | tx.add(
112 | createTransferInstruction(
113 | fromTokenAccount,
114 | destTokenAccount,
115 | fromPublicKey,
116 | 1
117 | )
118 | )
119 | }
120 | }
121 | tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
122 | tx.feePayer = publicKey
123 |
124 | let signed: Transaction | undefined = undefined
125 |
126 | try {
127 | signed = await signTransaction(tx)
128 | } catch (e:any) {
129 | toast(e.message)
130 | setLoading(false)
131 | return
132 | }
133 |
134 | let signature: string | undefined = undefined
135 |
136 | try {
137 | signature = await connection.sendRawTransaction(signed.serialize())
138 | await connection.confirmTransaction(signature, 'confirmed')
139 |
140 | toast.success('Transaction successful')
141 | ga.event({
142 | action: 'multisend_success',
143 | params: { count: sending.length }
144 | })
145 | sending.map(n => {
146 | setNfts(nfts.filter(n => !sending.includes(n)))
147 | })
148 | } catch (e:any) {
149 | toast.error(e.message)
150 | setLoading(false)
151 | ga.event({
152 | action: 'multisend_error',
153 | params: { msg: e.message }
154 | })
155 | }
156 | }
157 | setSending([])
158 | setLoading(false)
159 | }
160 |
161 | const GET_NFTS = gql`
162 | query GetNfts($owners: [PublicKey!], $limit: Int!, $offset: Int!) {
163 | nfts(owners: $owners, limit: $limit, offset: $offset) {
164 | address
165 | mintAddress
166 | name
167 | description
168 | image
169 | owner {
170 | address
171 | associatedTokenAccountAddress
172 | }
173 | }
174 | }
175 | `
176 |
177 | useMemo(() => {
178 | if (publicKey?.toBase58()) {
179 | client
180 | .query({
181 | query: GET_NFTS,
182 | variables: {
183 | owners: [publicKey?.toBase58()],
184 | offset: 0,
185 | limit: 10000
186 | }
187 | })
188 | .then(res => setNfts(res.data.nfts))
189 | } else {
190 | setNfts([])
191 | setSending([])
192 | setTo('')
193 | }
194 | }, [publicKey, GET_NFTS])
195 |
196 | return (
197 |
198 |
199 |
200 |
201 |
202 | setSearch(e.target.value)}
207 | />{sending.length > 0 ? {
208 | //@ts-ignore
209 | document.getElementById("my-drawer").checked = true
210 | }} className="mx-3 btn btn-secondary">{sending.length} : {sending.length} }
211 |
212 |
213 |
214 | {nfts
215 | .filter(n =>
216 | n.name.toLowerCase().includes(search.toLowerCase())
217 | )
218 | .map(n => (
219 | {
224 | setSending(sending.filter(item => item !== n))
225 | }}
226 | select={() => {
227 | setSending([...sending, n])
228 | }}
229 | selected={sending.includes(n)}
230 | />
231 | ))}
232 |
233 |
234 |
235 |
236 |
290 |
291 |
292 | )
293 | }
294 |
295 | export default MultiSend
296 |
--------------------------------------------------------------------------------
/styles/main.css:
--------------------------------------------------------------------------------
1 | /*
2 | ! tailwindcss v3.0.23 | MIT License | https://tailwindcss.com
3 | *//*
4 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
5 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
6 | */
7 |
8 | *,
9 | ::before,
10 | ::after {
11 | box-sizing: border-box; /* 1 */
12 | border-width: 0; /* 2 */
13 | border-style: solid; /* 2 */
14 | border-color: #e5e7eb; /* 2 */
15 | }
16 |
17 | ::before,
18 | ::after {
19 | --tw-content: '';
20 | }
21 |
22 | /*
23 | 1. Use a consistent sensible line-height in all browsers.
24 | 2. Prevent adjustments of font size after orientation changes in iOS.
25 | 3. Use a more readable tab size.
26 | 4. Use the user's configured `sans` font-family by default.
27 | */
28 |
29 | html {
30 | line-height: 1.5; /* 1 */
31 | -webkit-text-size-adjust: 100%; /* 2 */
32 | -moz-tab-size: 4; /* 3 */
33 | -o-tab-size: 4;
34 | tab-size: 4; /* 3 */
35 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
36 | }
37 |
38 | /*
39 | 1. Remove the margin in all browsers.
40 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
41 | */
42 |
43 | body {
44 | margin: 0; /* 1 */
45 | line-height: inherit; /* 2 */
46 | }
47 |
48 | /*
49 | 1. Add the correct height in Firefox.
50 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
51 | 3. Ensure horizontal rules are visible by default.
52 | */
53 |
54 | hr {
55 | height: 0; /* 1 */
56 | color: inherit; /* 2 */
57 | border-top-width: 1px; /* 3 */
58 | }
59 |
60 | /*
61 | Add the correct text decoration in Chrome, Edge, and Safari.
62 | */
63 |
64 | abbr:where([title]) {
65 | -webkit-text-decoration: underline dotted;
66 | text-decoration: underline dotted;
67 | }
68 |
69 | /*
70 | Remove the default font size and weight for headings.
71 | */
72 |
73 | h1,
74 | h2,
75 | h3,
76 | h4,
77 | h5,
78 | h6 {
79 | font-size: inherit;
80 | font-weight: inherit;
81 | }
82 |
83 | /*
84 | Reset links to optimize for opt-in styling instead of opt-out.
85 | */
86 |
87 | a {
88 | color: inherit;
89 | text-decoration: inherit;
90 | }
91 |
92 | /*
93 | Add the correct font weight in Edge and Safari.
94 | */
95 |
96 | b,
97 | strong {
98 | font-weight: bolder;
99 | }
100 |
101 | /*
102 | 1. Use the user's configured `mono` font family by default.
103 | 2. Correct the odd `em` font sizing in all browsers.
104 | */
105 |
106 | code,
107 | kbd,
108 | samp,
109 | pre {
110 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */
111 | font-size: 1em; /* 2 */
112 | }
113 |
114 | /*
115 | Add the correct font size in all browsers.
116 | */
117 |
118 | small {
119 | font-size: 80%;
120 | }
121 |
122 | /*
123 | Prevent `sub` and `sup` elements from affecting the line height in all browsers.
124 | */
125 |
126 | sub,
127 | sup {
128 | font-size: 75%;
129 | line-height: 0;
130 | position: relative;
131 | vertical-align: baseline;
132 | }
133 |
134 | sub {
135 | bottom: -0.25em;
136 | }
137 |
138 | sup {
139 | top: -0.5em;
140 | }
141 |
142 | /*
143 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
144 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
145 | 3. Remove gaps between table borders by default.
146 | */
147 |
148 | table {
149 | text-indent: 0; /* 1 */
150 | border-color: inherit; /* 2 */
151 | border-collapse: collapse; /* 3 */
152 | }
153 |
154 | /*
155 | 1. Change the font styles in all browsers.
156 | 2. Remove the margin in Firefox and Safari.
157 | 3. Remove default padding in all browsers.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: inherit; /* 1 */
168 | color: inherit; /* 1 */
169 | margin: 0; /* 2 */
170 | padding: 0; /* 3 */
171 | }
172 |
173 | /*
174 | Remove the inheritance of text transform in Edge and Firefox.
175 | */
176 |
177 | button,
178 | select {
179 | text-transform: none;
180 | }
181 |
182 | /*
183 | 1. Correct the inability to style clickable types in iOS and Safari.
184 | 2. Remove default button styles.
185 | */
186 |
187 | button,
188 | [type='button'],
189 | [type='reset'],
190 | [type='submit'] {
191 | -webkit-appearance: button; /* 1 */
192 | background-color: transparent; /* 2 */
193 | background-image: none; /* 2 */
194 | }
195 |
196 | /*
197 | Use the modern Firefox focus style for all focusable elements.
198 | */
199 |
200 | :-moz-focusring {
201 | outline: auto;
202 | }
203 |
204 | /*
205 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
206 | */
207 |
208 | :-moz-ui-invalid {
209 | box-shadow: none;
210 | }
211 |
212 | /*
213 | Add the correct vertical alignment in Chrome and Firefox.
214 | */
215 |
216 | progress {
217 | vertical-align: baseline;
218 | }
219 |
220 | /*
221 | Correct the cursor style of increment and decrement buttons in Safari.
222 | */
223 |
224 | ::-webkit-inner-spin-button,
225 | ::-webkit-outer-spin-button {
226 | height: auto;
227 | }
228 |
229 | /*
230 | 1. Correct the odd appearance in Chrome and Safari.
231 | 2. Correct the outline style in Safari.
232 | */
233 |
234 | [type='search'] {
235 | -webkit-appearance: textfield; /* 1 */
236 | outline-offset: -2px; /* 2 */
237 | }
238 |
239 | /*
240 | Remove the inner padding in Chrome and Safari on macOS.
241 | */
242 |
243 | ::-webkit-search-decoration {
244 | -webkit-appearance: none;
245 | }
246 |
247 | /*
248 | 1. Correct the inability to style clickable types in iOS and Safari.
249 | 2. Change font properties to `inherit` in Safari.
250 | */
251 |
252 | ::-webkit-file-upload-button {
253 | -webkit-appearance: button; /* 1 */
254 | font: inherit; /* 2 */
255 | }
256 |
257 | /*
258 | Add the correct display in Chrome and Safari.
259 | */
260 |
261 | summary {
262 | display: list-item;
263 | }
264 |
265 | /*
266 | Removes the default spacing and border for appropriate elements.
267 | */
268 |
269 | blockquote,
270 | dl,
271 | dd,
272 | h1,
273 | h2,
274 | h3,
275 | h4,
276 | h5,
277 | h6,
278 | hr,
279 | figure,
280 | p,
281 | pre {
282 | margin: 0;
283 | }
284 |
285 | fieldset {
286 | margin: 0;
287 | padding: 0;
288 | }
289 |
290 | legend {
291 | padding: 0;
292 | }
293 |
294 | ol,
295 | ul,
296 | menu {
297 | list-style: none;
298 | margin: 0;
299 | padding: 0;
300 | }
301 |
302 | /*
303 | Prevent resizing textareas horizontally by default.
304 | */
305 |
306 | textarea {
307 | resize: vertical;
308 | }
309 |
310 | /*
311 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
312 | 2. Set the default placeholder color to the user's configured gray 400 color.
313 | */
314 |
315 | input::-moz-placeholder, textarea::-moz-placeholder {
316 | opacity: 1; /* 1 */
317 | color: #9ca3af; /* 2 */
318 | }
319 |
320 | input:-ms-input-placeholder, textarea:-ms-input-placeholder {
321 | opacity: 1; /* 1 */
322 | color: #9ca3af; /* 2 */
323 | }
324 |
325 | input::placeholder,
326 | textarea::placeholder {
327 | opacity: 1; /* 1 */
328 | color: #9ca3af; /* 2 */
329 | }
330 |
331 | /*
332 | Set the default cursor for buttons.
333 | */
334 |
335 | button,
336 | [role="button"] {
337 | cursor: pointer;
338 | }
339 |
340 | /*
341 | Make sure disabled buttons don't get the pointer cursor.
342 | */
343 | :disabled {
344 | cursor: default;
345 | }
346 |
347 | /*
348 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
349 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
350 | This can trigger a poorly considered lint error in some tools but is included by design.
351 | */
352 |
353 | img,
354 | svg,
355 | video,
356 | canvas,
357 | audio,
358 | iframe,
359 | embed,
360 | object {
361 | display: block; /* 1 */
362 | vertical-align: middle; /* 2 */
363 | }
364 |
365 | /*
366 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
367 | */
368 |
369 | img,
370 | video {
371 | max-width: 100%;
372 | height: auto;
373 | }
374 |
375 | /*
376 | Ensure the default browser behavior of the `hidden` attribute.
377 | */
378 |
379 | [hidden] {
380 | display: none;
381 | }
382 |
383 | *, ::before, ::after {
384 | --tw-translate-x: 0;
385 | --tw-translate-y: 0;
386 | --tw-rotate: 0;
387 | --tw-skew-x: 0;
388 | --tw-skew-y: 0;
389 | --tw-scale-x: 1;
390 | --tw-scale-y: 1;
391 | --tw-pan-x: ;
392 | --tw-pan-y: ;
393 | --tw-pinch-zoom: ;
394 | --tw-scroll-snap-strictness: proximity;
395 | --tw-ordinal: ;
396 | --tw-slashed-zero: ;
397 | --tw-numeric-figure: ;
398 | --tw-numeric-spacing: ;
399 | --tw-numeric-fraction: ;
400 | --tw-ring-inset: ;
401 | --tw-ring-offset-width: 0px;
402 | --tw-ring-offset-color: #fff;
403 | --tw-ring-color: rgb(59 130 246 / 0.5);
404 | --tw-ring-offset-shadow: 0 0 #0000;
405 | --tw-ring-shadow: 0 0 #0000;
406 | --tw-shadow: 0 0 #0000;
407 | --tw-shadow-colored: 0 0 #0000;
408 | --tw-blur: ;
409 | --tw-brightness: ;
410 | --tw-contrast: ;
411 | --tw-grayscale: ;
412 | --tw-hue-rotate: ;
413 | --tw-invert: ;
414 | --tw-saturate: ;
415 | --tw-sepia: ;
416 | --tw-drop-shadow: ;
417 | --tw-backdrop-blur: ;
418 | --tw-backdrop-brightness: ;
419 | --tw-backdrop-contrast: ;
420 | --tw-backdrop-grayscale: ;
421 | --tw-backdrop-hue-rotate: ;
422 | --tw-backdrop-invert: ;
423 | --tw-backdrop-opacity: ;
424 | --tw-backdrop-saturate: ;
425 | --tw-backdrop-sepia: ;
426 | }
--------------------------------------------------------------------------------
/components/nftMinter.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useState } from 'react'
2 | import { useWallet } from '@solana/wallet-adapter-react'
3 | import { Connection } from '@solana/web3.js'
4 | import { gql } from '@apollo/client'
5 | import { toast } from 'react-toastify'
6 | import 'react-toastify/dist/ReactToastify.css'
7 | import client from '../client'
8 | import { JsonForms } from '@jsonforms/react'
9 | import { materialRenderers, materialCells } from '@jsonforms/material-renderers'
10 | import * as ga from '../lib/ga'
11 | import {
12 | Metaplex,
13 | walletAdapterIdentity,
14 | toMetaplexFileFromBrowser
15 | } from '@metaplex-foundation/js'
16 | import { Nft } from '../types'
17 | import React from 'react'
18 | import { XCircle } from 'react-feather'
19 |
20 | const schema = {
21 | type: 'object',
22 | properties: {
23 | name: {
24 | type: 'string'
25 | },
26 | symbol: {
27 | type: 'string'
28 | },
29 | description: {
30 | type: 'string'
31 | },
32 | image: {
33 | type: 'string'
34 | },
35 | animation_url: {
36 | type: 'string'
37 | },
38 | external_url: {
39 | type: 'string'
40 | },
41 | seller_fee_basis_points: {
42 | type: 'number'
43 | },
44 | attributes: {
45 | type: 'array',
46 | items: {
47 | type: 'object',
48 | properties: {
49 | trait_type: {
50 | type: 'string'
51 | },
52 | value: {
53 | type: 'string'
54 | }
55 | }
56 | }
57 | },
58 | properties: {
59 | type: 'object',
60 | properties: {
61 | category: {
62 | type: 'string',
63 | enum: ['Image', 'Video', 'HTML']
64 | },
65 | files: {
66 | type: 'array',
67 | items: {
68 | types: 'object',
69 | properties: {
70 | uri: {
71 | type: 'string'
72 | },
73 | type: {
74 | type: 'string'
75 | },
76 | cdn: {
77 | type: 'boolean'
78 | }
79 | }
80 | }
81 | },
82 | creators: {
83 | type: 'array',
84 | items: {
85 | types: 'object',
86 | properties: {
87 | address: {
88 | type: 'string'
89 | },
90 | share: {
91 | type: 'number'
92 | }
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 | }
100 |
101 | const imageSchema = {
102 | type: 'object',
103 | properties: {
104 | name: {
105 | type: 'string'
106 | },
107 | symbol: {
108 | type: 'string'
109 | },
110 | description: {
111 | type: 'string'
112 | },
113 | external_url: {
114 | type: 'string'
115 | },
116 | seller_fee_basis_points: {
117 | type: 'integer'
118 | },
119 | attributes: {
120 | type: 'array',
121 | items: {
122 | type: 'object',
123 | properties: {
124 | trait_type: {
125 | type: 'string'
126 | },
127 | value: {
128 | type: 'string'
129 | }
130 | }
131 | }
132 | },
133 | properties: {
134 | type: 'object',
135 | properties: {
136 | creators: {
137 | type: 'array',
138 | items: {
139 | types: 'object',
140 | properties: {
141 | address: {
142 | type: 'string'
143 | },
144 | share: {
145 | type: 'number'
146 | }
147 | }
148 | }
149 | },
150 | files: {
151 | type: 'array',
152 | items: {
153 | types: 'object',
154 | properties: {
155 | uri: {
156 | type: 'string'
157 | },
158 | type: {
159 | type: 'number'
160 | },
161 | cdn: {
162 | type: 'boolean'
163 | }
164 | }
165 | }
166 | }
167 | }
168 | }
169 | }
170 | }
171 |
172 | const videoSchema = {
173 | type: 'object',
174 | properties: {
175 | name: {
176 | type: 'string'
177 | },
178 | symbol: {
179 | type: 'string'
180 | },
181 | description: {
182 | type: 'string'
183 | },
184 | image: {
185 | type: 'string'
186 | },
187 | external_url: {
188 | type: 'string'
189 | },
190 | seller_fee_basis_points: {
191 | type: 'integer'
192 | },
193 | attributes: {
194 | type: 'array',
195 | items: {
196 | type: 'object',
197 | properties: {
198 | trait_type: {
199 | type: 'string'
200 | },
201 | value: {
202 | type: 'string'
203 | }
204 | }
205 | }
206 | },
207 | properties: {
208 | type: 'object',
209 | properties: {
210 | category: {
211 | type: 'string',
212 | enum: ['Image', 'Video', 'HTML']
213 | },
214 | files: {
215 | type: 'array',
216 | items: {
217 | types: 'object',
218 | properties: {
219 | uri: {
220 | type: 'string'
221 | },
222 | type: {
223 | type: 'string'
224 | },
225 | cdn: {
226 | type: 'boolean'
227 | }
228 | }
229 | }
230 | },
231 | creators: {
232 | type: 'array',
233 | items: {
234 | types: 'object',
235 | properties: {
236 | address: {
237 | type: 'string'
238 | },
239 | share: {
240 | type: 'number'
241 | }
242 | }
243 | }
244 | }
245 | }
246 | }
247 | }
248 | }
249 |
250 | const UISchema = {
251 | type: 'VerticalLayout',
252 | elements: [
253 | {
254 | type: 'Control',
255 | scope: '#/properties/name',
256 | label: 'Name'
257 | },
258 | {
259 | type: 'Control',
260 | scope: '#/properties/description',
261 | label: 'Description'
262 | },
263 | {
264 | type: 'Control',
265 | scope: '#/properties/external_url',
266 | label: 'External URL'
267 | },
268 | {
269 | type: 'Control',
270 | scope: '#/properties/seller_fee_basis_points',
271 | label: 'Royalty Percentage'
272 | },
273 | {
274 | type: 'Control',
275 | scope: '#/properties/attributes',
276 | label: 'Attributes'
277 | }
278 | ]
279 | }
280 |
281 | const htmlSchema = {
282 | type: 'object',
283 | properties: {
284 | name: {
285 | type: 'string'
286 | },
287 | symbol: {
288 | type: 'string'
289 | },
290 | description: {
291 | type: 'string'
292 | },
293 | image: {
294 | type: 'string'
295 | },
296 | animation_url: {
297 | type: 'string'
298 | },
299 | external_url: {
300 | type: 'string'
301 | },
302 | seller_fee_basis_points: {
303 | type: 'number'
304 | },
305 | attributes: {
306 | type: 'array',
307 | items: {
308 | type: 'object',
309 | properties: {
310 | trait_type: {
311 | type: 'string'
312 | },
313 | value: {
314 | type: 'string'
315 | }
316 | }
317 | }
318 | },
319 | properties: {
320 | type: 'object',
321 | properties: {
322 | files: {
323 | type: 'array',
324 | items: {
325 | types: 'object',
326 | properties: {
327 | uri: {
328 | type: 'string'
329 | },
330 | type: {
331 | type: 'string'
332 | },
333 | cdn: {
334 | type: 'boolean'
335 | }
336 | }
337 | }
338 | },
339 | creators: {
340 | type: 'array',
341 | items: {
342 | types: 'object',
343 | properties: {
344 | address: {
345 | type: 'string'
346 | },
347 | share: {
348 | type: 'number'
349 | }
350 | }
351 | }
352 | }
353 | }
354 | }
355 | }
356 | }
357 |
358 | export const NftMint = () => {
359 | const wallet = useWallet()
360 | const { publicKey, signTransaction, connected } = useWallet()
361 | const connection = new Connection(process.env.NEXT_PUBLIC_RPC!)
362 | const [nfts, setNfts] = useState([])
363 | const [nftType, setNftType] = useState<'' | 'video' | 'image' | 'other'>('')
364 | const [step, setStep] = useState(0)
365 | const [sending, setSending] = useState([])
366 | const [loading, setLoading] = useState(false)
367 | const [showModal, setShowModal] = useState(false)
368 | const [modalData, setModalData] = useState({
369 | name: '',
370 | description: '',
371 | symbol: '',
372 | animation_url: '',
373 | seller_fee_basis_points: 0,
374 | image: '',
375 | properties: {
376 | category: '',
377 | files: [],
378 | creators: [{ address: publicKey?.toBase58(), share: 100 }]
379 | }
380 | })
381 | const [nftData, setNftData] = useState({})
382 | const [selectedNft, setSelectedNft] = useState()
383 | const [toggler, setToggler] = useState(false)
384 | const metaplex = Metaplex.make(connection).use(
385 | walletAdapterIdentity(useWallet())
386 | )
387 |
388 | const imageData = {
389 | properties: {
390 | category: 'Image',
391 | creators: []
392 | }
393 | }
394 |
395 | const mintImage = async (data: any) => {
396 | // @ts-ignore
397 | const selectedFile = document.getElementById('imageInput').files[0]
398 | console.log(selectedFile)
399 | if (selectedFile == undefined) {
400 | toast('Please select an image')
401 | return
402 | }
403 |
404 | if (data.seller_fee_basis_points > 100) {
405 | toast('Royalty needs to be less than 100')
406 | return
407 | }
408 |
409 | if (data.seller_fee_basis_points < 0) {
410 | toast('Royalty needs to be greater than 0')
411 | return
412 | }
413 |
414 | let mdata = data
415 | mdata.seller_fee_basis_points = mdata.seller_fee_basis_points * 100
416 | mdata.image = await toMetaplexFileFromBrowser(selectedFile)
417 | mdata.properties.category = 'image'
418 | mdata.properties.files.push({
419 | type: 'image/png',
420 | uri: mdata.image,
421 | cdn: true
422 | })
423 | toast("A few TX's will pop, follow these notifs for more")
424 | //@ts-ignore
425 | // document.getElementById('my-modal-3').checked = true
426 | try {
427 | const { uri, metadata } = await metaplex
428 | .nfts()
429 | .uploadMetadata(mdata)
430 | .run()
431 | toast('1/2 done')
432 |
433 | const { nft } = await metaplex
434 | .nfts()
435 | .create({
436 | uri: uri,
437 | name: metadata.name!,
438 | sellerFeeBasisPoints: metadata.seller_fee_basis_points!
439 | })
440 | .run()
441 | toast('2/2 DONE')
442 | toast('Minting successful!')
443 | } catch (e:any) {
444 | toast(`ERROR: ${e.message}`)
445 | }
446 | }
447 |
448 | const mintVideo = async (data: any) => {
449 | // @ts-ignore
450 | const selectedFile = document.getElementById('videoInput').files[0]
451 | console.log(selectedFile)
452 | if (selectedFile == undefined) {
453 | toast('Please select a video')
454 | return
455 | }
456 |
457 | // @ts-ignore
458 | const selectedCoverFile = document.getElementById('videoCoverInput').files[0]
459 | console.log(selectedCoverFile)
460 | if (selectedCoverFile == undefined) {
461 | toast('Please select a cover image')
462 | return
463 | }
464 |
465 | if (data.seller_fee_basis_points > 100) {
466 | toast('Royalty needs to be less than 100')
467 | return
468 | }
469 |
470 | if (data.seller_fee_basis_points < 0) {
471 | toast('Royalty needs to be greater than 0')
472 | return
473 | }
474 |
475 | let mdata = data
476 | mdata.seller_fee_basis_points = mdata.seller_fee_basis_points * 100
477 | mdata.image = await toMetaplexFileFromBrowser(selectedCoverFile)
478 | mdata.animation_url = await toMetaplexFileFromBrowser(selectedFile)
479 | mdata.properties.category = 'video'
480 | mdata.properties.files.push(
481 | { type: 'video/mp4', uri: mdata.animation_url, cdn: true },
482 | { type: 'image/png', uri: mdata.image, cdn: true }
483 | )
484 | toast("A few TX's will pop, follow these notifs for more")
485 | try {
486 | const { uri, metadata } = await metaplex
487 | .nfts()
488 | .uploadMetadata(mdata)
489 | .run()
490 |
491 | toast('1/2 done')
492 | const { nft } = await metaplex
493 | .nfts()
494 | .create({
495 | uri: uri,
496 | name: metadata.name!,
497 | sellerFeeBasisPoints: metadata.seller_fee_basis_points!
498 | })
499 | .run()
500 | toast('2/2 DONE')
501 | toast('Minting successful!')
502 | } catch (e:any) {
503 | toast(`ERROR: ${e.message}`)
504 | }
505 | }
506 |
507 | const GET_NFTS = gql`
508 | query GetNfts(
509 | $creators: [PublicKey!]
510 | $updateAuthorities: [PublicKey!]
511 | $limit: Int!
512 | $offset: Int!
513 | ) {
514 | nfts(
515 | creators: $creators
516 | updateAuthorities: $updateAuthorities
517 | limit: $limit
518 | offset: $offset
519 | ) {
520 | address
521 | mintAddress
522 | name
523 | updateAuthorityAddress
524 | image
525 | }
526 | }
527 | `
528 |
529 | useMemo(() => {
530 | if (publicKey?.toBase58()) {
531 | client
532 | .query({
533 | query: GET_NFTS,
534 | variables: {
535 | creators: [publicKey?.toBase58()],
536 | updateAuthorities: [publicKey?.toBase58()],
537 | offset: 0,
538 | limit: 10000
539 | }
540 | })
541 | .then(res => setNfts(res.data.nfts))
542 | } else {
543 | setNfts([])
544 | setSending([])
545 | }
546 | }, [publicKey, GET_NFTS, toggler])
547 |
548 | return (
549 |
550 |
NFT Creator
551 |
683 |
684 |
685 |
686 |
690 | ✕
691 |
692 |
Preview nft here
693 |
694 | mint
695 |
696 |
697 |
698 | )
699 | }
700 |
701 | export default NftMint
702 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./pages/**/*.{js,ts,jsx,tsx}",
4 | "./components/**/*.{js,ts,jsx,tsx}",
5 | ],
6 | presets: [],
7 | darkMode: 'class', // or 'class'
8 | theme: {
9 | screens: {
10 | sm: '640px',
11 | md: '768px',
12 | lg: '1024px',
13 | xl: '1280px',
14 | '2xl': '1536px',
15 | },
16 | colors: ({ colors }) => ({
17 | inherit: colors.inherit,
18 | current: colors.current,
19 | transparent: colors.transparent,
20 | black: colors.black,
21 | white: colors.white,
22 | slate: colors.slate,
23 | gray: colors.gray,
24 | zinc: colors.zinc,
25 | neutral: colors.neutral,
26 | stone: colors.stone,
27 | red: colors.red,
28 | orange: colors.orange,
29 | amber: colors.amber,
30 | yellow: colors.yellow,
31 | lime: colors.lime,
32 | green: colors.green,
33 | emerald: colors.emerald,
34 | teal: colors.teal,
35 | cyan: colors.cyan,
36 | sky: colors.sky,
37 | blue: colors.blue,
38 | indigo: colors.indigo,
39 | violet: colors.violet,
40 | purple: colors.purple,
41 | fuchsia: colors.fuchsia,
42 | pink: colors.pink,
43 | rose: colors.rose,
44 | }),
45 | columns: {
46 | auto: 'auto',
47 | 1: '1',
48 | 2: '2',
49 | 3: '3',
50 | 4: '4',
51 | 5: '5',
52 | 6: '6',
53 | 7: '7',
54 | 8: '8',
55 | 9: '9',
56 | 10: '10',
57 | 11: '11',
58 | 12: '12',
59 | '3xs': '16rem',
60 | '2xs': '18rem',
61 | xs: '20rem',
62 | sm: '24rem',
63 | md: '28rem',
64 | lg: '32rem',
65 | xl: '36rem',
66 | '2xl': '42rem',
67 | '3xl': '48rem',
68 | '4xl': '56rem',
69 | '5xl': '64rem',
70 | '6xl': '72rem',
71 | '7xl': '80rem',
72 | },
73 | spacing: {
74 | px: '1px',
75 | 0: '0px',
76 | 0.5: '0.125rem',
77 | 1: '0.25rem',
78 | 1.5: '0.375rem',
79 | 2: '0.5rem',
80 | 2.5: '0.625rem',
81 | 3: '0.75rem',
82 | 3.5: '0.875rem',
83 | 4: '1rem',
84 | 5: '1.25rem',
85 | 6: '1.5rem',
86 | 7: '1.75rem',
87 | 8: '2rem',
88 | 9: '2.25rem',
89 | 10: '2.5rem',
90 | 11: '2.75rem',
91 | 12: '3rem',
92 | 14: '3.5rem',
93 | 16: '4rem',
94 | 20: '5rem',
95 | 24: '6rem',
96 | 28: '7rem',
97 | 32: '8rem',
98 | 36: '9rem',
99 | 40: '10rem',
100 | 44: '11rem',
101 | 48: '12rem',
102 | 52: '13rem',
103 | 56: '14rem',
104 | 60: '15rem',
105 | 64: '16rem',
106 | 72: '18rem',
107 | 80: '20rem',
108 | 96: '24rem',
109 | },
110 | animation: {
111 | none: 'none',
112 | spin: 'spin 1s linear infinite',
113 | ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
114 | pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
115 | bounce: 'bounce 1s infinite',
116 | },
117 | aspectRatio: {
118 | auto: 'auto',
119 | square: '1 / 1',
120 | video: '16 / 9',
121 | },
122 | backdropBlur: ({ theme }) => theme('blur'),
123 | backdropBrightness: ({ theme }) => theme('brightness'),
124 | backdropContrast: ({ theme }) => theme('contrast'),
125 | backdropGrayscale: ({ theme }) => theme('grayscale'),
126 | backdropHueRotate: ({ theme }) => theme('hueRotate'),
127 | backdropInvert: ({ theme }) => theme('invert'),
128 | backdropOpacity: ({ theme }) => theme('opacity'),
129 | backdropSaturate: ({ theme }) => theme('saturate'),
130 | backdropSepia: ({ theme }) => theme('sepia'),
131 | backgroundColor: ({ theme }) => theme('colors'),
132 | backgroundImage: {
133 | none: 'none',
134 | 'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))',
135 | 'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))',
136 | 'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))',
137 | 'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))',
138 | 'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))',
139 | 'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))',
140 | 'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))',
141 | 'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))',
142 | },
143 | backgroundOpacity: ({ theme }) => theme('opacity'),
144 | backgroundPosition: {
145 | bottom: 'bottom',
146 | center: 'center',
147 | left: 'left',
148 | 'left-bottom': 'left bottom',
149 | 'left-top': 'left top',
150 | right: 'right',
151 | 'right-bottom': 'right bottom',
152 | 'right-top': 'right top',
153 | top: 'top',
154 | },
155 | backgroundSize: {
156 | auto: 'auto',
157 | cover: 'cover',
158 | contain: 'contain',
159 | },
160 | blur: {
161 | 0: '0',
162 | none: '0',
163 | sm: '4px',
164 | DEFAULT: '8px',
165 | md: '12px',
166 | lg: '16px',
167 | xl: '24px',
168 | '2xl': '40px',
169 | '3xl': '64px',
170 | },
171 | brightness: {
172 | 0: '0',
173 | 50: '.5',
174 | 75: '.75',
175 | 90: '.9',
176 | 95: '.95',
177 | 100: '1',
178 | 105: '1.05',
179 | 110: '1.1',
180 | 125: '1.25',
181 | 150: '1.5',
182 | 200: '2',
183 | },
184 | borderColor: ({ theme }) => ({
185 | ...theme('colors'),
186 | DEFAULT: theme('colors.gray.200', 'currentColor'),
187 | }),
188 | borderOpacity: ({ theme }) => theme('opacity'),
189 | borderRadius: {
190 | none: '0px',
191 | sm: '0.125rem',
192 | DEFAULT: '0.25rem',
193 | md: '0.375rem',
194 | lg: '0.5rem',
195 | xl: '0.75rem',
196 | '2xl': '1rem',
197 | '3xl': '1.5rem',
198 | full: '9999px',
199 | },
200 | borderWidth: {
201 | DEFAULT: '1px',
202 | 0: '0px',
203 | 2: '2px',
204 | 4: '4px',
205 | 8: '8px',
206 | },
207 | boxShadow: {
208 | sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
209 | DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
210 | md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
211 | lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
212 | xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
213 | '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
214 | inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
215 | none: 'none',
216 | },
217 | boxShadowColor: ({ theme }) => theme('colors'),
218 | caretColor: ({ theme }) => theme('colors'),
219 | accentColor: ({ theme }) => ({
220 | ...theme('colors'),
221 | auto: 'auto',
222 | }),
223 | contrast: {
224 | 0: '0',
225 | 50: '.5',
226 | 75: '.75',
227 | 100: '1',
228 | 125: '1.25',
229 | 150: '1.5',
230 | 200: '2',
231 | },
232 | container: {},
233 | content: {
234 | none: 'none',
235 | },
236 | cursor: {
237 | auto: 'auto',
238 | default: 'default',
239 | pointer: 'pointer',
240 | wait: 'wait',
241 | text: 'text',
242 | move: 'move',
243 | help: 'help',
244 | 'not-allowed': 'not-allowed',
245 | none: 'none',
246 | 'context-menu': 'context-menu',
247 | progress: 'progress',
248 | cell: 'cell',
249 | crosshair: 'crosshair',
250 | 'vertical-text': 'vertical-text',
251 | alias: 'alias',
252 | copy: 'copy',
253 | 'no-drop': 'no-drop',
254 | grab: 'grab',
255 | grabbing: 'grabbing',
256 | 'all-scroll': 'all-scroll',
257 | 'col-resize': 'col-resize',
258 | 'row-resize': 'row-resize',
259 | 'n-resize': 'n-resize',
260 | 'e-resize': 'e-resize',
261 | 's-resize': 's-resize',
262 | 'w-resize': 'w-resize',
263 | 'ne-resize': 'ne-resize',
264 | 'nw-resize': 'nw-resize',
265 | 'se-resize': 'se-resize',
266 | 'sw-resize': 'sw-resize',
267 | 'ew-resize': 'ew-resize',
268 | 'ns-resize': 'ns-resize',
269 | 'nesw-resize': 'nesw-resize',
270 | 'nwse-resize': 'nwse-resize',
271 | 'zoom-in': 'zoom-in',
272 | 'zoom-out': 'zoom-out',
273 | },
274 | divideColor: ({ theme }) => theme('borderColor'),
275 | divideOpacity: ({ theme }) => theme('borderOpacity'),
276 | divideWidth: ({ theme }) => theme('borderWidth'),
277 | dropShadow: {
278 | sm: '0 1px 1px rgb(0 0 0 / 0.05)',
279 | DEFAULT: ['0 1px 2px rgb(0 0 0 / 0.1)', '0 1px 1px rgb(0 0 0 / 0.06)'],
280 | md: ['0 4px 3px rgb(0 0 0 / 0.07)', '0 2px 2px rgb(0 0 0 / 0.06)'],
281 | lg: ['0 10px 8px rgb(0 0 0 / 0.04)', '0 4px 3px rgb(0 0 0 / 0.1)'],
282 | xl: ['0 20px 13px rgb(0 0 0 / 0.03)', '0 8px 5px rgb(0 0 0 / 0.08)'],
283 | '2xl': '0 25px 25px rgb(0 0 0 / 0.15)',
284 | none: '0 0 #0000',
285 | },
286 | fill: ({ theme }) => theme('colors'),
287 | grayscale: {
288 | 0: '0',
289 | DEFAULT: '100%',
290 | },
291 | hueRotate: {
292 | 0: '0deg',
293 | 15: '15deg',
294 | 30: '30deg',
295 | 60: '60deg',
296 | 90: '90deg',
297 | 180: '180deg',
298 | },
299 | invert: {
300 | 0: '0',
301 | DEFAULT: '100%',
302 | },
303 | flex: {
304 | 1: '1 1 0%',
305 | auto: '1 1 auto',
306 | initial: '0 1 auto',
307 | none: 'none',
308 | },
309 | flexBasis: ({ theme }) => ({
310 | auto: 'auto',
311 | ...theme('spacing'),
312 | '1/2': '50%',
313 | '1/3': '33.333333%',
314 | '2/3': '66.666667%',
315 | '1/4': '25%',
316 | '2/4': '50%',
317 | '3/4': '75%',
318 | '1/5': '20%',
319 | '2/5': '40%',
320 | '3/5': '60%',
321 | '4/5': '80%',
322 | '1/6': '16.666667%',
323 | '2/6': '33.333333%',
324 | '3/6': '50%',
325 | '4/6': '66.666667%',
326 | '5/6': '83.333333%',
327 | '1/12': '8.333333%',
328 | '2/12': '16.666667%',
329 | '3/12': '25%',
330 | '4/12': '33.333333%',
331 | '5/12': '41.666667%',
332 | '6/12': '50%',
333 | '7/12': '58.333333%',
334 | '8/12': '66.666667%',
335 | '9/12': '75%',
336 | '10/12': '83.333333%',
337 | '11/12': '91.666667%',
338 | full: '100%',
339 | }),
340 | flexGrow: {
341 | 0: '0',
342 | DEFAULT: '1',
343 | },
344 | flexShrink: {
345 | 0: '0',
346 | DEFAULT: '1',
347 | },
348 | fontFamily: {
349 | sans: [
350 | 'ui-sans-serif',
351 | 'system-ui',
352 | '-apple-system',
353 | 'BlinkMacSystemFont',
354 | '"Segoe UI"',
355 | 'Roboto',
356 | '"Helvetica Neue"',
357 | 'Arial',
358 | '"Noto Sans"',
359 | 'sans-serif',
360 | '"Apple Color Emoji"',
361 | '"Segoe UI Emoji"',
362 | '"Segoe UI Symbol"',
363 | '"Noto Color Emoji"',
364 | ],
365 | serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
366 | mono: [
367 | 'ui-monospace',
368 | 'SFMono-Regular',
369 | 'Menlo',
370 | 'Monaco',
371 | 'Consolas',
372 | '"Liberation Mono"',
373 | '"Courier New"',
374 | 'monospace',
375 | ],
376 | },
377 | fontSize: {
378 | xs: ['0.75rem', { lineHeight: '1rem' }],
379 | sm: ['0.875rem', { lineHeight: '1.25rem' }],
380 | base: ['1rem', { lineHeight: '1.5rem' }],
381 | lg: ['1.125rem', { lineHeight: '1.75rem' }],
382 | xl: ['1.25rem', { lineHeight: '1.75rem' }],
383 | '2xl': ['1.5rem', { lineHeight: '2rem' }],
384 | '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
385 | '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
386 | '5xl': ['3rem', { lineHeight: '1' }],
387 | '6xl': ['3.75rem', { lineHeight: '1' }],
388 | '7xl': ['4.5rem', { lineHeight: '1' }],
389 | '8xl': ['6rem', { lineHeight: '1' }],
390 | '9xl': ['8rem', { lineHeight: '1' }],
391 | },
392 | fontWeight: {
393 | thin: '100',
394 | extralight: '200',
395 | light: '300',
396 | normal: '400',
397 | medium: '500',
398 | semibold: '600',
399 | bold: '700',
400 | extrabold: '800',
401 | black: '900',
402 | },
403 | gap: ({ theme }) => theme('spacing'),
404 | gradientColorStops: ({ theme }) => theme('colors'),
405 | gridAutoColumns: {
406 | auto: 'auto',
407 | min: 'min-content',
408 | max: 'max-content',
409 | fr: 'minmax(0, 1fr)',
410 | },
411 | gridAutoRows: {
412 | auto: 'auto',
413 | min: 'min-content',
414 | max: 'max-content',
415 | fr: 'minmax(0, 1fr)',
416 | },
417 | gridColumn: {
418 | auto: 'auto',
419 | 'span-1': 'span 1 / span 1',
420 | 'span-2': 'span 2 / span 2',
421 | 'span-3': 'span 3 / span 3',
422 | 'span-4': 'span 4 / span 4',
423 | 'span-5': 'span 5 / span 5',
424 | 'span-6': 'span 6 / span 6',
425 | 'span-7': 'span 7 / span 7',
426 | 'span-8': 'span 8 / span 8',
427 | 'span-9': 'span 9 / span 9',
428 | 'span-10': 'span 10 / span 10',
429 | 'span-11': 'span 11 / span 11',
430 | 'span-12': 'span 12 / span 12',
431 | 'span-full': '1 / -1',
432 | },
433 | gridColumnEnd: {
434 | auto: 'auto',
435 | 1: '1',
436 | 2: '2',
437 | 3: '3',
438 | 4: '4',
439 | 5: '5',
440 | 6: '6',
441 | 7: '7',
442 | 8: '8',
443 | 9: '9',
444 | 10: '10',
445 | 11: '11',
446 | 12: '12',
447 | 13: '13',
448 | },
449 | gridColumnStart: {
450 | auto: 'auto',
451 | 1: '1',
452 | 2: '2',
453 | 3: '3',
454 | 4: '4',
455 | 5: '5',
456 | 6: '6',
457 | 7: '7',
458 | 8: '8',
459 | 9: '9',
460 | 10: '10',
461 | 11: '11',
462 | 12: '12',
463 | 13: '13',
464 | },
465 | gridRow: {
466 | auto: 'auto',
467 | 'span-1': 'span 1 / span 1',
468 | 'span-2': 'span 2 / span 2',
469 | 'span-3': 'span 3 / span 3',
470 | 'span-4': 'span 4 / span 4',
471 | 'span-5': 'span 5 / span 5',
472 | 'span-6': 'span 6 / span 6',
473 | 'span-full': '1 / -1',
474 | },
475 | gridRowStart: {
476 | auto: 'auto',
477 | 1: '1',
478 | 2: '2',
479 | 3: '3',
480 | 4: '4',
481 | 5: '5',
482 | 6: '6',
483 | 7: '7',
484 | },
485 | gridRowEnd: {
486 | auto: 'auto',
487 | 1: '1',
488 | 2: '2',
489 | 3: '3',
490 | 4: '4',
491 | 5: '5',
492 | 6: '6',
493 | 7: '7',
494 | },
495 | gridTemplateColumns: {
496 | none: 'none',
497 | 1: 'repeat(1, minmax(0, 1fr))',
498 | 2: 'repeat(2, minmax(0, 1fr))',
499 | 3: 'repeat(3, minmax(0, 1fr))',
500 | 4: 'repeat(4, minmax(0, 1fr))',
501 | 5: 'repeat(5, minmax(0, 1fr))',
502 | 6: 'repeat(6, minmax(0, 1fr))',
503 | 7: 'repeat(7, minmax(0, 1fr))',
504 | 8: 'repeat(8, minmax(0, 1fr))',
505 | 9: 'repeat(9, minmax(0, 1fr))',
506 | 10: 'repeat(10, minmax(0, 1fr))',
507 | 11: 'repeat(11, minmax(0, 1fr))',
508 | 12: 'repeat(12, minmax(0, 1fr))',
509 | },
510 | gridTemplateRows: {
511 | none: 'none',
512 | 1: 'repeat(1, minmax(0, 1fr))',
513 | 2: 'repeat(2, minmax(0, 1fr))',
514 | 3: 'repeat(3, minmax(0, 1fr))',
515 | 4: 'repeat(4, minmax(0, 1fr))',
516 | 5: 'repeat(5, minmax(0, 1fr))',
517 | 6: 'repeat(6, minmax(0, 1fr))',
518 | },
519 | height: ({ theme }) => ({
520 | auto: 'auto',
521 | ...theme('spacing'),
522 | '1/2': '50%',
523 | '1/3': '33.333333%',
524 | '2/3': '66.666667%',
525 | '1/4': '25%',
526 | '2/4': '50%',
527 | '3/4': '75%',
528 | '1/5': '20%',
529 | '2/5': '40%',
530 | '3/5': '60%',
531 | '4/5': '80%',
532 | '1/6': '16.666667%',
533 | '2/6': '33.333333%',
534 | '3/6': '50%',
535 | '4/6': '66.666667%',
536 | '5/6': '83.333333%',
537 | full: '100%',
538 | screen: '100vh',
539 | min: 'min-content',
540 | max: 'max-content',
541 | fit: 'fit-content',
542 | }),
543 | inset: ({ theme }) => ({
544 | auto: 'auto',
545 | ...theme('spacing'),
546 | '1/2': '50%',
547 | '1/3': '33.333333%',
548 | '2/3': '66.666667%',
549 | '1/4': '25%',
550 | '2/4': '50%',
551 | '3/4': '75%',
552 | full: '100%',
553 | }),
554 | keyframes: {
555 | spin: {
556 | to: {
557 | transform: 'rotate(360deg)',
558 | },
559 | },
560 | ping: {
561 | '75%, 100%': {
562 | transform: 'scale(2)',
563 | opacity: '0',
564 | },
565 | },
566 | pulse: {
567 | '50%': {
568 | opacity: '.5',
569 | },
570 | },
571 | bounce: {
572 | '0%, 100%': {
573 | transform: 'translateY(-25%)',
574 | animationTimingFunction: 'cubic-bezier(0.8,0,1,1)',
575 | },
576 | '50%': {
577 | transform: 'none',
578 | animationTimingFunction: 'cubic-bezier(0,0,0.2,1)',
579 | },
580 | },
581 | },
582 | letterSpacing: {
583 | tighter: '-0.05em',
584 | tight: '-0.025em',
585 | normal: '0em',
586 | wide: '0.025em',
587 | wider: '0.05em',
588 | widest: '0.1em',
589 | },
590 | lineHeight: {
591 | none: '1',
592 | tight: '1.25',
593 | snug: '1.375',
594 | normal: '1.5',
595 | relaxed: '1.625',
596 | loose: '2',
597 | 3: '.75rem',
598 | 4: '1rem',
599 | 5: '1.25rem',
600 | 6: '1.5rem',
601 | 7: '1.75rem',
602 | 8: '2rem',
603 | 9: '2.25rem',
604 | 10: '2.5rem',
605 | },
606 | listStyleType: {
607 | none: 'none',
608 | disc: 'disc',
609 | decimal: 'decimal',
610 | },
611 | margin: ({ theme }) => ({
612 | auto: 'auto',
613 | ...theme('spacing'),
614 | }),
615 | maxHeight: ({ theme }) => ({
616 | ...theme('spacing'),
617 | full: '100%',
618 | screen: '100vh',
619 | min: 'min-content',
620 | max: 'max-content',
621 | fit: 'fit-content',
622 | }),
623 | maxWidth: ({ theme, breakpoints }) => ({
624 | none: 'none',
625 | 0: '0rem',
626 | xs: '20rem',
627 | sm: '24rem',
628 | md: '28rem',
629 | lg: '32rem',
630 | xl: '36rem',
631 | '2xl': '42rem',
632 | '3xl': '48rem',
633 | '4xl': '56rem',
634 | '5xl': '64rem',
635 | '6xl': '72rem',
636 | '7xl': '80rem',
637 | full: '100%',
638 | min: 'min-content',
639 | max: 'max-content',
640 | fit: 'fit-content',
641 | prose: '65ch',
642 | ...breakpoints(theme('screens')),
643 | }),
644 | minHeight: {
645 | 0: '0px',
646 | full: '100%',
647 | screen: '100vh',
648 | min: 'min-content',
649 | max: 'max-content',
650 | fit: 'fit-content',
651 | },
652 | minWidth: {
653 | 0: '0px',
654 | full: '100%',
655 | min: 'min-content',
656 | max: 'max-content',
657 | fit: 'fit-content',
658 | },
659 | objectPosition: {
660 | bottom: 'bottom',
661 | center: 'center',
662 | left: 'left',
663 | 'left-bottom': 'left bottom',
664 | 'left-top': 'left top',
665 | right: 'right',
666 | 'right-bottom': 'right bottom',
667 | 'right-top': 'right top',
668 | top: 'top',
669 | },
670 | opacity: {
671 | 0: '0',
672 | 5: '0.05',
673 | 10: '0.1',
674 | 20: '0.2',
675 | 25: '0.25',
676 | 30: '0.3',
677 | 40: '0.4',
678 | 50: '0.5',
679 | 60: '0.6',
680 | 70: '0.7',
681 | 75: '0.75',
682 | 80: '0.8',
683 | 90: '0.9',
684 | 95: '0.95',
685 | 100: '1',
686 | },
687 | order: {
688 | first: '-9999',
689 | last: '9999',
690 | none: '0',
691 | 1: '1',
692 | 2: '2',
693 | 3: '3',
694 | 4: '4',
695 | 5: '5',
696 | 6: '6',
697 | 7: '7',
698 | 8: '8',
699 | 9: '9',
700 | 10: '10',
701 | 11: '11',
702 | 12: '12',
703 | },
704 | padding: ({ theme }) => theme('spacing'),
705 | placeholderColor: ({ theme }) => theme('colors'),
706 | placeholderOpacity: ({ theme }) => theme('opacity'),
707 | outlineColor: ({ theme }) => theme('colors'),
708 | outlineOffset: {
709 | 0: '0px',
710 | 1: '1px',
711 | 2: '2px',
712 | 4: '4px',
713 | 8: '8px',
714 | },
715 | outlineWidth: {
716 | 0: '0px',
717 | 1: '1px',
718 | 2: '2px',
719 | 4: '4px',
720 | 8: '8px',
721 | },
722 | ringColor: ({ theme }) => ({
723 | DEFAULT: theme('colors.blue.500', '#3b82f6'),
724 | ...theme('colors'),
725 | }),
726 | ringOffsetColor: ({ theme }) => theme('colors'),
727 | ringOffsetWidth: {
728 | 0: '0px',
729 | 1: '1px',
730 | 2: '2px',
731 | 4: '4px',
732 | 8: '8px',
733 | },
734 | ringOpacity: ({ theme }) => ({
735 | DEFAULT: '0.5',
736 | ...theme('opacity'),
737 | }),
738 | ringWidth: {
739 | DEFAULT: '3px',
740 | 0: '0px',
741 | 1: '1px',
742 | 2: '2px',
743 | 4: '4px',
744 | 8: '8px',
745 | },
746 | rotate: {
747 | 0: '0deg',
748 | 1: '1deg',
749 | 2: '2deg',
750 | 3: '3deg',
751 | 6: '6deg',
752 | 12: '12deg',
753 | 45: '45deg',
754 | 90: '90deg',
755 | 180: '180deg',
756 | },
757 | saturate: {
758 | 0: '0',
759 | 50: '.5',
760 | 100: '1',
761 | 150: '1.5',
762 | 200: '2',
763 | },
764 | scale: {
765 | 0: '0',
766 | 50: '.5',
767 | 75: '.75',
768 | 90: '.9',
769 | 95: '.95',
770 | 100: '1',
771 | 105: '1.05',
772 | 110: '1.1',
773 | 125: '1.25',
774 | 150: '1.5',
775 | },
776 | scrollMargin: ({ theme }) => ({
777 | ...theme('spacing'),
778 | }),
779 | scrollPadding: ({ theme }) => theme('spacing'),
780 | sepia: {
781 | 0: '0',
782 | DEFAULT: '100%',
783 | },
784 | skew: {
785 | 0: '0deg',
786 | 1: '1deg',
787 | 2: '2deg',
788 | 3: '3deg',
789 | 6: '6deg',
790 | 12: '12deg',
791 | },
792 | space: ({ theme }) => ({
793 | ...theme('spacing'),
794 | }),
795 | stroke: ({ theme }) => theme('colors'),
796 | strokeWidth: {
797 | 0: '0',
798 | 1: '1',
799 | 2: '2',
800 | },
801 | textColor: ({ theme }) => theme('colors'),
802 | textDecorationColor: ({ theme }) => theme('colors'),
803 | textDecorationThickness: {
804 | auto: 'auto',
805 | 'from-font': 'from-font',
806 | 0: '0px',
807 | 1: '1px',
808 | 2: '2px',
809 | 4: '4px',
810 | 8: '8px',
811 | },
812 | textUnderlineOffset: {
813 | auto: 'auto',
814 | 0: '0px',
815 | 1: '1px',
816 | 2: '2px',
817 | 4: '4px',
818 | 8: '8px',
819 | },
820 | textIndent: ({ theme }) => ({
821 | ...theme('spacing'),
822 | }),
823 | textOpacity: ({ theme }) => theme('opacity'),
824 | transformOrigin: {
825 | center: 'center',
826 | top: 'top',
827 | 'top-right': 'top right',
828 | right: 'right',
829 | 'bottom-right': 'bottom right',
830 | bottom: 'bottom',
831 | 'bottom-left': 'bottom left',
832 | left: 'left',
833 | 'top-left': 'top left',
834 | },
835 | transitionDelay: {
836 | 75: '75ms',
837 | 100: '100ms',
838 | 150: '150ms',
839 | 200: '200ms',
840 | 300: '300ms',
841 | 500: '500ms',
842 | 700: '700ms',
843 | 1000: '1000ms',
844 | },
845 | transitionDuration: {
846 | DEFAULT: '150ms',
847 | 75: '75ms',
848 | 100: '100ms',
849 | 150: '150ms',
850 | 200: '200ms',
851 | 300: '300ms',
852 | 500: '500ms',
853 | 700: '700ms',
854 | 1000: '1000ms',
855 | },
856 | transitionProperty: {
857 | none: 'none',
858 | all: 'all',
859 | DEFAULT:
860 | 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter',
861 | colors: 'color, background-color, border-color, text-decoration-color, fill, stroke',
862 | opacity: 'opacity',
863 | shadow: 'box-shadow',
864 | transform: 'transform',
865 | },
866 | transitionTimingFunction: {
867 | DEFAULT: 'cubic-bezier(0.4, 0, 0.2, 1)',
868 | linear: 'linear',
869 | in: 'cubic-bezier(0.4, 0, 1, 1)',
870 | out: 'cubic-bezier(0, 0, 0.2, 1)',
871 | 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
872 | },
873 | translate: ({ theme }) => ({
874 | ...theme('spacing'),
875 | '1/2': '50%',
876 | '1/3': '33.333333%',
877 | '2/3': '66.666667%',
878 | '1/4': '25%',
879 | '2/4': '50%',
880 | '3/4': '75%',
881 | full: '100%',
882 | }),
883 | width: ({ theme }) => ({
884 | auto: 'auto',
885 | ...theme('spacing'),
886 | '1/2': '50%',
887 | '1/3': '33.333333%',
888 | '2/3': '66.666667%',
889 | '1/4': '25%',
890 | '2/4': '50%',
891 | '3/4': '75%',
892 | '1/5': '20%',
893 | '2/5': '40%',
894 | '3/5': '60%',
895 | '4/5': '80%',
896 | '1/6': '16.666667%',
897 | '2/6': '33.333333%',
898 | '3/6': '50%',
899 | '4/6': '66.666667%',
900 | '5/6': '83.333333%',
901 | '1/12': '8.333333%',
902 | '2/12': '16.666667%',
903 | '3/12': '25%',
904 | '4/12': '33.333333%',
905 | '5/12': '41.666667%',
906 | '6/12': '50%',
907 | '7/12': '58.333333%',
908 | '8/12': '66.666667%',
909 | '9/12': '75%',
910 | '10/12': '83.333333%',
911 | '11/12': '91.666667%',
912 | full: '100%',
913 | screen: '100vw',
914 | min: 'min-content',
915 | max: 'max-content',
916 | fit: 'fit-content',
917 | }),
918 | willChange: {
919 | auto: 'auto',
920 | scroll: 'scroll-position',
921 | contents: 'contents',
922 | transform: 'transform',
923 | },
924 | zIndex: {
925 | auto: 'auto',
926 | 0: '0',
927 | 10: '10',
928 | 20: '20',
929 | 30: '30',
930 | 40: '40',
931 | 50: '50',
932 | },
933 | },
934 | variantOrder: [
935 | 'first',
936 | 'last',
937 | 'odd',
938 | 'even',
939 | 'visited',
940 | 'checked',
941 | 'empty',
942 | 'read-only',
943 | 'group-hover',
944 | 'group-focus',
945 | 'focus-within',
946 | 'hover',
947 | 'focus',
948 | 'focus-visible',
949 | 'active',
950 | 'disabled',
951 | ],
952 | plugins: [require("daisyui")],
953 | }
954 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------