Pārlūkot izejas kodu

solang cnft example

John 2 gadi atpakaļ
vecāks
revīzija
7b2d09d039
31 mainītis faili ar 1502 papildinājumiem un 0 dzēšanām
  1. 8 0
      compression/cnft-solang/.gitignore
  2. 8 0
      compression/cnft-solang/.prettierignore
  3. 30 0
      compression/cnft-solang/Anchor.toml
  4. 3 0
      compression/cnft-solang/app/.eslintrc.json
  5. 37 0
      compression/cnft-solang/app/.gitignore
  6. 38 0
      compression/cnft-solang/app/README.md
  7. 30 0
      compression/cnft-solang/app/components/Confirmed.tsx
  8. 108 0
      compression/cnft-solang/app/components/QrCodeCnftMint.tsx
  9. 13 0
      compression/cnft-solang/app/components/WalletMultiButton.tsx
  10. 38 0
      compression/cnft-solang/app/contexts/WalletContextProvider.tsx
  11. 175 0
      compression/cnft-solang/app/idl/compressed_nft.ts
  12. 6 0
      compression/cnft-solang/app/next.config.js
  13. 36 0
      compression/cnft-solang/app/package.json
  14. 13 0
      compression/cnft-solang/app/pages/_app.tsx
  15. 13 0
      compression/cnft-solang/app/pages/_document.tsx
  16. 145 0
      compression/cnft-solang/app/pages/api/mintCnft.ts
  17. 25 0
      compression/cnft-solang/app/pages/index.tsx
  18. BIN
      compression/cnft-solang/app/public/favicon.ico
  19. 1 0
      compression/cnft-solang/app/public/next.svg
  20. 1 0
      compression/cnft-solang/app/public/vercel.svg
  21. 229 0
      compression/cnft-solang/app/styles/Home.module.css
  22. 107 0
      compression/cnft-solang/app/styles/globals.css
  23. 23 0
      compression/cnft-solang/app/tsconfig.json
  24. 15 0
      compression/cnft-solang/app/utils/setup.ts
  25. 12 0
      compression/cnft-solang/app/utils/uri.ts
  26. 12 0
      compression/cnft-solang/migrations/deploy.ts
  27. 21 0
      compression/cnft-solang/package.json
  28. 144 0
      compression/cnft-solang/solidity/compressed-nft.sol
  29. 188 0
      compression/cnft-solang/tests/compressed-nft.ts
  30. 11 0
      compression/cnft-solang/tsconfig.json
  31. 12 0
      compression/cnft-solang/utils/uri.ts

+ 8 - 0
compression/cnft-solang/.gitignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules
+test-ledger
+.yarn

+ 8 - 0
compression/cnft-solang/.prettierignore

@@ -0,0 +1,8 @@
+
+.anchor
+.DS_Store
+target
+node_modules
+dist
+build
+test-ledger

+ 30 - 0
compression/cnft-solang/Anchor.toml

@@ -0,0 +1,30 @@
+[features]
+seeds = false
+skip-lint = false
+[programs.devnet]
+compressed_nft = "BhDH6TLEnf4dLq9hLn2gLwm5rJdj8Cbdc9ZrsjUpL7kB"
+
+[registry]
+url = "https://api.apr.dev"
+
+[provider]
+cluster = "devnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+
+[test.validator]
+url = "https://api.mainnet-beta.solana.com"
+
+[[test.validator.clone]]
+address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
+
+[[test.validator.clone]]
+address = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY"
+
+[[test.validator.clone]]
+address = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"
+
+[[test.validator.clone]]
+address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK"

+ 3 - 0
compression/cnft-solang/app/.eslintrc.json

@@ -0,0 +1,3 @@
+{
+  "extends": "next/core-web-vitals"
+}

+ 37 - 0
compression/cnft-solang/app/.gitignore

@@ -0,0 +1,37 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+package-lock.json

+ 38 - 0
compression/cnft-solang/app/README.md

@@ -0,0 +1,38 @@
+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).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
+
+[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`.
+
+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.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+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.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

+ 30 - 0
compression/cnft-solang/app/components/Confirmed.tsx

@@ -0,0 +1,30 @@
+import { useEffect, useState } from "react"
+import { buildStyles, CircularProgressbar } from "react-circular-progressbar"
+import "react-circular-progressbar/dist/styles.css"
+
+const Confirmed = () => {
+  const [percentage, setPercentage] = useState(0)
+  const [text, setText] = useState("😋")
+
+  useEffect(() => {
+    const t1 = setTimeout(() => setPercentage(100), 100)
+    const t2 = setTimeout(() => setText("✅"), 600)
+
+    return () => {
+      clearTimeout(t1)
+      clearTimeout(t2)
+    }
+  }, [])
+
+  return (
+    <CircularProgressbar
+      value={percentage}
+      text={text}
+      styles={buildStyles({
+        pathColor: "#19fb9b",
+      })}
+    />
+  )
+}
+
+export default Confirmed

+ 108 - 0
compression/cnft-solang/app/components/QrCodeCnftMint.tsx

@@ -0,0 +1,108 @@
+import { Button, Flex, VStack } from "@chakra-ui/react"
+import {
+  createQR,
+  encodeURL,
+  findReference,
+  FindReferenceError,
+  TransactionRequestURLFields,
+  ValidateTransferError,
+} from "@solana/pay"
+import { clusterApiUrl, Connection, Keypair } from "@solana/web3.js"
+import { useEffect, useRef, useState } from "react"
+import Confirmed from "./Confirmed"
+
+interface Props {
+  onClose: () => void
+}
+
+const QrModal = ({ onClose }: Props) => {
+  const [confirmed, setConfirmed] = useState(false)
+  const connection = new Connection(clusterApiUrl("devnet"))
+  const qrRef = useRef<HTMLDivElement>(null)
+  const [reference] = useState(Keypair.generate().publicKey)
+
+  const [size, setSize] = useState(() =>
+    typeof window === "undefined" ? 100 : Math.min(window.outerWidth - 10, 512)
+  )
+
+  useEffect(() => {
+    const listener = () => setSize(Math.min(window.outerWidth - 10, 512))
+    window.addEventListener("resize", listener)
+    return () => window.removeEventListener("resize", listener)
+  }, [])
+
+  useEffect(() => {
+    const { location } = window
+    const params = new URLSearchParams()
+    params.append("reference", reference.toString())
+
+    const apiUrl = `${location.protocol}//${
+      location.host
+    }/api/mintCnft?${params.toString()}`
+    const urlParams: TransactionRequestURLFields = {
+      link: new URL(apiUrl),
+      label: "Label",
+      message: "Message",
+    }
+    const solanaUrl = encodeURL(urlParams)
+    console.log(solanaUrl)
+    const qr = createQR(solanaUrl, size, "white")
+    if (qrRef.current) {
+      qrRef.current.innerHTML = ""
+      qr.append(qrRef.current)
+    }
+  }, [window, size, reference])
+
+  useEffect(() => {
+    const interval = setInterval(async () => {
+      try {
+        const signatureInfo = await findReference(connection, reference, {
+          finality: "confirmed",
+        })
+        setConfirmed(true)
+      } catch (e) {
+        if (e instanceof FindReferenceError) return
+        if (e instanceof ValidateTransferError) {
+          console.error("Transaction is invalid", e)
+          return
+        }
+        console.error("Unknown error", e)
+      }
+    }, 500)
+    return () => {
+      clearInterval(interval)
+      setConfirmed(false)
+    }
+  }, [reference.toString()])
+
+  return (
+    <VStack
+      position="fixed"
+      top="50%"
+      left="50%"
+      transform="translate(-50%, -50%)"
+      backgroundColor="white"
+      padding="10px"
+      rounded="2xl"
+    >
+      {confirmed ? (
+        <div style={{ width: size }}>
+          <Confirmed />
+        </div>
+      ) : (
+        <Flex ref={qrRef} />
+      )}
+      <Button
+        color="gray"
+        onClick={() => {
+          setConfirmed(false)
+          onClose()
+        }}
+      >
+        Close
+      </Button>
+    </VStack>
+  )
+}
+
+export default QrModal

+ 13 - 0
compression/cnft-solang/app/components/WalletMultiButton.tsx

@@ -0,0 +1,13 @@
+import dynamic from "next/dynamic"
+
+export const WalletMultiButtonDynamic = dynamic(
+  async () =>
+    (await import("@solana/wallet-adapter-react-ui")).WalletMultiButton,
+  { ssr: false }
+)
+
+const WalletMultiButton = () => {
+  return <WalletMultiButtonDynamic />
+}
+
+export default WalletMultiButton

+ 38 - 0
compression/cnft-solang/app/contexts/WalletContextProvider.tsx

@@ -0,0 +1,38 @@
+import { FC, ReactNode, useMemo } from "react"
+import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"
+import {
+  ConnectionProvider,
+  WalletProvider,
+} from "@solana/wallet-adapter-react"
+import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"
+import {
+  PhantomWalletAdapter,
+  SolflareWalletAdapter,
+  BackpackWalletAdapter,
+} from "@solana/wallet-adapter-wallets"
+import { clusterApiUrl } from "@solana/web3.js"
+require("@solana/wallet-adapter-react-ui/styles.css")
+
+const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
+  const network = WalletAdapterNetwork.Devnet
+  const endpoint = useMemo(() => clusterApiUrl(network), [network])
+
+  const wallets = useMemo(
+    () => [
+      new BackpackWalletAdapter(),
+      new PhantomWalletAdapter(),
+      new SolflareWalletAdapter(),
+    ],
+    [network]
+  )
+
+  return (
+    <ConnectionProvider endpoint={endpoint}>
+      <WalletProvider wallets={wallets} autoConnect>
+        <WalletModalProvider>{children}</WalletModalProvider>
+      </WalletProvider>
+    </ConnectionProvider>
+  )
+}
+
+export default WalletContextProvider

+ 175 - 0
compression/cnft-solang/app/idl/compressed_nft.ts

@@ -0,0 +1,175 @@
+export type CompressedNft = {
+  version: "0.3.1";
+  name: "compressed_nft";
+  instructions: [
+    {
+      name: "new";
+      accounts: [
+        {
+          name: "dataAccount";
+          isMut: true;
+          isSigner: false;
+          isOptional: false;
+        },
+        {
+          name: "payer";
+          isMut: true;
+          isSigner: true;
+          isOptional: false;
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+          isOptional: false;
+        }
+      ];
+      args: [
+        {
+          name: "bump";
+          type: {
+            array: ["u8", 1];
+          };
+        }
+      ];
+    },
+    {
+      name: "mint";
+      accounts: [
+        {
+          name: "dataAccount";
+          isMut: true;
+          isSigner: false;
+          isOptional: false;
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+          isOptional: false;
+        }
+      ];
+      args: [
+        {
+          name: "treeAuthority";
+          type: "publicKey";
+        },
+        {
+          name: "leafOwner";
+          type: "publicKey";
+        },
+        {
+          name: "leafDelegate";
+          type: "publicKey";
+        },
+        {
+          name: "merkleTree";
+          type: "publicKey";
+        },
+        {
+          name: "payer";
+          type: "publicKey";
+        },
+        {
+          name: "treeDelegate";
+          type: "publicKey";
+        },
+        {
+          name: "uri";
+          type: "string";
+        }
+      ];
+    }
+  ];
+  metadata: {
+    address: "BhDH6TLEnf4dLq9hLn2gLwm5rJdj8Cbdc9ZrsjUpL7kB";
+  };
+};
+
+export const IDL: CompressedNft = {
+  version: "0.3.1",
+  name: "compressed_nft",
+  instructions: [
+    {
+      name: "new",
+      accounts: [
+        {
+          name: "dataAccount",
+          isMut: true,
+          isSigner: false,
+          isOptional: false,
+        },
+        {
+          name: "payer",
+          isMut: true,
+          isSigner: true,
+          isOptional: false,
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+          isOptional: false,
+        },
+      ],
+      args: [
+        {
+          name: "bump",
+          type: {
+            array: ["u8", 1],
+          },
+        },
+      ],
+    },
+    {
+      name: "mint",
+      accounts: [
+        {
+          name: "dataAccount",
+          isMut: true,
+          isSigner: false,
+          isOptional: false,
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+          isOptional: false,
+        },
+      ],
+      args: [
+        {
+          name: "treeAuthority",
+          type: "publicKey",
+        },
+        {
+          name: "leafOwner",
+          type: "publicKey",
+        },
+        {
+          name: "leafDelegate",
+          type: "publicKey",
+        },
+        {
+          name: "merkleTree",
+          type: "publicKey",
+        },
+        {
+          name: "payer",
+          type: "publicKey",
+        },
+        {
+          name: "treeDelegate",
+          type: "publicKey",
+        },
+        {
+          name: "uri",
+          type: "string",
+        },
+      ],
+    },
+  ],
+  metadata: {
+    address: "BhDH6TLEnf4dLq9hLn2gLwm5rJdj8Cbdc9ZrsjUpL7kB",
+  },
+};

+ 6 - 0
compression/cnft-solang/app/next.config.js

@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+  reactStrictMode: true,
+}
+
+module.exports = nextConfig

+ 36 - 0
compression/cnft-solang/app/package.json

@@ -0,0 +1,36 @@
+{
+  "name": "wallet-adapter-chakraui-nextjs",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "dev": "next dev",
+    "build": "next build",
+    "start": "next start",
+    "lint": "next lint"
+  },
+  "dependencies": {
+    "@chakra-ui/next-js": "^2.1.3",
+    "@chakra-ui/react": "^2.6.1",
+    "@emotion/react": "^11.11.0",
+    "@emotion/styled": "^11.11.0",
+    "@metaplex-foundation/mpl-bubblegum": "^0.7.0",
+    "@solana/pay": "^0.2.5",
+    "@solana/spl-account-compression": "^0.1.8",
+    "@solana/wallet-adapter-base": "^0.9.22",
+    "@solana/wallet-adapter-react": "^0.15.32",
+    "@solana/wallet-adapter-react-ui": "^0.9.31",
+    "@solana/wallet-adapter-wallets": "^0.19.16",
+    "@solana/web3.js": "^1.77.1",
+    "@types/node": "20.2.5",
+    "@types/react": "18.2.7",
+    "@types/react-dom": "18.2.4",
+    "eslint": "8.41.0",
+    "eslint-config-next": "13.4.4",
+    "framer-motion": "^10.12.16",
+    "next": "13.4.4",
+    "react": "18.2.0",
+    "react-circular-progressbar": "^2.1.0",
+    "react-dom": "18.2.0",
+    "typescript": "5.0.4"
+  }
+}

+ 13 - 0
compression/cnft-solang/app/pages/_app.tsx

@@ -0,0 +1,13 @@
+import { ChakraProvider } from "@chakra-ui/react"
+import WalletContextProvider from "../contexts/WalletContextProvider"
+import type { AppProps } from "next/app"
+
+export default function App({ Component, pageProps }: AppProps) {
+  return (
+    <ChakraProvider>
+      <WalletContextProvider>
+        <Component {...pageProps} />
+      </WalletContextProvider>
+    </ChakraProvider>
+  )
+}

+ 13 - 0
compression/cnft-solang/app/pages/_document.tsx

@@ -0,0 +1,13 @@
+import { Html, Head, Main, NextScript } from 'next/document'
+
+export default function Document() {
+  return (
+    <Html lang="en">
+      <Head />
+      <body>
+        <Main />
+        <NextScript />
+      </body>
+    </Html>
+  )
+}

+ 145 - 0
compression/cnft-solang/app/pages/api/mintCnft.ts

@@ -0,0 +1,145 @@
+import { NextApiRequest, NextApiResponse } from "next"
+import { PublicKey, SystemProgram, Transaction } from "@solana/web3.js"
+import {
+  SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
+  SPL_NOOP_PROGRAM_ID,
+} from "@solana/spl-account-compression"
+import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID } from "@metaplex-foundation/mpl-bubblegum"
+import { program, connection, treeAddress } from "@/utils/setup"
+import { uris } from "@/utils/uri"
+
+function get(res: NextApiResponse) {
+  res.status(200).json({
+    label: "My Store",
+    icon: "https://solana.com/src/img/branding/solanaLogoMark.svg",
+  })
+}
+
+async function post(req: NextApiRequest, res: NextApiResponse) {
+  const { account } = req.body
+  const { reference } = req.query
+
+  if (!account || !reference) {
+    res.status(400).json({
+      error: "Required data missing. Account or reference not provided.",
+    })
+    return
+  }
+
+  try {
+    const transaction = await buildTransaction(
+      new PublicKey(account),
+      new PublicKey(reference)
+    )
+    res.status(200).json({
+      transaction,
+      message: "Please approve the transaction to mint your NFT!",
+    })
+  } catch (error) {
+    console.error(error)
+    res.status(500).json({ error: "error creating transaction" })
+    return
+  }
+}
+
+async function buildTransaction(account: PublicKey, reference: PublicKey) {
+  // Required solang dataAccount, even though we're not using it.
+  const [dataAccount] = PublicKey.findProgramAddressSync(
+    [Buffer.from("seed")],
+    program.programId
+  )
+
+  // tree authority
+  const [treeAuthority] = PublicKey.findProgramAddressSync(
+    [treeAddress.toBuffer()],
+    BUBBLEGUM_PROGRAM_ID
+  )
+
+  // Randomly select a uri.
+  const randomUri = uris[Math.floor(Math.random() * uris.length)]
+
+  // Initialize the dataAccount.
+  const instruction = await program.methods
+    .mint(
+      treeAuthority, // treeAuthority
+      account, // leafOwner
+      account, // leafDelegate
+      treeAddress, // merkleTree
+      account, // payer
+      account, // treeDelegate, public tree (no delegate check, just require signer)
+      randomUri // uri
+    )
+    .accounts({ dataAccount: dataAccount })
+    .remainingAccounts([
+      {
+        pubkey: account,
+        isWritable: true,
+        isSigner: true,
+      },
+      {
+        pubkey: treeAuthority,
+        isWritable: true,
+        isSigner: false,
+      },
+      {
+        pubkey: treeAddress,
+        isWritable: true,
+        isSigner: false,
+      },
+      {
+        pubkey: SPL_NOOP_PROGRAM_ID,
+        isWritable: false,
+        isSigner: false,
+      },
+      {
+        pubkey: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
+        isWritable: false,
+        isSigner: false,
+      },
+      {
+        pubkey: BUBBLEGUM_PROGRAM_ID,
+        isWritable: false,
+        isSigner: false,
+      },
+      {
+        pubkey: SystemProgram.programId,
+        isWritable: false,
+        isSigner: false,
+      },
+    ])
+    .instruction()
+
+  // Add the reference account to the instruction
+  // Used in client to find the transaction once sent
+  instruction.keys.push({
+    pubkey: reference,
+    isSigner: false,
+    isWritable: false,
+  })
+
+  const latestBlockhash = await connection.getLatestBlockhash()
+
+  // create new Transaction and add instruction
+  const transaction = new Transaction({
+    feePayer: account,
+    blockhash: latestBlockhash.blockhash,
+    lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
+  }).add(instruction)
+
+  return transaction
+    .serialize({ requireAllSignatures: false })
+    .toString("base64")
+}
+
+export default async function handler(
+  req: NextApiRequest,
+  res: NextApiResponse
+) {
+  if (req.method === "GET") {
+    return get(res)
+  } else if (req.method === "POST") {
+    return await post(req, res)
+  } else {
+    return res.status(405).json({ error: "Method not allowed" })
+  }
+}

+ 25 - 0
compression/cnft-solang/app/pages/index.tsx

@@ -0,0 +1,25 @@
+import {
+  Box,
+  Button,
+  Flex,
+  Spacer,
+  VStack,
+  useDisclosure,
+} from "@chakra-ui/react"
+import WalletMultiButton from "@/components/WalletMultiButton"
+import QrModal from "@/components/QrCodeCnftMint"
+export default function Home() {
+  const { isOpen, onOpen, onClose } = useDisclosure()
+  return (
+    <Box>
+      <Flex px={4} py={4}>
+        <Spacer />
+        <WalletMultiButton />
+      </Flex>
+      <VStack justifyContent="center">
+        <Button onClick={onOpen}>Solana Pay Mint</Button>
+        {isOpen && <QrModal onClose={onClose} />}
+      </VStack>
+    </Box>
+  )
+}

BIN
compression/cnft-solang/app/public/favicon.ico


+ 1 - 0
compression/cnft-solang/app/public/next.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

+ 1 - 0
compression/cnft-solang/app/public/vercel.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

+ 229 - 0
compression/cnft-solang/app/styles/Home.module.css

@@ -0,0 +1,229 @@
+.main {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-items: center;
+  padding: 6rem;
+  min-height: 100vh;
+}
+
+.description {
+  display: inherit;
+  justify-content: inherit;
+  align-items: inherit;
+  font-size: 0.85rem;
+  max-width: var(--max-width);
+  width: 100%;
+  z-index: 2;
+  font-family: var(--font-mono);
+}
+
+.description a {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 0.5rem;
+}
+
+.description p {
+  position: relative;
+  margin: 0;
+  padding: 1rem;
+  background-color: rgba(var(--callout-rgb), 0.5);
+  border: 1px solid rgba(var(--callout-border-rgb), 0.3);
+  border-radius: var(--border-radius);
+}
+
+.code {
+  font-weight: 700;
+  font-family: var(--font-mono);
+}
+
+.grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(25%, auto));
+  width: var(--max-width);
+  max-width: 100%;
+}
+
+.card {
+  padding: 1rem 1.2rem;
+  border-radius: var(--border-radius);
+  background: rgba(var(--card-rgb), 0);
+  border: 1px solid rgba(var(--card-border-rgb), 0);
+  transition: background 200ms, border 200ms;
+}
+
+.card span {
+  display: inline-block;
+  transition: transform 200ms;
+}
+
+.card h2 {
+  font-weight: 600;
+  margin-bottom: 0.7rem;
+}
+
+.card p {
+  margin: 0;
+  opacity: 0.6;
+  font-size: 0.9rem;
+  line-height: 1.5;
+  max-width: 30ch;
+}
+
+.center {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+  padding: 4rem 0;
+}
+
+.center::before {
+  background: var(--secondary-glow);
+  border-radius: 50%;
+  width: 480px;
+  height: 360px;
+  margin-left: -400px;
+}
+
+.center::after {
+  background: var(--primary-glow);
+  width: 240px;
+  height: 180px;
+  z-index: -1;
+}
+
+.center::before,
+.center::after {
+  content: '';
+  left: 50%;
+  position: absolute;
+  filter: blur(45px);
+  transform: translateZ(0);
+}
+
+.logo {
+  position: relative;
+}
+/* Enable hover only on non-touch devices */
+@media (hover: hover) and (pointer: fine) {
+  .card:hover {
+    background: rgba(var(--card-rgb), 0.1);
+    border: 1px solid rgba(var(--card-border-rgb), 0.15);
+  }
+
+  .card:hover span {
+    transform: translateX(4px);
+  }
+}
+
+@media (prefers-reduced-motion) {
+  .card:hover span {
+    transform: none;
+  }
+}
+
+/* Mobile */
+@media (max-width: 700px) {
+  .content {
+    padding: 4rem;
+  }
+
+  .grid {
+    grid-template-columns: 1fr;
+    margin-bottom: 120px;
+    max-width: 320px;
+    text-align: center;
+  }
+
+  .card {
+    padding: 1rem 2.5rem;
+  }
+
+  .card h2 {
+    margin-bottom: 0.5rem;
+  }
+
+  .center {
+    padding: 8rem 0 6rem;
+  }
+
+  .center::before {
+    transform: none;
+    height: 300px;
+  }
+
+  .description {
+    font-size: 0.8rem;
+  }
+
+  .description a {
+    padding: 1rem;
+  }
+
+  .description p,
+  .description div {
+    display: flex;
+    justify-content: center;
+    position: fixed;
+    width: 100%;
+  }
+
+  .description p {
+    align-items: center;
+    inset: 0 0 auto;
+    padding: 2rem 1rem 1.4rem;
+    border-radius: 0;
+    border: none;
+    border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
+    background: linear-gradient(
+      to bottom,
+      rgba(var(--background-start-rgb), 1),
+      rgba(var(--callout-rgb), 0.5)
+    );
+    background-clip: padding-box;
+    backdrop-filter: blur(24px);
+  }
+
+  .description div {
+    align-items: flex-end;
+    pointer-events: none;
+    inset: auto 0 0;
+    padding: 2rem;
+    height: 200px;
+    background: linear-gradient(
+      to bottom,
+      transparent 0%,
+      rgb(var(--background-end-rgb)) 40%
+    );
+    z-index: 1;
+  }
+}
+
+/* Tablet and Smaller Desktop */
+@media (min-width: 701px) and (max-width: 1120px) {
+  .grid {
+    grid-template-columns: repeat(2, 50%);
+  }
+}
+
+@media (prefers-color-scheme: dark) {
+  .vercelLogo {
+    filter: invert(1);
+  }
+
+  .logo {
+    filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
+  }
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(360deg);
+  }
+  to {
+    transform: rotate(0deg);
+  }
+}

+ 107 - 0
compression/cnft-solang/app/styles/globals.css

@@ -0,0 +1,107 @@
+:root {
+  --max-width: 1100px;
+  --border-radius: 12px;
+  --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
+    'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
+    'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
+
+  --foreground-rgb: 0, 0, 0;
+  --background-start-rgb: 214, 219, 220;
+  --background-end-rgb: 255, 255, 255;
+
+  --primary-glow: conic-gradient(
+    from 180deg at 50% 50%,
+    #16abff33 0deg,
+    #0885ff33 55deg,
+    #54d6ff33 120deg,
+    #0071ff33 160deg,
+    transparent 360deg
+  );
+  --secondary-glow: radial-gradient(
+    rgba(255, 255, 255, 1),
+    rgba(255, 255, 255, 0)
+  );
+
+  --tile-start-rgb: 239, 245, 249;
+  --tile-end-rgb: 228, 232, 233;
+  --tile-border: conic-gradient(
+    #00000080,
+    #00000040,
+    #00000030,
+    #00000020,
+    #00000010,
+    #00000010,
+    #00000080
+  );
+
+  --callout-rgb: 238, 240, 241;
+  --callout-border-rgb: 172, 175, 176;
+  --card-rgb: 180, 185, 188;
+  --card-border-rgb: 131, 134, 135;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --foreground-rgb: 255, 255, 255;
+    --background-start-rgb: 0, 0, 0;
+    --background-end-rgb: 0, 0, 0;
+
+    --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
+    --secondary-glow: linear-gradient(
+      to bottom right,
+      rgba(1, 65, 255, 0),
+      rgba(1, 65, 255, 0),
+      rgba(1, 65, 255, 0.3)
+    );
+
+    --tile-start-rgb: 2, 13, 46;
+    --tile-end-rgb: 2, 5, 19;
+    --tile-border: conic-gradient(
+      #ffffff80,
+      #ffffff40,
+      #ffffff30,
+      #ffffff20,
+      #ffffff10,
+      #ffffff10,
+      #ffffff80
+    );
+
+    --callout-rgb: 20, 20, 20;
+    --callout-border-rgb: 108, 108, 108;
+    --card-rgb: 100, 100, 100;
+    --card-border-rgb: 200, 200, 200;
+  }
+}
+
+* {
+  box-sizing: border-box;
+  padding: 0;
+  margin: 0;
+}
+
+html,
+body {
+  max-width: 100vw;
+  overflow-x: hidden;
+}
+
+body {
+  color: rgb(var(--foreground-rgb));
+  background: linear-gradient(
+      to bottom,
+      transparent,
+      rgb(var(--background-end-rgb))
+    )
+    rgb(var(--background-start-rgb));
+}
+
+a {
+  color: inherit;
+  text-decoration: none;
+}
+
+@media (prefers-color-scheme: dark) {
+  html {
+    color-scheme: dark;
+  }
+}

+ 23 - 0
compression/cnft-solang/app/tsconfig.json

@@ -0,0 +1,23 @@
+{
+  "compilerOptions": {
+    "target": "es5",
+    "lib": ["dom", "dom.iterable", "esnext"],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "noEmit": true,
+    "esModuleInterop": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "jsx": "preserve",
+    "incremental": true,
+    "paths": {
+      "@/*": ["./*"]
+    }
+  },
+  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+  "exclude": ["node_modules"]
+}

+ 15 - 0
compression/cnft-solang/app/utils/setup.ts

@@ -0,0 +1,15 @@
+import { Program, Idl } from "@coral-xyz/anchor";
+import { IDL, CompressedNft } from "../idl/compressed_nft";
+import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
+
+export const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
+
+const programId = IDL.metadata.address;
+
+export const program = new Program(IDL as Idl, programId, {
+  connection,
+}) as unknown as Program<CompressedNft>;
+
+export const treeAddress = new PublicKey(
+  "FYwZ4rMtexsHBTx2aCnQRVg51K5WXZJ1n3SYacXFvado"
+);

+ 12 - 0
compression/cnft-solang/app/utils/uri.ts

@@ -0,0 +1,12 @@
+export const uris = [
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/40_183_132/40_183_132.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/12_217_47/12_217_47.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/255_68_196/255_68_196.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/156_61_27/156_61_27.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/32_150_152/32_150_152.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/78_96_197/78_96_197.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/38_47_11/38_47_11.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/190_85_238/190_85_238.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/69_46_192/69_46_192.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/142_17_9/142_17_9.json",
+]

+ 12 - 0
compression/cnft-solang/migrations/deploy.ts

@@ -0,0 +1,12 @@
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@coral-xyz/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+};

+ 21 - 0
compression/cnft-solang/package.json

@@ -0,0 +1,21 @@
+{
+    "scripts": {
+        "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
+        "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
+    },
+    "dependencies": {
+        "@coral-xyz/anchor": "^0.28.0",
+        "@metaplex-foundation/mpl-bubblegum": "^0.7.0",
+        "@solana/spl-account-compression": "^0.1.8"
+    },
+    "devDependencies": {
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "prettier": "^2.6.2",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
+    }
+}

+ 144 - 0
compression/cnft-solang/solidity/compressed-nft.sol

@@ -0,0 +1,144 @@
+
+import "solana";
+
+@program_id("BhDH6TLEnf4dLq9hLn2gLwm5rJdj8Cbdc9ZrsjUpL7kB")
+contract compressed_nft {
+
+    @payer(payer) // payer address
+    @seed("seed") // hardcoded seed
+    constructor(
+        @bump bytes1 bump // bump seed for pda address
+    ) {
+        // Creating a dataAccount for the program, which is required by Solang
+        // However, this account is not used in the program
+    }
+
+    // Mint a compressed NFT to an existing merkle tree, via a cross-program invocation to the Bubblegum program
+    // Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/lib.rs#L922
+    // Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/lib.rs#L67
+    function mint(
+        address tree_authority, // authority of the merkle tree
+        address leaf_owner, // owner of the new compressed NFT
+        address leaf_delegate, // delegate of the new compressed NFT (can be the same as leaf_owner)
+        address merkle_tree, // address of the merkle tree
+        address payer, // payer
+        address tree_delegate, // delegate of the merkle tree
+        string uri // uri of the new compressed NFT (metadata)
+    ) public {
+        print("Minting Compressed NFT");
+
+        // Create a creator array with a single creator
+        Creator[] memory creators = new Creator[](1);
+        // Set the creator to the payer
+        creators[0] = Creator({
+            creatorAddress: payer,
+            verified: false,
+            share: 100
+        });
+
+        // Create the metadata args, representing the metadata of the new compressed NFT
+        // Solidity does not support optional arguments,
+        // So we have to explicitly declare if the optional arguments are present or not
+        // If not present, we comment them out, otherwise the transaction will fail with a invalid instruction data error
+        MetadataArgs memory args = MetadataArgs({
+            name: "RGB",
+            symbol: "RGB",
+            uri: uri,
+            sellerFeeBasisPoints: 0,
+            primarySaleHappened: false,
+            isMutable: true,
+            editionNoncePresent: false,
+            // editionNonce: 0,
+            tokenStandardPresent: true,
+            tokenStandard: TokenStandard.NonFungible,
+            collectionPresent: false,
+            // collection: Collection({
+            //     verified: false,
+            //     key: address(0)
+            // }),
+            usesPresent: false,
+            // uses: Uses({
+            //     useMethod: UseMethod.Burn,
+            //     remaining: 0,
+            //     total: 0
+            // }),
+            tokenProgramVersion: TokenProgramVersion.Original,
+            creators: creators
+        });
+
+        AccountMeta[9] metas = [
+            AccountMeta({pubkey: tree_authority, is_writable: true, is_signer: false}),
+            AccountMeta({pubkey: leaf_owner, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: leaf_delegate, is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: merkle_tree, is_writable: true, is_signer: false}),
+            AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: tree_delegate, is_writable: true, is_signer: true}),
+            AccountMeta({pubkey: address"noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: address"cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK", is_writable: false, is_signer: false}),
+            AccountMeta({pubkey: address"11111111111111111111111111111111", is_writable: false, is_signer: false})
+        ];
+
+        // Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/js/src/generated/instructions/mintV1.ts#L64
+        bytes8 discriminator = 0x9162c076b8937668;
+        bytes instructionData = abi.encode(discriminator, args);
+
+        // Invoking the Bubblegum program
+        address'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY'.call{accounts: metas}(instructionData);
+    }
+
+    // Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L81
+    struct MetadataArgs {
+        string name;
+        string symbol;
+        string uri;
+        uint16 sellerFeeBasisPoints;
+        bool primarySaleHappened;
+        bool isMutable;
+        bool editionNoncePresent;
+        // uint8 editionNonce;
+        bool tokenStandardPresent;
+        TokenStandard tokenStandard;
+        bool collectionPresent;
+        // Collection collection;
+        bool usesPresent;
+        // Uses uses;
+        TokenProgramVersion tokenProgramVersion;
+        Creator[] creators;
+    }
+
+    enum TokenStandard {
+        NonFungible,
+        FungibleAsset,
+        Fungible,
+        NonFungibleEdition
+    }
+
+    enum TokenProgramVersion {
+        Original,
+        Token2022
+    }
+
+    struct Creator {
+        address creatorAddress;
+        bool verified;
+        uint8 share;
+    }
+
+    struct Collection {
+        bool verified;
+        address key;
+    }
+
+    struct Uses {
+        UseMethod useMethod;
+        uint64 remaining;
+        uint64 total;
+    }
+
+    enum UseMethod {
+        Burn,
+        Multiple,
+        Single
+    }
+
+}

+ 188 - 0
compression/cnft-solang/tests/compressed-nft.ts

@@ -0,0 +1,188 @@
+import * as anchor from "@coral-xyz/anchor";
+import { Program, Wallet } from "@coral-xyz/anchor";
+import { CompressedNft } from "../target/types/compressed_nft";
+import {
+  PublicKey,
+  SystemProgram,
+  Transaction,
+  Keypair,
+  sendAndConfirmTransaction,
+} from "@solana/web3.js";
+import {
+  SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
+  ValidDepthSizePair,
+  SPL_NOOP_PROGRAM_ID,
+  createAllocTreeIx,
+} from "@solana/spl-account-compression";
+import {
+  PROGRAM_ID as BUBBLEGUM_PROGRAM_ID,
+  createCreateTreeInstruction,
+} from "@metaplex-foundation/mpl-bubblegum";
+import { uris } from "../utils/uri";
+
+describe("compressed-nft", () => {
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+
+  const wallet = provider.wallet as Wallet;
+  const connection = provider.connection;
+
+  const program = anchor.workspace.CompressedNft as Program<CompressedNft>;
+
+  // Generate a new keypair for the merkle tree.
+  const treeKeypair = Keypair.generate();
+
+  // Derive the PDA that will be the tree authority.
+  // This is required by the bubblegum program.
+  const [treeAuthority] = PublicKey.findProgramAddressSync(
+    [treeKeypair.publicKey.toBuffer()],
+    BUBBLEGUM_PROGRAM_ID
+  );
+
+  // Derive the PDA that will be used to initialize the dataAccount.
+  // Required by Solang even though we're not using it.
+  const [dataAccount, bump] = PublicKey.findProgramAddressSync(
+    [Buffer.from("seed")],
+    program.programId
+  );
+
+  // Create a merkle tree account.
+  before(async () => {
+    // Maximum depth and buffer size for the merkle tree.
+    // 2^maxDepth determines the maximum number of leaves that can be stored in the tree.
+    // maxBufferSize determines maximum concurrent updates that can be made within one slot.
+    const maxDepthSizePair: ValidDepthSizePair = {
+      maxDepth: 14,
+      maxBufferSize: 64,
+    };
+
+    // Depth of the canopy (how much of the tree is stored on-chain)
+    const canopyDepth = 0;
+
+    // Instruction to create an account with enough space to store the merkle tree.
+    const allocTreeIx = await createAllocTreeIx(
+      connection,
+      treeKeypair.publicKey,
+      wallet.publicKey,
+      maxDepthSizePair,
+      canopyDepth
+    );
+
+    // Instruction to initialize the merkle tree account with the bubblegum program.
+    const createTreeIx = createCreateTreeInstruction(
+      {
+        treeAuthority,
+        merkleTree: treeKeypair.publicKey,
+        payer: wallet.publicKey,
+        treeCreator: wallet.publicKey,
+        logWrapper: SPL_NOOP_PROGRAM_ID,
+        compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
+      },
+      {
+        maxBufferSize: maxDepthSizePair.maxBufferSize,
+        maxDepth: maxDepthSizePair.maxDepth,
+        public: true, // creating a "public" tree, so anyone can mint cnfts to it
+      },
+      BUBBLEGUM_PROGRAM_ID
+    );
+
+    try {
+      const tx = new Transaction().add(allocTreeIx, createTreeIx);
+      tx.feePayer = wallet.publicKey;
+
+      const txSignature = await sendAndConfirmTransaction(
+        connection,
+        tx,
+        [treeKeypair, wallet.payer],
+        {
+          commitment: "confirmed",
+          skipPreflight: true,
+        }
+      );
+
+      console.log(
+        `https://explorer.solana.com/tx/${txSignature}?cluster=devnet`
+      );
+
+      console.log("Tree Address:", treeKeypair.publicKey.toBase58());
+    } catch (err: any) {
+      console.error("\nFailed to create merkle tree:", err);
+      throw err;
+    }
+
+    console.log("\n");
+  });
+
+  it("Is initialized!", async () => {
+    // Initialize the dataAccount.
+    const tx = await program.methods
+      .new([bump])
+      .accounts({ dataAccount: dataAccount })
+      .rpc();
+    console.log("Your transaction signature", tx);
+  });
+
+  it("Mint Compressed NFT", async () => {
+    // Mint a compressed nft to random receiver.
+    const receiver = Keypair.generate().publicKey;
+
+    // Use a random uri (off-chain metadata) from the list for the test.
+    const randomUri = uris[Math.floor(Math.random() * uris.length)];
+
+    const tx = await program.methods
+      .mint(
+        treeAuthority, // treeAuthority
+        receiver, // leafOwner
+        receiver, // leafDelegate
+        treeKeypair.publicKey, // merkleTree
+        wallet.publicKey, // payer
+        wallet.publicKey, // treeDelegate
+        randomUri // uri
+      )
+      .accounts({ dataAccount: dataAccount }) // dataAccount required by Solang even though its unused.
+      .remainingAccounts([
+        {
+          pubkey: wallet.publicKey, // payer (and tree delegate in this example)
+          isWritable: true,
+          isSigner: true,
+        },
+        {
+          pubkey: receiver, // new leaf owner
+          isWritable: false,
+          isSigner: false,
+        },
+        {
+          pubkey: treeAuthority, // tree authority
+          isWritable: true,
+          isSigner: false,
+        },
+        {
+          pubkey: treeKeypair.publicKey, // tree account address
+          isWritable: true,
+          isSigner: false,
+        },
+        {
+          pubkey: SPL_NOOP_PROGRAM_ID,
+          isWritable: false,
+          isSigner: false,
+        },
+        {
+          pubkey: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
+          isWritable: false,
+          isSigner: false,
+        },
+        {
+          pubkey: BUBBLEGUM_PROGRAM_ID,
+          isWritable: false,
+          isSigner: false,
+        },
+        {
+          pubkey: SystemProgram.programId,
+          isWritable: false,
+          isSigner: false,
+        },
+      ])
+      .rpc({ skipPreflight: true });
+    console.log("Your transaction signature", tx);
+  });
+});

+ 11 - 0
compression/cnft-solang/tsconfig.json

@@ -0,0 +1,11 @@
+{
+            "compilerOptions": {
+              "types": ["mocha", "chai"],
+              "typeRoots": ["./node_modules/@types"],
+              "lib": ["es2015"],
+              "module": "commonjs",
+              "target": "es6",
+              "esModuleInterop": true
+            }
+          }
+          

+ 12 - 0
compression/cnft-solang/utils/uri.ts

@@ -0,0 +1,12 @@
+export const uris = [
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/40_183_132/40_183_132.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/12_217_47/12_217_47.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/255_68_196/255_68_196.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/156_61_27/156_61_27.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/32_150_152/32_150_152.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/78_96_197/78_96_197.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/38_47_11/38_47_11.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/190_85_238/190_85_238.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/69_46_192/69_46_192.json",
+  "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/142_17_9/142_17_9.json",
+]