Repository: solana-labs/chatgpt-plugin Branch: master Commit: 6b7c56e757c0 Files: 77 Total size: 225.9 KB Directory structure: gitextract_4oy13ijj/ ├── .gitignore ├── DISCLAIMER.md ├── LICENSE ├── README.md ├── chatgpt-plugin/ │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── next.config.js │ ├── postcss.config.js │ ├── public/ │ │ └── openapi.yaml │ ├── src/ │ │ ├── app/ │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── lib/ │ │ │ ├── address.ts │ │ │ ├── features.tsx │ │ │ ├── helloMoon/ │ │ │ │ └── index.ts │ │ │ ├── hyperspace/ │ │ │ │ ├── account.ts │ │ │ │ ├── constants.ts │ │ │ │ └── idl/ │ │ │ │ └── hyperspace.ts │ │ │ ├── middleware.ts │ │ │ └── on-chain-metadata/ │ │ │ └── index.ts │ │ └── pages/ │ │ └── api/ │ │ ├── constants.ts │ │ ├── handlers/ │ │ │ ├── getAccountInfo/ │ │ │ │ └── index.ts │ │ │ ├── getAssetsByOwner/ │ │ │ │ └── index.ts │ │ │ ├── getBalance/ │ │ │ │ └── index.ts │ │ │ ├── getCollectionsByFloorPrice/ │ │ │ │ └── index.ts │ │ │ ├── getCollectionsByName/ │ │ │ │ └── index.ts │ │ │ ├── getListedCollectionNFTs/ │ │ │ │ └── index.ts │ │ │ ├── getSignaturesForAddress/ │ │ │ │ └── index.ts │ │ │ ├── getTokenAccounts/ │ │ │ │ └── index.ts │ │ │ ├── getTotalValue/ │ │ │ │ └── index.ts │ │ │ ├── getTransaction/ │ │ │ │ └── index.ts │ │ │ ├── solana-pay/ │ │ │ │ ├── page/ │ │ │ │ │ └── [methodName].ts │ │ │ │ ├── qr/ │ │ │ │ │ └── [methodName].ts │ │ │ │ ├── sign/ │ │ │ │ │ ├── createBurnAsset.ts │ │ │ │ │ ├── createBuyNFT.ts │ │ │ │ │ ├── createCloseNFTMetadata.ts │ │ │ │ │ ├── createListNFT.ts │ │ │ │ │ ├── createMerkleTree.ts │ │ │ │ │ ├── createMintCNFT.ts │ │ │ │ │ ├── createMintNFT.ts │ │ │ │ │ ├── createSetProfilePic.ts │ │ │ │ │ ├── createTransferAsset.ts │ │ │ │ │ ├── createTransferSol.ts │ │ │ │ │ ├── createTransferToken.ts │ │ │ │ │ ├── createWriteNFTMetadata.ts │ │ │ │ │ └── index.ts │ │ │ │ └── utils/ │ │ │ │ └── helpers.ts │ │ │ ├── tokenName/ │ │ │ │ └── index.ts │ │ │ ├── tx-link/ │ │ │ │ └── [txSlug].ts │ │ │ └── walletName/ │ │ │ └── index.ts │ │ ├── helloMoon/ │ │ │ ├── defi/ │ │ │ │ ├── defi.yaml │ │ │ │ ├── programNewUsers.ts │ │ │ │ ├── programOverlap.ts │ │ │ │ ├── tokenStats.ts │ │ │ │ └── tokenUsers.ts │ │ │ ├── jupiter/ │ │ │ │ ├── README.md │ │ │ │ ├── historical.ts │ │ │ │ ├── pairs.ts │ │ │ │ ├── summary.ts │ │ │ │ └── swaps.ts │ │ │ └── jupiter.yaml │ │ ├── metaplex/ │ │ │ ├── getCNFTRent/ │ │ │ │ └── index.ts │ │ │ ├── getPublicTree/ │ │ │ │ └── index.ts │ │ │ └── metaplex.yaml │ │ ├── solflare-pfp/ │ │ │ └── getProfilePic.ts │ │ └── tiplink/ │ │ └── makeLink.ts │ └── tailwind.config.js ├── langchain_examples/ │ ├── README.md │ └── python/ │ └── main.py ├── pyproject.toml └── scripts/ ├── createNFTCollection.ts ├── createTree.ts ├── helper.ts ├── runCreateCollection.ts ├── send.ts └── uploadMetadata.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .env node_modules/ yarn* tmp* # Debugging tools scripts/collection.ts scripts/sharp.ts langchain_examples/python/__pycache__ *json ================================================ FILE: DISCLAIMER.md ================================================ # Disclaimer All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the good faith efforts Solana Labs, Inc. and its affiliates ("SL"). It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment. Any content produced by SL or developer resources that SL provides have not been subject to audit and are for educational and inspiration purposes only. SL does not encourage, induce or sanction the deployment, integration or use of any such applications (including the code comprising the Solana blockchain protocol) in violation of applicable laws or regulations and hereby prohibits any such deployment, integration or use. This includes use of any such applications by the reader (a) in violation of export control or sanctions laws of the United States or any other applicable jurisdiction, (b) if the reader is located in or ordinarily resident in a country or territory subject to comprehensive sanctions administered by the U.S. Office of Foreign Assets Control (OFAC), or (c) if the reader is or is working on behalf of a Specially Designated National (SDN) or a person subject to similar blocking or denied party prohibitions. The reader should be aware that U.S. export control and sanctions laws prohibit U.S. persons (and other persons that are subject to such laws) from transacting with persons in certain countries and territories or that are on the SDN list. As a project based primarily on open-source software, it is possible that such sanctioned persons may nevertheless bypass prohibitions, obtain the code comprising the Solana blockchain protocol (or other project code or applications) and deploy, integrate, or otherwise use it. Accordingly, there is a risk to individuals that other persons using the Solana blockchain protocol may be sanctioned persons and that transactions with such persons would be a violation of U.S. export controls and sanctions law. This risk applies to individuals, organizations, and other ecosystem participants that deploy, integrate, or use the Solana blockchain protocol code directly (e.g., as a node operator), and individuals that transact on the Solana blockchain through light clients, third party interfaces, and/or wallet software. ================================================ FILE: LICENSE ================================================ Copyright 2023 Solana Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Solana ChatGPT Plugin A ChatGPT plugin for Solana. Install as an unverified plugin with url `https://chatgpt.solanalabs.com`.
Search NFTs in ChatGPT
## Endpoints ChatGPT can POST to the following resources, as described by `.well-known/openapi.yaml`.
/getAccountInfo { address } Returns the output of `getAccountInfo` method from the RPC with buffer data, and if it can be deserialized by its program IDL, then the response payload has additional field called `extended` that has a JSON serialized string of the anchor data. Chat GPT's plugin model seems to be able to read this pretty well. ```json { ..., "extended": "{\"authority\":\"8fbqVvpK3Dj7fdP2c8JJhtD7Zy3n9qtwAeGfbkgPu625\",\"numMinted\":50}" } ```
/getBalance { address } Returns ```json { "sol": 0.40296 } ```
/getAssetsByOwner { address } Returns the assets returned by the [Metaplex Read API spec](https://github.com/metaplex-foundation/api-specifications/blob/main/specifications/read_api/openrpc_spec.json)
/getTransaction { signature } Accepts ```json { "signature": "h51pjmFcn8LkxejofUQoDYkyubUKaB7bNtyMMSCCamSEYRutS2G2vm2w1ERShko8boRqdaaTAs4MR6sGYkTByNF" } ``` Returns human-readable transaction information, parsed from the `getTransaction` method of the Solana RPC.
/getTokenAccounts { address } Returns the token accounts owned by a user with an amount > 0. Derived from the `getTokenAccountsByOwner` method on the Solana RPC.
/getSignaturesForAddress { address } Returns the transaction signatures returned in `getSignaturesForAddress` method from the Solana RPC.
/getTotalValue { address } Returns the total value of the assets owned by `address`, broken down by NFTs and tokens. Token prices and NFT price estimates are provided by HelloMoon. An example output is provided below ```json { "total": "50.00", "nftTotal": "25.00", "tokenTotal": "25.00" } ```
### Endpoints for NFT discovery These endpoints are under development and subject to rapid change. These currently use the [Hyperspace API](https://docs.hyperspace.xyz).
/getCollectionsByFloorPrice { maxFloorPrice, minFloorPrice, orderBy, pageSize } Returns ```json { "projects": [ { "id": "", "desc": "collection description", "img": "collection image url", "website": "collection website url", "floor_price": 0.1 } ], "hasMore": true, "currentPage'": 1 } ```
/getListedCollectionNFTs { projectId, pageSize, priceOrder } Returns LLM friendly response of available NFTs: ```json { "listings": [ { "price": 0.1, "token": "", "marketplace": "" } ], "hasMore": true, "currentPage": 1 } ```
## Private endpoints (not LLM accessible) ### Endpoints for Sending Transactions Note: these endpoints are currently disabled in the production version of the ChatGPT plugin
/createBuyTransaction { token, price } Right now we are trusting Hyperspace to craft a valid transaction for us. In the future we will setup a write interface for programs on Solana to adhere to in order to be a target of LLM transaction composition. Returns ```json { "linkToSign": "" } ```
/createTransferSol { destination, amount } Creates a transaction to transfer an amount in Sol. Returns ```json { "linkToSign": "" } ```
/createTransferToken { destination, mint, amount } Creates a transaction to transfer an amount of token (from the mint). Returns ```json { "linkToSign": "" } ```
### Endpoints for Transaction Composition These are also subject to change, and we may create actual webpages to inspect the transaction before signing. However for now, these are simply redirect links to ensure that SolanaPay QR codes show up in the ChatGPT link previews.
/page/:methodName Returns a webpage with [OpenGraph](https://ogp.me/) metadata that will be rendered in the ChatGPT rich link preview. All ChatGPT links should be proxied through this sort of pipeline to maximize user engagement of links. The `og:image` tag is to `/qr/:methodName` to show a SolanaPay QR code in link previews. This is currently a blank page, but we may show a preview of the transaction in the future.
/qr/:methodName Returns a PNG QR code that has been optimized to show in the particular aspect ratio of ChatGPT plugins. This just encodes a SolanaPay link that redirects to `/sign/:methodName`.
/sign/:methodName This is the final redirect link that actually returns transaction bytes in a SolanaPay compatible format so users can sign transactions that are recommended by ChatGPT. ```json { "transaction": "" } ```
## Development To install dependencies, just execute `yarn`. This project uses `node` with version `>=16.17.0`. To start a development server, execute `yarn dev`. This will start the plugin available from `localhost:3333` with its own configuration settings in `.well-known-dev/`. # License This codebase is released under [Apache License 2.0](LICENSE.md). # Disclaimer By accessing or using this codebase or any of its components, you accept and agree with the [Disclaimer](DISCLAIMER.md). ================================================ FILE: chatgpt-plugin/.gitignore ================================================ # 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 ================================================ FILE: chatgpt-plugin/.prettierrc ================================================ { "tabWidth": 2, "useTabs": false, "singleQuote": false, "printWidth": 100, "trailingComma": "all", "arrowParens": "avoid", "endOfLine": "auto", "proseWrap": "always" } ================================================ FILE: chatgpt-plugin/README.md ================================================ 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 `app/page.tsx`. The page auto-updates as you edit the file. 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. ================================================ FILE: chatgpt-plugin/next.config.js ================================================ /** @type {import('next').NextConfig} */ const nextConfig = { async rewrites() { return [ // temporary until Sidekick has plugin autoselect { source: "/api/helloMoon/defi/tokenName", destination: "/api/handlers/tokenName", }, ]; }, }; module.exports = nextConfig; ================================================ FILE: chatgpt-plugin/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ================================================ FILE: chatgpt-plugin/public/openapi.yaml ================================================ openapi: 3.0.2 info: title: Solana Labs API description: An API for retrieving human-readable information about the Solana blockchain. version: 1.0.0 paths: /api/handlers/getAssetsByOwner: post: summary: getAssetsByOwner description: Accepts Solana publicKey address. Returns Metaplex NFTs owned by the address operationId: query_assets_by_owner requestBody: content: application/json: schema: $ref: "#/components/schemas/getAccountInfoRequest" required: true responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getAccountInfo: post: summary: getAccountInfo description: Returns information about the data stored by that account in a human-readable format. Human-readable formatting is only possible when the account's corresponding program owner has published an Anchor IDL on the Solana blockchain. operationId: query_account_info requestBody: content: application/json: schema: $ref: "#/components/schemas/getAccountInfoRequest" required: true responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getTokenAccounts: post: summary: getTokenAccounts description: Returns the fungible and non-fungible tokens and amounts owned by the address. May show tokens not listed in get_assets_by_owner. operationId: query_token_accounts requestBody: content: application/json: schema: $ref: "#/components/schemas/getBalanceRequest" required: true responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getBalance: post: summary: getBalance description: Accepts Solana publicKey address. Returns the amount of lamports that the account has available. operationId: query_balance requestBody: content: application/json: schema: $ref: "#/components/schemas/getBalanceRequest" required: true responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getTransaction: post: summary: getTransaction description: Accepts a transaction signature. Returns the publicly available transaction information and metadata. Only high level summaries based on instruction data should be provided to users unless otherwise specified. Logs, compute units, and fees are available in devMode. operationId: query_transaction requestBody: content: application/json: schema: $ref: "#/components/schemas/getTransactionRequest" responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getSignaturesForAddress: post: summary: getSignaturesForAddress description: Accepts Solana publicKey address. Returns the latest transaction signatures that involve that address. operationId: query_signatures_for_address requestBody: content: application/json: schema: $ref: "#/components/schemas/getSignaturesForAddressRequest" required: true responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getCollectionsByFloorPrice: post: summary: Search through Solana NFT collections by floor price operationId: query_nft_collections_by_fp requestBody: content: application/json: schema: $ref: "#/components/schemas/getCollectionsByFloorPriceRequest" responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getListedCollectionNFTs: post: summary: Returns the listed NFTs in a collection available to purchase operationId: query_listed_nfts_for_collection requestBody: content: application/json: schema: $ref: "#/components/schemas/getListedCollectionNFTsRequest" responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getCollectionsByName: post: summary: Searches for NFT collections based on project name operationId: query_nfts_collections_by_name requestBody: content: application/json: schema: $ref: "#/components/schemas/getCollectionsByNameRequest" responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/getTotalValue: post: summary: Calculates the total value of a the address's holdings in USD, with breakdown by NFTs and tokens. operationId: query_total_value requestBody: content: application/json: schema: $ref: "#/components/schemas/getTotalValueRequest" responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/walletName: post: summary: Resolves wallet names to the actual solana address, or if it is already a Solana address, it looks up all the wallet names associated with that publickey. This works especially well for .sol, .glow, .backpack, or .poor domains. operationId: query_wallet_name requestBody: content: application/json: schema: type: object properties: walletName: type: string responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] /api/handlers/tokenName: post: summary: Searches tokens by name, and returns the mint address, logo, name and symbol for up to 10 relevant, verified tokens. operationId: search_token_name requestBody: content: application/json: schema: type: object properties: tokenName: type: string responses: "200": description: Successful Response "500": description: Validation Error security: - HTTPBearer: [] components: schemas: getAccountInfoRequest: type: object required: - address properties: address: type: string description: Base58 encoded PublicKey getSignaturesForAddressRequest: title: GetSignaturesForAddressRequest type: object required: - address properties: address: type: string description: Base58 encoded PublicKey beforeSignature: type: string untilSignature: type: string getBalanceRequest: title: GetBalanceRequest type: object required: - address properties: address: title: Address type: string description: Base58 encoded PublicKey getTransactionRequest: type: object required: - signature properties: signature: type: string devMode: type: boolean getCollectionsByFloorPriceRequest: type: object properties: maxFloorPrice: type: number nullable: true minFloorPrice: type: number nullable: true orderBy: type: string enum: - ASC - DESC nullable: true getCollectionsByNameRequest: type: object properties: projectName: type: string getListedCollectionNFTsRequest: type: object properties: projectId: type: string priceOrder: type: string nullable: true pageSize: type: number nullable: true getTotalValueRequest: type: object properties: address: type: string description: Base58 encoded PublicKey securitySchemes: HTTPBearer: type: http scheme: bearer ================================================ FILE: chatgpt-plugin/src/app/globals.css ================================================ @tailwind base; @tailwind components; :root { --foreground-rgb: 255, 255, 255; --background-start-rgb: 0, 0, 0; --background-end-rgb: 0, 0, 0; } body { color: rgb(var(--foreground-rgb)); background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb)); } .ms-6 { margin-left: 2rem !important; } img, svg { vertical-align: middle; } @media (min-width: 768px) { .AiHero_hero__title__ZatKJ { font-size: 6rem; line-height: 6rem; margin-bottom: 32px !important; } } .AiHero_hero__title__ZatKJ { font-weight: 700; font-size: 3.5rem; margin-bottom: 24px !important; } h2 { font-size: 2.425rem; line-height: 3.15rem; font-weight: 400; } .czAcYK.btn { font-family: DSemi, monospace; font-weight: normal; text-transform: uppercase; line-height: 1.25rem; font-size: 0.95rem; width: fit-content; background-color: rgb(153, 69, 255); border-radius: 2.25rem; padding: 0.875rem 1.5rem; color: white; border: 1px solid transparent; } .lift { transition: box-shadow 0.25s ease, transform 0.25s ease; } .btn { background-color: rgb(153, 69, 255); @apply flex items-center gap-3 px-5 py-3 text-lg transition transform whitespace-nowrap rounded-3xl; @apply hover:bg-white hover:text-black; @apply hover:-translate-y-1; } .btn svg { @apply w-5 h-5; } .disclosure { @apply flex items-center justify-between w-full px-4 py-2 rounded-xl; @apply text-white bg-gray-800; @apply hover:bg-opacity-80; @apply border border-gray-700; } .disclosure-open { @apply font-semibold; @apply bg-gray-800 border-gray-600 hover:bg-opacity-100; } .disclosure svg { @apply w-5 h-5; } /* utilities at the end to make the inline classes a priority */ @tailwind utilities; .legal { text-decoration: underline; color: #00ffbd; } ================================================ FILE: chatgpt-plugin/src/app/layout.tsx ================================================ import Link from "next/link"; import "./globals.css"; import { Inter } from "next/font/google"; import Image from "next/image"; const inter = Inter({ subsets: ["latin"] }); export const metadata = { title: "Solana Labs - ChatGPT Plugin", description: "Get a taste of Solana", }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ================================================ FILE: chatgpt-plugin/src/app/page.tsx ================================================ "use client"; import Head from "next/head"; import Link from "next/link"; import { features } from "@/lib/features"; import { Disclosure } from "@headlessui/react"; import { ArrowRightIcon, ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline"; export default function Home() { return ( <> Solana Labs - ChatGPT Plugin

Solana Labs

ChatGPT Plugin

The Solana Labs ChatGPT Plugin helps you query information on the Solana blockchain. Solana Labs has provided the code as a reference implementation with permissive licensing on Github. A list of features and example queries is provided below.

GitHub Repo AI Grants

Features

{features.map((endpoint, id) => ( {({ open }) => (
{endpoint.label} {open ? : } {endpoint.description}
)}
))}
); } ================================================ FILE: chatgpt-plugin/src/lib/address.ts ================================================ import { walletAddressToDotAnything, walletAddressToDotGlow, walletAddressToDotBackpack, walletNameToAddressAndProfilePicture, } from "@portal-payments/solana-wallet-names"; import { Connection, PublicKey } from "@solana/web3.js"; import { Client } from "@solflare-wallet/utl-sdk"; import configConstants, { CONNECTION } from "../pages/api/constants"; configConstants(); export interface WalletNames { walletNames: string[]; } export const walletAddressToDotSolCustom = async ( connection: Connection, wallet: PublicKey, ): Promise => { try { const result = await fetch( // See https://github.com/Bonfida/sns-sdk#sdk-proxy // There's a 'favorite-domain' endpoint butmost SNS users haven't set up a // favorite domain, as the UI to do so is complex // `https://sns-sdk-proxy.bonfida.workers.dev/favorite-domain/${wallet.toBase58()}` `https://sns-sdk-proxy.bonfida.workers.dev/domains/${wallet.toBase58()}`, { method: "GET", }, ); const body = await result.json(); let walletNames: string[] = body.result.map((info: any) => `${info.domain}.sol`); return { walletNames, }; } catch (thrownObject) { const error = thrownObject as Error; if (error.message === "Invalid wallet account provided") { return { walletNames: [], }; } throw error; } }; export const walletAddressToNameAndProfilePictureCustom = async ( connection: Connection, wallet: PublicKey, backpackJWT: string | null = null, ): Promise => { let walletNames: string[] = []; const dotAnything = await walletAddressToDotAnything(connection, wallet); if (dotAnything.walletName) { walletNames.push(dotAnything.walletName); } const dotSol = await walletAddressToDotSolCustom(connection, wallet); walletNames = walletNames.concat(dotSol.walletNames); const dotGlow = await walletAddressToDotGlow(wallet); if (dotGlow?.walletName && dotGlow.walletName !== "null.glow") { walletNames.push(dotGlow.walletName); } if (backpackJWT) { const dotBackpack = await walletAddressToDotBackpack(wallet, backpackJWT); if (dotBackpack?.walletName) { walletNames.push(dotBackpack.walletName); } } return { walletNames, }; }; export async function resolveAddress(address: string): Promise { if (!address) { throw new Error(`No address provided for: ${address}`); } if (address.search(/\./g) !== -1) { let info: { walletAddress: string | null }; try { info = await walletNameToAddressAndProfilePicture(CONNECTION, address); } catch (e) { throw new Error(`Wallet name ${address} does not exist or cannot be resolved.`); } if (info.walletAddress === null) { throw new Error(`Wallet name does not exist: ${address}`); } return new PublicKey(info.walletAddress); } try { return new PublicKey(address); } catch (_e) { throw new Error(`Provided address is invalid base58 Solana address: ${address}`); } } export async function resolveToken(tokenName: string): Promise { if (!tokenName) { throw new Error(`No token name provided for: ${tokenName}`); } const utl = new Client(); try { let results = (await utl.searchMints(tokenName)) .filter((res: any) => { return res["verified"] && res["holders"] > 0 && res["chainId"] === 101; }) .map((res: any) => { return { mintAddress: res["address"], tokenName: res["name"], tokenSymbol: res["symbol"], holders: res["holders"], verified: res["verified"], chainId: res["chainId"], logoURI: res["logoURI"], }; }); return new PublicKey(results[0].mintAddress); } catch (_e) { throw new Error( `Provided token address does not have any relevant token addresses: ${tokenName}`, ); } } ================================================ FILE: chatgpt-plugin/src/lib/features.tsx ================================================ import Link from "next/link"; export type Endpoint = { label: string; description: React.ReactNode; }; export const features: Array = [ { label: "Fetch account balance", description: (

{`Example: "What is the balance of `} MY-WALLET-ADDRESS {` ?"`}

{`Returns the amount of Sol that the wallet address owns.`}

), }, { label: "Interpret Solana accounts", description: (

{`Example: "What data is in `} ACCOUNT-ADDRESS {` ?"`}

{`Returns data about the account, including the owner, the amount of Sol, and the potentially human-readable data stored in the account, if it's owner program has an Anchor IDL.`}

), }, { label: "Interpret Solana transactions", description: (

{`Example: "What happened in transaction `} TRANSACTION-ID {` ?"`}

{`Returns information about the transaction, and potentially human readable information about program instructions, if the programs have Anchor IDLs.`}

), }, { label: "Find NFTs for a wallet", description: (

{`Example: "What NFTs does `} MY-WALLET-ADDRESS {` own?"`}

{`The plugin can list your NFTs and their metadata. If you have a lot of NFTs, you can ask it to list all of your NFTs 5 at a time.`}

), }, { label: "Inspect wallet activity", description: (

{`Example: "What are the latest transactions for `} WALLET-ADDRESS {` ?"`}

{`The plugin can help you go through wallet transactions and explore on-chain activity.`}

), }, { label: "Search NFT collections by name or floor price", description: (

{`Example: "Can you find me the cheapest Mad Lads NFT for sale?"`}

{`NFTs are searched by name and sorted by floor price, you can ask the model to search explicitly by either target floor price name or by name. NFTs within a collection can also be sorted by floor price.`}

Note: {` If you cannot find your NFT Collection, please see if it exists on `} hyperspace.xyz {`. We use Hyperspace's API to support this plugin`}

), }, { label: "Calculate wallet value", description: (

{`Example: "What is the value of `} MY-ADDRESS {` ?"`}

{`Wallet value is calculated by estimating the value of all tokens and NFTs owned by the wallet. Please note that these values are in USD and are subject to change due to market fluctuations. Price estimates are provided by `} {`HelloMoon's`} {` API.`}

), }, // { // label: "Buy an NFT (SolanaPay)", // description: ( //

// {`Example: "Can you help me transfer 1 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v to `} // DESTINATION // {` ?"`} //
//
// This will generate a QR code that you can scan from within your mobile wallet to send 1 USDC // (mint address EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v) from your wallet to a // destination wallet. See the{" "} // // Disclaimer // // {" and "} // // Terms of Service // // {"."} //

// ), // }, // { // label: "Transfer Sol (SolanaPay)", // description: ( //

// {`Example: "Can you help me transfer 1 SOL to `} // DESTINATION // {` ?"`} //
//
// This will generate a QR code that you can scan from within your mobile wallet to send 1 Sol // from your wallet to a destination wallet address. See the{" "} // // Disclaimer // // {" and "} // // Terms of Service // // {"."} //

// ), // }, // { // label: "Transfer a token (SolanaPay)", // description: ( //

// {`Example: "Can you help me transfer 1 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v to `} // DESTINATION // {` ?"`} //
//
// This will generate a QR code that you can scan from within your mobile wallet to send 1 USDC // (mint address EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v) from your wallet to a // destination wallet. See the{" "} // // Disclaimer // // {" and "} // // Terms of Service // // {"."} //

// ), // }, ]; ================================================ FILE: chatgpt-plugin/src/lib/helloMoon/index.ts ================================================ export const VALID_OPERATORS = ["=", "!=", ">", "<", ">="]; export type Comparison = | { operator: "=" | "!=" | ">" | "<" | ">="; value: number; } | undefined; export function buildComparison( operator: string | undefined, value: number | undefined, ): Comparison { if (operator && !VALID_OPERATORS.includes(operator)) { throw new Error( "Invalid operator: " + operator + ". Valid operators are: " + VALID_OPERATORS.join(", ") + ".", ); } const comparison = operator && value !== undefined ? { operator, value: new Number(value).valueOf(), } : undefined; return comparison as Comparison; } /** * Rewrites a "B - A" pair to "A-B" pair * @param pair */ export function cleanSwapPair(pair: string) { let cleaned = pair.replaceAll(" ", ""); let split = cleaned.split("-"); let pairA = split[0]; let pairB = split[1]; if (pairA < pairB) { return `${pairA} - ${pairB}`; } return `${pairB} - ${pairA}`; } ================================================ FILE: chatgpt-plugin/src/lib/hyperspace/account.ts ================================================ import { Keypair, PublicKey } from "@solana/web3.js"; import * as anchor from "@coral-xyz/anchor"; import { HYPERSPACE, FEE_PAYER, HYPERSPACE_ID as MARKETPLACE_PROGRAM_ID, } from "./constants"; import { hyperspaceIdl } from "./idl/hyperspace"; import { AnchorProvider } from "@coral-xyz/anchor"; import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID as SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, } from "@solana/spl-token"; import { PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID } from "@metaplex-foundation/mpl-token-metadata"; export async function loadHyperspaceProgram( anchorWallet: any, connection: anchor.web3.Connection ) { const provider = new AnchorProvider(connection, anchorWallet, { preflightCommitment: "recent", }); return new anchor.Program(hyperspaceIdl, MARKETPLACE_PROGRAM_ID, provider); } export const getHyperspaceBuyerEscrow = async ( auctionHouse: anchor.web3.PublicKey, wallet: anchor.web3.PublicKey ): Promise<[PublicKey, number]> => { return await anchor.web3.PublicKey.findProgramAddress( [Buffer.from(HYPERSPACE), auctionHouse.toBuffer(), wallet.toBuffer()], MARKETPLACE_PROGRAM_ID ); }; export const getHyperspaceTradeState = async ( is_buy: boolean, wallet: anchor.web3.PublicKey, tokenAccount: anchor.web3.PublicKey, tokenMint: anchor.web3.PublicKey, tokenSize: anchor.BN ): Promise<[PublicKey, number]> => { return await anchor.web3.PublicKey.findProgramAddress( [ Buffer.from(HYPERSPACE), is_buy ? new anchor.BN(1).toArrayLike(Buffer, "le", 1) : new anchor.BN(0).toArrayLike(Buffer, "le", 1), wallet.toBuffer(), tokenAccount.toBuffer(), tokenMint.toBuffer(), tokenSize.toArrayLike(Buffer, "le", 8), ], MARKETPLACE_PROGRAM_ID ); }; export const getMetadata = async ( mint: anchor.web3.PublicKey ): Promise => { return ( await anchor.web3.PublicKey.findProgramAddress( [ Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), ], TOKEN_METADATA_PROGRAM_ID ) )[0]; }; export const getHyperspace = async ( creator: anchor.web3.PublicKey ): Promise<[PublicKey, number]> => { return await anchor.web3.PublicKey.findProgramAddress( [Buffer.from(HYPERSPACE), creator.toBuffer()], MARKETPLACE_PROGRAM_ID ); }; export const getHyperspaceFeeAcct = async ( auctionHouse: anchor.web3.PublicKey ): Promise<[PublicKey, number]> => { return await anchor.web3.PublicKey.findProgramAddress( [Buffer.from(HYPERSPACE), auctionHouse.toBuffer(), Buffer.from(FEE_PAYER)], MARKETPLACE_PROGRAM_ID ); }; export const getAtaForMint = async ( mint: anchor.web3.PublicKey, buyer: anchor.web3.PublicKey ): Promise<[anchor.web3.PublicKey, number]> => { return await anchor.web3.PublicKey.findProgramAddress( [buyer.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()], SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID ); }; export const getEditionDataAccount = async ( mint: anchor.web3.PublicKey ): Promise<[anchor.web3.PublicKey, number]> => { return await anchor.web3.PublicKey.findProgramAddress( [ Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), Buffer.from("edition"), ], TOKEN_METADATA_PROGRAM_ID ); }; export const getHyperspaceProgramAsSigner = async (): Promise< [PublicKey, number] > => { return await anchor.web3.PublicKey.findProgramAddress( [Buffer.from(HYPERSPACE), Buffer.from("signer")], MARKETPLACE_PROGRAM_ID ); }; export const findTokenRecordPda = ( mint: PublicKey, token: PublicKey ): PublicKey => { return PublicKey.findProgramAddressSync( [ Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), Buffer.from("token_record"), token.toBuffer(), ], TOKEN_METADATA_PROGRAM_ID )[0]; }; ================================================ FILE: chatgpt-plugin/src/lib/hyperspace/constants.ts ================================================ import { PublicKey } from "@solana/web3.js"; export const FEE_PAYER = "fee_payer"; export const HYPERSPACE = "hyperspace"; export const HYPERSPACE_ID = new PublicKey( "HYPERfwdTjyJ2SCaKHmpF2MtrXqWxrsotYDsTrshHWq8" ); export const HYPERSPACE_MARKETPLACE_INSTANCE_ID_STRING = "5pdaXth4ijgDCeYDKgSx3jAbN7m8h4gy1LRCErAAN1LM"; export const HYPERSPACE_MARKETPLACE_INSTANCE = new PublicKey( "5pdaXth4ijgDCeYDKgSx3jAbN7m8h4gy1LRCErAAN1LM" ); ================================================ FILE: chatgpt-plugin/src/lib/hyperspace/idl/hyperspace.ts ================================================ export type Hyperspace = { version: "0.1.0"; name: "hyperspace"; instructions: [ { name: "initProgramAsSigner"; accounts: [ { name: "wallet"; isMut: true; isSigner: true; }, { name: "programAsSigner"; isMut: true; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; } ]; args: [ { name: "programAsSignerBump"; type: "u8"; } ]; }, { name: "updateHyperspace"; accounts: [ { name: "payer"; isMut: false; isSigner: true; }, { name: "authority"; isMut: false; isSigner: true; }, { name: "newAuthority"; isMut: false; isSigner: false; }, { name: "feeWithdrawalDestination"; isMut: true; isSigner: false; }, { name: "treasuryWithdrawalDestination"; isMut: true; isSigner: false; }, { name: "treasuryWithdrawalDestinationOwner"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "ataProgram"; isMut: false; isSigner: false; }, { name: "rent"; isMut: false; isSigner: false; } ]; args: [ { name: "sellerFeeBasisPoints"; type: { option: "u16"; }; }, { name: "requiresSignOff"; type: { option: "bool"; }; }, { name: "canChangeSalePrice"; type: { option: "bool"; }; } ]; }, { name: "withdraw"; accounts: [ { name: "wallet"; isMut: false; isSigner: false; }, { name: "receiptAccount"; isMut: true; isSigner: false; }, { name: "escrowPaymentAccount"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "rent"; isMut: false; isSigner: false; } ]; args: [ { name: "escrowPaymentBump"; type: "u8"; }, { name: "amount"; type: "u64"; } ]; }, { name: "deposit"; accounts: [ { name: "wallet"; isMut: false; isSigner: true; }, { name: "paymentAccount"; isMut: true; isSigner: false; }, { name: "transferAuthority"; isMut: false; isSigner: false; }, { name: "escrowPaymentAccount"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "hyperspaceFeeAccount"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "rent"; isMut: false; isSigner: false; } ]; args: [ { name: "escrowPaymentBump"; type: "u8"; }, { name: "amount"; type: "u64"; } ]; }, { name: "permissionlessCancel"; accounts: [ { name: "wallet"; isMut: false; isSigner: true; }, { name: "tokenMint"; isMut: false; isSigner: false; }, { name: "seller"; isMut: false; isSigner: false; }, { name: "tokenAccount"; isMut: false; isSigner: false; }, { name: "tradeState"; isMut: true; isSigner: false; }, { name: "programAsSigner"; isMut: false; isSigner: false; } ]; args: [ { name: "programAsSignerBump"; type: "u8"; }, { name: "tradeStateBump"; type: "u8"; }, { name: "tokenSize"; type: "u64"; } ]; }, { name: "collectionCancelBuy"; accounts: [ { name: "wallet"; isMut: true; isSigner: false; }, { name: "identifierPubkey"; isMut: false; isSigner: false; }, { name: "authority"; isMut: true; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "collectionBuyerTradeState"; isMut: true; isSigner: false; }, { name: "programAsSigner"; isMut: false; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; } ]; args: [ { name: "buyerPrice"; type: "u64"; }, { name: "programAsSignerBump"; type: "u8"; }, { name: "tradeStateBump"; type: "u8"; }, { name: "collectionTradeStateType"; type: "u8"; }, { name: "identifierIndex"; type: "u8"; } ]; }, { name: "cancel"; accounts: [ { name: "wallet"; isMut: true; isSigner: false; docs: ["CHECK OWNER OF TRADE_STATE TODO"]; }, { name: "tokenMint"; isMut: false; isSigner: false; }, { name: "tokenAccount"; isMut: true; isSigner: false; }, { name: "metadata"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "tradeState"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "metadataProgram"; isMut: false; isSigner: false; }, { name: "programAsSigner"; isMut: false; isSigner: false; }, { name: "instructions"; isMut: false; isSigner: false; }, { name: "tokenRecord"; isMut: true; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "editionAccount"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "authorizationRules"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "mplTokenAuthRulesProgram"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; } ]; args: [ { name: "isBuy"; type: "u8"; }, { name: "programAsSignerBump"; type: "u8"; }, { name: "tradeStateBump"; type: "u8"; }, { name: "tokenSize"; type: "u64"; } ]; }, { name: "collectionExecuteSale"; accounts: [ { name: "buyer"; isMut: true; isSigner: false; }, { name: "buyerBrokerWallet"; isMut: true; isSigner: false; }, { name: "seller"; isMut: true; isSigner: false; }, { name: "sellerBrokerWallet"; isMut: true; isSigner: false; }, { name: "tokenAccount"; isMut: true; isSigner: false; }, { name: "tokenMint"; isMut: false; isSigner: false; }, { name: "metadata"; isMut: true; isSigner: false; }, { name: "editionData"; isMut: false; isSigner: false; }, { name: "escrowPaymentAccount"; isMut: true; isSigner: false; }, { name: "buyerReceiptTokenAccount"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "hyperspaceTreasury"; isMut: true; isSigner: false; }, { name: "identifierPubkey"; isMut: false; isSigner: false; }, { name: "collectionBuyerTradeState"; isMut: true; isSigner: false; }, { name: "sellerTradeState"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "ataProgram"; isMut: false; isSigner: false; }, { name: "metadataProgram"; isMut: false; isSigner: false; }, { name: "mplTokenAuthRulesProgram"; isMut: false; isSigner: false; }, { name: "programAsSigner"; isMut: false; isSigner: false; }, { name: "rent"; isMut: false; isSigner: false; }, { name: "instructions"; isMut: false; isSigner: false; }, { name: "ownerTokenRecord"; isMut: true; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "destinationTokenRecord"; isMut: true; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "authorizationRules"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; } ]; args: [ { name: "escrowPaymentBump"; type: "u8"; }, { name: "programAsSignerBump"; type: "u8"; }, { name: "buyerTradeStateBump"; type: "u8"; }, { name: "sellerTradeStateBump"; type: "u8"; }, { name: "maxAmountToPay"; type: "u64"; }, { name: "buyerBrokerBasisPoints"; type: "u16"; }, { name: "sellerBrokerBasisPoints"; type: "u16"; }, { name: "tokenSize"; type: "u64"; }, { name: "collectionTradeStateType"; type: "u8"; }, { name: "identifierIndex"; type: "u8"; }, { name: "royaltyBasisPoints"; type: "u16"; } ]; }, { name: "executeSale"; accounts: [ { name: "buyer"; isMut: true; isSigner: false; }, { name: "buyerBrokerWallet"; isMut: true; isSigner: false; }, { name: "seller"; isMut: true; isSigner: false; }, { name: "sellerBrokerWallet"; isMut: true; isSigner: false; }, { name: "tokenAccount"; isMut: true; isSigner: false; }, { name: "tokenMint"; isMut: false; isSigner: false; }, { name: "metadata"; isMut: true; isSigner: false; }, { name: "escrowPaymentAccount"; isMut: true; isSigner: false; }, { name: "buyerReceiptTokenAccount"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "hyperspaceTreasury"; isMut: true; isSigner: false; }, { name: "buyerTradeState"; isMut: true; isSigner: false; }, { name: "sellerTradeState"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "ataProgram"; isMut: false; isSigner: false; }, { name: "metadataProgram"; isMut: false; isSigner: false; }, { name: "mplTokenAuthRulesProgram"; isMut: false; isSigner: false; }, { name: "programAsSigner"; isMut: false; isSigner: false; }, { name: "rent"; isMut: false; isSigner: false; }, { name: "instructions"; isMut: false; isSigner: false; }, { name: "editionAccount"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "ownerTokenRecord"; isMut: true; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "destinationTokenRecord"; isMut: true; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "authorizationRules"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; } ]; args: [ { name: "escrowPaymentBump"; type: "u8"; }, { name: "programAsSignerBump"; type: "u8"; }, { name: "buyerTradeStateBump"; type: "u8"; }, { name: "sellerTradeStateBump"; type: "u8"; }, { name: "maxAmountToPay"; type: "u64"; }, { name: "buyerBrokerBasisPoints"; type: "u16"; }, { name: "sellerBrokerBasisPoints"; type: "u16"; }, { name: "tokenSize"; type: "u64"; }, { name: "royaltyBasisPoints"; type: "u16"; } ]; }, { name: "sell"; accounts: [ { name: "wallet"; isMut: false; isSigner: true; }, { name: "sellerBrokerWallet"; isMut: false; isSigner: false; }, { name: "tokenMint"; isMut: false; isSigner: false; }, { name: "tokenAccount"; isMut: true; isSigner: false; }, { name: "metadata"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "hyperspaceFeeAccount"; isMut: true; isSigner: false; }, { name: "sellerTradeState"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "metadataProgram"; isMut: false; isSigner: false; }, { name: "programAsSigner"; isMut: false; isSigner: false; }, { name: "instructions"; isMut: false; isSigner: false; }, { name: "tokenRecord"; isMut: true; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "editionAccount"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "authorizationRules"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "mplTokenAuthRulesProgram"; isMut: false; isSigner: false; docs: ["CHECK no check needed"]; }, { name: "clock"; isMut: false; isSigner: false; } ]; args: [ { name: "tradeStateBump"; type: "u8"; }, { name: "programAsSignerBump"; type: "u8"; }, { name: "minAmountToReceive"; type: "u64"; }, { name: "brokerBasisPoints"; type: "u16"; }, { name: "tokenSize"; type: "u64"; }, { name: "royaltyBasisPoints"; type: "u16"; } ]; }, { name: "buy"; accounts: [ { name: "wallet"; isMut: false; isSigner: true; }, { name: "buyerBrokerWallet"; isMut: false; isSigner: false; }, { name: "paymentAccount"; isMut: true; isSigner: false; }, { name: "transferAuthority"; isMut: false; isSigner: false; }, { name: "tokenAccount"; isMut: false; isSigner: false; }, { name: "buyerReceiptTokenAccount"; isMut: false; isSigner: false; }, { name: "metadata"; isMut: false; isSigner: false; }, { name: "escrowPaymentAccount"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "hyperspaceFeeAccount"; isMut: true; isSigner: false; }, { name: "buyerTradeState"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "rent"; isMut: false; isSigner: false; }, { name: "clock"; isMut: false; isSigner: false; } ]; args: [ { name: "tradeStateBump"; type: "u8"; }, { name: "escrowPaymentBump"; type: "u8"; }, { name: "maxAmountToPay"; type: "u64"; }, { name: "brokerBasisPoints"; type: "u16"; }, { name: "tokenSize"; type: "u64"; }, { name: "royaltyBasisPoints"; type: "u16"; } ]; }, { name: "collectionBuy"; accounts: [ { name: "wallet"; isMut: true; isSigner: true; }, { name: "buyerBrokerWallet"; isMut: false; isSigner: false; }, { name: "identifierPubkey"; isMut: false; isSigner: false; }, { name: "escrowPaymentAccount"; isMut: true; isSigner: false; }, { name: "authority"; isMut: false; isSigner: false; }, { name: "hyperspace"; isMut: false; isSigner: false; }, { name: "hyperspaceFeeAccount"; isMut: true; isSigner: false; }, { name: "collectionBuyerTradeState"; isMut: true; isSigner: false; }, { name: "tokenProgram"; isMut: false; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "rent"; isMut: false; isSigner: false; }, { name: "clock"; isMut: false; isSigner: false; } ]; args: [ { name: "tradeStateBump"; type: "u8"; }, { name: "escrowPaymentBump"; type: "u8"; }, { name: "buyerPrice"; type: "u64"; }, { name: "brokerBasisPoints"; type: "u16"; }, { name: "collectionTradeStateType"; type: "u8"; }, { name: "identifierIndex"; type: "u8"; }, { name: "royaltyBasisPoints"; type: "u16"; } ]; }, { name: "createCollectionBuyTradeState"; accounts: [ { name: "wallet"; isMut: true; isSigner: true; }, { name: "identifierPubkey"; isMut: false; isSigner: false; }, { name: "brokerWallet"; isMut: false; isSigner: false; }, { name: "tradeState"; isMut: true; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "clock"; isMut: false; isSigner: false; } ]; args: [ { name: "tradeStateBump"; type: "u8"; }, { name: "buyPrice"; type: "u64"; }, { name: "brokerBasisPoints"; type: "u16"; }, { name: "collectionTradeStateType"; type: "u8"; }, { name: "identifierIndex"; type: "u8"; }, { name: "royaltyBasisPoints"; type: "u16"; } ]; }, { name: "createTradeState"; accounts: [ { name: "wallet"; isMut: true; isSigner: true; }, { name: "collection"; isMut: false; isSigner: false; }, { name: "brokerWallet"; isMut: false; isSigner: false; }, { name: "tokenAccount"; isMut: false; isSigner: false; }, { name: "tokenMint"; isMut: false; isSigner: false; }, { name: "tradeState"; isMut: true; isSigner: false; }, { name: "systemProgram"; isMut: false; isSigner: false; }, { name: "clock"; isMut: false; isSigner: false; } ]; args: [ { name: "isBuy"; type: "u8"; }, { name: "tradeStateBump"; type: "u8"; }, { name: "buyPrice"; type: "u64"; }, { name: "brokerBasisPoints"; type: "u16"; }, { name: "tokenSize"; type: "u64"; }, { name: "royaltyBasisPoints"; type: "u16"; } ]; } ]; accounts: [ { name: "hyperspace"; type: { kind: "struct"; fields: [ { name: "hyperspaceFeeAccount"; type: "publicKey"; }, { name: "hyperspaceTreasury"; type: "publicKey"; }, { name: "treasuryWithdrawalDestination"; type: "publicKey"; }, { name: "feeWithdrawalDestination"; type: "publicKey"; }, { name: "authority"; type: "publicKey"; }, { name: "creator"; type: "publicKey"; }, { name: "bump"; type: "u8"; }, { name: "treasuryBump"; type: "u8"; }, { name: "feePayerBump"; type: "u8"; }, { name: "sellerFeeBasisPoints"; type: "u16"; }, { name: "requiresSignOff"; type: "bool"; }, { name: "canChangeSalePrice"; type: "bool"; } ]; }; }, { name: "programAsSigner"; type: { kind: "struct"; fields: [ { name: "firstByte"; type: "u8"; } ]; }; }, { name: "tradeState"; type: { kind: "struct"; fields: [ { name: "buyPrice"; type: "u64"; }, { name: "userWallet"; type: "publicKey"; }, { name: "isBuy"; type: "u8"; }, { name: "collection"; type: "publicKey"; }, { name: "brokerWallet"; type: "publicKey"; }, { name: "brokerBasisPoints"; type: "u16"; }, { name: "tokenMint"; type: "publicKey"; }, { name: "royaltyBasisPoints"; type: "u16"; }, { name: "timestamp"; type: "i64"; } ]; }; }, { name: "collectionBuyTradeState"; type: { kind: "struct"; fields: [ { name: "userWallet"; type: "publicKey"; }, { name: "collectionTradeStateType"; type: "u8"; }, { name: "identifierIndex"; type: "u8"; }, { name: "identifierPubkey"; type: "publicKey"; }, { name: "brokerWallet"; type: "publicKey"; }, { name: "brokerBasisPoints"; type: "u16"; }, { name: "royaltyBasisPoints"; type: "u16"; }, { name: "timestamp"; type: "i64"; } ]; }; } ]; types: [ { name: "AuthorizationDataLocal"; type: { kind: "struct"; fields: [ { name: "payload"; type: { vec: { defined: "TaggedPayload"; }; }; } ]; }; }, { name: "TaggedPayload"; type: { kind: "struct"; fields: [ { name: "name"; type: "string"; }, { name: "payload"; type: { defined: "PayloadTypeLocal"; }; } ]; }; }, { name: "SeedsVecLocal"; type: { kind: "struct"; fields: [ { name: "seeds"; docs: ["The vector of derivation seeds."]; type: { vec: "bytes"; }; } ]; }; }, { name: "ProofInfoLocal"; type: { kind: "struct"; fields: [ { name: "proof"; docs: ["The merkle proof."]; type: { vec: { array: ["u8", 32]; }; }; } ]; }; }, { name: "PayloadTypeLocal"; type: { kind: "enum"; variants: [ { name: "Pubkey"; fields: ["publicKey"]; }, { name: "Seeds"; fields: [ { defined: "SeedsVecLocal"; } ]; }, { name: "MerkleProof"; fields: [ { defined: "ProofInfoLocal"; } ]; }, { name: "Number"; fields: ["u64"]; } ]; }; } ]; errors: [ { code: 6000; name: "PublicKeyMismatch"; msg: "PublicKeyMismatch"; }, { code: 6001; name: "InvalidMintAuthority"; msg: "InvalidMintAuthority"; }, { code: 6002; name: "UninitializedAccount"; msg: "UninitializedAccount"; }, { code: 6003; name: "IncorrectOwner"; msg: "IncorrectOwner"; }, { code: 6004; name: "PublicKeysShouldBeUnique"; msg: "PublicKeysShouldBeUnique"; }, { code: 6005; name: "StatementFalse"; msg: "StatementFalse"; }, { code: 6006; name: "NotRentExempt"; msg: "NotRentExempt"; }, { code: 6007; name: "NumericalOverflow"; msg: "NumericalOverflow"; }, { code: 6008; name: "ExpectedSolAccount"; msg: "Expected a sol account but got an spl token account instead"; }, { code: 6009; name: "CannotExchangeSOLForSol"; msg: "Cannot exchange sol for sol"; }, { code: 6010; name: "SOLWalletMustSign"; msg: "If paying with sol, sol wallet must be signer"; }, { code: 6011; name: "CannotTakeThisActionWithoutHyperspaceSignOff"; msg: "Cannot take this action without hyperspace signing too"; }, { code: 6012; name: "NoPayerPresent"; msg: "No payer present on this txn"; }, { code: 6013; name: "DerivedKeyInvalid"; msg: "Derived key invalid"; }, { code: 6014; name: "MetadataDoesntExist"; msg: "Metadata doesn't exist"; }, { code: 6015; name: "InvalidTokenAmount"; msg: "Invalid token amount"; }, { code: 6016; name: "BothPartiesNeedToAgreeToSale"; msg: "Both parties need to agree to this sale"; }, { code: 6017; name: "CannotMatchFreeSalesWithoutHyperspaceOrSellerSignoff"; msg: "Cannot match free sales unless the hyperspace or seller signs off"; }, { code: 6018; name: "SaleRequiresSigner"; msg: "This sale requires a signer"; }, { code: 6019; name: "OldSellerNotInitialized"; msg: "Old seller not initialized"; }, { code: 6020; name: "SellerATACannotHaveDelegate"; msg: "Seller ata cannot have a delegate set"; }, { code: 6021; name: "BuyerATACannotHaveDelegate"; msg: "Buyer ata cannot have a delegate set"; }, { code: 6022; name: "NoValidSignerPresent"; msg: "No valid signer present"; }, { code: 6023; name: "InvalidBasisPoints"; msg: "BP must be less than or equal to 10000"; }, { code: 6024; name: "InvalidBrokerInformation"; msg: "Broker information must match"; }, { code: 6025; name: "InvalidTokenAccount"; msg: "Token Account holding selling token must be owned by seller"; }, { code: 6026; name: "InvalidPermissionlessCancel"; msg: "Cannot permissionless cancel this trade state"; }, { code: 6027; name: "InvalidCollectionTradeStateType"; msg: "Invalid Collection Trade State Type"; }, { code: 6028; name: "InvalidCollectionTradeStateIdentifier"; msg: "Invalid Collection Trade State Identifier"; }, { code: 6029; name: "BumpSeedNotInHashMap"; msg: "Bump seed not in hash map."; }, { code: 6030; name: "MetaplexTransferFailed"; msg: "Failed to transfer NFT using metaplex"; }, { code: 6031; name: "MetaplexDelegateFailed"; msg: "Failed to set Sale delegate on NFT using metaplex"; }, { code: 6032; name: "MetaplexRevokeFailed"; msg: "Failed to revoke Sale delegate on NFT using metaplex"; }, { code: 6033; name: "IncorrectTokenStandard"; msg: "Token standard must be Programmable NFT or Standard NFT"; }, { code: 6034; name: "PNFTDelegateSetAlready"; msg: "PNFT Delegate set already"; }, { code: 6035; name: "RoyaltyBPMismatch"; msg: "Royalty basis points mismatch"; } ]; }; export const hyperspaceIdl: Hyperspace = { version: "0.1.0", name: "hyperspace", instructions: [ { name: "initProgramAsSigner", accounts: [ { name: "wallet", isMut: true, isSigner: true, }, { name: "programAsSigner", isMut: true, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, ], args: [ { name: "programAsSignerBump", type: "u8", }, ], }, { name: "updateHyperspace", accounts: [ { name: "payer", isMut: false, isSigner: true, }, { name: "authority", isMut: false, isSigner: true, }, { name: "newAuthority", isMut: false, isSigner: false, }, { name: "feeWithdrawalDestination", isMut: true, isSigner: false, }, { name: "treasuryWithdrawalDestination", isMut: true, isSigner: false, }, { name: "treasuryWithdrawalDestinationOwner", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "ataProgram", isMut: false, isSigner: false, }, { name: "rent", isMut: false, isSigner: false, }, ], args: [ { name: "sellerFeeBasisPoints", type: { option: "u16", }, }, { name: "requiresSignOff", type: { option: "bool", }, }, { name: "canChangeSalePrice", type: { option: "bool", }, }, ], }, { name: "withdraw", accounts: [ { name: "wallet", isMut: false, isSigner: false, }, { name: "receiptAccount", isMut: true, isSigner: false, }, { name: "escrowPaymentAccount", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "rent", isMut: false, isSigner: false, }, ], args: [ { name: "escrowPaymentBump", type: "u8", }, { name: "amount", type: "u64", }, ], }, { name: "deposit", accounts: [ { name: "wallet", isMut: false, isSigner: true, }, { name: "paymentAccount", isMut: true, isSigner: false, }, { name: "transferAuthority", isMut: false, isSigner: false, }, { name: "escrowPaymentAccount", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "hyperspaceFeeAccount", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "rent", isMut: false, isSigner: false, }, ], args: [ { name: "escrowPaymentBump", type: "u8", }, { name: "amount", type: "u64", }, ], }, { name: "permissionlessCancel", accounts: [ { name: "wallet", isMut: false, isSigner: true, }, { name: "tokenMint", isMut: false, isSigner: false, }, { name: "seller", isMut: false, isSigner: false, }, { name: "tokenAccount", isMut: false, isSigner: false, }, { name: "tradeState", isMut: true, isSigner: false, }, { name: "programAsSigner", isMut: false, isSigner: false, }, ], args: [ { name: "programAsSignerBump", type: "u8", }, { name: "tradeStateBump", type: "u8", }, { name: "tokenSize", type: "u64", }, ], }, { name: "collectionCancelBuy", accounts: [ { name: "wallet", isMut: true, isSigner: false, }, { name: "identifierPubkey", isMut: false, isSigner: false, }, { name: "authority", isMut: true, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "collectionBuyerTradeState", isMut: true, isSigner: false, }, { name: "programAsSigner", isMut: false, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, ], args: [ { name: "buyerPrice", type: "u64", }, { name: "programAsSignerBump", type: "u8", }, { name: "tradeStateBump", type: "u8", }, { name: "collectionTradeStateType", type: "u8", }, { name: "identifierIndex", type: "u8", }, ], }, { name: "cancel", accounts: [ { name: "wallet", isMut: true, isSigner: false, docs: ["CHECK OWNER OF TRADE_STATE TODO"], }, { name: "tokenMint", isMut: false, isSigner: false, }, { name: "tokenAccount", isMut: true, isSigner: false, }, { name: "metadata", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "tradeState", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "metadataProgram", isMut: false, isSigner: false, }, { name: "programAsSigner", isMut: false, isSigner: false, }, { name: "instructions", isMut: false, isSigner: false, }, { name: "tokenRecord", isMut: true, isSigner: false, docs: ["CHECK no check needed"], }, { name: "editionAccount", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, { name: "authorizationRules", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, { name: "mplTokenAuthRulesProgram", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, ], args: [ { name: "isBuy", type: "u8", }, { name: "programAsSignerBump", type: "u8", }, { name: "tradeStateBump", type: "u8", }, { name: "tokenSize", type: "u64", }, ], }, { name: "collectionExecuteSale", accounts: [ { name: "buyer", isMut: true, isSigner: false, }, { name: "buyerBrokerWallet", isMut: true, isSigner: false, }, { name: "seller", isMut: true, isSigner: false, }, { name: "sellerBrokerWallet", isMut: true, isSigner: false, }, { name: "tokenAccount", isMut: true, isSigner: false, }, { name: "tokenMint", isMut: false, isSigner: false, }, { name: "metadata", isMut: true, isSigner: false, }, { name: "editionData", isMut: false, isSigner: false, }, { name: "escrowPaymentAccount", isMut: true, isSigner: false, }, { name: "buyerReceiptTokenAccount", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "hyperspaceTreasury", isMut: true, isSigner: false, }, { name: "identifierPubkey", isMut: false, isSigner: false, }, { name: "collectionBuyerTradeState", isMut: true, isSigner: false, }, { name: "sellerTradeState", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "ataProgram", isMut: false, isSigner: false, }, { name: "metadataProgram", isMut: false, isSigner: false, }, { name: "mplTokenAuthRulesProgram", isMut: false, isSigner: false, }, { name: "programAsSigner", isMut: false, isSigner: false, }, { name: "rent", isMut: false, isSigner: false, }, { name: "instructions", isMut: false, isSigner: false, }, { name: "ownerTokenRecord", isMut: true, isSigner: false, docs: ["CHECK no check needed"], }, { name: "destinationTokenRecord", isMut: true, isSigner: false, docs: ["CHECK no check needed"], }, { name: "authorizationRules", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, ], args: [ { name: "escrowPaymentBump", type: "u8", }, { name: "programAsSignerBump", type: "u8", }, { name: "buyerTradeStateBump", type: "u8", }, { name: "sellerTradeStateBump", type: "u8", }, { name: "maxAmountToPay", type: "u64", }, { name: "buyerBrokerBasisPoints", type: "u16", }, { name: "sellerBrokerBasisPoints", type: "u16", }, { name: "tokenSize", type: "u64", }, { name: "collectionTradeStateType", type: "u8", }, { name: "identifierIndex", type: "u8", }, { name: "royaltyBasisPoints", type: "u16", }, ], }, { name: "executeSale", accounts: [ { name: "buyer", isMut: true, isSigner: false, }, { name: "buyerBrokerWallet", isMut: true, isSigner: false, }, { name: "seller", isMut: true, isSigner: false, }, { name: "sellerBrokerWallet", isMut: true, isSigner: false, }, { name: "tokenAccount", isMut: true, isSigner: false, }, { name: "tokenMint", isMut: false, isSigner: false, }, { name: "metadata", isMut: true, isSigner: false, }, { name: "escrowPaymentAccount", isMut: true, isSigner: false, }, { name: "buyerReceiptTokenAccount", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "hyperspaceTreasury", isMut: true, isSigner: false, }, { name: "buyerTradeState", isMut: true, isSigner: false, }, { name: "sellerTradeState", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "ataProgram", isMut: false, isSigner: false, }, { name: "metadataProgram", isMut: false, isSigner: false, }, { name: "mplTokenAuthRulesProgram", isMut: false, isSigner: false, }, { name: "programAsSigner", isMut: false, isSigner: false, }, { name: "rent", isMut: false, isSigner: false, }, { name: "instructions", isMut: false, isSigner: false, }, { name: "editionAccount", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, { name: "ownerTokenRecord", isMut: true, isSigner: false, docs: ["CHECK no check needed"], }, { name: "destinationTokenRecord", isMut: true, isSigner: false, docs: ["CHECK no check needed"], }, { name: "authorizationRules", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, ], args: [ { name: "escrowPaymentBump", type: "u8", }, { name: "programAsSignerBump", type: "u8", }, { name: "buyerTradeStateBump", type: "u8", }, { name: "sellerTradeStateBump", type: "u8", }, { name: "maxAmountToPay", type: "u64", }, { name: "buyerBrokerBasisPoints", type: "u16", }, { name: "sellerBrokerBasisPoints", type: "u16", }, { name: "tokenSize", type: "u64", }, { name: "royaltyBasisPoints", type: "u16", }, ], }, { name: "sell", accounts: [ { name: "wallet", isMut: false, isSigner: true, }, { name: "sellerBrokerWallet", isMut: false, isSigner: false, }, { name: "tokenMint", isMut: false, isSigner: false, }, { name: "tokenAccount", isMut: true, isSigner: false, }, { name: "metadata", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "hyperspaceFeeAccount", isMut: true, isSigner: false, }, { name: "sellerTradeState", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "metadataProgram", isMut: false, isSigner: false, }, { name: "programAsSigner", isMut: false, isSigner: false, }, { name: "instructions", isMut: false, isSigner: false, }, { name: "tokenRecord", isMut: true, isSigner: false, docs: ["CHECK no check needed"], }, { name: "editionAccount", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, { name: "authorizationRules", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, { name: "mplTokenAuthRulesProgram", isMut: false, isSigner: false, docs: ["CHECK no check needed"], }, { name: "clock", isMut: false, isSigner: false, }, ], args: [ { name: "tradeStateBump", type: "u8", }, { name: "programAsSignerBump", type: "u8", }, { name: "minAmountToReceive", type: "u64", }, { name: "brokerBasisPoints", type: "u16", }, { name: "tokenSize", type: "u64", }, { name: "royaltyBasisPoints", type: "u16", }, ], }, { name: "buy", accounts: [ { name: "wallet", isMut: false, isSigner: true, }, { name: "buyerBrokerWallet", isMut: false, isSigner: false, }, { name: "paymentAccount", isMut: true, isSigner: false, }, { name: "transferAuthority", isMut: false, isSigner: false, }, { name: "tokenAccount", isMut: false, isSigner: false, }, { name: "buyerReceiptTokenAccount", isMut: false, isSigner: false, }, { name: "metadata", isMut: false, isSigner: false, }, { name: "escrowPaymentAccount", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "hyperspaceFeeAccount", isMut: true, isSigner: false, }, { name: "buyerTradeState", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "rent", isMut: false, isSigner: false, }, { name: "clock", isMut: false, isSigner: false, }, ], args: [ { name: "tradeStateBump", type: "u8", }, { name: "escrowPaymentBump", type: "u8", }, { name: "maxAmountToPay", type: "u64", }, { name: "brokerBasisPoints", type: "u16", }, { name: "tokenSize", type: "u64", }, { name: "royaltyBasisPoints", type: "u16", }, ], }, { name: "collectionBuy", accounts: [ { name: "wallet", isMut: true, isSigner: true, }, { name: "buyerBrokerWallet", isMut: false, isSigner: false, }, { name: "identifierPubkey", isMut: false, isSigner: false, }, { name: "escrowPaymentAccount", isMut: true, isSigner: false, }, { name: "authority", isMut: false, isSigner: false, }, { name: "hyperspace", isMut: false, isSigner: false, }, { name: "hyperspaceFeeAccount", isMut: true, isSigner: false, }, { name: "collectionBuyerTradeState", isMut: true, isSigner: false, }, { name: "tokenProgram", isMut: false, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "rent", isMut: false, isSigner: false, }, { name: "clock", isMut: false, isSigner: false, }, ], args: [ { name: "tradeStateBump", type: "u8", }, { name: "escrowPaymentBump", type: "u8", }, { name: "buyerPrice", type: "u64", }, { name: "brokerBasisPoints", type: "u16", }, { name: "collectionTradeStateType", type: "u8", }, { name: "identifierIndex", type: "u8", }, { name: "royaltyBasisPoints", type: "u16", }, ], }, { name: "createCollectionBuyTradeState", accounts: [ { name: "wallet", isMut: true, isSigner: true, }, { name: "identifierPubkey", isMut: false, isSigner: false, }, { name: "brokerWallet", isMut: false, isSigner: false, }, { name: "tradeState", isMut: true, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "clock", isMut: false, isSigner: false, }, ], args: [ { name: "tradeStateBump", type: "u8", }, { name: "buyPrice", type: "u64", }, { name: "brokerBasisPoints", type: "u16", }, { name: "collectionTradeStateType", type: "u8", }, { name: "identifierIndex", type: "u8", }, { name: "royaltyBasisPoints", type: "u16", }, ], }, { name: "createTradeState", accounts: [ { name: "wallet", isMut: true, isSigner: true, }, { name: "collection", isMut: false, isSigner: false, }, { name: "brokerWallet", isMut: false, isSigner: false, }, { name: "tokenAccount", isMut: false, isSigner: false, }, { name: "tokenMint", isMut: false, isSigner: false, }, { name: "tradeState", isMut: true, isSigner: false, }, { name: "systemProgram", isMut: false, isSigner: false, }, { name: "clock", isMut: false, isSigner: false, }, ], args: [ { name: "isBuy", type: "u8", }, { name: "tradeStateBump", type: "u8", }, { name: "buyPrice", type: "u64", }, { name: "brokerBasisPoints", type: "u16", }, { name: "tokenSize", type: "u64", }, { name: "royaltyBasisPoints", type: "u16", }, ], }, ], accounts: [ { name: "hyperspace", type: { kind: "struct", fields: [ { name: "hyperspaceFeeAccount", type: "publicKey", }, { name: "hyperspaceTreasury", type: "publicKey", }, { name: "treasuryWithdrawalDestination", type: "publicKey", }, { name: "feeWithdrawalDestination", type: "publicKey", }, { name: "authority", type: "publicKey", }, { name: "creator", type: "publicKey", }, { name: "bump", type: "u8", }, { name: "treasuryBump", type: "u8", }, { name: "feePayerBump", type: "u8", }, { name: "sellerFeeBasisPoints", type: "u16", }, { name: "requiresSignOff", type: "bool", }, { name: "canChangeSalePrice", type: "bool", }, ], }, }, { name: "programAsSigner", type: { kind: "struct", fields: [ { name: "firstByte", type: "u8", }, ], }, }, { name: "tradeState", type: { kind: "struct", fields: [ { name: "buyPrice", type: "u64", }, { name: "userWallet", type: "publicKey", }, { name: "isBuy", type: "u8", }, { name: "collection", type: "publicKey", }, { name: "brokerWallet", type: "publicKey", }, { name: "brokerBasisPoints", type: "u16", }, { name: "tokenMint", type: "publicKey", }, { name: "royaltyBasisPoints", type: "u16", }, { name: "timestamp", type: "i64", }, ], }, }, { name: "collectionBuyTradeState", type: { kind: "struct", fields: [ { name: "userWallet", type: "publicKey", }, { name: "collectionTradeStateType", type: "u8", }, { name: "identifierIndex", type: "u8", }, { name: "identifierPubkey", type: "publicKey", }, { name: "brokerWallet", type: "publicKey", }, { name: "brokerBasisPoints", type: "u16", }, { name: "royaltyBasisPoints", type: "u16", }, { name: "timestamp", type: "i64", }, ], }, }, ], types: [ { name: "AuthorizationDataLocal", type: { kind: "struct", fields: [ { name: "payload", type: { vec: { defined: "TaggedPayload", }, }, }, ], }, }, { name: "TaggedPayload", type: { kind: "struct", fields: [ { name: "name", type: "string", }, { name: "payload", type: { defined: "PayloadTypeLocal", }, }, ], }, }, { name: "SeedsVecLocal", type: { kind: "struct", fields: [ { name: "seeds", docs: ["The vector of derivation seeds."], type: { vec: "bytes", }, }, ], }, }, { name: "ProofInfoLocal", type: { kind: "struct", fields: [ { name: "proof", docs: ["The merkle proof."], type: { vec: { array: ["u8", 32], }, }, }, ], }, }, { name: "PayloadTypeLocal", type: { kind: "enum", variants: [ { name: "Pubkey", fields: ["publicKey"], }, { name: "Seeds", fields: [ { defined: "SeedsVecLocal", }, ], }, { name: "MerkleProof", fields: [ { defined: "ProofInfoLocal", }, ], }, { name: "Number", fields: ["u64"], }, ], }, }, ], errors: [ { code: 6000, name: "PublicKeyMismatch", msg: "PublicKeyMismatch", }, { code: 6001, name: "InvalidMintAuthority", msg: "InvalidMintAuthority", }, { code: 6002, name: "UninitializedAccount", msg: "UninitializedAccount", }, { code: 6003, name: "IncorrectOwner", msg: "IncorrectOwner", }, { code: 6004, name: "PublicKeysShouldBeUnique", msg: "PublicKeysShouldBeUnique", }, { code: 6005, name: "StatementFalse", msg: "StatementFalse", }, { code: 6006, name: "NotRentExempt", msg: "NotRentExempt", }, { code: 6007, name: "NumericalOverflow", msg: "NumericalOverflow", }, { code: 6008, name: "ExpectedSolAccount", msg: "Expected a sol account but got an spl token account instead", }, { code: 6009, name: "CannotExchangeSOLForSol", msg: "Cannot exchange sol for sol", }, { code: 6010, name: "SOLWalletMustSign", msg: "If paying with sol, sol wallet must be signer", }, { code: 6011, name: "CannotTakeThisActionWithoutHyperspaceSignOff", msg: "Cannot take this action without hyperspace signing too", }, { code: 6012, name: "NoPayerPresent", msg: "No payer present on this txn", }, { code: 6013, name: "DerivedKeyInvalid", msg: "Derived key invalid", }, { code: 6014, name: "MetadataDoesntExist", msg: "Metadata doesn't exist", }, { code: 6015, name: "InvalidTokenAmount", msg: "Invalid token amount", }, { code: 6016, name: "BothPartiesNeedToAgreeToSale", msg: "Both parties need to agree to this sale", }, { code: 6017, name: "CannotMatchFreeSalesWithoutHyperspaceOrSellerSignoff", msg: "Cannot match free sales unless the hyperspace or seller signs off", }, { code: 6018, name: "SaleRequiresSigner", msg: "This sale requires a signer", }, { code: 6019, name: "OldSellerNotInitialized", msg: "Old seller not initialized", }, { code: 6020, name: "SellerATACannotHaveDelegate", msg: "Seller ata cannot have a delegate set", }, { code: 6021, name: "BuyerATACannotHaveDelegate", msg: "Buyer ata cannot have a delegate set", }, { code: 6022, name: "NoValidSignerPresent", msg: "No valid signer present", }, { code: 6023, name: "InvalidBasisPoints", msg: "BP must be less than or equal to 10000", }, { code: 6024, name: "InvalidBrokerInformation", msg: "Broker information must match", }, { code: 6025, name: "InvalidTokenAccount", msg: "Token Account holding selling token must be owned by seller", }, { code: 6026, name: "InvalidPermissionlessCancel", msg: "Cannot permissionless cancel this trade state", }, { code: 6027, name: "InvalidCollectionTradeStateType", msg: "Invalid Collection Trade State Type", }, { code: 6028, name: "InvalidCollectionTradeStateIdentifier", msg: "Invalid Collection Trade State Identifier", }, { code: 6029, name: "BumpSeedNotInHashMap", msg: "Bump seed not in hash map.", }, { code: 6030, name: "MetaplexTransferFailed", msg: "Failed to transfer NFT using metaplex", }, { code: 6031, name: "MetaplexDelegateFailed", msg: "Failed to set Sale delegate on NFT using metaplex", }, { code: 6032, name: "MetaplexRevokeFailed", msg: "Failed to revoke Sale delegate on NFT using metaplex", }, { code: 6033, name: "IncorrectTokenStandard", msg: "Token standard must be Programmable NFT or Standard NFT", }, { code: 6034, name: "PNFTDelegateSetAlready", msg: "PNFT Delegate set already", }, { code: 6035, name: "RoyaltyBPMismatch", msg: "Royalty basis points mismatch", }, ], }; ================================================ FILE: chatgpt-plugin/src/lib/middleware.ts ================================================ import { NextApiRequest, NextApiResponse, NextApiHandler } from "next"; import { resolveAddress, resolveToken } from "./address"; export type Options = { addresses?: string[]; tokens?: string[]; rewriteQuery?: boolean; }; export function makeApiPostRequest(handler: NextApiHandler, options?: Options): NextApiHandler { return async (req: NextApiRequest, res: NextApiResponse) => { const rewriteQuery = options ? options.rewriteQuery : false; if (req.method != "POST") { res.status(405).send({ message: "Only POST requests allowed" }); return; } const target = rewriteQuery ? req.query : req.body; try { if (options && options.addresses) { for (const addr of options.addresses) { const publicKey = await resolveAddress(target[addr]); target[addr] = publicKey; } } if (options && options.tokens) { for (const token of options.tokens) { const publicKey = await resolveToken(target[token]); target[token] = publicKey; } } } catch (e) { res.status(400).send({ message: (e as Error).toString() }); return; } await handler(req, res); }; } ================================================ FILE: chatgpt-plugin/src/lib/on-chain-metadata/index.ts ================================================ import * as anchor from "@coral-xyz/anchor"; import { Connection } from "@solana/web3.js"; type SolanaPayTx = { transaction: string; message?: string; }; /** * Need this to fake a wallet with only a publicKey for the anchor provider */ class FakeWallet { publicKey: anchor.web3.PublicKey; constructor(publicKey: anchor.web3.PublicKey) { this.publicKey = publicKey; } async signTransaction< T extends anchor.web3.Transaction | anchor.web3.VersionedTransaction >(tx: T): Promise { return tx; } async signAllTransactions< T extends anchor.web3.Transaction | anchor.web3.VersionedTransaction >(txs: T[]): Promise { return txs; } } export async function createWriteNFTMetadataTx( connection: Connection, owner: string, metadata: Object ): Promise { const program = await anchor.Program.at( process.env.ON_CHAIN_METADATA_PROGRAM as string, new anchor.AnchorProvider( connection, new FakeWallet(new anchor.web3.PublicKey(owner)), {} ) ); let metadataBytes = JSON.stringify(metadata); console.log(metadataBytes.length); if (metadataBytes.length > 500) { throw new Error("Metadata too large: " + metadataBytes.length); } const metadataKp = anchor.web3.Keypair.generate(); // Create metadata let initIx = await program.methods .initialize(new anchor.BN(metadataBytes.length)) .accounts({ owner, metadata: metadataKp.publicKey, }) .signers([metadataKp]) .instruction(); // Write metadata let writeIx = await program.methods .write(new anchor.BN(0), Buffer.from(metadataBytes)) .accounts({ metadata: metadataKp.publicKey, owner, }) .instruction(); // Validate metadata let validateIx = await program.methods .validate() .accounts({ metadata: metadataKp.publicKey }) .instruction(); let tx = new anchor.web3.Transaction(); tx = tx.add(initIx).add(writeIx).add(validateIx); tx.recentBlockhash = (await connection.getRecentBlockhash()).blockhash; tx.feePayer = new anchor.web3.PublicKey(owner); tx.partialSign(metadataKp); return { transaction: tx .serialize({ requireAllSignatures: false }) .toString("base64"), }; } export async function createCloseNFTMetadataTx( connection: Connection, owner: string, account: string ): Promise { const program = await anchor.Program.at( process.env.ON_CHAIN_METADATA_PROGRAM as string, new anchor.AnchorProvider( connection, new FakeWallet(new anchor.web3.PublicKey(owner)), {} ) ); let tx = await program.methods .close() .accounts({ metadata: account, recipient: owner, owner, }) .transaction(); tx.recentBlockhash = (await connection.getRecentBlockhash()).blockhash; tx.feePayer = new anchor.web3.PublicKey(owner); return { transaction: tx .serialize({ requireAllSignatures: false }) .toString("base64"), }; } ================================================ FILE: chatgpt-plugin/src/pages/api/constants.ts ================================================ import express from "express"; import { RestClient } from "@hellomoon/api"; import { Connection } from "@solana/web3.js"; import { HyperspaceClient } from "hyperspace-client-js"; export const APP = express(); export const PORT = process.env.PORT || 3333; import dotenv from "dotenv"; dotenv.config(); // Internal Solana Pay constants export const SOLANA_PAY_LABEL = "Solana Labs ChatGPT Plugin"; export const TRANSACTION_ENDPOINTS = [ "createBuyNFT", "createWriteNFTMetadata", "createCloseNFTMetadata", ]; export type TransactionEndpoints = (typeof TRANSACTION_ENDPOINTS)[number]; export const TX_DESCRIPTIONS: Record = { createBuyNFT: "Sign to Buy NFT", createWriteNFTMetadata: "Sign to Write NFT Metadata", createCloseNFTMetadata: "Sign to Close NFT Metadata", createTransferToken: "Sign to Transfer Token", createTransferSol: "Sign to Transfer Sol", }; // Inferred Constants export let HELIUS_URL: string; export let SELF_URL: string; export let HYPERSPACE_CLIENT: HyperspaceClient; export let HELLOMOON_CLIENT: RestClient; export let CONNECTION: Connection; export default function index() { HELIUS_URL = `https://rpc.helius.xyz/?api-key=${process.env.HELIUS_API_KEY}`; CONNECTION = new Connection(HELIUS_URL); SELF_URL = process.env.SELF_URL as string; HYPERSPACE_CLIENT = new HyperspaceClient(process.env.HYPERSPACE_API_KEY as string); HELLOMOON_CLIENT = new RestClient(process.env.HELLOMOON_API_KEY as string); } ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getAccountInfo/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { PublicKey, Connection, Keypair } from "@solana/web3.js"; import { Program, AnchorProvider, BorshAccountsCoder, BN } from "@coral-xyz/anchor"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; import configConstants, { CONNECTION } from "../../constants"; import { makeApiPostRequest } from "@/lib/middleware"; configConstants(); /** * Replace Anchor data (BNs, PublicKeys) with stringified data * @param obj * @returns */ export function stringifyAnchorObject(obj: any): any { if (obj === true || obj === false) { return obj; } else if (!obj) { return {}; } else if (obj instanceof BN) { return obj.toString(); } else if (obj instanceof PublicKey) { return obj.toString(); } else if (typeof obj === "bigint") { let bigInt = obj as BigInt; return bigInt.toString(); } if (typeof obj === "object" && obj !== null) { return Object.keys(obj).reduce((acc: Record, key: string) => { acc[key] = stringifyAnchorObject(obj[key]); return acc; }, {}); } return obj; } /** * Returns accountInfo or extends it with deserialized account data if the account is a program account of an Anchor program * @param accountAddress * @returns */ async function getParsedAccountInfo(connection: Connection, accountAddress: PublicKey) { // TODO: copy the explorer code here that manually deserializes a bunch of stuff, like Mango & Pyth const accountInfo = (await connection.getAccountInfo(accountAddress)) as any; // If acccount is not a program, check for Anchor IDL if (accountInfo?.owner && !accountInfo.executable) { try { const program = await Program.at( accountInfo.owner, new AnchorProvider(connection, new NodeWallet(Keypair.generate()), { commitment: "confirmed", }), ); // Search through Anchor IDL for the account type const rawData = accountInfo.data; const coder = new BorshAccountsCoder(program.idl); const accountDefTmp = program.idl.accounts?.find((accountType: any) => (rawData as Buffer) .slice(0, 8) .equals(BorshAccountsCoder.accountDiscriminator(accountType.name)), ); // If we found the Anchor IDL type, decode the account state if (accountDefTmp) { const accountDef = accountDefTmp; // Decode the anchor data & stringify the data const decodedAccountData = stringifyAnchorObject(coder.decode(accountDef.name, rawData)); let payload = { ...accountInfo, extended: decodedAccountData, }; delete payload["data"]; return payload; } } catch (err) { console.log(err); } } return accountInfo || {}; } async function handler(req: NextApiRequest, res: NextApiResponse) { const accountInfo = await getParsedAccountInfo(CONNECTION, req.body["address"]); res.status(200).json(accountInfo); } export default makeApiPostRequest(handler, { addresses: ["address"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getAssetsByOwner/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELIUS_URL } from "../../constants"; configConstants(); import axios from "axios"; import { makeApiPostRequest } from "@/lib/middleware"; /** * Returns the data from the Metaplex Read API * @param address * @param page (optional) page number * @param limit (optional) set to 5 to prevent overflowing GPT context window * @returns */ export const readApiGetAssetsByOwner = async ( address: string, page: number = 1, limit: number = 5, ) => { const sortBy = { sortBy: "created", sortDirection: "asc", }; const before = ""; const after = ""; const { data } = await axios.post(HELIUS_URL, { jsonrpc: "2.0", id: "my-id", method: "getAssetsByOwner", params: [address, sortBy, limit, page, before, after], }); return data.result; }; async function handler(req: NextApiRequest, res: NextApiResponse) { const assets = await readApiGetAssetsByOwner(req.body["address"].toString()); res.status(200).send(assets); } export default makeApiPostRequest(handler, { addresses: ["address"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getBalance/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; import configConstants, { CONNECTION } from "../../constants"; configConstants(); import { makeApiPostRequest } from "@/lib/middleware"; async function handler(req: NextApiRequest, res: NextApiResponse) { const { address } = req.body; const balance = await CONNECTION.getBalance(new PublicKey(address)); res.status(200).send({ sol: balance / LAMPORTS_PER_SOL }); } export default makeApiPostRequest(handler, { addresses: ["address"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getCollectionsByFloorPrice/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; import configConstants, { HYPERSPACE_CLIENT } from "../../constants"; import { makeApiPostRequest } from "@/lib/middleware"; configConstants(); type CollectionStats = { id: string; desc: string; img: string; website: string; floor_price: number; }; /** * Provides a feed of NFT collections that the user can afford. */ async function hyperspaceGetCollectionsByFloorPrice( maxFloorPrice: number | undefined, minFloorPrice: number | undefined, pageSize: number = 5, orderBy: string = "DESC", humanReadableSlugs: boolean = false, ) { let pageNumber = 1; let results: CollectionStats[] = []; let hasMore = true; const PAGE_SCRAPE_LIMIT = 10; while (results.length < pageSize && pageNumber < PAGE_SCRAPE_LIMIT && hasMore) { let projects = await HYPERSPACE_CLIENT.getProjects({ condition: { floorPriceFilter: { min: minFloorPrice ?? null, max: maxFloorPrice ?? null, }, }, orderBy: { field_name: "floor_price", sort_order: orderBy.toLocaleUpperCase() as any, }, paginationInfo: { page_size: 512, page_number: pageNumber, }, }); let stats: CollectionStats[] = projects.getProjectStats.project_stats ?.filter(project => { return (project.volume_7day ?? 0 > 0) && (project.floor_price ?? 0 > 0); }) .map(project => { return { id: project.project_id, desc: project.project?.display_name ?? "", img: project.project?.img_url ?? "", website: project.project?.website ?? "", floor_price: project.floor_price ?? 0, }; }) ?? []; if (humanReadableSlugs) { stats = stats?.filter(stat => { try { bs58.decode(stat.id!); return false; } catch (err) { return true; } }); } pageNumber += 1; console.log("\tFetching collection info... ", stats.length, pageNumber); results = results.concat(stats!); hasMore = projects.getProjectStats.pagination_info.has_next_page; } return { projects: results.slice(0, pageSize), hasMore: hasMore, }; } async function handler(req: NextApiRequest, res: NextApiResponse) { const { maxFloorPrice, minFloorPrice, orderBy, pageSize, humanReadable } = req.body; const result = await hyperspaceGetCollectionsByFloorPrice( maxFloorPrice, minFloorPrice, pageSize, orderBy, humanReadable, ); res.status(200).send(JSON.stringify(result)); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getCollectionsByName/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HYPERSPACE_CLIENT } from "../../constants"; configConstants(); import { makeApiPostRequest } from "@/lib/middleware"; async function getCollectionsByName(req: NextApiRequest, res: NextApiResponse) { const { projectName } = req.body; const projects = await HYPERSPACE_CLIENT.searchProjectByName({ condition: { // In practice, it was harder to find projects based on this condition // matchName: { // operation: "FUZZY" as StringInputOperationEnum.Fuzzy, // value: projectName as string, // }, name: projectName as string, }, }); const paredResponse = projects.getProjectStatByName.project_stats ?.map(project => { return { id: project.project_id, verified: project.project?.is_verified ?? false, name: project.project?.display_name ?? "", img: project.project?.img_url ?? "", website: project.project?.website ?? "", floorPrice: project.floor_price ?? 0, twitterFollowers: project.twitter_followers ?? 0, twitter: project.project?.twitter ?? "", }; }) .filter(project => project.verified) ?? []; const result = { projects: paredResponse, }; res.status(200).send(JSON.stringify(result)); } export default makeApiPostRequest(getCollectionsByName); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getListedCollectionNFTs/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HYPERSPACE_CLIENT } from "../../constants"; import { makeApiPostRequest } from "@/lib/middleware"; configConstants(); type NFTListing = { price: number; image: string; token: string; }; type ListedNFTResponse = { listings: NFTListing[]; hasMore: boolean; }; async function hyperspaceGetListedCollectionNFTs( projectId: string, pageSize: number = 5, priceOrder: string = "DESC", ): Promise { let listedNFTs: NFTListing[] = []; let hasMore = true; let pageNumber = 1; let pagesScraped = 0; const PAGE_SCRAPE_LIMIT = 10; while (listedNFTs.length < pageSize && hasMore && pagesScraped < PAGE_SCRAPE_LIMIT) { let results = await HYPERSPACE_CLIENT.getMarketplaceSnapshot({ condition: { projects: [{ project_id: projectId }], onlyListings: true, }, orderBy: { field_name: "lowest_listing_price", sort_order: priceOrder.toLocaleUpperCase() as any, }, paginationInfo: { page_number: pageNumber, }, }); let snaps = results.getMarketPlaceSnapshots.market_place_snapshots!; let orderedListings = snaps.sort( (a, b) => a.lowest_listing_mpa!.price! - b.lowest_listing_mpa!.price!, ); pageNumber += 1; let crucialInfo: NFTListing[] = orderedListings .filter( arr => // We filter out Magic Eden's marketplace because they // require an API key to make purchases programmatically arr.lowest_listing_mpa?.marketplace_program_id !== "M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K", ) .map(arr => { return { price: arr.lowest_listing_mpa!.price!, token: arr.token_address, image: arr.meta_data_img ?? "", marketplace: arr.lowest_listing_mpa!.marketplace_program_id!, }; }); listedNFTs = listedNFTs.concat(crucialInfo); hasMore = results.getMarketPlaceSnapshots.pagination_info.has_next_page; } return { listings: listedNFTs.slice(0, pageSize), hasMore, }; } async function handler(req: NextApiRequest, res: NextApiResponse) { const { projectId, pageSize, priceOrder } = req.body; const result = await hyperspaceGetListedCollectionNFTs(projectId, pageSize, priceOrder); res.status(200).send(JSON.stringify(result)); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getSignaturesForAddress/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { PublicKey } from "@solana/web3.js"; import configConstants, { CONNECTION } from "../../constants"; import { makeApiPostRequest } from "@/lib/middleware"; configConstants(); async function handler(req: NextApiRequest, res: NextApiResponse) { const accountAddress = req.body["address"]; const beforeSig = req.body.beforeSignature ?? ""; const untilSig = req.body.untilSignature ?? ""; const signatures = await CONNECTION.getSignaturesForAddress(accountAddress, { limit: 11, before: beforeSig.length > 0 ? beforeSig : undefined, until: untilSig.length > 0 ? untilSig : undefined, }); res.status(200).send({ hasMore: signatures.length === 11, nextPage: signatures.length === 11 ? { beforeSignature: signatures[10].signature } : null, signatures: signatures.map(sig => { return { slot: sig.slot, signature: sig.signature, err: sig.err ?? undefined, memo: sig.memo ?? undefined, confirmationStatus: sig.confirmationStatus, blockTime: new Date((sig.blockTime as number) * 1000).toDateString(), }; }), }); } export default makeApiPostRequest(handler, { addresses: ["address"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getTokenAccounts/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import configConstants, { CONNECTION } from "../../constants"; import { makeApiPostRequest } from "@/lib/middleware"; configConstants(); type TokenInfo = { mint: string; amount: string; }; async function handler(req: NextApiRequest, res: NextApiResponse) { let result = await CONNECTION.getParsedTokenAccountsByOwner( req.body["address"], { programId: TOKEN_PROGRAM_ID }, "confirmed", ); const tokenInfos: TokenInfo[] = []; for (const accountInfo of result.value) { const info = accountInfo.account.data.parsed.info; if (info.tokenAmount.uiAmount !== 0) { tokenInfos.push({ mint: info.mint.toString(), amount: info.tokenAmount.uiAmountString, }); } } res.status(200).json(tokenInfos); } export default makeApiPostRequest(handler, { addresses: ["address"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getTotalValue/index.ts ================================================ import { TokenPriceBatchedRequest, NftMintPriceByCreatorAvgRequest } from "@hellomoon/api"; import { PublicKey } from "@solana/web3.js"; import { NextApiRequest, NextApiResponse } from "next"; import { readApiGetAssetsByOwner } from "../getAssetsByOwner"; import configConstants, { CONNECTION, HELLOMOON_CLIENT } from "../../constants"; import { makeApiPostRequest } from "@/lib/middleware"; configConstants(); async function getTokenTotal(address: string) { let connection = CONNECTION; let tokenInfos = await ( await fetch( `https://api.helius.xyz/v0/addresses/${address}/balances?api-key=${process.env.HELIUS_API_KEY}`, ) ).json(); let mints: string[] = []; let mintAmount: Record = {}; for (const info of tokenInfos["tokens"] as any) { if (info.amount !== "0") { mints.push(info.mint); mintAmount[info.mint] = Number.parseInt(info.amount); } } let mintPrice: Record = {}; let mintVolume: Record = {}; let pricePromises: Promise[] = []; let decimalPromises: Promise[] = []; let counter = 0; let batchSize = 10; while (counter < mints.length) { pricePromises.push( HELLOMOON_CLIENT.send( new TokenPriceBatchedRequest({ mints: mints.slice(counter, counter + batchSize) }), ), ); for (let i = counter; i < Math.min(counter + batchSize, mints.length); i++) { decimalPromises.push(connection.getParsedAccountInfo(new PublicKey(mints[i]))); } counter += batchSize; } let allData: any[] = []; try { allData = await Promise.all([...decimalPromises, ...pricePromises]); } catch (e) { console.error("Failed to getParsedAccountInfo:", e); throw e; } let decimalData = allData.slice(0, mints.length); let mintDecimals: Record = {}; decimalData.forEach((accountInfo, index) => { mintDecimals[mints[index]] = accountInfo!.value!.data?.parsed?.info?.decimals; }); let priceData = allData.slice(mints.length); let tokenTotal = 0; for (const mintPriceData of priceData) { for (const price of (mintPriceData as any).data) { if (price.price) { let decimal = mintDecimals[price.mints]; mintPrice[price.mints] = price.price / 1e6; mintVolume[price.mints] = price.volume; tokenTotal += (mintPrice[price.mints] * mintAmount[price.mints]) / Math.pow(10, decimal); } } } return (Number(tokenTotal * 100).toFixed(1) as any as number) / 100; } async function getNFTTotal(address: string) { let assets = ((await readApiGetAssetsByOwner(address, 1, 100)) as any).items; assets = assets.filter((asset: any) => asset.grouping.length > 0); let groupings: Record = {}; for (const asset of assets) { if (asset.grouping.length === 0) { continue; } let grouping = asset.grouping[0]; let collection = grouping.group_value; groupings[collection] = (groupings[collection] || 0) + 1; } let assetIds = assets.map((asset: any) => asset.id); let nftPricePromises = assetIds.map((id: string) => { return HELLOMOON_CLIENT.send( new NftMintPriceByCreatorAvgRequest({ nftMint: id, }), ).then(price => price.data[0]?.avg_usd_price ?? 0); }); let prices = await Promise.all(nftPricePromises); let nftTotal: number = prices.reduce((a, b) => a + b, 0); return (Number(nftTotal * 100).toFixed(1) as any as number) / 100; } async function handler(req: NextApiRequest, res: NextApiResponse) { const address = req.body["address"]; let totals = await Promise.all([ getTokenTotal(address.toString()), getNFTTotal(address.toString()), ]); let [tokenTotal, nftTotal] = totals; res.status(200).json({ tokenTotal, nftTotal, total: tokenTotal + nftTotal }); } export default makeApiPostRequest(handler, { addresses: ["address"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/getTransaction/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { CONNECTION } from "../../constants"; configConstants(); import * as anchor from "@coral-xyz/anchor"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; import { stringifyAnchorObject } from "../getAccountInfo"; import { AccountMeta, ComputeBudgetInstruction, ComputeBudgetInstructionType, ComputeBudgetProgram, LAMPORTS_PER_SOL, SystemInstruction, SystemProgram, TransactionError, TransactionInstruction, } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, decodeInstruction, TokenInstruction, } from "@solana/spl-token"; import { u8 } from "@solana/buffer-layout"; // Setup Solflare import { Client } from "@solflare-wallet/utl-sdk"; import { makeApiPostRequest } from "@/lib/middleware"; const utl = new Client(); type IxArgs = Record; type Instruction = { programId: string; keys: string[]; ixData: string | IxArgs; depth: number; }; let PROGRAM_CACHE = new Map(); type ParsedTxResponse = { // TODO(ngundotra): I'm pretty sure amount needs to be a string bc u57 overflow, but this is what SPL Token uses tokenBalanceChanges: Record; solBalanceChanges: Record; instructions: Instruction[]; success: boolean; timestamp: string; feePayer: string; err?: TransactionError; // Dev Mode Only params computeUnitsConsumed?: number; txFeeInLamports?: number; txVersion?: string; slot?: number; logs?: string[]; }; async function getAnchorProgram(programId: string) { if (PROGRAM_CACHE.has(programId)) { return PROGRAM_CACHE.get(programId); } try { const program = await anchor.Program.at( programId, new anchor.AnchorProvider(CONNECTION, new NodeWallet(anchor.web3.Keypair.generate()), {}), ); PROGRAM_CACHE.set(programId, program); return program; } catch (err) { PROGRAM_CACHE.set(programId, null); return null; } } async function parseAnchorIxData(programId: anchor.web3.PublicKey, ixData: string) { const program = await getAnchorProgram(programId.toString()); if (!program) { return null; } let ixCoder = new anchor.BorshInstructionCoder(program.idl); return ixCoder.decode(Buffer.from(ixData, "base64")); } async function parseAnchorIxAccounts( programAddress: string, accounts: string[], ix: anchor.Instruction, ) { const program = await getAnchorProgram(programAddress); if (!program) { return null; } const ixDef = program?.idl.instructions.find((ixDef: any) => ixDef.name === ix.name); if (ixDef) { let parsedAccounts = ixDef.accounts as { // type coercing since anchor doesn't export the underlying type name: string; isMut: boolean; isSigner: boolean; pda?: object; }[]; let allAccounts = parsedAccounts.map((acct, idx) => { return JSON.stringify({ name: acct.name, isMut: acct.isMut, isSigner: acct.isSigner, address: accounts[idx], }); }); if (parsedAccounts && parsedAccounts.length < accounts.length) { allAccounts = allAccounts.concat(accounts.slice(parsedAccounts.length)); } return allAccounts; } return null; } export function snakeToTitleCase(str: string): string { const result = str.replace(/([-_]\w)/g, g => ` ${g[1].toUpperCase()}`); return result.charAt(0).toUpperCase() + result.slice(1); } async function parseAnchorIx(ix: Instruction) { let parsedIx = await parseAnchorIxData( new anchor.web3.PublicKey(ix.programId), ix.ixData as string, ); if (!parsedIx) { return ix; } let parsedAccounts = await parseAnchorIxAccounts(ix.programId, ix.keys, parsedIx); let ixTitle = parsedIx.name; ixTitle = ixTitle.charAt(0).toUpperCase() + ixTitle.slice(1); let program = await getAnchorProgram(ix.programId); return { programId: `${snakeToTitleCase(program!.idl.name)} (${ix.programId})`, ixData: `${ixTitle} ${JSON.stringify(stringifyAnchorObject(parsedIx.data))}`, keys: parsedAccounts ?? ix.keys, depth: ix.depth, }; } async function parseIx(ix: TransactionInstruction, depth: number): Promise { let programAddress = ix.programId.toBase58(); let parsedIx: Instruction = { programId: programAddress, keys: ix.keys.map(k => k.pubkey.toBase58()), ixData: "", depth, }; if (programAddress === SystemProgram.programId.toBase58()) { parsedIx.programId = "System Program"; let type = SystemInstruction.decodeInstructionType(ix); switch (type) { case "AdvanceNonceAccount": parsedIx.ixData = stringifyAnchorObject({ AdvanceNonceAccount: SystemInstruction.decodeNonceAdvance(ix), }); break; case "Allocate": parsedIx.ixData = stringifyAnchorObject({ Allocate: SystemInstruction.decodeAllocate(ix), }); break; case "AllocateWithSeed": parsedIx.ixData = stringifyAnchorObject({ AllocateWithSeed: SystemInstruction.decodeAllocateWithSeed(ix), }); break; case "Assign": parsedIx.ixData = stringifyAnchorObject({ Assign: SystemInstruction.decodeAssign(ix) }); break; case "AssignWithSeed": parsedIx.ixData = stringifyAnchorObject({ AssignWithSeed: SystemInstruction.decodeAssignWithSeed(ix), }); break; case "AuthorizeNonceAccount": parsedIx.ixData = stringifyAnchorObject({ AuthorizeNonceAccount: SystemInstruction.decodeNonceAuthorize(ix), }); break; case "Create": parsedIx.ixData = stringifyAnchorObject({ Create: SystemInstruction.decodeCreateAccount(ix), }); break; case "CreateWithSeed": parsedIx.ixData = stringifyAnchorObject({ CreateWithSeed: SystemInstruction.decodeCreateWithSeed(ix), }); break; case "InitializeNonceAccount": parsedIx.ixData = stringifyAnchorObject({ InitializeNonceAccount: SystemInstruction.decodeNonceInitialize(ix), }); break; case "Transfer": parsedIx.ixData = stringifyAnchorObject({ Transfer: SystemInstruction.decodeTransfer(ix) }); break; case "TransferWithSeed": parsedIx.ixData = stringifyAnchorObject({ TransferWithSeed: SystemInstruction.decodeTransferWithSeed(ix), }); break; case "WithdrawNonceAccount": parsedIx.ixData = stringifyAnchorObject({ WithdrawNonceAccount: SystemInstruction.decodeNonceWithdraw(ix), }); break; case "UpgradeNonceAccount": break; } } else if (programAddress === TOKEN_2022_PROGRAM_ID.toBase58()) { parsedIx.programId = "SPL Token Program 2022"; parsedIx.ixData = stringifyAnchorObject(await parseTokenInstruction(ix)); } else if (programAddress === TOKEN_PROGRAM_ID.toBase58()) { parsedIx.programId = "SPL Token Program"; parsedIx.ixData = stringifyAnchorObject(await parseTokenInstruction(ix)); } else if (programAddress === ComputeBudgetProgram.programId.toBase58()) { parsedIx.programId = "Compute Budget Program"; let type: ComputeBudgetInstructionType = ComputeBudgetInstruction.decodeInstructionType(ix); switch (type) { case "RequestHeapFrame": parsedIx.ixData = stringifyAnchorObject({ RequestHeapFrame: ComputeBudgetInstruction.decodeRequestHeapFrame(ix), }); break; case "RequestUnits": parsedIx.ixData = stringifyAnchorObject({ RequestUnits: ComputeBudgetInstruction.decodeRequestUnits(ix), }); break; case "SetComputeUnitLimit": parsedIx.ixData = stringifyAnchorObject({ SetComputeUnitLimit: ComputeBudgetInstruction.decodeSetComputeUnitLimit(ix), }); break; case "SetComputeUnitPrice": parsedIx.ixData = stringifyAnchorObject({ SetComputeUnit: ComputeBudgetInstruction.decodeSetComputeUnitPrice(ix), }); break; } } else { return await parseAnchorIx({ programId: programAddress, ixData: anchor.utils.bytes.base64.encode(ix.data), keys: ix.keys.map(k => k.pubkey.toBase58()), depth, }); } return parsedIx; } /** * Flattens `keys` to just be the name of the account and the address * Extends `mint` addresses with the mint name (e.g. "(USDC)") */ async function parseTokenInstruction(ix: TransactionInstruction): Promise> { let ixTypeByte = u8().decode(ix.data.slice(0, 1)); let ixName = TokenInstruction[ixTypeByte]; let decoded: Record = {}; let decodedIx = decodeInstruction(ix); let keys: Record = {}; for (const keyName of Object.keys(decodedIx.keys)) { const meta = (decodedIx.keys as Record)[keyName]; if (keyName === "multiSigners") { continue; } let address = meta.pubkey.toBase58(); if (keyName === "mint") { let mintData = await utl.fetchMint(meta.pubkey); address = `${mintData.symbol}`; } keys[keyName] = address; } // @ts-ignore decodedIx.keys = keys; if (decodedIx.data && "amount" in decodedIx.data) { let amount: anchor.BN; if (typeof decodedIx.data.amount === "bigint") { amount = new anchor.BN(decodedIx.data.amount.toString()); } else { amount = new anchor.BN(decodedIx.data.amount); } // Todo(ngundotra): add support for decimals if ("decimals" in decodedIx.data) { let decimals = decodedIx.data.decimals; amount = amount.div(new anchor.BN(10).pow(new anchor.BN(decimals))); // @ts-ignore delete decodedIx.data["decimals"]; } // @ts-ignore decodedIx.data.amount = amount; } decoded[ixName] = decodedIx; return decoded; } async function parseTokenChanges( preBalances: anchor.web3.ConfirmedTransactionMeta["preTokenBalances"], postBalances: anchor.web3.ConfirmedTransactionMeta["postTokenBalances"], ) { let mints = new Set([...preBalances!.map(b => b.mint), ...postBalances!.map(b => b.mint)]); let mintData = await utl.fetchMints( Array.from(mints.keys()).map(k => new anchor.web3.PublicKey(k)), ); let mintMap: Record = {}; mintData .filter(token => token.verified ?? true) .forEach(m => { mintMap[m.address.toString()] = m.symbol; }); if (!postBalances || !preBalances || postBalances.length != preBalances.length) { return null; } let changes: Record = {}; for (let i = 0; i < postBalances?.length; i++) { let pre = preBalances[i]; let post = postBalances[i]; let tokenChange = (post.uiTokenAmount.uiAmount ?? 0) - (pre.uiTokenAmount.uiAmount ?? 0); if (tokenChange !== 0) { changes[pre.owner!] = { mint: mintMap[pre.mint] ?? pre.mint, amount: tokenChange }; } } return changes; } function parseSolChanges( accounts: anchor.web3.PublicKey[], preBalances: number[], postBalances: number[], ) { if (preBalances.length != postBalances.length) { return null; } const changes: Record = {}; for (let i = 0; i < preBalances.length; i++) { let pre = preBalances[i]; let post = postBalances[i]; let solChange = post - pre; if (solChange > 0.0000000001) { changes[accounts[i].toString()] = solChange / LAMPORTS_PER_SOL; } } return changes; } function parseLogs(logs: string[]): { programId: string; depth: number }[] { let traces: { programId: string; depth: number }[] = []; for (const log of logs) { let match = log.match(/Program (.*) invoke \[(.*)\]/); if (match) { let program = match[1]; let depth = match[2]; traces.push({ programId: program, depth: Number.parseInt(depth) }); } } return traces; } // TODO(ngundotra): add support for System program + SPL programs async function handler(req: NextApiRequest, res: NextApiResponse) { const signature = req.body.signature; const devMode = req.body.devMode ?? false; const transaction = await CONNECTION.getTransaction(signature, { maxSupportedTransactionVersion: 2, }); if (!transaction) { res.status(404).send("Transaction not found"); } let instructions: Instruction[] = []; // Reconstruct account array let accounts = transaction?.transaction.message.staticAccountKeys ?? []; accounts = accounts.concat(transaction?.meta?.loadedAddresses?.readonly ?? []); accounts = accounts.concat(transaction?.meta?.loadedAddresses?.writable ?? []); let tokenBalanceChanges = await parseTokenChanges( transaction?.meta?.preTokenBalances, transaction?.meta?.postTokenBalances, ); let solChanges = parseSolChanges( accounts, transaction?.meta?.preBalances ?? [], transaction?.meta?.postBalances ?? [], ); // Keep track of order of instructions (may not be able get stack depth for each inner ix because of log truncation) let traceIdx = 0; let parsedTrace = parseLogs(transaction?.meta?.logMessages ?? []); // Loop through top-level instructions let outerIdx = 0; let innerIdx = 0; for (const outerIx of transaction?.transaction.message.compiledInstructions ?? []) { const programId = accounts[outerIx.programIdIndex]; instructions.push( await parseIx( { programId, data: Buffer.from(outerIx.data ?? []), keys: outerIx.accountKeyIndexes.map(idx => { return { pubkey: accounts[idx], isSigner: false, isWritable: false, }; }), }, 0, ), ); traceIdx += 1; // Loop through inner instructions let innerIxBucket = transaction?.meta?.innerInstructions?.[innerIdx]; if (innerIxBucket && innerIxBucket.index === outerIdx) { for (const innerIx of innerIxBucket.instructions) { if (parsedTrace[traceIdx].programId !== accounts[innerIx.programIdIndex].toBase58()) { // Make a note that the depth is unknown (somehow) parsedTrace[traceIdx].depth = 1; } instructions.push( await parseIx( { programId: accounts[innerIx.programIdIndex], keys: innerIx.accounts.map(key => { return { pubkey: accounts[key], isSigner: false, isWritable: false, }; }), data: anchor.utils.bytes.bs58.decode(innerIx.data), }, parsedTrace[traceIdx].depth, ), ); traceIdx += 1; } innerIdx += 1; } outerIdx += 1; } const feePayer = transaction?.transaction.message.staticAccountKeys[0]!.toBase58(); let timestamp: Date | undefined; if (transaction?.blockTime) { timestamp = new Date(); timestamp.setTime(transaction?.blockTime! * 1000); } let response: ParsedTxResponse = { err: transaction?.meta?.err ?? undefined, feePayer: feePayer!, instructions, tokenBalanceChanges: tokenBalanceChanges!, solBalanceChanges: solChanges!, success: transaction?.meta?.err === null, timestamp: timestamp?.toISOString() ?? "unknown", }; if (devMode) { response = { ...response, computeUnitsConsumed: transaction?.meta?.computeUnitsConsumed!, txFeeInLamports: transaction?.meta?.fee!, txVersion: transaction?.version?.toString() ?? "legacy", logs: transaction?.meta?.logMessages ?? [], slot: transaction?.slot!, }; } res.status(200).send(JSON.stringify(response)); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/page/[methodName].ts ================================================ /// DEPRECATED - NO LONGER IN USE import { NextApiRequest, NextApiResponse } from "next"; import { encode } from "querystring"; import configConstants, { SELF_URL, TX_DESCRIPTIONS } from "../../../constants"; configConstants(); function createOpenGraphMetaPage( methodName: string, encoded: string, description: string ): string { let qrCodeUri = new URL( `${SELF_URL}/api/handlers/solana-pay/qr/${methodName}?${encoded}` ); return ` `; } export default async function handler( req: NextApiRequest, res: NextApiResponse ) { const { query: { methodName }, } = req; console.log("OpenGraph metapage requested:", methodName, req.query); let description = TX_DESCRIPTIONS[methodName as string]; res .status(200) .send( createOpenGraphMetaPage( methodName as string, encode(Object(req.query)), description ) ); } ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/qr/[methodName].ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { encode } from "querystring"; import { encodeURL } from "@solana/pay"; import * as qrcode from "qrcode"; import sharp from "sharp"; import configConstants, { SELF_URL, SOLANA_PAY_LABEL, } from "../../../constants"; configConstants(); async function createQRCodePng( methodName: string, encoded: string ): Promise { let uri = new URL( `${SELF_URL}/api/handlers/solana-pay/sign/${methodName}?${encoded}` ); let solanaPayUrl = encodeURL({ link: uri, label: SOLANA_PAY_LABEL, }); let dataUrl = await qrcode.toDataURL(solanaPayUrl.toString(), { errorCorrectionLevel: "H", }); const base64Data = dataUrl.replace(/^data:image\/png;base64,/, ""); const imageBuffer = Buffer.from(base64Data, "base64"); return await sharp(imageBuffer) .extend({ extendWith: "background", background: "#ffffff", left: 10, right: 10, }) .toBuffer(); } export default async function handler( req: NextApiRequest, res: NextApiResponse ) { const { query: { methodName }, } = req; console.log("QR code requested:", methodName, req.query); let buffer = await createQRCodePng( methodName as string, encode(Object(req.query)) ); res.setHeader("Content-Type", "image/png"); res.setHeader("Content-Disposition", "inline"); res.status(200).send(buffer); } ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createBurnAsset.ts ================================================ // DEPRECATED - not in use import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; import { createBurnInstruction } from "@metaplex-foundation/mpl-bubblegum"; import { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, } from "@solana/spl-account-compression"; import { PublicKey, Transaction } from "@solana/web3.js"; import { NextApiRequest } from "next"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; import { bufferToArray, getAsset, getAssetProof, getBubblegumAuthorityPDA } from "../utils/helpers"; configConstants(); export async function createBurnAsset(req: NextApiRequest) { const { assetId } = req.query; const { account: owner } = req.body; if (!owner || !assetId) { throw new Error("Missing required parameters"); } let assetProof = await getAssetProof(assetId, CONNECTION.rpcEndpoint); const rpcAsset = await getAsset(assetId, CONNECTION.rpcEndpoint); const leafNonce = rpcAsset.compression.leaf_id; let proofPath = assetProof.proof.map((node: string) => ({ pubkey: new PublicKey(node), isSigner: false, isWritable: false, })); const treeAuthority = await getBubblegumAuthorityPDA(new PublicKey(assetProof.tree_id)); const leafDelegate = rpcAsset.ownership.delegate ? new PublicKey(rpcAsset.ownership.delegate) : new PublicKey(rpcAsset.ownership.owner); if (rpcAsset.ownership.owner !== owner) { throw new Error( `NFT is not owned by the expected owner. Expected ${new PublicKey(owner)} but got ${ rpcAsset.ownership.owner }.`, ); } const burnIx = createBurnInstruction( { treeAuthority, leafOwner: new PublicKey(rpcAsset.ownership.owner), leafDelegate, merkleTree: new PublicKey(assetProof.tree_id), logWrapper: SPL_NOOP_PROGRAM_ID, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, anchorRemainingAccounts: proofPath, }, { root: bufferToArray(bs58.decode(assetProof.root)), dataHash: bufferToArray(bs58.decode(rpcAsset.compression.data_hash.trim())), creatorHash: bufferToArray(bs58.decode(rpcAsset.compression.creator_hash.trim())), nonce: leafNonce, index: leafNonce, }, ); const tx = new Transaction().add(burnIx); tx.feePayer = new PublicKey(owner); tx.recentBlockhash = (await CONNECTION.getLatestBlockhash()).blockhash; return { transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"), }; } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createBurnAsset)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createBuyNFT.ts ================================================ // DEPRECATED - not in use import { NextApiRequest } from "next"; import { base64 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; import configConstants, { HYPERSPACE_CLIENT } from "../../../constants"; configConstants(); import { makeRespondToSolanaPayPost, makeRespondToSolanaPayGet } from "."; async function hyperspaceCreateBuyTx(buyer: string, token: string, price: number) { let transactionData = await HYPERSPACE_CLIENT.createBuyTx({ buyerAddress: buyer, tokenAddress: token, price: price, // Take no fee on making tx for ChatGPT users buyerBroker: "", buyerBrokerBasisPoints: 0, }); const txBytes = base64.encode(Buffer.from(transactionData.createBuyTx.stdBuffer!)); return { transaction: txBytes, }; } export async function createBuyNFT(req: NextApiRequest) { const { token, price } = req.query; const { account: buyer } = req.body; return await hyperspaceCreateBuyTx( buyer as string, token as string, Number.parseFloat(price as string), ); } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createBuyNFT)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createCloseNFTMetadata.ts ================================================ // DEPRECATED - not in use import { NextApiRequest } from "next"; import { createCloseNFTMetadataTx } from "@/lib/on-chain-metadata"; import { CONNECTION } from "../../../constants"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; async function createCloseNFTMetadata(req: NextApiRequest) { const { account } = req.query; const { account: owner } = req.body; return await createCloseNFTMetadataTx(CONNECTION, owner as string, account as string); } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createCloseNFTMetadata)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createListNFT.ts ================================================ // DEPRECATED - not in use /** * Deprecated because Solana Pay compatible wallets block NFT/token delegation to prevent scams * This means we can't list via Solana Pay, but maybe we can get around this by using NFT AMMs * to sell at the floor. */ import { NextApiRequest } from "next"; import configConstants, { CONNECTION } from "../../../constants"; configConstants(); import { makeRespondToSolanaPayPost, makeRespondToSolanaPayGet } from "."; import { AnchorProvider, BN, Program } from "@coral-xyz/anchor"; import { Hyperspace, hyperspaceIdl } from "@/lib/hyperspace/idl/hyperspace"; import { Keypair, LAMPORTS_PER_SOL, PublicKey, SYSVAR_CLOCK_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, SystemProgram, Transaction, TransactionInstruction, } from "@solana/web3.js"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; import { findTokenRecordPda, getAtaForMint, getEditionDataAccount, getHyperspaceProgramAsSigner, getHyperspaceTradeState, getMetadata, } from "@/lib/hyperspace/account"; import { TOKEN_PROGRAM_ID, getAccount } from "@solana/spl-token"; import { Metadata, PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID, } from "@metaplex-foundation/mpl-token-metadata"; import { HYPERSPACE_ID, HYPERSPACE_MARKETPLACE_INSTANCE } from "@/lib/hyperspace/constants"; const AUTH_PROGRAM_ID = new PublicKey("auth9SigNpDKz4sJJ1DfCTuZrZNSAgh9sFD3rboVmgg"); // async function hyperspaceCreateListTx( // seller: string, // token: string, // price: number // ) { // console.log(seller, token, price); // let transactionData = await HYPERSPACE_CLIENT.createListTx({ // sellerAddress: seller, // tokenAddress: token, // price: price, // // Take no fee on making tx for ChatGPT users // sellerBroker: "", // sellerBrokerBasisPoints: 0, // }); // console.log(transactionData); // const txBytes = base64.encode( // Buffer.from(transactionData.createListTx.stdBuffer!) // ); // console.log(txBytes); // return { // transaction: txBytes, // }; // } export const getPriceWithMantissa = async ( price: number, mint: PublicKey, walletKeyPair: any, anchorProgram: Program, ): Promise => { // // const token = new Token( // // anchorProgram.provider.connection, // // new PublicKey(mint), // // TOKEN_PROGRAM_ID, // // walletKeyPair // // ); // let ata = getAssociatedTokenAddressSync(mint, walletKeypair); // const mintInfo = await token.getMintInfo(); // const mantissa = 10 ** mintInfo.decimals; // return Math.ceil(price * mantissa); return 1; }; async function createTradeStateInstruction( anchorProgram: Program, isBuy: boolean, tradeBump: number, buyPrice: BN, brokerBasisPoints: number, royalty_basis_points = 0, tokenSize: BN, user: PublicKey, broker: PublicKey, tokenAccount: PublicKey, tokenMint: PublicKey, tradeStateAddress: PublicKey, ): Promise { console.log(`Create instruction for creating trade state with address ${tradeStateAddress} for ${user.toBase58()} (broker: ${broker.toBase58()}, basis points: ${brokerBasisPoints}) for token ${tokenMint.toBase58()} at price ${buyPrice}`); let instruction = anchorProgram.instruction.createTradeState( isBuy ? 1 : 0, tradeBump, buyPrice, brokerBasisPoints, tokenSize, royalty_basis_points, { accounts: { wallet: user, collection: HYPERSPACE_ID, brokerWallet: broker, tokenAccount: tokenAccount, tokenMint: tokenMint, tradeState: tradeStateAddress, systemProgram: SystemProgram.programId, clock: SYSVAR_CLOCK_PUBKEY, }, }, ); instruction.keys.filter(k => k.pubkey.equals(user)).map(k => (k.isSigner = true)); return instruction; } async function helper( anchorProgram: Program, seller: PublicKey, mintPublicKey: PublicKey, sellerBrokerKey: PublicKey, minAmountToReceive: BN, // We don't charge for creating this brokerBasisPoints: number = 0, // This only matters when you cross royaltyBasisPoints: number = 0, ) { const tokenSizeAdjusted = new BN( await getPriceWithMantissa(1, mintPublicKey, seller, anchorProgram), ); const tokenAccountKey = (await getAtaForMint(mintPublicKey, seller))[0]; // You should check that ATA exists let instructions: TransactionInstruction[] = []; const [programAsSigner, programAsSignerBump] = await getHyperspaceProgramAsSigner(); const [tradeState, tradeBump] = await getHyperspaceTradeState( false, seller, tokenAccountKey, mintPublicKey, tokenSizeAdjusted, ); const tradeStateAccount = await anchorProgram.provider.connection.getAccountInfo( tradeState, "confirmed", ); if (!tradeStateAccount) { const initTradeStateInstruction = await createTradeStateInstruction( anchorProgram, false, tradeBump, minAmountToReceive, brokerBasisPoints, royaltyBasisPoints, tokenSizeAdjusted, seller, sellerBrokerKey, tokenAccountKey, mintPublicKey, tradeState, ); instructions.push(initTradeStateInstruction); } const tokenRecord = findTokenRecordPda(mintPublicKey, tokenAccountKey); const editionAccount = (await getEditionDataAccount(mintPublicKey))[0]; const metadataAccount = await getMetadata(mintPublicKey); const metadataObj = await anchorProgram.provider.connection.getAccountInfo( metadataAccount, "confirmed", ); if (!metadataObj) { throw Error("NFT does not have a metadata account, it may have been burnt."); } const metadataParsed = Metadata.deserialize(metadataObj.data)[0]; const signers: Keypair[] = []; let marketplaceObj = await anchorProgram.account.hyperspace.fetch( HYPERSPACE_MARKETPLACE_INSTANCE, ); const sellInstruction = await anchorProgram.instruction.sell( tradeBump, programAsSignerBump, minAmountToReceive, brokerBasisPoints, tokenSizeAdjusted, royaltyBasisPoints, { accounts: { wallet: seller, sellerBrokerWallet: sellerBrokerKey, tokenMint: mintPublicKey, tokenAccount: tokenAccountKey, metadata: metadataAccount, authority: marketplaceObj.authority, hyperspace: HYPERSPACE_MARKETPLACE_INSTANCE, hyperspaceFeeAccount: marketplaceObj.hyperspaceFeeAccount, sellerTradeState: tradeState, tokenProgram: TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, metadataProgram: TOKEN_METADATA_PROGRAM_ID, programAsSigner: programAsSigner, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, tokenRecord: tokenRecord, editionAccount: editionAccount, authorizationRules: metadataParsed.programmableConfig?.ruleSet ? metadataParsed.programmableConfig.ruleSet : TOKEN_METADATA_PROGRAM_ID, mplTokenAuthRulesProgram: AUTH_PROGRAM_ID, clock: SYSVAR_CLOCK_PUBKEY, }, signers, }, ); instructions.push(sellInstruction); return instructions; } async function hyperspaceCreateListTx(seller: string, token: string, price: number) { let provider = new AnchorProvider(CONNECTION, new NodeWallet(Keypair.generate()), {}); let program = new Program(hyperspaceIdl, HYPERSPACE_ID, provider); const sellerKey = new PublicKey(seller); const tokenKey = new PublicKey(token); let mint: PublicKey; try { const tokenAI = await getAccount(CONNECTION, tokenKey); console.log("found tokenAI:", tokenAI); mint = tokenAI.mint; } catch (e) { mint = tokenKey; } let instructions = await helper( program, sellerKey, mint, SystemProgram.programId, new BN(price * LAMPORTS_PER_SOL), ); let tx = new Transaction(); for (const ix of instructions) { tx = tx.add(ix); } tx.recentBlockhash = (await CONNECTION.getLatestBlockhash()).blockhash; tx.feePayer = sellerKey; const txBytes = tx.serialize({ requireAllSignatures: false }).toString("base64"); return { transaction: txBytes, }; } export async function createListNFT(req: NextApiRequest) { const { token, price } = req.query; const { account: seller } = req.body; console.log(seller, token, price); return await hyperspaceCreateListTx( seller as string, token as string, Number.parseFloat(price as string), ); } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createListNFT)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createMerkleTree.ts ================================================ // DEPRECATED - not in use import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, createCreateTreeInstruction, } from "@metaplex-foundation/mpl-bubblegum"; import { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, createAllocTreeIx, } from "@solana/spl-account-compression"; import { Keypair, PublicKey, Transaction } from "@solana/web3.js"; import dotenv from "dotenv"; import { NextApiRequest } from "next"; import { CONNECTION } from "../../../constants"; dotenv.config(); //TODO(@ngundotra): have to add closeTree instruction before exposing this endpoint export interface MerkleTreeArgs { maxDepth: number; maxBufferSize: number; canopyHeight: number; } export async function createTree(req: NextApiRequest) { const { account: payerPublicKey, canopyDepth, maxDepthSizePair } = req.body; const treeKeypair = Keypair.generate(); const [treeAuthority, _bump] = PublicKey.findProgramAddressSync( [treeKeypair.publicKey.toBuffer()], BUBBLEGUM_PROGRAM_ID, ); // instruction for space allocation for tree account const allocTreeIx = await createAllocTreeIx( CONNECTION, treeKeypair.publicKey, payerPublicKey, maxDepthSizePair, canopyDepth, ); // instruction for tree creation const createTreeIx = createCreateTreeInstruction( { payer: payerPublicKey, treeCreator: payerPublicKey, treeAuthority: treeAuthority, merkleTree: treeKeypair.publicKey, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, logWrapper: SPL_NOOP_PROGRAM_ID, }, { maxBufferSize: maxDepthSizePair.maxBufferSize, maxDepth: maxDepthSizePair.maxDepth, public: false, }, BUBBLEGUM_PROGRAM_ID, ); try { const tx = new Transaction(); tx.add(allocTreeIx); tx.add(createTreeIx); tx.feePayer = payerPublicKey; return tx; } catch (e) { console.error(e); throw e; } } ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createMintCNFT.ts ================================================ // DEPRECATED - not in use import { JsonMetadata, Metaplex } from "@metaplex-foundation/js"; import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, MetadataArgs, TokenProgramVersion, TokenStandard, createMintToCollectionV1Instruction, } from "@metaplex-foundation/mpl-bubblegum"; import { PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID } from "@metaplex-foundation/mpl-token-metadata"; import { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, } from "@solana/spl-account-compression"; import { Keypair, PublicKey, Transaction } from "@solana/web3.js"; import dotenv from "dotenv"; import { NextApiRequest } from "next"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; configConstants(); dotenv.config(); async function createMintCNFT(req: NextApiRequest) { const { metadataUri } = req.query; const { account: payer } = req.body; const metaplex = Metaplex.make(CONNECTION); // create compressed nft const nftMetadata = await metaplex.storage().downloadJson(metadataUri as string); const compressedNftMetadata: MetadataArgs = { name: nftMetadata.name ?? "", symbol: nftMetadata.symbol ?? "", uri: metadataUri as string, sellerFeeBasisPoints: 0, creators: [ { address: new PublicKey(payer), verified: true, share: 100, }, ], editionNonce: 0, uses: null, collection: null, isMutable: false, primarySaleHappened: false, tokenProgramVersion: TokenProgramVersion.Original, tokenStandard: TokenStandard.NonFungible, }; // derive PDA (owned bt Bubblegum) to act as the signer of the compressed minting const [bubblegumSigner, _bump] = PublicKey.findProgramAddressSync( [Buffer.from("collection_cpi", "utf8")], BUBBLEGUM_PROGRAM_ID, ); const collectionOwner = Keypair.fromSecretKey( Uint8Array.from(Buffer.from(process.env.COLLECTION_OWNER_SECRET_KEY as string, "base64")), ); const mintToCollectionIx = createMintToCollectionV1Instruction( { payer: new PublicKey(payer), merkleTree: new PublicKey(process.env.TREE_ADDRESS_1 as string), treeAuthority: new PublicKey(process.env.TREE_AUTHORITY_1 as string), treeDelegate: new PublicKey(payer), collectionMint: new PublicKey(process.env.CHATGPT_COLLECTION_MINT as string), collectionAuthority: collectionOwner.publicKey, collectionMetadata: new PublicKey(process.env.CHATGPT_COLLECTION_METADATA_ACCOUNT as string), collectionAuthorityRecordPda: BUBBLEGUM_PROGRAM_ID, editionAccount: new PublicKey( process.env.CHATGPT_COLLECTION_MASTER_EDITION_ACCOUNT as string, ), leafOwner: new PublicKey(payer), leafDelegate: new PublicKey(payer), // other accounts compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, logWrapper: SPL_NOOP_PROGRAM_ID, bubblegumSigner: bubblegumSigner, tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID, }, { metadataArgs: Object.assign(compressedNftMetadata, { collection: { key: new PublicKey(process.env.CHATGPT_COLLECTION_MINT as string), verified: false, }, }), }, ); const tx = new Transaction(); tx.add(mintToCollectionIx); tx.feePayer = new PublicKey(payer); tx.recentBlockhash = (await CONNECTION.getLatestBlockhash()).blockhash; tx.partialSign(collectionOwner); return { transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"), }; } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createMintCNFT)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createMintNFT.ts ================================================ // DEPRECATED - not in use import { Metaplex } from "@metaplex-foundation/js"; import { PublicKey } from "@solana/web3.js"; import { NextApiRequest } from "next"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; configConstants(); async function createMintNFT(req: NextApiRequest) { const { name, metadataUri, sellerFee = 0 } = req.query; const { account: sender } = req.body; const metaplex = Metaplex.make(CONNECTION); const mintTransactionBuilder = await metaplex .nfts() .builders() .create({ name: name as string, uri: metadataUri as string, sellerFeeBasisPoints: Number(sellerFee), }); const latestBlockhashWithExpiryBlockHeight = await CONNECTION.getLatestBlockhash(); const tx = mintTransactionBuilder.toTransaction(latestBlockhashWithExpiryBlockHeight); tx.feePayer = new PublicKey(sender); return { transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"), }; } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createMintNFT)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createSetProfilePic.ts ================================================ // DEPRECATED - not in use import { NextApiRequest } from "next"; import { PublicKey } from "@solana/web3.js"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; import { createSetProfilePictureTransaction } from "@solflare-wallet/pfp"; configConstants(); async function createSetProfilePic(req: NextApiRequest) { const { mintPublicKey, tokenAccountPublicKey } = req.query; const { account: ownerAccountPublicKey } = req.body; const tx = await createSetProfilePictureTransaction( new PublicKey(ownerAccountPublicKey), new PublicKey(mintPublicKey as string), new PublicKey(tokenAccountPublicKey as string), ); tx.feePayer = new PublicKey(ownerAccountPublicKey); tx.recentBlockhash = (await CONNECTION.getLatestBlockhash()).blockhash; return { transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"), }; } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createSetProfilePic)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createTransferAsset.ts ================================================ // DEPRECATED - not in use import { SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, } from "@solana/spl-account-compression"; import { PublicKey, Transaction } from "@solana/web3.js"; import { NextApiRequest } from "next"; import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; import { createTransferInstruction } from "@metaplex-foundation/mpl-bubblegum"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; import { bufferToArray, getAsset, getAssetProof, getBubblegumAuthorityPDA } from "../utils/helpers"; configConstants(); async function createTransferAsset(req: NextApiRequest) { const { destination, assetId } = req.query; const { account: sender } = req.body; if (!sender || !destination || !assetId) { throw new Error("Missing required parameters"); } const assetProof = await getAssetProof(assetId, CONNECTION.rpcEndpoint); if (!assetProof?.proof || assetProof.proof.length === 0) { throw new Error("Proof retrieved for the given assetId is empty. Please check the assetId."); } const proofPath = assetProof.proof.map((node: string) => ({ pubkey: new PublicKey(node), isSigner: false, isWritable: false, })); const rpcAsset = await getAsset(assetId, CONNECTION.rpcEndpoint); if (rpcAsset.ownership.owner !== sender) { throw new Error( `NFT is not owned by the expected owner. Expected ${new PublicKey(sender)} but got ${ rpcAsset.ownership.owner }.`, ); } const leafNonce = rpcAsset.compression.leaf_id; const treeAuthority = await getBubblegumAuthorityPDA(new PublicKey(assetProof.tree_id)); const leafDelegate = rpcAsset.ownership.delegate ? new PublicKey(rpcAsset.ownership.delegate) : new PublicKey(rpcAsset.ownership.owner); let transferIx = createTransferInstruction( { treeAuthority, leafOwner: new PublicKey(rpcAsset.ownership.owner), leafDelegate: leafDelegate, newLeafOwner: new PublicKey(destination as string), merkleTree: new PublicKey(assetProof.tree_id), logWrapper: SPL_NOOP_PROGRAM_ID, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, anchorRemainingAccounts: proofPath, }, { root: bufferToArray(bs58.decode(assetProof.root)), dataHash: bufferToArray(bs58.decode(rpcAsset.compression.data_hash.trim())), creatorHash: bufferToArray(bs58.decode(rpcAsset.compression.creator_hash.trim())), nonce: leafNonce, index: leafNonce, }, ); const tx = new Transaction().add(transferIx); tx.feePayer = new PublicKey(sender); tx.recentBlockhash = (await CONNECTION.getLatestBlockhash()).blockhash; return { transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"), }; } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createTransferAsset)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createTransferSol.ts ================================================ import { NextApiRequest } from "next"; import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL } from "@solana/web3.js"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; configConstants(); async function createTransferSol(req: NextApiRequest) { const { amount } = req.query; const { account } = req.body; const sender = new PublicKey(account); const destination = req.query["destination"] as any as PublicKey; const tx = new Transaction(); tx.add( SystemProgram.transfer({ fromPubkey: sender, toPubkey: destination, lamports: Math.floor(parseFloat(amount as string) * LAMPORTS_PER_SOL), }), ); tx.feePayer = sender; tx.recentBlockhash = (await CONNECTION.getLatestBlockhash()).blockhash; return { transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"), }; } export default makeRespondToSolanaPayGet( makeRespondToSolanaPayPost(createTransferSol, { addresses: ["destination"] }), ); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createTransferToken.ts ================================================ import { NextApiRequest } from "next"; import { PublicKey, Transaction } from "@solana/web3.js"; import { getAssociatedTokenAddressSync, createTransferCheckedInstruction, getMint, Mint, } from "@solana/spl-token"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; configConstants(); async function createTransferToken(req: NextApiRequest) { const { amount } = req.query; const { account } = req.body; const sender = new PublicKey(account); const destination = req.query["destination"] as any as PublicKey; const mint = req.query["mint"] as any as PublicKey; let mintAccount: Mint; try { mintAccount = await getMint(CONNECTION, mint, "confirmed"); } catch (error) { throw new Error(`Mint ${mint.toString()} not found`); } const sourceToken = getAssociatedTokenAddressSync(mint, sender); const destinationToken = getAssociatedTokenAddressSync(mint, destination); const tx = new Transaction(); const tokens = Math.round(Number(amount as string) * Math.pow(10, mintAccount.decimals)); tx.add( createTransferCheckedInstruction( sourceToken, mint, destinationToken, sender, tokens, mintAccount.decimals, ), ); tx.feePayer = sender; tx.recentBlockhash = (await CONNECTION.getLatestBlockhash()).blockhash; return { transaction: tx.serialize({ requireAllSignatures: false }).toString("base64"), }; } export default makeRespondToSolanaPayGet( makeRespondToSolanaPayPost(createTransferToken, { addresses: ["destination"], tokens: ["mint"] }), ); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/createWriteNFTMetadata.ts ================================================ // DEPRECATED - not in use import { createWriteNFTMetadataTx } from "@/lib/on-chain-metadata"; import { NextApiRequest } from "next"; import { makeRespondToSolanaPayGet, makeRespondToSolanaPayPost } from "."; import configConstants, { CONNECTION } from "../../../constants"; configConstants(); async function createWriteNFTMetadata(req: NextApiRequest) { const { image } = req.query; const { account: owner } = req.body; return await createWriteNFTMetadataTx(CONNECTION, owner as string, { image, }); } export default makeRespondToSolanaPayGet(makeRespondToSolanaPayPost(createWriteNFTMetadata)); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/sign/index.ts ================================================ import { NextApiHandler, NextApiRequest, NextApiResponse } from "next"; import { SOLANA_PAY_LABEL } from "../../../constants"; import { Options, makeApiPostRequest } from "@/lib/middleware"; export type TransactionHandler = (req: NextApiRequest) => Promise<{ transaction: string }>; export function makeRespondToSolanaPayGet(apiHandler: NextApiHandler) { return async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === "GET") { res.status(200).json({ label: SOLANA_PAY_LABEL, icon: "https://solanapay.com/src/img/branding/Solanapay.com/downloads/gradient.svg", }); } else { await apiHandler(req, res); } }; } export function makeRespondToSolanaPayPost(handler: TransactionHandler, options?: Options) { return makeApiPostRequest( async (req: NextApiRequest, res: NextApiResponse) => { let result = await handler(req); res.status(200).json(result); }, { ...options, rewriteQuery: true }, ); } ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/solana-pay/utils/helpers.ts ================================================ import { BN } from "@coral-xyz/anchor"; import { PROGRAM_ID, TreeConfig } from "@metaplex-foundation/mpl-bubblegum"; import { PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID } from "@metaplex-foundation/mpl-token-metadata"; import { Connection, PublicKey } from "@solana/web3.js"; import axios from "axios"; export async function getBubblegumAuthorityPDA(merkleRollPubKey: PublicKey) { const [bubblegumAuthorityPDAKey] = await PublicKey.findProgramAddress( [merkleRollPubKey.toBuffer()], PROGRAM_ID, ); return bubblegumAuthorityPDAKey; } export async function getNonceCount(connection: Connection, tree: PublicKey): Promise { const treeAuthority = await getBubblegumAuthorityPDA(tree); return new BN((await TreeConfig.fromAccountAddress(connection, treeAuthority)).numMinted); } export function bufferToArray(buffer: Buffer): number[] { const nums = []; for (let i = 0; i < buffer.length; i++) { nums.push(buffer[i]); } return nums; } export async function getVoucherPDA(tree: PublicKey, leafIndex: number): Promise { const [voucher] = await PublicKey.findProgramAddress( [ Buffer.from("voucher", "utf8"), tree.toBuffer(), Uint8Array.from(new BN(leafIndex).toArray("le", 8)), ], PROGRAM_ID, ); return voucher; } export async function getMetadata(mint: PublicKey) { return ( await PublicKey.findProgramAddress( [Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()], TOKEN_METADATA_PROGRAM_ID, ) )[0]; } export async function getMasterEdition(mint: PublicKey) { return ( await PublicKey.findProgramAddress( [ Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), Buffer.from("edition"), ], TOKEN_METADATA_PROGRAM_ID, ) )[0]; } export async function getAsset(assetId: any, rpcUrl: any): Promise { try { const response = await axios.post(rpcUrl, { jsonrpc: "2.0", method: "get_asset", id: "compression-example", params: [assetId], }); return response.data.result; } catch (error) { console.error(error); } } export async function getAssetProof(assetId: any, rpcUrl: any): Promise { try { const response = await axios.post(rpcUrl, { jsonrpc: "2.0", method: "get_asset_proof", id: "compression-example", params: [assetId], }); return response.data.result; } catch (error) { console.error(error); } } ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/tokenName/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { Client } from "@solflare-wallet/utl-sdk"; import { makeApiPostRequest } from "@/lib/middleware"; const utl = new Client(); async function handler(req: NextApiRequest, res: NextApiResponse) { const { tokenName } = req.body; let results = await ( await utl.searchMints(tokenName) ) .filter((res: any) => { return res["verified"] && res["holders"] > 0 && res["chainId"] === 101; }) .map((res: any) => { return { mintAddress: res["address"], tokenName: res["name"], tokenSymbol: res["symbol"], holders: res["holders"], verified: res["verified"], chainId: res["chainId"], logoURI: res["logoURI"], }; }); res.status(200).json(results.slice(0, 10)); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/tx-link/[txSlug].ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { encode } from "querystring"; import configConstants, { SELF_URL } from "../../constants"; configConstants(); import { makeApiPostRequest } from "@/lib/middleware"; async function handler(req: NextApiRequest, res: NextApiResponse) { const { query: { txSlug }, } = req; let encoded = encode(Object(req.body)); res.status(200).send({ qrCode: `${SELF_URL}/api/handlers/solana-pay/qr/${txSlug}?${encoded}`, disclaimer: `This product uses artificial intelligence ("AI"), which may produce inaccurate information. You are responsible for transactions you authorize, so please confirm accuracy of instructions prior to authorizing any transaction.`, }); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/handlers/walletName/index.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { PublicKey } from "@solana/web3.js"; import { walletNameToAddressAndProfilePicture } from "@portal-payments/solana-wallet-names"; import { walletAddressToNameAndProfilePictureCustom } from "@/lib/address"; import configConstants, { CONNECTION } from "../../constants"; configConstants(); import { makeApiPostRequest } from "@/lib/middleware"; async function handler(req: NextApiRequest, res: NextApiResponse) { const { walletName } = req.body; try { const dotAnything = await walletAddressToNameAndProfilePictureCustom( CONNECTION, new PublicKey(walletName), ); res.status(200).send(dotAnything); } catch (error) { const walletInfo = await walletNameToAddressAndProfilePicture(CONNECTION, walletName); res.status(200).send(walletInfo); } } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/defi/defi.yaml ================================================ openapi: 3.1.0 info: title: HelloMoon Defi Summary API description: An API for querying information about defi protocols on Solana version: 1.0.0 servers: - url: https://chatgpt.solanalabs.com/api/helloMoon paths: /defi/programNewUsers: post: summary: Shows new users of a program over time. description: Shows new users of a program over time. A new user is defined as the first time a publicKey has been a fee payer for this program address. operationId: query_program_new_users requestBody: content: application/json: schema: $ref: "#/components/schemas/programNewUsersRequest" responses: "200": description: Successful Response /defi/programOverlap: post: summary: Shows overlap between users of two different programs description: Program overlap shows the overlap between the users of two protocols. The data will show what percentage of 'aProgramIds' users also use 'bProgramId' operationId: query_program_user_overlap requestBody: content: application/json: schema: $ref: "#/components/schemas/programOverlapRequest" responses: "200": description: Successful Response /defi/tokenUsers: post: summary: description: operationId: query_token_users requestBody: content: application/json: schema: $ref: "#/components/schemas/tokenUsersRequest" responses: "200": description: Successful Response /defi/tokenStats: post: summary: description: operationId: query_token_stats requestBody: content: application/json: schema: $ref: "#/components/schemas/tokenStatsRequest" responses: "200": description: Successful Response components: schemas: stringComparisonOperator: type: string enum: - "=" - "!=" - ">" - "<" - ">=" nullable: true nullableNumber: type: number nullable: true programNewUsersRequest: type: object required: - programId properties: programId: type: string day: type: string nullable: true limit: $ref: "#/components/schemas/nullableNumber" programOverlapRequest: type: object properties: aProgramId: type: string bProgramId: type: string limit: $ref: "#/components/schemas/nullableNumber" tokenUsersRequest: type: object properties: day: type: string nullable: true mint: type: string limit: $ref: "#/components/schemas/nullableNumber" tokenStatsRequest: type: object properties: granularity: type: string enum: - "one_week" - "one_month" - "one_day" - "six_hour" - "one_hour" - "thirty_min" nullable: true mint: type: string nullable: true limit: $ref: "#/components/schemas/nullableNumber" ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/defi/programNewUsers.ts ================================================ // Deprecated - Not in use import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; configConstants(); import { DefiProgramNetNewUsersDailyRequest } from "@hellomoon/api"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { programId, limit, day } = req.body; let date: Date = new Date(); if (day) { try { date = new Date(day); } catch (e) { res.status(500).send("Invalid day: " + day); return; } } let args = new DefiProgramNetNewUsersDailyRequest({ programId, limit: limit ?? 10, day: date ? date.toISOString() : undefined, }); let data = await HELLOMOON_CLIENT.send(args) .then(result => result.data) .catch(console.error); res.status(200).send(data); } ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/defi/programOverlap.ts ================================================ // Deprecated - Not in use import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; import { DefiProgramOverlapRequest } from "@hellomoon/api"; configConstants(); export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { aProgramId, bProgramId, limit } = req.body; let args = new DefiProgramOverlapRequest({ aProgramId, bProgramId, limit: limit ?? 10, }); let data = await HELLOMOON_CLIENT.send(args) .then(result => result.data) .catch(console.error); res.status(200).send(data); } ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/defi/tokenStats.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; configConstants(); import { DefiTokenLeaderboardV3Request } from "@hellomoon/api"; import { makeApiPostRequest } from "@/lib/middleware"; const GRANULARITY = ["one_day", "one_week", "one_month", "thirty_min", "one_hour", "six_hour"]; async function handler(req: NextApiRequest, res: NextApiResponse) { const { granularity, limit } = req.body; if (granularity && !GRANULARITY.includes(granularity.toLowerCase())) { res.status(500).send("Invalid granularity: " + granularity); return; } let args = new DefiTokenLeaderboardV3Request({ granularity: granularity ? ((granularity as string).toLocaleUpperCase() as any) : "ONE_DAY", mint: req.body["mint"].toString(), limit: limit ?? 10, }); let data = await HELLOMOON_CLIENT.send(args) .then(result => result.data) .catch(console.error); res.status(200).send(data); } export default makeApiPostRequest(handler, { tokens: ["mint"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/defi/tokenUsers.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; configConstants(); import { DefiTokenUsersDailyRequest } from "@hellomoon/api"; import { makeApiPostRequest } from "@/lib/middleware"; async function handler(req: NextApiRequest, res: NextApiResponse) { const { day, limit } = req.body; let args = new DefiTokenUsersDailyRequest({ mint: req.body["mint"].toString(), day: day ? new Date(day).toISOString() : undefined, limit: limit ?? 10, }); let data = await HELLOMOON_CLIENT.send(args) .then(result => result.data) .catch(console.error); res.status(200).send(data); } export default makeApiPostRequest(handler, { tokens: ["mint"] }); ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/jupiter/README.md ================================================ # Test Queries #### Summary Can you summarize Jupiter activity this past week on Solana? ``` In the past week, Jupiter on Solana has seen a total trading volume of approximately $11,179,154.79 USD. There have been 29,968 unique users and 686,025 transactions during this period. Overall, Jupiter has been quite active on Solana. Is there anything else you would like to know? ``` #### Historical Can you just print out the last 7 days of jupiter volume in USD? ``` Here are the last 7 days of Jupiter volume in USD: July 20, 2023: $7,557,612.35 USD July 19, 2023: $10,989,070.90 USD July 18, 2023: $12,249,619.75 USD July 17, 2023: $16,730,144.59 USD July 16, 2023: $10,583,573.39 USD July 15, 2023: $17,052,417.78 USD July 14, 2023: $36,812,293.04 USD Please note that these values represent the daily trading volume on each respective day. Is there anything else you would like to know? ``` #### Pairs What was mSOL - wSOL pair volume this week on Jupiter? ``` The mSOL - wSOL pair volume this week on Jupiter was approximately $229,340.42. ``` #### Swaps What was the whole market volume of wSOL to mSOL swaps done on Solana this week? ``` The total market volume of wSOL to mSOL swaps done on Solana this week was $16,499,552.12. ``` What about the volume of mSOL - wSOL swaps just on Jupiter this week? ``` The volume of mSOL - wSOL swaps on Jupiter this week was $2,801,324.60. ``` ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/jupiter/historical.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; configConstants(); import { Comparison, buildComparison } from "@/lib/helloMoon"; import { JupiterHistoricalTradingStatsRequest } from "@hellomoon/api"; import { makeApiPostRequest } from "@/lib/middleware"; const VALID_GRANULARITY = ["DAILY", "WEEKLY", "MONTHLY"]; async function handler(req: NextApiRequest, res: NextApiResponse) { const { granularity, limit } = req.body; if (granularity && !VALID_GRANULARITY.includes((granularity as string).toLocaleUpperCase())) { res .status(500) .send( "Invalid granularity: " + granularity + ". Valid granularities are: " + VALID_GRANULARITY.join(", ") + ".", ); return; } let numTxns: Comparison; let usdVolume: Comparison; let numUsers: Comparison; try { numTxns = buildComparison(req.body.numTxnsOperator, req.body.numTxnsValue); usdVolume = buildComparison(req.body.usdVolumeOperator, req.body.usdVolumeValue); numUsers = buildComparison(req.body.numUsersOperator, req.body.numUsersValue); } catch (error) { res.status(500).send((error as Error).message); return; } let args = new JupiterHistoricalTradingStatsRequest({ granularity: ((granularity as string).toLocaleUpperCase() as any) ?? "DAILY", limit: limit ?? 10, numTxns, usdVolume, numUsers, }); let data = await HELLOMOON_CLIENT.send(args) .then(result => result.data) .catch(console.error); res.status(200).send(data); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/jupiter/pairs.ts ================================================ // DEPRECATED - not in use import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; import { Comparison, buildComparison, cleanSwapPair } from "@/lib/helloMoon"; import { JupiterSwapStatsRequest } from "@hellomoon/api"; configConstants(); export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { swapPair, limit } = req.body; let dailyVolume: Comparison; let weeklyVolume: Comparison; let monthlyVolume: Comparison; try { dailyVolume = buildComparison(req.body.usdVolume24HrOperator, req.body.usdVolume24HrValue); weeklyVolume = buildComparison(req.body.usdVolume7DOperator, req.body.usdVolume7DValue); monthlyVolume = buildComparison(req.body.usdVolume30DOperator, req.body.usdVolume30DValue); } catch (error) { res.status(500).send((error as Error).message); return; } let args = new JupiterSwapStatsRequest({ swapPair: cleanSwapPair(swapPair), limit: limit ?? 10, usdVolume24Hr: dailyVolume, usdVolume7D: weeklyVolume, usdVolume30D: monthlyVolume, }); let data = await HELLOMOON_CLIENT.send(args) .then(result => result.data) .catch(console.error); res.status(200).send(data); } ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/jupiter/summary.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; import { JupiterCurrentStatsRequest } from "@hellomoon/api"; import { makeApiPostRequest } from "@/lib/middleware"; configConstants(); async function handler(req: NextApiRequest, res: NextApiResponse) { let data = await HELLOMOON_CLIENT.send(new JupiterCurrentStatsRequest({})) .then(result => result.data[0]) .catch(console.error); res.status(200).send(data); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/jupiter/swaps.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { HELLOMOON_CLIENT } from "../../constants"; configConstants(); import { JupiterPairsBrokenDownWeeklyRequest } from "@hellomoon/api"; import { cleanSwapPair } from "@/lib/helloMoon"; import { makeApiPostRequest } from "@/lib/middleware"; const VALID_CATEGORY = ["per amm", "whole market", "jupiter only"]; // Make params case-insensitive so it's easier for LLMs to get right const CATEGORY_MAP: { [key: string]: string } = { "per amm": "Per AMM", "whole market": "Whole market", "jupiter only": "Jupiter only", }; async function handler(req: NextApiRequest, res: NextApiResponse) { const { category, swapPair, limit } = req.body; if (category && !VALID_CATEGORY.includes((category as string).toLocaleLowerCase())) { res .status(500) .send( "Invalid category: " + category + ". Valid categories are: " + VALID_CATEGORY.join(", ") + ".", ); return; } let mapped = CATEGORY_MAP[category as string] as any; let args = new JupiterPairsBrokenDownWeeklyRequest({ category: mapped, swapPair: swapPair ? cleanSwapPair(swapPair) : undefined, limit, }); let data = await HELLOMOON_CLIENT.send(args) .then(result => result.data) .catch(console.error); res.status(200).send(data); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/src/pages/api/helloMoon/jupiter.yaml ================================================ openapi: 3.1.0 info: title: HelloMoon API description: An API for querying information about protocols built on Solana version: 1.0.0 servers: - url: https://chatgpt.solanalabs.com/api/helloMoon paths: /jupiter/summary: post: summary: Shows high-level information related to DeFi protocol Jupiter on Solana description: Shows the volume, number of users, and number of transactions on Jupiter over the past 24 hours, 7 days, and 30 days in USD operationId: query_jupiter_summary responses: "200": description: Successful Response /jupiter/swaps: post: summary: Shows swap data on Jupiter protocol by amm description: Breakdown of swapping pair volume weekly on and off of Jupiter weekly. Data goes back 90 days. Data can be broken down by AMM if needed. Note that swapPair is the combination of the source and destination symbol. The order will be in alphabetical order. For example, "USDC - wSOL" operationId: query_jupiter_swap_summary requestBody: content: application/json: schema: $ref: "#/components/schemas/jupiterSwapRequest" responses: "200": description: Successful Response /jupiter/historical: post: summary: Shows the volume, number of users, and number of transactions on Jupiter over time description: Shows the volume, number of users, and number of transactions on Jupiter over time operationId: query_jupiter_historical_summary requestBody: content: application/json: schema: $ref: "#/components/schemas/jupiterHistoricalRequest" responses: "200": description: Successful Response /jupiter/pairs: post: summary: Shows the volume of swaps for token pairs on Jupiter description: Shows the volume of swaps for token pairs on Jupiter. Note that swapPair is the combination of the source and destination symbol. The order will be in alphabetical order. For example, "USDC - wSOL". Note that "USD" is not a valid token, and should be converted to "USDC" when using this API. Also, 24hr is a day, 7D is a week, 30D is a month. operationId: query_jupiter_pair_summary requestBody: content: application/json: schema: $ref: "#/components/schemas/jupiterPairsRequest" responses: "200": description: Successful Response components: schemas: stringComparisonOperator: type: string enum: - "=" - "!=" - ">" - "<" - ">=" nullable: true nullableNumber: type: number nullable: true jupiterSwapRequest: type: object properties: category: type: string enum: - "whole market" - "jupiter only" - "per amm" nullable: true ammProgramId: type: string nullable: true swapPair: type: string nullable: true limit: type: number nullable: true jupiterHistoricalRequest: type: object properties: granularity: type: string enum: - "daily" - "weekly" - "monthly" nullable: true limit: $ref: "#/components/schemas/nullableNumber" numTxnsOperator: $ref: "#/components/schemas/stringComparisonOperator" numTxnsValue: $ref: "#/components/schemas/nullableNumber" usdVolumeValue: $ref: "#/components/schemas/nullableNumber" usdVolumeOperator: $ref: "#/components/schemas/stringComparisonOperator" numUsersValue: $ref: "#/components/schemas/nullableNumber" numUsersOperator: $ref: "#/components/schemas/stringComparisonOperator" jupiterPairsRequest: type: object properties: swapPair: type: string nullable: true limit: $ref: "#/components/schemas/nullableNumber" usdVolume7DOperator: $ref: "#/components/schemas/stringComparisonOperator" usdVolume30DOperator: $ref: "#/components/schemas/stringComparisonOperator" usdVolume24HrOperator: $ref: "#/components/schemas/stringComparisonOperator" usdVolume7DValue: $ref: "#/components/schemas/nullableNumber" usdVolume30DValue: $ref: "#/components/schemas/nullableNumber" usdVolume24HrValue: $ref: "#/components/schemas/nullableNumber" ================================================ FILE: chatgpt-plugin/src/pages/api/metaplex/getCNFTRent/index.ts ================================================ // Deprecated - Not in use import { ALL_DEPTH_SIZE_PAIRS, DepthSizePair, getConcurrentMerkleTreeAccountSize, } from "@solana/spl-account-compression"; import { LAMPORTS_PER_SOL } from "@solana/web3.js"; import { NextApiRequest, NextApiResponse } from "next"; import configConstants, { CONNECTION } from "../../constants"; configConstants(); function isValidDepthSizePair(maxDepth: number, maxBufferSize: number): boolean { const pair: DepthSizePair = { maxDepth, maxBufferSize, }; return ALL_DEPTH_SIZE_PAIRS.some(validPair => isEqualDepthSizePair(validPair, pair)); } function isEqualDepthSizePair(pair1: DepthSizePair, pair2: DepthSizePair): boolean { return pair1.maxDepth === pair2.maxDepth && pair1.maxBufferSize === pair2.maxBufferSize; } export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method != "POST") { res.status(405).send({ message: "Only POST requests allowed" }); return; } const { treeSize, canopySize, customMaxDepth, customMaxBufferSize, customCanopyDepth } = req.body; let maxDepth: number, maxBufferSize: number, canopyDepth: number; if (treeSize.toLowerCase() == "custom") { if (customMaxDepth == undefined || customMaxBufferSize == undefined) { res.status(400).send({ message: "Missing tree size custom parameters" }); return; } maxDepth = customMaxDepth; maxBufferSize = customMaxBufferSize; } else if (treeSize.toLowerCase() == "small") { maxDepth = 14; maxBufferSize = 64; } else if (treeSize.toLowerCase() == "medium") { maxDepth = 20; maxBufferSize = 64; } else if (treeSize.toLowerCase() == "large") { maxDepth = 30; maxBufferSize = 512; } else { res.status(400).send({ message: "Invalid tree size enum" }); return; } if (!isValidDepthSizePair(maxDepth, maxBufferSize)) { res.status(400).send({ message: "Invalid tree Depth and Buffer Size pair" }); return; } if (canopySize.toLowerCase() == "custom") { if (customCanopyDepth == undefined) { res.status(400).send({ message: "Missing custom canopy depth parameter" }); return; } canopyDepth = customCanopyDepth; } else if (canopySize.toLowerCase() == "small") { canopyDepth = 3; } else if (canopySize.toLowerCase() == "medium") { canopyDepth = 14; } else if (canopySize.toLowerCase() == "large") { canopyDepth = 17; } else if (canopySize.toLowerCase() == "none") { canopyDepth = 0; } else { res.status(400).send({ message: "Invalid canopy size enum" }); return; } const requiredSpace = getConcurrentMerkleTreeAccountSize(maxDepth, maxBufferSize, canopyDepth); const rent = await CONNECTION.getMinimumBalanceForRentExemption(requiredSpace); res.status(200).send({ rentInSol: rent / LAMPORTS_PER_SOL, maxNumberOfNFTs: 2 ** maxDepth, }); } ================================================ FILE: chatgpt-plugin/src/pages/api/metaplex/getPublicTree/index.ts ================================================ // Deprecated - Not in use import { NextApiRequest, NextApiResponse } from "next"; import configConstants from "../../constants"; configConstants(); const publicTreeAddresses: String[] = [ "3cnMCu5hezx9h6kjEYdTb7PRKch4a6KRFBoUF8SJEsT8", "GNsnin9c2nDGp78E69tGXyMScWfysnu2PuxQxXy1jh3R", ]; export default async function handler(req: NextApiRequest, res: NextApiResponse) { res.status(200).send({ publicTreeAddresses: publicTreeAddresses }); } ================================================ FILE: chatgpt-plugin/src/pages/api/metaplex/metaplex.yaml ================================================ openapi: 3.0.2 info: title: Metaplex API description: An API for interacting with the Solana NFT collections using Metaplex version: 1.0.0 servers: - url: https://chatgpt.solanalabs.com paths: /api/handlers/solana-pay/qr/createMintCNFT: post: summary: createMintCNFT description: >- Mints a compressed NFT with the metadata uri. It does not need the rent or the treeAddress. Returns a QR code that the user can scan to approve the transaction. operationId: create_mint_cnft requestBody: content: application/json: schema: $ref: "#/components/schemas/createMintCNFTRequest" responses: "200": description: Successful Response /api/metaplex/getCNFTRent: post: summary: getCNFTRent description: >- Returns the rent for creating a tree on mainnet and the maximum capacity. operationId: get_cnft_rent requestBody: content: application/json: schema: $ref: "#/components/schemas/getCNFTRentRequest" responses: "200": description: Successful Response /api/metaplex/getPublicTree: post: summary: getPublicTree description: >- Shows the public tree addresses with available space. operationId: get_public_tree requestBody: content: application/json: schema: $ref: "#/components/schemas/getPublicTreeRequest" responses: "200": description: Successful Response /api/handlers/solana-pay/qr/createMintNFT: post: summary: createMintNFT description: >- Mints a normal uncompressed NFT with the given name, metadata uri and seller fees. operationId: create_mint_nft requestBody: content: application/json: schema: $ref: "#/components/schemas/createMintNFTRequest" responses: "200": description: Successful Response /api/handlers/solana-pay/qr/createTransferAsset: post: summary: createTransferAsset description: >- Allows the user to send (transfer) compressed NFT to another account operationId: create_transfer_asset requestBody: content: application/json: schema: $ref: "#/components/schemas/createTransferAssetRequest" responses: "200": description: Successful Response /api/handlers/solana-pay/qr/createBurnAsset: post: summary: createBurnAsset description: >- Allows the user to burn a compressed NFT operationId: create_burn_asset requestBody: content: application/json: schema: $ref: "#/components/schemas/createBurnAssetRequest" responses: "200": description: Successful Response components: schemas: getCNFTRentRequest: title: GetCNFTRentRequest type: object required: - treeSize - canopySize properties: treeSize: type: string enum: - "custom" - "small" - "medium" - "large" canopySize: type: string enum: - "custom" - "small" - "medium" - "large" - "none" customMaxDepth: type: number nullable: true customMaxBufferSize: type: number nullable: true customCanopySize: type: number nullable: true createMintCNFTRequest: title: CreateMintCNFTRequest type: object required: - metadataUri properties: metadataUri: type: "string" format: "uri" maxLength: 255 createMintNFTRequest: title: CreateMintNFTRequest type: object required: - name - metadataUri properties: name: type: string metadataUri: type: string format: "uri" maxLength: 255 sellerFee: type: number createTransferAssetRequest: type: object required: - destination - assetId properties: destination: type: string assetId: type: string createBurnAssetRequest: type: object required: - assetId properties: assetId: type: string getPublicTreeRequest: type: object properties: {} ================================================ FILE: chatgpt-plugin/src/pages/api/solflare-pfp/getProfilePic.ts ================================================ // Deprecated - Not in use import { NextApiRequest, NextApiResponse } from "next"; import { getProfilePicture } from "@solflare-wallet/pfp"; import configConstants, { CONNECTION } from "../constants"; configConstants(); export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method != "POST") { res.status(405).send({ message: "Only POST requests allowed" }); return; } let publicKey = req.body.publicKey; let pfpResult = await getProfilePicture(CONNECTION, publicKey); res.status(200).send(pfpResult); } ================================================ FILE: chatgpt-plugin/src/pages/api/tiplink/makeLink.ts ================================================ import { NextApiRequest, NextApiResponse } from "next"; import { TipLink } from "@tiplink/api"; import { makeApiPostRequest } from "@/lib/middleware"; async function handler(req: NextApiRequest, res: NextApiResponse) { const tp = await TipLink.create(); res.status(200).send({ url: tp.url, tipLinkAddress: tp.keypair.publicKey.toBase58() }); } export default makeApiPostRequest(handler); ================================================ FILE: chatgpt-plugin/tailwind.config.js ================================================ /** @type {import('tailwindcss').Config} */ module.exports = { content: [ './src/pages/**/*.{js,ts,jsx,tsx,mdx}', './src/components/**/*.{js,ts,jsx,tsx,mdx}', './src/app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [], } ================================================ FILE: langchain_examples/README.md ================================================ # Langchain Usage Langchain has support for using ChatGPTs in their Agent via their `tool` interface. We have provided examples below ### Python Example To run the python example, you will have to have an OpenAI key (you do not need GPT-4 access to run this example). ```bash poetry install cd langchain_examples OPENAI_API_KEY=sk-xxxxxxx DEV=true python python/main.py ``` ================================================ FILE: langchain_examples/python/main.py ================================================ from langchain.chat_models import ChatOpenAI from langchain.agents import load_tools, initialize_agent from langchain.agents import AgentType from langchain.tools import AIPluginTool if __name__ == '__main__': # Setup environment variables import os DEV_ENV = os.environ['DEV'] == 'true' URL = "http://localhost:3333" if DEV_ENV else "https://solana-gpt-plugin.onrender.com" llm = ChatOpenAI(temperature=0) # AI Agent does best when it only has one available tool # to engage with URLs tools = load_tools(["requests_post"]) # AIPluginTool only fetches and returns the openapi.yaml linked to in /.well-known/ai-plugin.json # This may need some more work to avoid blowing up LLM context window tool = AIPluginTool.from_plugin_url(URL + "/.well-known/ai-plugin.json") tools += [tool] # Setup an agent to answer the question without further human feedback agent_chain = initialize_agent( tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True) # Ask the question, and the agent loop agent_chain.run( "How many lamports does 8fbqVvpK3Dj7fdP2c8JJhtD7Zy3n9qtwAeGfbkgPu625 have?") ================================================ FILE: pyproject.toml ================================================ [tool.poetry] name = "solana-gpt-plugin" version = "0.1.0" description = "" authors = ["ngundotra "] license = "Apache-2.0" readme = "README.md" packages = [{include = "solana_gpt_plugin"}] [tool.poetry.dependencies] python = "^3.9" langchain = "^0.0.136" requests = "^2.28.2" openai = "^0.27.4" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" ================================================ FILE: scripts/createNFTCollection.ts ================================================ import { Metaplex, toMetaplexFile, UploadMetadataInput, } from "@metaplex-foundation/js"; import { createAssociatedTokenAccount, createMint, mintTo, } from "@solana/spl-token"; import { Keypair, sendAndConfirmTransaction, Transaction, } from "@solana/web3.js"; import { savePublicKeyToFile } from "./helper"; import { PublicKey } from "@metaplex-foundation/js"; import { createCreateMasterEditionV3Instruction, createCreateMetadataAccountV3Instruction, CreateMetadataAccountArgsV3, createSetCollectionSizeInstruction, PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID, } from "@metaplex-foundation/mpl-token-metadata"; import * as fs from "fs"; import dotenv from "dotenv"; dotenv.config(); const ASSETS_DIR = "../chatgpt-plugin/public/assets/"; export async function createNFTCollection(payer: Keypair, metaplex: Metaplex) { console.log(metaplex.connection); // Create collection metadata const buffer = fs.readFileSync(`${ASSETS_DIR}chatgpt-collection-logo.png`); const file = toMetaplexFile(buffer, `chatgpt-collection-logo.png`); console.log("before Uploading file to Metaplex"); const imageUri = await metaplex.storage().upload(file); console.log("after Uploading file to Metaplex"); const data = fs.readFileSync( `${ASSETS_DIR}chatgpt-collection-metadata.json`, "utf-8" ); const collectionInfo = JSON.parse(data); const collectionMetadata: UploadMetadataInput = { name: collectionInfo.name, symbol: collectionInfo.symbol, image: imageUri, description: collectionInfo.description, }; console.log("before Uploading metadata to Metaplex"); const { uri } = await metaplex.nfts().uploadMetadata(collectionMetadata); console.log("after Uploading metadata to Metaplex"); // Create collection const collectionMetadataV3: CreateMetadataAccountArgsV3 = { data: { name: `${collectionMetadata.name ?? ""}`, symbol: `${collectionMetadata.symbol ?? ""}`, uri, sellerFeeBasisPoints: 100, creators: [ { address: payer.publicKey, verified: true, share: 100, }, ], collection: null, uses: null, }, isMutable: false, collectionDetails: null, }; console.log("before creating mint"); const collectionMint = await createMint( metaplex.connection, payer, payer.publicKey, // mintAuthority payer.publicKey, // freezeAuthority 0 // collection -> decimal == 0 ); console.log("before creating account"); // const tokenAccount = await createAccount( // metaplex.connection, // payer, // collectionMint, // payer.publicKey, // ); console.log(collectionMint.toString()); const tokenAccount = await createAssociatedTokenAccount( metaplex.connection, payer, collectionMint, payer.publicKey, { commitment: "confirmed" } ); console.log("before minting"); await mintTo( metaplex.connection, payer, collectionMint, tokenAccount, payer, 1, [], { commitment: "confirmed" } ); console.log("after minting"); const [collectionMetadataAccount, _bump] = PublicKey.findProgramAddressSync( [ Buffer.from("metadata", "utf8"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), collectionMint.toBuffer(), ], TOKEN_METADATA_PROGRAM_ID ); const createMetadataIx = createCreateMetadataAccountV3Instruction( { metadata: collectionMetadataAccount, mint: collectionMint, mintAuthority: payer.publicKey, payer: payer.publicKey, updateAuthority: payer.publicKey, }, { createMetadataAccountArgsV3: collectionMetadataV3, } ); // create account for showing supply of collection metadata, the proof of the Non-Fungible of the token const [collectionMasterEditionAccount, _bump2] = PublicKey.findProgramAddressSync( [ Buffer.from("metadata", "utf8"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), collectionMint.toBuffer(), Buffer.from("edition", "utf8"), ], TOKEN_METADATA_PROGRAM_ID ); const createMasterEditionIx = createCreateMasterEditionV3Instruction( { edition: collectionMasterEditionAccount, payer: payer.publicKey, mint: collectionMint, mintAuthority: payer.publicKey, updateAuthority: payer.publicKey, metadata: collectionMetadataAccount, }, { createMasterEditionArgs: { maxSupply: 0, }, } ); // create collection size const collectionSizeIx = createSetCollectionSizeInstruction( { collectionMetadata: collectionMetadataAccount, collectionAuthority: payer.publicKey, collectionMint: collectionMint, }, { setCollectionSizeArgs: { size: 1000000 }, } ); try { const tx = new Transaction(); tx.add(createMetadataIx); tx.add(createMasterEditionIx); tx.add(collectionSizeIx); tx.feePayer = payer.publicKey; // tx.sign(payer); ?? await sendAndConfirmTransaction(metaplex.connection, tx, [payer], { commitment: "confirmed", skipPreflight: true, //? }); } catch (e) { console.error("\nFailed to create collection:", e); throw e; } savePublicKeyToFile( "collectionMint", collectionMint, `${ASSETS_DIR}/chatgpt-collection-keys.json` ); savePublicKeyToFile( "collectionMetadataAccount", collectionMetadataAccount, `${ASSETS_DIR}chatgpt-collection-keys.json` ); savePublicKeyToFile( "collectionMasterEditionAccount", collectionMasterEditionAccount, `${ASSETS_DIR}chatgpt-collection-keys.json` ); console.log("Collection Mint Address: ", collectionMint.toBase58()); console.log( "Collection Metadata Address: ", collectionMetadataAccount.toBase58() ); console.log( "Collection Master Edition Address: ", collectionMasterEditionAccount.toBase58() ); return { collectionMint, collectionMetadataAccount, collectionMasterEditionAccount, }; } ================================================ FILE: scripts/createTree.ts ================================================ import { Keypair, Connection, PublicKey, Transaction, sendAndConfirmTransaction, } from "@solana/web3.js"; import { ValidDepthSizePair, createAllocTreeIx, SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, } from "@solana/spl-account-compression"; import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, createCreateTreeInstruction, } from "@metaplex-foundation/mpl-bubblegum"; import { savePublicKeyToFile } from "./helper"; import dotenv from "dotenv"; dotenv.config(); export interface MerkleTreeArgs { maxDepth: number; maxBufferSize: number; canopyHeight: number; } export async function createTree( payer: Keypair, connection: Connection, treeNumber: number | null, canopyDepth: number, maxDepthSizePair: ValidDepthSizePair ) { const treeKeypair = Keypair.generate(); const [treeAuthority, _bump] = PublicKey.findProgramAddressSync( [treeKeypair.publicKey.toBuffer()], BUBBLEGUM_PROGRAM_ID ); // allocate space for tree account instruction const allocTreeIx = await createAllocTreeIx( connection, treeKeypair.publicKey, payer.publicKey, maxDepthSizePair, canopyDepth ); // create tree const createTreeIx = createCreateTreeInstruction( { payer: payer.publicKey, treeCreator: payer.publicKey, treeAuthority: treeAuthority, merkleTree: treeKeypair.publicKey, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, logWrapper: SPL_NOOP_PROGRAM_ID, }, { maxBufferSize: maxDepthSizePair.maxBufferSize, maxDepth: maxDepthSizePair.maxDepth, public: true, }, BUBBLEGUM_PROGRAM_ID ); try { const tx = new Transaction(); tx.add(allocTreeIx); tx.add(createTreeIx); tx.feePayer = payer.publicKey; await sendAndConfirmTransaction(connection, tx, [payer, treeKeypair], { skipPreflight: true, commitment: "confirmed", }); } catch (e) { console.error(e); throw e; } savePublicKeyToFile(`treeAddress${treeNumber}`, treeKeypair.publicKey, "./assets/chatgpt-collection-keys.json"); savePublicKeyToFile(`treeAuthority${treeNumber}`, treeAuthority, "./assets/chatgpt-collection-keys.json"); console.log(`Tree Address${treeNumber}: `, treeKeypair.publicKey.toBase58()); return { treeAddress: treeKeypair.publicKey, treeAuthority }; } ================================================ FILE: scripts/helper.ts ================================================ import fs from "fs"; import path from "path"; import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, } from "@solana/web3.js"; // define some default locations const DEFAULT_KEY_DIR_NAME = ".local_keys"; const DEFAULT_PUBLIC_KEY_FILE = "keys.json"; const DEFAULT_NONCE_FILE = "nonce.json"; /* Load locally stored PublicKey addresses */ export function loadPublicKeysFromFile( absPath: string = `${DEFAULT_KEY_DIR_NAME}/${DEFAULT_PUBLIC_KEY_FILE}` ) { try { if (!absPath) throw Error("No path provided"); if (!fs.existsSync(absPath)) throw Error("File does not exist."); // load the public keys from the file const data = JSON.parse(fs.readFileSync(absPath, { encoding: "utf-8" })) || {}; // convert all loaded keyed values into valid public keys for (const [key, value] of Object.entries(data)) { data[key] = new PublicKey(value as string) ?? ""; } return data; } catch (err) { // console.warn("Unable to load local file"); } // always return an object return {}; } /* Locally save a PublicKey addresses to the filesystem for later retrieval */ export function savePublicKeyToFile( name: string, publicKey: PublicKey, absPath: string = `${DEFAULT_KEY_DIR_NAME}/${DEFAULT_PUBLIC_KEY_FILE}` ) { try { // if (!absPath) throw Error("No path provided"); // if (!fs.existsSync(absPath)) throw Error("File does not exist."); // fetch all the current values let data: any = loadPublicKeysFromFile(absPath); // convert all loaded keyed values from PublicKeys to strings for (const [key, value] of Object.entries(data)) { data[key as any] = (value as PublicKey).toBase58(); } data = { ...data, [name]: publicKey.toBase58() }; // actually save the data to the file fs.writeFileSync(absPath, JSON.stringify(data), { encoding: "utf-8", }); console.log("Saved to file: ", absPath); // reload the keys for sanity data = loadPublicKeysFromFile(absPath); return data; } catch (err) { console.warn("Unable to save to file"); } // always return an object return {}; } /* Load locally stored nonce of nft counr */ export function loadNonceFromFile( absPath: string = `${DEFAULT_KEY_DIR_NAME}/${DEFAULT_NONCE_FILE}` ) { try { if (!absPath) throw Error("No path provided"); if (!fs.existsSync(absPath)) throw Error("File does not exist."); // load the public keys from the file const data = JSON.parse(fs.readFileSync(absPath, { encoding: "utf-8" })) || {}; // convert all loaded keyed values into valid public keys for (const [key, value] of Object.entries(data)) { data[key] = value as number; } return data; } catch (err) { // console.warn("Unable to load local file"); } // always return an object return {}; } /* Locally save a PublicKey addresses to the filesystem for later retrieval */ export function saveNonceToFile( nonce: number, absPath: string = `${DEFAULT_KEY_DIR_NAME}/${DEFAULT_NONCE_FILE}` ) { try { // if (!absPath) throw Error("No path provided"); // if (!fs.existsSync(absPath)) throw Error("File does not exist."); // fetch all the current values let data: any = loadNonceFromFile(absPath); // convert all loaded keyed values from PublicKeys to strings for (const [key, value] of Object.entries(data)) { data[key as any] = value as number; } data = { nonce }; // actually save the data to the file fs.writeFileSync(absPath, JSON.stringify(data), { encoding: "utf-8", }); // reload the keys for sanity data = loadPublicKeysFromFile(absPath); return data; } catch (err) { console.warn("Unable to save to file"); } // always return an object return {}; } ================================================ FILE: scripts/runCreateCollection.ts ================================================ import { Metaplex, bundlrStorage, keypairIdentity, } from "@metaplex-foundation/js"; import { Connection, Keypair } from "@solana/web3.js"; import dotenv from "dotenv"; import { uploadMetadata } from "./uploadMetadata"; dotenv.config(); (async () => { console.log("process.env.HELIUS_API_KEY: ", process.env.HELIUS_API_KEY); const HELIUS_URL = `https://rpc.helius.xyz/?api-key=${process.env.HELIUS_API_KEY}`; // // const HELIUS_URL = `https://devnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}` const CONNECTION = new Connection(HELIUS_URL); const payer = Keypair.fromSecretKey( Uint8Array.from( Buffer.from(process.env.COLLECTION_OWNER_SECRET_KEY as string, "base64") ) ); console.log("collection owner(payer): ", payer.publicKey.toBase58()); const balance = await CONNECTION.getBalance(payer.publicKey); console.log("balance: ", balance); const metaplex = Metaplex.make(CONNECTION) .use(keypairIdentity(payer)) .use( bundlrStorage({ address: "https://node1.bundlr.network", providerUrl: HELIUS_URL, timeout: 60000, }) ); //STEP 1: create a collection on mainnet/devnet // await createNFTCollection(payer, metaplex); //STEP 2: create a nft on mainnet/devnet // following code is to create a sample metadata to be supplied to the chatbot, sample creating nft #1 for the collection const uri = uploadMetadata(metaplex, 4); // following is the code to create a tree on the devnet, on mainnet you have to use already created trees with empty leaves // await createTree(payer, metaplex.connection, 1, 2, { // maxDepth: 5, // maxBufferSize: 8, // }); })(); ================================================ FILE: scripts/send.ts ================================================ // Deprecated - Not in use import { Connection, Transaction, Keypair, Signer } from "@solana/web3.js"; import { readFileSync } from "fs"; const connection = new Connection("https://api.mainnet-beta.solana.com"); async function simulate(base64Transaction: string, signers: Signer[]) { const transaction = Transaction.from( Buffer.from(base64Transaction, "base64") ); return await connection.simulateTransaction(transaction, signers); } function parse(): { keypair: Keypair; base64Transaction: string; } { let args = process.argv.slice(2); if (args.length !== 4 || args[0] !== "--tx" || args[2] !== "--keypair") { console.error( "Usage: ts-node scripts/send.ts --tx --keypair " ); process.exit(1); } const base64Transaction = args[1]; const keypairPath = args[3]; const keypair = Keypair.fromSecretKey( new Uint8Array(JSON.parse(readFileSync(keypairPath, "utf-8"))) ); return { keypair, base64Transaction }; } async function main() { let { keypair, base64Transaction } = parse(); let simulationResult = await simulate(base64Transaction, [keypair]); console.log(simulationResult); } main(); ================================================ FILE: scripts/uploadMetadata.ts ================================================ // Deprecated - Not in use import { Metaplex, toMetaplexFile, UploadMetadataInput, } from "@metaplex-foundation/js"; import * as fs from "fs"; const ASSETS_DIR = "../chatgpt-plugin/public/assets/"; export async function uploadMetadata(metaplex: Metaplex, nonce: number) { const EXTENSION = "png"; const buffer = fs.readFileSync(`${ASSETS_DIR}${nonce}.${EXTENSION}`); const file = toMetaplexFile(buffer, `${nonce}.${EXTENSION}`); const imageUri = await metaplex.storage().upload(file); console.log(`${ASSETS_DIR}${nonce}.json`); const data = fs.readFileSync(`${ASSETS_DIR}${nonce}.json`, "utf-8"); const nftInfo = JSON.parse(data); const nftMetadata: UploadMetadataInput = { name: `${nftInfo.name ?? "NFT"}`, symbol: `${nftInfo.symbol ?? "NFT"}`, description: `${nftInfo.description ?? "NFT"}`, image: imageUri, properties: { files: [ { uri: `${nonce}.${EXTENSION}`, type: `image/${EXTENSION}`, }, ], }, }; const { uri } = await metaplex.nfts().uploadMetadata(nftMetadata); console.log("metadata URI", uri); }