From 544a93e24c368b3cae225cfa970468999130e201 Mon Sep 17 00:00:00 2001 From: Roy Scheeren Date: Fri, 30 Aug 2024 18:11:20 +0200 Subject: [PATCH] first iteration Signed-off-by: Roy Scheeren --- .env | 4 + .gitignore | 2 + app/api/token/[token]/route.ts | 19 + app/api/tokens.ts | 12 + app/create/page.tsx | 16 + app/globals.css | 122 +- app/layout.tsx | 5 +- app/token/[address]/page.tsx | 104 ++ app/tokens/page.tsx | 20 + common/fetcher/fetcher.ts | 4 + common/fetcher/index.ts | 1 + common/types/index.ts | 1 + common/types/types.ts | 196 +++ common/utils/index.ts | 1 + common/utils/utils.ts | 124 ++ libs/pumpdotfun-sdk/IDL/index.ts | 2 + libs/pumpdotfun-sdk/IDL/pump-fun.json | 925 +++++++++++++ libs/pumpdotfun-sdk/IDL/pump-fun.ts | 865 ++++++++++++ libs/pumpdotfun-sdk/amm.ts | 88 ++ libs/pumpdotfun-sdk/bondingCurveAccount.ts | 134 ++ libs/pumpdotfun-sdk/events.ts | 53 + libs/pumpdotfun-sdk/globalAccount.ts | 77 ++ libs/pumpdotfun-sdk/index.ts | 8 + libs/pumpdotfun-sdk/pumpfun.ts | 476 +++++++ libs/pumpdotfun-sdk/types.ts | 80 ++ libs/pumpdotfun-sdk/util.ts | 131 ++ modules/Token/Token.tsx | 10 + modules/Token/actions/getTokenChart.action.ts | 11 + .../Token/actions/getTokenDetails.action.ts | 11 + modules/Token/index.ts | 3 + modules/TokenCard/TokenCard.tsx | 53 + modules/TokenCard/index.ts | 1 + modules/TokenGrid/TokenGrid.tsx | 19 + modules/TokenGrid/actions/getTokens.action.ts | 21 + modules/TokenGrid/index.ts | 2 + modules/TokenGrid/types | 0 modules/createToken/CreateToken.tsx | 142 ++ .../createToken/actions/createToken.action.ts | 150 ++ modules/createToken/index.ts | 2 + package-lock.json | 1220 ++++++++++++++++- package.json | 22 +- public/fonts/boska/Boska-Variable.eot | Bin 0 -> 130334 bytes public/fonts/boska/Boska-Variable.ttf | Bin 0 -> 130120 bytes public/fonts/boska/Boska-Variable.woff | Bin 0 -> 40988 bytes public/fonts/boska/Boska-Variable.woff2 | Bin 0 -> 47276 bytes public/fonts/boska/Boska-VariableItalic.eot | Bin 0 -> 185054 bytes public/fonts/boska/Boska-VariableItalic.ttf | Bin 0 -> 184828 bytes public/fonts/boska/Boska-VariableItalic.woff | Bin 0 -> 48624 bytes public/fonts/boska/Boska-VariableItalic.woff2 | Bin 0 -> 56860 bytes public/fonts/epilogue/Epilogue-Variable.eot | Bin 0 -> 203184 bytes public/fonts/epilogue/Epilogue-Variable.ttf | Bin 0 -> 202996 bytes public/fonts/epilogue/Epilogue-Variable.woff | Bin 0 -> 57248 bytes public/fonts/epilogue/Epilogue-Variable.woff2 | Bin 0 -> 80868 bytes .../epilogue/Epilogue-VariableItalic.eot | Bin 0 -> 209488 bytes .../epilogue/Epilogue-VariableItalic.ttf | Bin 0 -> 209304 bytes .../epilogue/Epilogue-VariableItalic.woff | Bin 0 -> 59816 bytes .../epilogue/Epilogue-VariableItalic.woff2 | Bin 0 -> 85928 bytes public/fonts/satoshi/Satoshi-Variable.eot | Bin 0 -> 127628 bytes public/fonts/satoshi/Satoshi-Variable.ttf | Bin 0 -> 127420 bytes public/fonts/satoshi/Satoshi-Variable.woff | Bin 0 -> 35160 bytes public/fonts/satoshi/Satoshi-Variable.woff2 | Bin 0 -> 42588 bytes .../fonts/satoshi/Satoshi-VariableItalic.eot | Bin 0 -> 129984 bytes .../fonts/satoshi/Satoshi-VariableItalic.ttf | Bin 0 -> 129748 bytes .../fonts/satoshi/Satoshi-VariableItalic.woff | Bin 0 -> 36472 bytes .../satoshi/Satoshi-VariableItalic.woff2 | Bin 0 -> 43844 bytes tailwind.config.ts | 6 +- tsconfig.json | 26 +- 67 files changed, 5122 insertions(+), 47 deletions(-) create mode 100644 .env create mode 100644 app/api/token/[token]/route.ts create mode 100644 app/api/tokens.ts create mode 100644 app/create/page.tsx create mode 100644 app/token/[address]/page.tsx create mode 100644 app/tokens/page.tsx create mode 100644 common/fetcher/fetcher.ts create mode 100644 common/fetcher/index.ts create mode 100644 common/types/index.ts create mode 100644 common/types/types.ts create mode 100644 common/utils/index.ts create mode 100644 common/utils/utils.ts create mode 100644 libs/pumpdotfun-sdk/IDL/index.ts create mode 100644 libs/pumpdotfun-sdk/IDL/pump-fun.json create mode 100644 libs/pumpdotfun-sdk/IDL/pump-fun.ts create mode 100644 libs/pumpdotfun-sdk/amm.ts create mode 100644 libs/pumpdotfun-sdk/bondingCurveAccount.ts create mode 100644 libs/pumpdotfun-sdk/events.ts create mode 100644 libs/pumpdotfun-sdk/globalAccount.ts create mode 100644 libs/pumpdotfun-sdk/index.ts create mode 100644 libs/pumpdotfun-sdk/pumpfun.ts create mode 100644 libs/pumpdotfun-sdk/types.ts create mode 100644 libs/pumpdotfun-sdk/util.ts create mode 100644 modules/Token/Token.tsx create mode 100644 modules/Token/actions/getTokenChart.action.ts create mode 100644 modules/Token/actions/getTokenDetails.action.ts create mode 100644 modules/Token/index.ts create mode 100644 modules/TokenCard/TokenCard.tsx create mode 100644 modules/TokenCard/index.ts create mode 100644 modules/TokenGrid/TokenGrid.tsx create mode 100644 modules/TokenGrid/actions/getTokens.action.ts create mode 100644 modules/TokenGrid/index.ts create mode 100644 modules/TokenGrid/types create mode 100644 modules/createToken/CreateToken.tsx create mode 100644 modules/createToken/actions/createToken.action.ts create mode 100644 modules/createToken/index.ts create mode 100644 public/fonts/boska/Boska-Variable.eot create mode 100644 public/fonts/boska/Boska-Variable.ttf create mode 100644 public/fonts/boska/Boska-Variable.woff create mode 100644 public/fonts/boska/Boska-Variable.woff2 create mode 100644 public/fonts/boska/Boska-VariableItalic.eot create mode 100644 public/fonts/boska/Boska-VariableItalic.ttf create mode 100644 public/fonts/boska/Boska-VariableItalic.woff create mode 100644 public/fonts/boska/Boska-VariableItalic.woff2 create mode 100644 public/fonts/epilogue/Epilogue-Variable.eot create mode 100644 public/fonts/epilogue/Epilogue-Variable.ttf create mode 100644 public/fonts/epilogue/Epilogue-Variable.woff create mode 100644 public/fonts/epilogue/Epilogue-Variable.woff2 create mode 100644 public/fonts/epilogue/Epilogue-VariableItalic.eot create mode 100644 public/fonts/epilogue/Epilogue-VariableItalic.ttf create mode 100644 public/fonts/epilogue/Epilogue-VariableItalic.woff create mode 100644 public/fonts/epilogue/Epilogue-VariableItalic.woff2 create mode 100644 public/fonts/satoshi/Satoshi-Variable.eot create mode 100644 public/fonts/satoshi/Satoshi-Variable.ttf create mode 100644 public/fonts/satoshi/Satoshi-Variable.woff create mode 100644 public/fonts/satoshi/Satoshi-Variable.woff2 create mode 100644 public/fonts/satoshi/Satoshi-VariableItalic.eot create mode 100644 public/fonts/satoshi/Satoshi-VariableItalic.ttf create mode 100644 public/fonts/satoshi/Satoshi-VariableItalic.woff create mode 100644 public/fonts/satoshi/Satoshi-VariableItalic.woff2 diff --git a/.env b/.env new file mode 100644 index 0000000..e58b299 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +HELIUS_RPC_URL=https://devnet.helius-rpc.com +HELIUS_API_URL=https://api-devnet.helius.xyz +HELIUS_API_KEY=fa7af444-1a05-431e-af2b-a34625d5ce06 +WALLET_ADDRESS=8yADMASmbZ5Gnz1edrvHsFY9yuf6kr2dVoczDTSxJVyD diff --git a/.gitignore b/.gitignore index fd3dbb5..2645d57 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.keys diff --git a/app/api/token/[token]/route.ts b/app/api/token/[token]/route.ts new file mode 100644 index 0000000..33d136f --- /dev/null +++ b/app/api/token/[token]/route.ts @@ -0,0 +1,19 @@ +import { fetchData } from '@coral-xyz/anchor/dist/cjs/utils/registry' +import type { NextApiRequest, NextApiResponse } from 'next' + +type ResponseData = { + message: string +} + +export async function GET( + request: Request, + { params }: { params: { token: string } } +) { + const { token } = params + + // const result = await fetch(`https://gmgn.ai/defi/quotation/v1/tokens/sol/${token}`) + const result = await fetch(`https://gmgn.ai/defi/quotation/v1/tokens/sol/63MjbqC4EJCvJXSRcH1YptsUy9dnm626XjdYCM3Spump`) + const data = await result.json() + + return Response.json(data) +} diff --git a/app/api/tokens.ts b/app/api/tokens.ts new file mode 100644 index 0000000..69bf531 --- /dev/null +++ b/app/api/tokens.ts @@ -0,0 +1,12 @@ +import type { NextApiRequest, NextApiResponse } from 'next' + +type ResponseData = { + message: string +} + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + +} diff --git a/app/create/page.tsx b/app/create/page.tsx new file mode 100644 index 0000000..66d3a4d --- /dev/null +++ b/app/create/page.tsx @@ -0,0 +1,16 @@ +import { CreateToken, createToken } from "@/modules/createToken"; + + +export default function Create() { + + return ( +
+
+
+

Create Token

+
+ +
+
+ ); +} diff --git a/app/globals.css b/app/globals.css index 875c01e..b5245d3 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,32 +2,110 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; +@font-face { + font-family: 'Epilogue'; + src: url('../public/fonts/epilogue/Epilogue-Variable.woff2') format('woff2'), + url('../public/fonts/epilogue/Epilogue-Variable.woff') format('woff'), + url('../public/fonts/epilogue/Epilogue-Variable.ttf') format('truetype'); + font-weight: 100 900; + font-display: swap; + font-style: normal; } -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } +@font-face { + font-family: 'Epilogue'; + src: url('../public/fonts/epilogue/Epilogue-VariableItalic.woff2') format('woff2'), + url('../public/fonts/epilogue/Epilogue-VariableItalic.woff') format('woff'), + url('../public/fonts/epilogue/Epilogue-VariableItalic.ttf') format('truetype'); + font-weight: 100 900; + font-display: swap; + font-style: italic; } -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); +@font-face { + font-family: 'Boska'; + src: url('../public/fonts/boska/Boska-Variable.woff2') format('woff2'), + url('../public/fonts/boska/Boska-Variable.woff') format('woff'), + url('../public/fonts/boska/Boska-Variable.ttf') format('truetype'); + font-weight: 200 900; + font-display: swap; + font-style: normal; } -@layer utilities { - .text-balance { - text-wrap: balance; - } +@font-face { + font-family: 'Boska'; + src: url('../public/fonts/boska/Boska-VariableItalic.woff2') format('woff2'), + url('../public/fonts/boska/Boska-VariableItalic.woff') format('woff'), + url('../public/fonts/boska/Boska-VariableItalic.ttf') format('truetype'); + font-weight: 200 900; + font-display: swap; + font-style: italic; +} + +@font-face { + font-family: 'Satoshi'; + src: url('../public/fonts/satoshi/Satoshi-Variable.woff2') format('woff2'), + url('../public/fonts/satoshi/Satoshi-Variable.woff') format('woff'), + url('../public/fonts/satoshi/Satoshi-Variable.ttf') format('truetype'); + font-weight: 300 900; + font-display: swap; + font-style: normal; } + +@font-face { + font-family: 'Satoshi'; + src: url('../public/fonts/satoshi/Satoshi-VariableItalic.woff2') format('woff2'), + url('../public/fonts/satoshi/Satoshi-VariableItalic.woff') format('woff'), + url('../public/fonts/satoshi/Satoshi-VariableItalic.ttf') format('truetype'); + font-weight: 300 900; + font-display: swap; + font-style: italic; +} + +@base { + @layer base { + body { + font-family: 'satoshi'; + @apply text-neutral-900; + } + + h1, h2, h3, h4 { + @apply font-bold leading-none mb-4 tracking-tighter text-yellow-500; + font-family: 'epilogue' + } + + h1 { + @apply text-5xl; + } + + h2 { + @apply text-5xl; + } + + p { + font-family: 'satoshi'; + margin-bottom: 1.5rem; + } + + a { + @apply transition-colors; + } + + a:hover { + @apply text-yellow-500; + } + + .boska { + font-family: 'boska'; + } + + .epilogue { + font-family: 'epilogue'; + @apply tracking-wide; + } + + .satoshi { + font-family: 'satoshi'; + } + } +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 3314e47..756fcce 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,9 +1,6 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; import "./globals.css"; -const inter = Inter({ subsets: ["latin"] }); - export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", @@ -16,7 +13,7 @@ export default function RootLayout({ }>) { return ( - {children} + {children} ); } diff --git a/app/token/[address]/page.tsx b/app/token/[address]/page.tsx new file mode 100644 index 0000000..cb6f312 --- /dev/null +++ b/app/token/[address]/page.tsx @@ -0,0 +1,104 @@ +import { currencyFormatter, formatDate } from "@/common/utils"; +import clsx from "clsx"; +import { map, replace } from "ramda"; +import { FaArrowDownLong, FaArrowUpLong, FaGlobe, FaSeedling, FaTelegram, FaX, FaXTwitter } from "react-icons/fa6"; + +export default async function Tokens({ params: { address } }: { params: { address: string } }) { + const result = await fetch(`https://api.moonshot.cc/token/v1/solana/${address}`) + const tokenData = await result.json() + + const image = replace(/64/g, '2048', tokenData.profile.icon) + console.log(tokenData) + return ( +
+
+
+

{tokenData?.baseToken?.name}

+
+ {' '} {formatDate(tokenData?.createdAt)} +
+

{tokenData?.profile?.decription}

+
+
+
+

SOL:

+ {currencyFormatter.format(tokenData?.priceNative)} +
+
+

USD:

+ + {currencyFormatter.format(tokenData?.priceUsd)} + +
+
+

Market cap:

+ + ${currencyFormatter.format(tokenData?.marketCap)} + +
+
+

FDV:

+ + ${currencyFormatter.format(tokenData?.fdv)} + +
+
+
+
+

Price change:

+
+
+ 5m:{' '}
{tokenData?.priceChange?.m5 < 0 ? : }
{tokenData?.priceChange?.m5}%
+
+
+ 1h:{' '}
{tokenData?.priceChange?.h1 < 0 ? : }
{tokenData?.priceChange?.h1}%
+
+
+ 6h:{' '}
{tokenData?.priceChange?.h6 < 0 ? : }
{tokenData?.priceChange?.h6}%
+
+
+ 24h:{' '}
{tokenData?.priceChange?.h24 < 0 ? : }
{tokenData?.priceChange?.h24}%
+
+
+
+
+

Progress:

+
+
{tokenData?.moonshot?.progress}%
+
+
+
+

Links:

+
+ {tokenData?.profile?.links.map((link: any) => { + if (link.includes('t.me')) { + return ( + + + + ) + } + if (link.includes('x.com')) { + return ( + + + + ) + } + return ( + + + + ) + })} +
+
+
+
+
+
+ ); +} diff --git a/app/tokens/page.tsx b/app/tokens/page.tsx new file mode 100644 index 0000000..ec830e9 --- /dev/null +++ b/app/tokens/page.tsx @@ -0,0 +1,20 @@ +import { TokenGrid, getTokens } from "@/modules/TokenGrid"; + +export default async function Tokens() { + const tokens = await getTokens() + + return ( +
+
+
+
+

Cult Coins

+
+
+
+ +
+
+
+ ); +} diff --git a/common/fetcher/fetcher.ts b/common/fetcher/fetcher.ts new file mode 100644 index 0000000..ce86ccf --- /dev/null +++ b/common/fetcher/fetcher.ts @@ -0,0 +1,4 @@ +import { request } from 'graphql-request' + +export const rest = (url: string) => fetch(url).then((res) => res.json()) +export const graphql = ([url, query]: [url: string, query: string]) => request(url, query) diff --git a/common/fetcher/index.ts b/common/fetcher/index.ts new file mode 100644 index 0000000..b0d1bd5 --- /dev/null +++ b/common/fetcher/index.ts @@ -0,0 +1 @@ +export { rest } from "./fetcher"; diff --git a/common/types/index.ts b/common/types/index.ts new file mode 100644 index 0000000..d332e4e --- /dev/null +++ b/common/types/index.ts @@ -0,0 +1 @@ +export type { CreateToken, CreateTokenResult, Token, TokenDetails } from "./types" diff --git a/common/types/types.ts b/common/types/types.ts new file mode 100644 index 0000000..fe14078 --- /dev/null +++ b/common/types/types.ts @@ -0,0 +1,196 @@ +export type CreateToken = { + name: string; + symbol: string; + decimals: number; + supply: number; + image: File | null; + description: string; + revokeUpdate: boolean; + revokeFreeze: boolean; + revokeMint: boolean; +} + +export type CreateTokenResult = { + transaction?: string; + message?: string; + success: boolean; +}; + +export type CreateTokenMetadata = { + name: string; + symbol: string; + description: string; +}; + +export type TokenMetadata = { + name: string; + symbol: string; + description: string; + image: string; +}; + +export interface Token { + account: string, + onChainAccountInfo: { + accountInfo: { + key: string, + isSigner: boolean, + isWritable: boolean, + lamports: number, + data: { + parsed: { + info: { + decimals: number, + freezeAuthority: string, + isInitialized: boolean, + mintAuthority: string, + supply: string + }, + type: string + }, + program: string, + space: number + }, + owner: string, + executable: boolean, + rentEpoch: number + }, + error: string + }, + onChainMetadata: { + metadata: { + tokenStandard: string, + key: string, + updateAuthority: string, + mint: string, + data: { + name: string, + symbol: string, + uri: string, + sellerFeeBasisPoints: string, + creators: any + }, + primarySaleHappened: boolean, + isMutable: boolean, + editionNonce: number, + uses: { + useMethod: string, + remaining: number, + total: number + }, + collection: string | null, + collectionDetails: string | null, + }, + error: string + }, + offChainMetadata: { + metadata: { + createdOn: string, + description: string, + image: string, + name: string, + showName: boolean, + symbol: string + }, + uri: string, + error: '' + }, + legacyMetadata: string | null +} + +export interface TokenDetails { + address: string, + holder_count: number, + symbol: string, + name: string, + decimals: number, + price: number, + logo: string, + price_1m: number, + price_5m: number, + price_1h: number, + price_6h: number, + price_24h: number, + volume_24h: number, + swaps_5m: number, + swaps_1h: number, + swaps_6h: number, + swaps_24h: number, + liquidity: number, + max_supply: number, + total_supply: number, + biggest_pool_address: string, + chain: string, + creation_timestamp: number, + open_timestamp: number, + circulating_supply: number | null, + high_price: number | null, + high_price_timestamp: number | null, + low_price: number | null, + low_price_timestamp: number | null, + buys_1m: number, + sells_1m: number, + swaps_1m: number, + volume_1m: number, + buy_volume_1m: number, + sell_volume_1m: number, + net_in_volume_1m: number, + buys_5m: number, + sells_5m: number, + volume_5m: number, + buy_volume_5m: number, + sell_volume_5m: number, + net_in_volume_5m: number, + buys_1h: number, + sells_1h: number, + volume_1h: number, + buy_volume_1h: number, + sell_volume_1h: number, + net_in_volume_1h: number, + buys_6h:number, + sells_6h: number, + volume_6h: number, + buy_volume_6h: number, + sell_volume_6h: number, + net_in_volume_6h: number, + buys_24h:number, + sells_24h: number, + buy_volume_24h: number, + sell_volume_24h: number, + net_in_volume_24h: number, + fdv: number, + market_cap: number, + circulating_market_cap: number | null, + link: any, + social_links: any, + hot_level: string, + is_show_alert: false, + buy_tax: number | null, + sell_tax: number | null, + is_honeypot: boolean | null, + renounced: boolean | null, + top_10_holder_rate: number, + renounced_mint: number, + renounced_freeze_account: number, + burn_ratio: string, + burn_status: string, + pool_info: any, + launchpad: string, + launchpad_status: 1, + rug_ratio: number | null, + holder_rugged_num: number | null, + holder_token_num: number | null, + rugged_tokens: string[], + creator_address: string, + creator_balance: string, + creator_token_balance: string, + creator_close: boolean, + creator_percentage: string, + creator_token_status: string, + dev_token_burn_amount: number | null, + dev_token_burn_ratio: number | null, + twitter_name_change_history: string[], + dexscr_ad: number, + dexscr_update_link: number, + cto_flag: number +} diff --git a/common/utils/index.ts b/common/utils/index.ts new file mode 100644 index 0000000..d3e973c --- /dev/null +++ b/common/utils/index.ts @@ -0,0 +1 @@ +export { getOrCreateKeypair, printSOLBalance, getSPLBalance, printSPLBalance, baseToValue, valueToBase, getDiscriminator, currencyFormatter, formatDate } from "./utils" diff --git a/common/utils/utils.ts b/common/utils/utils.ts new file mode 100644 index 0000000..cffbbd1 --- /dev/null +++ b/common/utils/utils.ts @@ -0,0 +1,124 @@ +import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { + Keypair, + PublicKey, + Connection, + LAMPORTS_PER_SOL, +} from "@solana/web3.js"; +import { sha256 } from "js-sha256"; + +import fs from "fs"; + +export function getOrCreateKeypair(dir: string, keyName: string): Keypair { + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + const authorityKey = dir + "/" + keyName + ".json"; + console.log(authorityKey) + if (fs.existsSync(authorityKey)) { + const data: { + secretKey: string; + publicKey: string; + } = JSON.parse(fs.readFileSync(authorityKey, "utf-8")); + return Keypair.fromSecretKey(bs58.decode(data.secretKey)); + } else { + const keypair = Keypair.generate(); + keypair.secretKey; + console.log(authorityKey) + const a = fs.writeFileSync( + authorityKey, + JSON.stringify({ + secretKey: bs58.encode(keypair.secretKey), + publicKey: keypair.publicKey.toBase58(), + }) + ); + console.log(a) + return keypair; + } +} + +export function createMintKeyPair(dir: string): Keypair { + const keypair = Keypair.generate(); + const secretKey = keypair.secretKey; + const publicKey = keypair.publicKey; + const file = fs.writeFileSync( + `${dir}/${keypair.publicKey.toBase58()}.json`, + JSON.stringify({ + secretKey: bs58.encode(secretKey), + publicKey: publicKey.toBase58(), + }) + ) + console.log(file) + return keypair +} + +export const printSOLBalance = async ( + connection: Connection, + pubKey: PublicKey, + info: string = "" +) => { + const balance = await connection.getBalance(pubKey); + console.log( + `${info ? info + " " : ""}${pubKey.toBase58()}:`, + balance / LAMPORTS_PER_SOL, + `SOL` + ); +}; + +export const getSPLBalance = async ( + connection: Connection, + mintAddress: PublicKey, + pubKey: PublicKey, + allowOffCurve: boolean = false +) => { + try { + let ata = getAssociatedTokenAddressSync(mintAddress, pubKey, allowOffCurve); + const balance = await connection.getTokenAccountBalance(ata, "processed"); + return balance.value.uiAmount; + } catch (e) {} + return null; +}; + +export const printSPLBalance = async ( + connection: Connection, + mintAddress: PublicKey, + user: PublicKey, + info: string = "" +) => { + const balance = await getSPLBalance(connection, mintAddress, user); + if (balance === null) { + console.log( + `${info ? info + " " : ""}${user.toBase58()}:`, + "No Account Found" + ); + } else { + console.log(`${info ? info + " " : ""}${user.toBase58()}:`, balance); + } +}; + +export const baseToValue = (base: number, decimals: number): number => { + return base * Math.pow(10, decimals); +}; + +export const valueToBase = (value: number, decimals: number): number => { + return value / Math.pow(10, decimals); +}; + +//i.e. account:BondingCurve +export function getDiscriminator(name: string) { + return sha256.digest(name).slice(0, 8); +} + +export const currencyFormatter = Intl.NumberFormat('en', { notation: 'compact' }) + +export const formatDate = (timestamp: string) => { + const date = new Date(timestamp) + const year = date.getFullYear() + const month = date.getMonth() + 1 + const day = date.getDate() + const hours = date.getHours() + const minutes = date.getMinutes() + const seconds = date.getSeconds() + + // Combine into a readable format + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` +} diff --git a/libs/pumpdotfun-sdk/IDL/index.ts b/libs/pumpdotfun-sdk/IDL/index.ts new file mode 100644 index 0000000..b8deffe --- /dev/null +++ b/libs/pumpdotfun-sdk/IDL/index.ts @@ -0,0 +1,2 @@ +export { default as IDL } from "./pump-fun.json"; +export type { PumpFun } from "./pump-fun"; \ No newline at end of file diff --git a/libs/pumpdotfun-sdk/IDL/pump-fun.json b/libs/pumpdotfun-sdk/IDL/pump-fun.json new file mode 100644 index 0000000..eea03f0 --- /dev/null +++ b/libs/pumpdotfun-sdk/IDL/pump-fun.json @@ -0,0 +1,925 @@ +{ + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "metadata": { + "name": "pump", + "version": "0.1.0", + "spec": "0.1.0" + }, + "instructions": [ + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "docs": ["Creates the global state."], + "accounts": [ + { + "name": "global", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "setParams", + "discriminator": [165, 31, 134, 53, 189, 180, 130, 255], + "docs": ["Sets the global state parameters."], + "accounts": [ + { + "name": "global", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "event_authority", + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "feeRecipient", + "type": "pubkey" + }, + { + "name": "initialVirtualTokenReserves", + "type": "u64" + }, + { + "name": "initialVirtualSolReserves", + "type": "u64" + }, + { + "name": "initialRealTokenReserves", + "type": "u64" + }, + { + "name": "tokenTotalSupply", + "type": "u64" + }, + { + "name": "feeBasisPoints", + "type": "u64" + } + ] + }, + { + "name": "create", + "discriminator": [24, 30, 200, 40, 5, 28, 7, 119], + "docs": ["Creates a new coin and bonding curve."], + "accounts": [ + { + "name": "mint", + "writable": true, + "signer": true + }, + { + "name": "mint_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 105, + 110, + 116, + 45, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "bonding_curve", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ] + }, + { + "kind": "account", + "path": "mint" + } + ] + } + }, + { + "name": "associated_bonding_curve", + "writable": true, + "signer": false + }, + { + "name": "global", + "writable": false, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "mpl_token_metadata", + "address": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" + }, + { + "name": "metadata", + "writable": true, + "signer": false + }, + { + "name": "user", + "isMut": true, + "isSigner": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "token_program", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "associated_token_program", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "rent", + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "event_authority", + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "uri", + "type": "string" + } + ] + }, + { + "name": "buy", + "discriminator": [102, 6, 61, 18, 1, 218, 235, 234], + "docs": ["Buys tokens from a bonding curve."], + "accounts": [ + { + "name": "global", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "fee_recipient", + "writable": true, + "signer": false + }, + { + "name": "mint", + "writable": false, + "signer": false + }, + { + "name": "bonding_curve", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ] + }, + { + "kind": "account", + "path": "mint" + } + ] + } + }, + { + "name": "associated_bonding_curve", + "writable": true, + "signer": false + }, + { + "name": "associated_user", + "writable": true, + "signer": false + }, + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "token_program", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "rent", + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "event_authority", + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "maxSolCost", + "type": "u64" + } + ] + }, + { + "name": "sell", + "discriminator": [51, 230, 133, 164, 1, 127, 131, 173], + "docs": ["Sells tokens into a bonding curve."], + "accounts": [ + { + "name": "global", + "writable": false, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "feeRecipient", + "writable": true, + "signer": false + }, + { + "name": "mint", + "writable": false, + "signer": false + }, + { + "name": "bonding_curve", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ] + }, + { + "kind": "account", + "path": "mint" + } + ] + } + }, + { + "name": "associatedBondingCurve", + "writable": true, + "signer": false + }, + { + "name": "associatedUser", + "writable": true, + "signer": false + }, + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "associated_token_program", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "token_program", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "event_authority", + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "minSolOutput", + "type": "u64" + } + ] + }, + { + "name": "withdraw", + "discriminator": [183, 18, 70, 156, 148, 109, 161, 34], + "docs": [ + "Allows the admin to withdraw liquidity for a migration once the bonding curve completes" + ], + "accounts": [ + { + "name": "global", + "writable": false, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "lastWithdraw", + "writable": true, + "signer": false + }, + { + "name": "mint", + "writable": false, + "signer": false + }, + { + "name": "bonding_curve", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ] + }, + { + "kind": "account", + "path": "mint" + } + ] + } + }, + { + "name": "associatedBondingCurve", + "writable": true, + "signer": false + }, + { + "name": "associatedUser", + "writable": true, + "signer": false + }, + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "token_program", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "rent", + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "event_authority", + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "BondingCurve", + "discriminator": [ + 23, + 183, + 248, + 55, + 96, + 216, + 172, + 96 + ] + }, + { + "name": "Global", + "discriminator": [ + 167, + 232, + 232, + 177, + 200, + 108, + 114, + 127 + ] + } + ], + "events": [ + { + "name": "CreateEvent", + "discriminator": [27, 114, 169, 77, 222, 235, 99, 118] + }, + { + "name": "TradeEvent", + "discriminator": [189, 219, 127, 211, 78, 230, 97, 238] + }, + { + "name": "CompleteEvent", + "discriminator": [95, 114, 97, 156, 212, 46, 152, 8] + }, + { + "name": "SetParamsEvent", + "discriminator": [223, 195, 159, 246, 62, 48, 143, 131] + } + ], + "types": [ + { + "name": "Global", + "type": { + "kind": "struct", + "fields": [ + { + "name": "initialized", + "type": "bool" + }, + { + "name": "authority", + "type": "pubkey" + }, + { + "name": "feeRecipient", + "type": "pubkey" + }, + { + "name": "initialVirtualTokenReserves", + "type": "u64" + }, + { + "name": "initialVirtualSolReserves", + "type": "u64" + }, + { + "name": "initialRealTokenReserves", + "type": "u64" + }, + { + "name": "tokenTotalSupply", + "type": "u64" + }, + { + "name": "feeBasisPoints", + "type": "u64" + } + ] + } + }, + { + "name": "LastWithdraw", + "type": { + "kind": "struct", + "fields": [ + { + "name": "lastWithdrawTimestamp", + "type": "i64" + } + ] + } + }, + { + "name": "BondingCurve", + "type": { + "kind": "struct", + "fields": [ + { + "name": "virtualTokenReserves", + "type": "u64" + }, + { + "name": "virtualSolReserves", + "type": "u64" + }, + { + "name": "realTokenReserves", + "type": "u64" + }, + { + "name": "realSolReserves", + "type": "u64" + }, + { + "name": "tokenTotalSupply", + "type": "u64" + }, + { + "name": "complete", + "type": "bool" + } + ] + } + }, + { + "name": "CreateEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "name", + "type": "string", + "index": false + }, + { + "name": "symbol", + "type": "string", + "index": false + }, + { + "name": "uri", + "type": "string", + "index": false + }, + { + "name": "mint", + "type": "pubkey", + "index": false + }, + { + "name": "bondingCurve", + "type": "pubkey", + "index": false + }, + { + "name": "user", + "type": "pubkey", + "index": false + } + ] + } + }, + { + "name": "TradeEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "mint", + "type": "pubkey", + "index": false + }, + { + "name": "solAmount", + "type": "u64", + "index": false + }, + { + "name": "tokenAmount", + "type": "u64", + "index": false + }, + { + "name": "isBuy", + "type": "bool", + "index": false + }, + { + "name": "user", + "type": "pubkey", + "index": false + }, + { + "name": "timestamp", + "type": "i64", + "index": false + }, + { + "name": "virtualSolReserves", + "type": "u64", + "index": false + }, + { + "name": "virtualTokenReserves", + "type": "u64", + "index": false + }, + { + "name": "realSolReserves", + "type": "u64", + "index": false + }, + { + "name": "realTokenReserves", + "type": "u64", + "index": false + } + ] + } + }, + { + "name": "CompleteEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "user", + "type": "pubkey", + "index": false + }, + { + "name": "mint", + "type": "pubkey", + "index": false + }, + { + "name": "bondingCurve", + "type": "pubkey", + "index": false + }, + { + "name": "timestamp", + "type": "i64", + "index": false + } + ] + } + }, + { + "name": "SetParamsEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "feeRecipient", + "type": "pubkey", + "index": false + }, + { + "name": "initialVirtualTokenReserves", + "type": "u64", + "index": false + }, + { + "name": "initialVirtualSolReserves", + "type": "u64", + "index": false + }, + { + "name": "initialRealTokenReserves", + "type": "u64", + "index": false + }, + { + "name": "tokenTotalSupply", + "type": "u64", + "index": false + }, + { + "name": "feeBasisPoints", + "type": "u64", + "index": false + } + ] + } + } + ], + "errors": [ + { + "code": 6000, + "name": "NotAuthorized", + "msg": "The given account is not authorized to execute this instruction." + }, + { + "code": 6001, + "name": "AlreadyInitialized", + "msg": "The program is already initialized." + }, + { + "code": 6002, + "name": "TooMuchSolRequired", + "msg": "slippage: Too much SOL required to buy the given amount of tokens." + }, + { + "code": 6003, + "name": "TooLittleSolReceived", + "msg": "slippage: Too little SOL received to sell the given amount of tokens." + }, + { + "code": 6004, + "name": "MintDoesNotMatchBondingCurve", + "msg": "The mint does not match the bonding curve." + }, + { + "code": 6005, + "name": "BondingCurveComplete", + "msg": "The bonding curve has completed and liquidity migrated to raydium." + }, + { + "code": 6006, + "name": "BondingCurveNotComplete", + "msg": "The bonding curve has not completed." + }, + { + "code": 6007, + "name": "NotInitialized", + "msg": "The program is not initialized." + }, + { + "code": 6008, + "name": "WithdrawTooFrequent", + "msg": "Withdraw too frequent" + } + ] +} diff --git a/libs/pumpdotfun-sdk/IDL/pump-fun.ts b/libs/pumpdotfun-sdk/IDL/pump-fun.ts new file mode 100644 index 0000000..99cc93e --- /dev/null +++ b/libs/pumpdotfun-sdk/IDL/pump-fun.ts @@ -0,0 +1,865 @@ +export type PumpFun = { + address: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; + metadata: { + name: "pump"; + version: "0.1.0"; + spec: "0.1.0"; + }; + instructions: [ + { + name: "initialize"; + discriminator: [175, 175, 109, 31, 13, 152, 155, 237]; + docs: ["Creates the global state."]; + accounts: [ + { + name: "global"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [103, 108, 111, 98, 97, 108]; + } + ]; + }; + }, + { + name: "user"; + writable: true; + signer: true; + }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + } + ]; + args: []; + }, + { + name: "setParams"; + discriminator: [165, 31, 134, 53, 189, 180, 130, 255]; + docs: ["Sets the global state parameters."]; + accounts: [ + { + name: "global"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [103, 108, 111, 98, 97, 108]; + } + ]; + }; + }, + { + name: "user"; + writable: true; + signer: true; + }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + }, + { + name: "eventAuthority"; + address: "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"; + }, + { + name: "program"; + address: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; + } + ]; + args: [ + { + name: "feeRecipient"; + type: "pubkey"; + }, + { + name: "initialVirtualTokenReserves"; + type: "u64"; + }, + { + name: "initialVirtualSolReserves"; + type: "u64"; + }, + { + name: "initialRealTokenReserves"; + type: "u64"; + }, + { + name: "tokenTotalSupply"; + type: "u64"; + }, + { + name: "feeBasisPoints"; + type: "u64"; + } + ]; + }, + { + name: "create"; + discriminator: [24, 30, 200, 40, 5, 28, 7, 119]; + docs: ["Creates a new coin and bonding curve."]; + accounts: [ + { + name: "mint"; + writable: true; + signer: true; + }, + { + name: "mint_authority"; + pda: { + seeds: [ + { + kind: "const"; + value: [ + 109, + 105, + 110, + 116, + 45, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ]; + } + ]; + }; + }, + { + name: "bondingCurve"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ]; + }, + { + kind: "account"; + path: "mint"; + } + ]; + }; + }, + { + name: "associatedBondingCurve"; + writable: true; + signer: false; + }, + { + name: "global"; + writable: false; + pda: { + seeds: [ + { + kind: "const"; + value: [103, 108, 111, 98, 97, 108]; + } + ]; + }; + }, + { + name: "mplTokenMetadata"; + address: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"; + }, + { + name: "metadata"; + writable: true; + signer: false; + }, + { + name: "user"; + isMut: true; + isSigner: true; + }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + }, + { + name: "tokenProgram"; + address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + }, + { + name: "associatedTokenProgram"; + address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"; + }, + { + name: "rent"; + address: "SysvarRent111111111111111111111111111111111"; + }, + { + name: "eventAuthority"; + address: "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"; + }, + { + name: "program"; + address: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; + } + ]; + args: [ + { + name: "name"; + type: "string"; + }, + { + name: "symbol"; + type: "string"; + }, + { + name: "uri"; + type: "string"; + } + ]; + }, + { + name: "buy"; + discriminator: [102, 6, 61, 18, 1, 218, 235, 234]; + docs: ["Buys tokens from a bonding curve."]; + accounts: [ + { + name: "global"; + pda: { + seeds: [ + { + kind: "const"; + value: [103, 108, 111, 98, 97, 108]; + } + ]; + }; + }, + { + name: "feeRecipient"; + writable: true; + signer: false; + }, + { + name: "mint"; + writable: false; + signer: false; + }, + { + name: "bondingCurve"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ]; + }, + { + kind: "account"; + path: "mint"; + } + ]; + }; + }, + { + name: "associatedBondingCurve"; + writable: true; + signer: false; + }, + { + name: "associatedUser"; + writable: true; + signer: false; + }, + { + name: "user"; + writable: true; + signer: true; + }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + }, + { + name: "tokenProgram"; + address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + }, + { + name: "rent"; + address: "SysvarRent111111111111111111111111111111111"; + }, + { + name: "eventAuthority"; + address: "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"; + }, + { + name: "program"; + address: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + }, + { + name: "maxSolCost"; + type: "u64"; + } + ]; + }, + { + name: "sell"; + discriminator: [51, 230, 133, 164, 1, 127, 131, 173]; + docs: ["Sells tokens into a bonding curve."]; + accounts: [ + { + name: "global"; + writable: false; + pda: { + seeds: [ + { + kind: "const"; + value: [103, 108, 111, 98, 97, 108]; + } + ]; + }; + }, + { + name: "feeRecipient"; + writable: true; + signer: false; + }, + { + name: "mint"; + writable: false; + signer: false; + }, + { + name: "bondingCurve"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ]; + }, + { + kind: "account"; + path: "mint"; + } + ]; + }; + }, + { + name: "associatedBondingCurve"; + writable: true; + signer: false; + }, + { + name: "associatedUser"; + writable: true; + signer: false; + }, + { + name: "user"; + writable: true; + signer: true; + }, + { + name: "systemProgram"; + address: "11111111111111111111111111111111"; + }, + { + name: "associatedTokenProgram"; + address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"; + }, + { + name: "tokenProgram"; + address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + }, + { + name: "eventAuthority"; + address: "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"; + }, + { + name: "program"; + address: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; + } + ]; + args: [ + { + name: "amount"; + type: "u64"; + }, + { + name: "minSolOutput"; + type: "u64"; + } + ]; + }, + { + name: "withdraw"; + discriminator: [183, 18, 70, 156, 148, 109, 161, 34]; + docs: [ + "Allows the admin to withdraw liquidity for a migration once the bonding curve completes" + ]; + accounts: [ + { + name: "global"; + writable: false; + pda: { + seeds: [ + { + kind: "const"; + value: [103, 108, 111, 98, 97, 108]; + } + ]; + }; + }, + { + name: "lastWithdraw"; + writable: true; + signer: false; + }, + { + name: "mint"; + writable: false; + signer: false; + }, + { + name: "bondingCurve"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [ + 98, + 111, + 110, + 100, + 105, + 110, + 103, + 45, + 99, + 117, + 114, + 118, + 101 + ]; + }, + { + kind: "account"; + path: "mint"; + } + ]; + }; + }, + { + name: "associatedBondingCurve"; + writable: true; + signer: false; + }, + { + name: "associatedUser"; + writable: true; + signer: false; + }, + { + name: "user"; + writable: true; + signer: true; + }, + { + name: "system_program"; + address: "11111111111111111111111111111111"; + }, + { + name: "tokenProgram"; + address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; + }, + { + name: "rent"; + address: "SysvarRent111111111111111111111111111111111"; + }, + { + name: "eventAuthority"; + address: "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"; + }, + { + name: "program"; + address: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; + } + ]; + args: []; + } + ]; + accounts: [ + { + name: "bondingCurve"; + discriminator: [23, 183, 248, 55, 96, 216, 172, 96]; + }, + { + name: "global"; + discriminator: [167, 232, 232, 177, 200, 108, 114, 127]; + } + ]; + events: [ + { + name: "createEvent"; + discriminator: [27, 114, 169, 77, 222, 235, 99, 118]; + }, + { + name: "tradeEvent"; + discriminator: [189, 219, 127, 211, 78, 230, 97, 238]; + }, + { + name: "completeEvent"; + discriminator: [95, 114, 97, 156, 212, 46, 152, 8]; + }, + { + name: "setParamsEvent"; + discriminator: [223, 195, 159, 246, 62, 48, 143, 131]; + } + ]; + types: [ + { + name: "global"; + type: { + kind: "struct"; + fields: [ + { + name: "initialized"; + type: "bool"; + }, + { + name: "authority"; + type: "pubkey"; + }, + { + name: "feeRecipient"; + type: "pubkey"; + }, + { + name: "initialVirtualTokenReserves"; + type: "u64"; + }, + { + name: "initialVirtualSolReserves"; + type: "u64"; + }, + { + name: "initialRealTokenReserves"; + type: "u64"; + }, + { + name: "tokenTotalSupply"; + type: "u64"; + }, + { + name: "feeBasisPoints"; + type: "u64"; + } + ]; + }; + }, + { + name: "lastWithdraw"; + type: { + kind: "struct"; + fields: [ + { + name: "lastWithdrawTimestamp"; + type: "i64"; + } + ]; + }; + }, + { + name: "bondingCurve"; + type: { + kind: "struct"; + fields: [ + { + name: "virtualTokenReserves"; + type: "u64"; + }, + { + name: "virtualSolReserves"; + type: "u64"; + }, + { + name: "realTokenReserves"; + type: "u64"; + }, + { + name: "realSolReserves"; + type: "u64"; + }, + { + name: "tokenTotalSupply"; + type: "u64"; + }, + { + name: "complete"; + type: "bool"; + } + ]; + }; + }, + { + name: "createEvent"; + type: { + kind: "struct"; + fields: [ + { + name: "name"; + type: "string"; + index: false; + }, + { + name: "symbol"; + type: "string"; + index: false; + }, + { + name: "uri"; + type: "string"; + index: false; + }, + { + name: "mint"; + type: "pubkey"; + index: false; + }, + { + name: "bondingCurve"; + type: "pubkey"; + index: false; + }, + { + name: "user"; + type: "pubkey"; + index: false; + } + ]; + }; + }, + { + name: "tradeEvent"; + type: { + kind: "struct"; + fields: [ + { + name: "mint"; + type: "pubkey"; + index: false; + }, + { + name: "solAmount"; + type: "u64"; + index: false; + }, + { + name: "tokenAmount"; + type: "u64"; + index: false; + }, + { + name: "isBuy"; + type: "bool"; + index: false; + }, + { + name: "user"; + type: "pubkey"; + index: false; + }, + { + name: "timestamp"; + type: "i64"; + index: false; + }, + { + name: "virtualSolReserves"; + type: "u64"; + index: false; + }, + { + name: "virtualTokenReserves"; + type: "u64"; + index: false; + }, + { + name: "realSolReserves"; + type: "u64"; + index: false; + }, + { + name: "realTokenReserves"; + type: "u64"; + index: false; + } + ]; + }; + }, + { + name: "completeEvent"; + type: { + kind: "struct"; + fields: [ + { + name: "user"; + type: "pubkey"; + index: false; + }, + { + name: "mint"; + type: "pubkey"; + index: false; + }, + { + name: "bondingCurve"; + type: "pubkey"; + index: false; + }, + { + name: "timestamp"; + type: "i64"; + index: false; + } + ]; + }; + }, + { + name: "setParamsEvent"; + type: { + kind: "struct"; + fields: [ + { + name: "feeRecipient"; + type: "pubkey"; + index: false; + }, + { + name: "initialVirtualTokenReserves"; + type: "u64"; + index: false; + }, + { + name: "initialVirtualSolReserves"; + type: "u64"; + index: false; + }, + { + name: "initialRealTokenReserves"; + type: "u64"; + index: false; + }, + { + name: "tokenTotalSupply"; + type: "u64"; + index: false; + }, + { + name: "feeBasisPoints"; + type: "u64"; + index: false; + } + ]; + }; + } + ]; + errors: [ + { + code: 6000; + name: "NotAuthorized"; + msg: "The given account is not authorized to execute this instruction."; + }, + { + code: 6001; + name: "AlreadyInitialized"; + msg: "The program is already initialized."; + }, + { + code: 6002; + name: "TooMuchSolRequired"; + msg: "slippage: Too much SOL required to buy the given amount of tokens."; + }, + { + code: 6003; + name: "TooLittleSolReceived"; + msg: "slippage: Too little SOL received to sell the given amount of tokens."; + }, + { + code: 6004; + name: "MintDoesNotMatchBondingCurve"; + msg: "The mint does not match the bonding curve."; + }, + { + code: 6005; + name: "BondingCurveComplete"; + msg: "The bonding curve has completed and liquidity migrated to raydium."; + }, + { + code: 6006; + name: "BondingCurveNotComplete"; + msg: "The bonding curve has not completed."; + }, + { + code: 6007; + name: "NotInitialized"; + msg: "The program is not initialized."; + }, + { + code: 6008; + name: "WithdrawTooFrequent"; + msg: "Withdraw too frequent"; + } + ]; +}; \ No newline at end of file diff --git a/libs/pumpdotfun-sdk/amm.ts b/libs/pumpdotfun-sdk/amm.ts new file mode 100644 index 0000000..f48889a --- /dev/null +++ b/libs/pumpdotfun-sdk/amm.ts @@ -0,0 +1,88 @@ +import { BondingCurveAccount } from "./bondingCurveAccount"; +import { GlobalAccount } from "./globalAccount"; + +export type BuyResult = { + token_amount: bigint; + sol_amount: bigint; +}; + +export type SellResult = { + token_amount: bigint; + sol_amount: bigint; +}; + +export class AMM { + constructor( + public virtualSolReserves: bigint, + public virtualTokenReserves: bigint, + public realSolReserves: bigint, + public realTokenReserves: bigint, + public initialVirtualTokenReserves: bigint + ) {} + + static fromGlobalAccount(global: GlobalAccount): AMM { + return new AMM( + global.initialVirtualSolReserves, + global.initialVirtualTokenReserves, + 0n, + global.initialRealTokenReserves, + global.initialVirtualTokenReserves + ); + } + + static fromBondingCurveAccount(bonding_curve: BondingCurveAccount, initialVirtualTokenReserves: bigint): AMM { + return new AMM( + bonding_curve.virtualSolReserves, + bonding_curve.virtualTokenReserves, + bonding_curve.realSolReserves, + bonding_curve.realTokenReserves, + initialVirtualTokenReserves + ); + } + + getBuyPrice(tokens: bigint): bigint { + const product_of_reserves = this.virtualSolReserves * this.virtualTokenReserves; + const new_virtual_token_reserves = this.virtualTokenReserves - tokens; + const new_virtual_sol_reserves = product_of_reserves / new_virtual_token_reserves + 1n; + const amount_needed = new_virtual_sol_reserves > this.virtualSolReserves ? new_virtual_sol_reserves - this.virtualSolReserves : 0n; + return amount_needed > 0n ? amount_needed : 0n; + } + + applyBuy(token_amount: bigint): BuyResult { + const final_token_amount = token_amount > this.realTokenReserves ? this.realTokenReserves : token_amount; + const sol_amount = this.getBuyPrice(final_token_amount); + + this.virtualTokenReserves = this.virtualTokenReserves - final_token_amount; + this.realTokenReserves = this.realTokenReserves - final_token_amount; + + this.virtualSolReserves = this.virtualSolReserves + sol_amount; + this.realSolReserves = this.realSolReserves + sol_amount; + + return { + token_amount: final_token_amount, + sol_amount: sol_amount + } + } + + applySell(token_amount: bigint): SellResult { + this.virtualTokenReserves = this.virtualTokenReserves + token_amount; + this.realTokenReserves = this.realTokenReserves + token_amount; + + const sell_price = this.getSellPrice(token_amount); + + this.virtualSolReserves = this.virtualSolReserves - sell_price; + this.realSolReserves = this.realSolReserves - sell_price; + + return { + token_amount: token_amount, + sol_amount: sell_price + } + } + + getSellPrice(tokens: bigint): bigint { + const scaling_factor = this.initialVirtualTokenReserves; + const token_sell_proportion = (tokens * scaling_factor) / this.virtualTokenReserves; + const sol_received = (this.virtualSolReserves * token_sell_proportion) / scaling_factor; + return sol_received < this.realSolReserves ? sol_received : this.realSolReserves; + } +} \ No newline at end of file diff --git a/libs/pumpdotfun-sdk/bondingCurveAccount.ts b/libs/pumpdotfun-sdk/bondingCurveAccount.ts new file mode 100644 index 0000000..f2133e5 --- /dev/null +++ b/libs/pumpdotfun-sdk/bondingCurveAccount.ts @@ -0,0 +1,134 @@ +import { struct, bool, u64, Layout } from "@coral-xyz/borsh"; + +export class BondingCurveAccount { + public discriminator: bigint; + public virtualTokenReserves: bigint; + public virtualSolReserves: bigint; + public realTokenReserves: bigint; + public realSolReserves: bigint; + public tokenTotalSupply: bigint; + public complete: boolean; + + constructor( + discriminator: bigint, + virtualTokenReserves: bigint, + virtualSolReserves: bigint, + realTokenReserves: bigint, + realSolReserves: bigint, + tokenTotalSupply: bigint, + complete: boolean + ) { + this.discriminator = discriminator; + this.virtualTokenReserves = virtualTokenReserves; + this.virtualSolReserves = virtualSolReserves; + this.realTokenReserves = realTokenReserves; + this.realSolReserves = realSolReserves; + this.tokenTotalSupply = tokenTotalSupply; + this.complete = complete; + } + + getBuyPrice(amount: bigint): bigint { + if (this.complete) { + throw new Error("Curve is complete"); + } + + if (amount <= 0n) { + return 0n; + } + + // Calculate the product of virtual reserves + let n = this.virtualSolReserves * this.virtualTokenReserves; + + // Calculate the new virtual sol reserves after the purchase + let i = this.virtualSolReserves + amount; + + // Calculate the new virtual token reserves after the purchase + let r = n / i + 1n; + + // Calculate the amount of tokens to be purchased + let s = this.virtualTokenReserves - r; + + // Return the minimum of the calculated tokens and real token reserves + return s < this.realTokenReserves ? s : this.realTokenReserves; + } + + getSellPrice(amount: bigint, feeBasisPoints: bigint): bigint { + if (this.complete) { + throw new Error("Curve is complete"); + } + + if (amount <= 0n) { + return 0n; + } + + // Calculate the proportional amount of virtual sol reserves to be received + let n = + (amount * this.virtualSolReserves) / (this.virtualTokenReserves + amount); + + // Calculate the fee amount in the same units + let a = (n * feeBasisPoints) / 10000n; + + // Return the net amount after deducting the fee + return n - a; + } + + getMarketCapSOL(): bigint { + if (this.virtualTokenReserves === 0n) { + return 0n; + } + + return ( + (this.tokenTotalSupply * this.virtualSolReserves) / + this.virtualTokenReserves + ); + } + + getFinalMarketCapSOL(feeBasisPoints: bigint): bigint { + let totalSellValue = this.getBuyOutPrice( + this.realTokenReserves, + feeBasisPoints + ); + let totalVirtualValue = this.virtualSolReserves + totalSellValue; + let totalVirtualTokens = this.virtualTokenReserves - this.realTokenReserves; + + if (totalVirtualTokens === 0n) { + return 0n; + } + + return (this.tokenTotalSupply * totalVirtualValue) / totalVirtualTokens; + } + + getBuyOutPrice(amount: bigint, feeBasisPoints: bigint): bigint { + let solTokens = + amount < this.realSolReserves ? this.realSolReserves : amount; + let totalSellValue = + (solTokens * this.virtualSolReserves) / + (this.virtualTokenReserves - solTokens) + + 1n; + let fee = (totalSellValue * feeBasisPoints) / 10000n; + return totalSellValue + fee; + } + + public static fromBuffer(buffer: Buffer): BondingCurveAccount { + const structure: Layout = struct([ + u64("discriminator"), + u64("virtualTokenReserves"), + u64("virtualSolReserves"), + u64("realTokenReserves"), + u64("realSolReserves"), + u64("tokenTotalSupply"), + bool("complete"), + ]); + + let value = structure.decode(buffer); + return new BondingCurveAccount( + BigInt(value.discriminator), + BigInt(value.virtualTokenReserves), + BigInt(value.virtualSolReserves), + BigInt(value.realTokenReserves), + BigInt(value.realSolReserves), + BigInt(value.tokenTotalSupply), + value.complete + ); + } +} diff --git a/libs/pumpdotfun-sdk/events.ts b/libs/pumpdotfun-sdk/events.ts new file mode 100644 index 0000000..bf001f8 --- /dev/null +++ b/libs/pumpdotfun-sdk/events.ts @@ -0,0 +1,53 @@ +import { PublicKey } from "@solana/web3.js"; +import { + CompleteEvent, + CreateEvent, + SetParamsEvent, + TradeEvent, +} from "./types"; + +export function toCreateEvent(event: CreateEvent): CreateEvent { + return { + name: event.name, + symbol: event.symbol, + uri: event.uri, + mint: new PublicKey(event.mint), + bondingCurve: new PublicKey(event.bondingCurve), + user: new PublicKey(event.user), + }; +} + +export function toCompleteEvent(event: CompleteEvent): CompleteEvent { + return { + user: new PublicKey(event.user), + mint: new PublicKey(event.mint), + bondingCurve: new PublicKey(event.bondingCurve), + timestamp: event.timestamp, + }; +} + +export function toTradeEvent(event: TradeEvent): TradeEvent { + return { + mint: new PublicKey(event.mint), + solAmount: BigInt(event.solAmount), + tokenAmount: BigInt(event.tokenAmount), + isBuy: event.isBuy, + user: new PublicKey(event.user), + timestamp: Number(event.timestamp), + virtualSolReserves: BigInt(event.virtualSolReserves), + virtualTokenReserves: BigInt(event.virtualTokenReserves), + realSolReserves: BigInt(event.realSolReserves), + realTokenReserves: BigInt(event.realTokenReserves), + }; +} + +export function toSetParamsEvent(event: SetParamsEvent): SetParamsEvent { + return { + feeRecipient: new PublicKey(event.feeRecipient), + initialVirtualTokenReserves: BigInt(event.initialVirtualTokenReserves), + initialVirtualSolReserves: BigInt(event.initialVirtualSolReserves), + initialRealTokenReserves: BigInt(event.initialRealTokenReserves), + tokenTotalSupply: BigInt(event.tokenTotalSupply), + feeBasisPoints: BigInt(event.feeBasisPoints), + }; +} diff --git a/libs/pumpdotfun-sdk/globalAccount.ts b/libs/pumpdotfun-sdk/globalAccount.ts new file mode 100644 index 0000000..b422c50 --- /dev/null +++ b/libs/pumpdotfun-sdk/globalAccount.ts @@ -0,0 +1,77 @@ +import { PublicKey } from "@solana/web3.js"; +import { struct, bool, u64, publicKey, Layout } from "@coral-xyz/borsh"; + +export class GlobalAccount { + public discriminator: bigint; + public initialized: boolean = false; + public authority: PublicKey; + public feeRecipient: PublicKey; + public initialVirtualTokenReserves: bigint; + public initialVirtualSolReserves: bigint; + public initialRealTokenReserves: bigint; + public tokenTotalSupply: bigint; + public feeBasisPoints: bigint; + + constructor( + discriminator: bigint, + initialized: boolean, + authority: PublicKey, + feeRecipient: PublicKey, + initialVirtualTokenReserves: bigint, + initialVirtualSolReserves: bigint, + initialRealTokenReserves: bigint, + tokenTotalSupply: bigint, + feeBasisPoints: bigint + ) { + this.discriminator = discriminator; + this.initialized = initialized; + this.authority = authority; + this.feeRecipient = feeRecipient; + this.initialVirtualTokenReserves = initialVirtualTokenReserves; + this.initialVirtualSolReserves = initialVirtualSolReserves; + this.initialRealTokenReserves = initialRealTokenReserves; + this.tokenTotalSupply = tokenTotalSupply; + this.feeBasisPoints = feeBasisPoints; + } + + getInitialBuyPrice(amount: bigint): bigint { + if (amount <= 0n) { + return 0n; + } + + let n = this.initialVirtualSolReserves * this.initialVirtualTokenReserves; + let i = this.initialVirtualSolReserves + amount; + let r = n / i + 1n; + let s = this.initialVirtualTokenReserves - r; + return s < this.initialRealTokenReserves + ? s + : this.initialRealTokenReserves; + } + + public static fromBuffer(buffer: Buffer): GlobalAccount { + const structure: Layout = struct([ + u64("discriminator"), + bool("initialized"), + publicKey("authority"), + publicKey("feeRecipient"), + u64("initialVirtualTokenReserves"), + u64("initialVirtualSolReserves"), + u64("initialRealTokenReserves"), + u64("tokenTotalSupply"), + u64("feeBasisPoints"), + ]); + + let value = structure.decode(buffer); + return new GlobalAccount( + BigInt(value.discriminator), + value.initialized, + value.authority, + value.feeRecipient, + BigInt(value.initialVirtualTokenReserves), + BigInt(value.initialVirtualSolReserves), + BigInt(value.initialRealTokenReserves), + BigInt(value.tokenTotalSupply), + BigInt(value.feeBasisPoints) + ); + } +} diff --git a/libs/pumpdotfun-sdk/index.ts b/libs/pumpdotfun-sdk/index.ts new file mode 100644 index 0000000..f849dbe --- /dev/null +++ b/libs/pumpdotfun-sdk/index.ts @@ -0,0 +1,8 @@ +export * from './pumpfun' +export * from './util' +export * from './types' +export * from './events' +export * from './globalAccount' +export * from './bondingCurveAccount' +export * from './amm' +export { PumpFunSDK } from './pumpfun' diff --git a/libs/pumpdotfun-sdk/pumpfun.ts b/libs/pumpdotfun-sdk/pumpfun.ts new file mode 100644 index 0000000..1073ffa --- /dev/null +++ b/libs/pumpdotfun-sdk/pumpfun.ts @@ -0,0 +1,476 @@ +import { + Commitment, + Connection, + Finality, + Keypair, + PublicKey, + Transaction, +} from "@solana/web3.js"; +import { Program, Provider } from "@coral-xyz/anchor"; +import { GlobalAccount } from "./globalAccount"; +import { + CompleteEvent, + CreateEvent, + CreateTokenMetadata, + PriorityFee, + PumpFunEventHandlers, + PumpFunEventType, + SetParamsEvent, + TradeEvent, + TransactionResult, +} from "./types"; +import { + toCompleteEvent, + toCreateEvent, + toSetParamsEvent, + toTradeEvent, +} from "./events"; +import { + createAssociatedTokenAccountInstruction, + getAccount, + getAssociatedTokenAddress, +} from "@solana/spl-token"; +import { BondingCurveAccount } from "./bondingCurveAccount"; +import { BN } from "bn.js"; +import { + DEFAULT_COMMITMENT, + DEFAULT_FINALITY, + calculateWithSlippageBuy, + calculateWithSlippageSell, + sendTx, +} from "./util"; +import { PumpFun, IDL } from "./IDL"; +import { create } from "domain"; + +const PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; +const MPL_TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"; + +export const GLOBAL_ACCOUNT_SEED = "global"; +export const MINT_AUTHORITY_SEED = "mint-authority"; +export const BONDING_CURVE_SEED = "bonding-curve"; +export const METADATA_SEED = "metadata"; + +export const DEFAULT_DECIMALS = 6; + +export class PumpFunSDK { + public program: Program; + public connection: Connection; + constructor(provider?: Provider) { + this.program = new Program(IDL as PumpFun, provider); + this.connection = this.program.provider.connection; + } + + async createAndBuy( + creator: Keypair, + mint: Keypair, + createTokenMetadata: CreateTokenMetadata, + buyAmountSol: bigint, + slippageBasisPoints: bigint = BigInt(500), + priorityFees?: PriorityFee, + commitment: Commitment = DEFAULT_COMMITMENT, + finality: Finality = DEFAULT_FINALITY + ): Promise { + + let tokenMetadata = await this.createTokenMetadata(createTokenMetadata); + + let createTx = await this.getCreateInstructions( + creator.publicKey, + createTokenMetadata.name, + createTokenMetadata.symbol, + tokenMetadata.metadataUri, + mint + ); + + let newTx = new Transaction().add(createTx); + + if (buyAmountSol > 0) { + const globalAccount = await this.getGlobalAccount(commitment); + const buyAmount = globalAccount.getInitialBuyPrice(buyAmountSol); + const buyAmountWithSlippage = calculateWithSlippageBuy( + buyAmountSol, + slippageBasisPoints + ); + + const buyTx = await this.getBuyInstructions( + creator.publicKey, + mint.publicKey, + globalAccount.feeRecipient, + buyAmount, + buyAmountWithSlippage + ); + + newTx.add(buyTx); + } + + let createResults = await sendTx( + this.connection, + newTx, + creator.publicKey, + [creator, mint], + priorityFees, + commitment, + finality + ); + return createResults; + } + + async buy( + buyer: Keypair, + mint: PublicKey, + buyAmountSol: bigint, + slippageBasisPoints: bigint = BigInt(500), + priorityFees?: PriorityFee, + commitment: Commitment = DEFAULT_COMMITMENT, + finality: Finality = DEFAULT_FINALITY + ): Promise { + let buyTx = await this.getBuyInstructionsBySolAmount( + buyer.publicKey, + mint, + buyAmountSol, + slippageBasisPoints, + commitment + ); + + let buyResults = await sendTx( + this.connection, + buyTx, + buyer.publicKey, + [buyer], + priorityFees, + commitment, + finality + ); + return buyResults; + } + + async sell( + seller: Keypair, + mint: PublicKey, + sellTokenAmount: bigint, + slippageBasisPoints: bigint = BigInt(500), + priorityFees?: PriorityFee, + commitment: Commitment = DEFAULT_COMMITMENT, + finality: Finality = DEFAULT_FINALITY + ): Promise { + let sellTx = await this.getSellInstructionsByTokenAmount( + seller.publicKey, + mint, + sellTokenAmount, + slippageBasisPoints, + commitment + ); + + let sellResults = await sendTx( + this.connection, + sellTx, + seller.publicKey, + [seller], + priorityFees, + commitment, + finality + ); + return sellResults; + } + + //create token instructions + async getCreateInstructions( + creator: PublicKey, + name: string, + symbol: string, + uri: string, + mint: Keypair + ) { + const mplTokenMetadata = new PublicKey(MPL_TOKEN_METADATA_PROGRAM_ID); + console.log("mplTokenMetadata", mplTokenMetadata); + const [metadataPDA] = PublicKey.findProgramAddressSync( + [ + Buffer.from(METADATA_SEED), + mplTokenMetadata.toBuffer(), + mint.publicKey.toBuffer(), + ], + mplTokenMetadata + ); + console.log("metadataPDA", metadataPDA) + const associatedBondingCurve = await getAssociatedTokenAddress( + mint.publicKey, + this.getBondingCurvePDA(mint.publicKey), + true + ); + console.log("associatedBondingCurve", associatedBondingCurve); + return this.program.methods + .create(name, symbol, uri) + .accounts({ + mint: mint.publicKey, + associatedBondingCurve: associatedBondingCurve, + metadata: metadataPDA, + user: creator, + }) + .signers([mint]) + .transaction(); + } + + async getBuyInstructionsBySolAmount( + buyer: PublicKey, + mint: PublicKey, + buyAmountSol: bigint, + slippageBasisPoints: bigint = BigInt(500), + commitment: Commitment = DEFAULT_COMMITMENT + ) { + let bondingCurveAccount = await this.getBondingCurveAccount( + mint, + commitment + ); + if (!bondingCurveAccount) { + throw new Error(`Bonding curve account not found: ${mint.toBase58()}`); + } + + let buyAmount = bondingCurveAccount.getBuyPrice(buyAmountSol); + let buyAmountWithSlippage = calculateWithSlippageBuy( + buyAmountSol, + slippageBasisPoints + ); + + let globalAccount = await this.getGlobalAccount(commitment); + + return await this.getBuyInstructions( + buyer, + mint, + globalAccount.feeRecipient, + buyAmount, + buyAmountWithSlippage + ); + } + + //buy + async getBuyInstructions( + buyer: PublicKey, + mint: PublicKey, + feeRecipient: PublicKey, + amount: bigint, + solAmount: bigint, + commitment: Commitment = DEFAULT_COMMITMENT + ) { + const associatedBondingCurve = await getAssociatedTokenAddress( + mint, + this.getBondingCurvePDA(mint), + true + ); + + const associatedUser = await getAssociatedTokenAddress(mint, buyer, false); + + let transaction = new Transaction(); + + try { + await getAccount(this.connection, associatedUser, commitment); + } catch (e) { + transaction.add( + createAssociatedTokenAccountInstruction( + buyer, + associatedUser, + buyer, + mint + ) + ); + } + + transaction.add( + await this.program.methods + .buy(new BN(amount.toString()), new BN(solAmount.toString())) + .accounts({ + feeRecipient: feeRecipient, + mint: mint, + associatedBondingCurve: associatedBondingCurve, + associatedUser: associatedUser, + user: buyer, + }) + .transaction() + ); + + return transaction; + } + + //sell + async getSellInstructionsByTokenAmount( + seller: PublicKey, + mint: PublicKey, + sellTokenAmount: bigint, + slippageBasisPoints: bigint = BigInt(500), + commitment: Commitment = DEFAULT_COMMITMENT + ) { + let bondingCurveAccount = await this.getBondingCurveAccount( + mint, + commitment + ); + if (!bondingCurveAccount) { + throw new Error(`Bonding curve account not found: ${mint.toBase58()}`); + } + + let globalAccount = await this.getGlobalAccount(commitment); + + let minSolOutput = bondingCurveAccount.getSellPrice( + sellTokenAmount, + globalAccount.feeBasisPoints + ); + + let sellAmountWithSlippage = calculateWithSlippageSell( + minSolOutput, + slippageBasisPoints + ); + + return await this.getSellInstructions( + seller, + mint, + globalAccount.feeRecipient, + sellTokenAmount, + sellAmountWithSlippage + ); + } + + async getSellInstructions( + seller: PublicKey, + mint: PublicKey, + feeRecipient: PublicKey, + amount: bigint, + minSolOutput: bigint + ) { + const associatedBondingCurve = await getAssociatedTokenAddress( + mint, + this.getBondingCurvePDA(mint), + true + ); + + const associatedUser = await getAssociatedTokenAddress(mint, seller, false); + + let transaction = new Transaction(); + + transaction.add( + await this.program.methods + .sell(new BN(amount.toString()), new BN(minSolOutput.toString())) + .accounts({ + feeRecipient: feeRecipient, + mint: mint, + associatedBondingCurve: associatedBondingCurve, + associatedUser: associatedUser, + user: seller, + }) + .transaction() + ); + + return transaction; + } + + async getBondingCurveAccount( + mint: PublicKey, + commitment: Commitment = DEFAULT_COMMITMENT + ) { + const tokenAccount = await this.connection.getAccountInfo( + this.getBondingCurvePDA(mint), + commitment + ); + if (!tokenAccount) { + return null; + } + return BondingCurveAccount.fromBuffer(tokenAccount!.data); + } + + async getGlobalAccount(commitment: Commitment = DEFAULT_COMMITMENT) { + const [globalAccountPDA] = PublicKey.findProgramAddressSync( + [Buffer.from(GLOBAL_ACCOUNT_SEED)], + new PublicKey(PROGRAM_ID) + ); + + const tokenAccount = await this.connection.getAccountInfo( + globalAccountPDA, + commitment + ); + + return GlobalAccount.fromBuffer(tokenAccount!.data); + } + + getBondingCurvePDA(mint: PublicKey) { + return PublicKey.findProgramAddressSync( + [Buffer.from(BONDING_CURVE_SEED), mint.toBuffer()], + this.program.programId + )[0]; + } + + async createTokenMetadata(create: CreateTokenMetadata) { + let formData = new FormData(); + formData.append("file", create.file), + formData.append("name", create.name), + formData.append("symbol", create.symbol), + formData.append("description", create.description), + formData.append("twitter", create.twitter || ""), + formData.append("telegram", create.telegram || ""), + formData.append("website", create.website || ""), + formData.append("showName", "true"); + try { + let request = await fetch("https://pump.fun/api/ipfs", { + method: "POST", + body: formData, + }); + + return request.json(); + } catch (e) { + console.error("Error creating token metadata", e); + } + } + + //EVENTS + addEventListener( + eventType: T, + callback: ( + event: PumpFunEventHandlers[T], + slot: number, + signature: string + ) => void + ) { + return this.program.addEventListener( + eventType, + (event: any, slot: number, signature: string) => { + let processedEvent; + switch (eventType) { + case "createEvent": + processedEvent = toCreateEvent(event as CreateEvent); + callback( + processedEvent as PumpFunEventHandlers[T], + slot, + signature + ); + break; + case "tradeEvent": + processedEvent = toTradeEvent(event as TradeEvent); + callback( + processedEvent as PumpFunEventHandlers[T], + slot, + signature + ); + break; + case "completeEvent": + processedEvent = toCompleteEvent(event as CompleteEvent); + callback( + processedEvent as PumpFunEventHandlers[T], + slot, + signature + ); + console.log("completeEvent", event, slot, signature); + break; + case "setParamsEvent": + processedEvent = toSetParamsEvent(event as SetParamsEvent); + callback( + processedEvent as PumpFunEventHandlers[T], + slot, + signature + ); + break; + default: + console.error("Unhandled event type:", eventType); + } + } + ); + } + + removeEventListener(eventId: number) { + this.program.removeEventListener(eventId); + } +} diff --git a/libs/pumpdotfun-sdk/types.ts b/libs/pumpdotfun-sdk/types.ts new file mode 100644 index 0000000..e42c826 --- /dev/null +++ b/libs/pumpdotfun-sdk/types.ts @@ -0,0 +1,80 @@ +import { PublicKey, VersionedTransactionResponse } from "@solana/web3.js"; + +export type CreateTokenMetadata = { + name: string; + symbol: string; + description: string; + file: Blob; + twitter?: string; + telegram?: string; + website?: string; +}; + +export type TokenMetadata = { + name: string; + symbol: string; + description: string; + image: string; + showName: boolean; + createdOn: string; + twitter: string; +}; + +export type CreateEvent = { + name: string; + symbol: string; + uri: string; + mint: PublicKey; + bondingCurve: PublicKey; + user: PublicKey; +}; + +export type TradeEvent = { + mint: PublicKey; + solAmount: bigint; + tokenAmount: bigint; + isBuy: boolean; + user: PublicKey; + timestamp: number; + virtualSolReserves: bigint; + virtualTokenReserves: bigint; + realSolReserves: bigint; + realTokenReserves: bigint; +}; + +export type CompleteEvent = { + user: PublicKey; + mint: PublicKey; + bondingCurve: PublicKey; + timestamp: number; +}; + +export type SetParamsEvent = { + feeRecipient: PublicKey; + initialVirtualTokenReserves: bigint; + initialVirtualSolReserves: bigint; + initialRealTokenReserves: bigint; + tokenTotalSupply: bigint; + feeBasisPoints: bigint; +}; + +export interface PumpFunEventHandlers { + createEvent: CreateEvent; + tradeEvent: TradeEvent; + completeEvent: CompleteEvent; + setParamsEvent: SetParamsEvent; +} + +export type PumpFunEventType = keyof PumpFunEventHandlers; + +export type PriorityFee = { + unitLimit: number; + unitPrice: number; +}; + +export type TransactionResult = { + signature?: string; + error?: unknown; + results?: VersionedTransactionResponse; + success: boolean; +}; diff --git a/libs/pumpdotfun-sdk/util.ts b/libs/pumpdotfun-sdk/util.ts new file mode 100644 index 0000000..42669ab --- /dev/null +++ b/libs/pumpdotfun-sdk/util.ts @@ -0,0 +1,131 @@ +import { + Commitment, + ComputeBudgetProgram, + Connection, + Finality, + Keypair, + PublicKey, + SendTransactionError, + Transaction, + TransactionMessage, + VersionedTransaction, + VersionedTransactionResponse, +} from "@solana/web3.js"; +import { PriorityFee, TransactionResult } from "./types"; + +export const DEFAULT_COMMITMENT: Commitment = "finalized"; +export const DEFAULT_FINALITY: Finality = "finalized"; + +export const calculateWithSlippageBuy = ( + amount: bigint, + basisPoints: bigint +) => { + return amount + (amount * basisPoints) / 10000n; +}; + +export const calculateWithSlippageSell = ( + amount: bigint, + basisPoints: bigint +) => { + return amount - (amount * basisPoints) / 10000n; +}; + +export async function sendTx( + connection: Connection, + tx: Transaction, + payer: PublicKey, + signers: Keypair[], + priorityFees?: PriorityFee, + commitment: Commitment = DEFAULT_COMMITMENT, + finality: Finality = DEFAULT_FINALITY +): Promise { + let newTx = new Transaction(); + + if (priorityFees) { + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: priorityFees.unitLimit, + }); + + const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: priorityFees.unitPrice, + }); + newTx.add(modifyComputeUnits); + newTx.add(addPriorityFee); + } + + newTx.add(tx); + + let versionedTx = await buildVersionedTx(connection, payer, newTx, commitment); + versionedTx.sign(signers); + + try { + const sig = await connection.sendTransaction(versionedTx, { + skipPreflight: false, + }); + console.log("sig:", `https://solscan.io/tx/${sig}`); + + let txResult = await getTxDetails(connection, sig, commitment, finality); + if (!txResult) { + return { + success: false, + error: "Transaction failed", + }; + } + return { + success: true, + signature: sig, + results: txResult, + }; + } catch (e) { + if (e instanceof SendTransactionError) { + let ste = e as SendTransactionError; + console.log(await ste.getLogs(connection)); + } else { + console.error(e); + } + return { + error: e, + success: false, + }; + } +} + +export const buildVersionedTx = async ( + connection: Connection, + payer: PublicKey, + tx: Transaction, + commitment: Commitment = DEFAULT_COMMITMENT +): Promise => { + const blockHash = (await connection.getLatestBlockhash(commitment)) + .blockhash; + + let messageV0 = new TransactionMessage({ + payerKey: payer, + recentBlockhash: blockHash, + instructions: tx.instructions, + }).compileToV0Message(); + + return new VersionedTransaction(messageV0); +}; + +export const getTxDetails = async ( + connection: Connection, + sig: string, + commitment: Commitment = DEFAULT_COMMITMENT, + finality: Finality = DEFAULT_FINALITY +): Promise => { + const latestBlockHash = await connection.getLatestBlockhash(); + await connection.confirmTransaction( + { + blockhash: latestBlockHash.blockhash, + lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, + signature: sig, + }, + commitment + ); + + return connection.getTransaction(sig, { + maxSupportedTransactionVersion: 0, + commitment: finality, + }); +}; diff --git a/modules/Token/Token.tsx b/modules/Token/Token.tsx new file mode 100644 index 0000000..f3b36fe --- /dev/null +++ b/modules/Token/Token.tsx @@ -0,0 +1,10 @@ +import { TokenDetails } from '@/common/types' +import React, { FC } from 'react' + +interface TokenProps { + token: TokenDetails +} + +export const Token:FC = ({}) => { + return (
) +} diff --git a/modules/Token/actions/getTokenChart.action.ts b/modules/Token/actions/getTokenChart.action.ts new file mode 100644 index 0000000..6690b7e --- /dev/null +++ b/modules/Token/actions/getTokenChart.action.ts @@ -0,0 +1,11 @@ +export async function getTokenChart(address: string) { + const chartRequest = await fetch(`http://161.35.212.32/chart/${address}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': 'nnvzkj9WyAt1XJfu', + } + }) + + return chartRequest.text() +} diff --git a/modules/Token/actions/getTokenDetails.action.ts b/modules/Token/actions/getTokenDetails.action.ts new file mode 100644 index 0000000..a9cfcb8 --- /dev/null +++ b/modules/Token/actions/getTokenDetails.action.ts @@ -0,0 +1,11 @@ +export async function getTokenDetails(token: string): Promise { + const tokenDetailsRequest = await fetch(`http://161.35.212.32/token/${token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': 'nnvzkj9WyAt1XJfu', + } + }) + + return tokenDetailsRequest.json() +} diff --git a/modules/Token/index.ts b/modules/Token/index.ts new file mode 100644 index 0000000..f4b42e8 --- /dev/null +++ b/modules/Token/index.ts @@ -0,0 +1,3 @@ +export { Token } from './Token' +export { getTokenChart } from './actions/getTokenChart.action' +export { getTokenDetails } from './actions/getTokenDetails.action' diff --git a/modules/TokenCard/TokenCard.tsx b/modules/TokenCard/TokenCard.tsx new file mode 100644 index 0000000..4818b23 --- /dev/null +++ b/modules/TokenCard/TokenCard.tsx @@ -0,0 +1,53 @@ + +import { currencyFormatter } from '@/common/utils' +import clsx from 'clsx' +import Link from 'next/link' +import { replace } from 'ramda' +import React, { FC } from 'react' +import { FaArrowUpLong, FaArrowDownLong } from "react-icons/fa6" + +interface TokenCardProps { + token: string +} + +export const TokenCard:FC = async ({token}) => { + const result = await fetch(`https://api.moonshot.cc/token/v1/solana/${token}`) + const tokenData = await result.json() + + const image = replace(/64/g, '480', tokenData.profile.icon) + + return ( + +
+ {tokenData.baseToken.name} +
+
+
+
{tokenData.baseToken.name}
+
+
+ 24h Vol:{' '} + +
{tokenData.priceChange.h24 < 0 ? : } {tokenData.priceChange.h24}
+
{' '} + + +
+
+ Market cap:{' '} + + ${currencyFormatter.format(tokenData.marketCap)} + {' '} + + +
+
+
+
{tokenData?.moonshot?.progress}%
+
+
+
+
+ + ) +} diff --git a/modules/TokenCard/index.ts b/modules/TokenCard/index.ts new file mode 100644 index 0000000..9696369 --- /dev/null +++ b/modules/TokenCard/index.ts @@ -0,0 +1 @@ +export { TokenCard } from './TokenCard' diff --git a/modules/TokenGrid/TokenGrid.tsx b/modules/TokenGrid/TokenGrid.tsx new file mode 100644 index 0000000..e757896 --- /dev/null +++ b/modules/TokenGrid/TokenGrid.tsx @@ -0,0 +1,19 @@ +import React, { FC } from 'react' +import { TokenCard } from '../TokenCard/TokenCard' + +interface TokenGridProps { + tokens: string[] +} + +export const TokenGrid:FC = ({ tokens }) => { + + return ( +
+ { + tokens.map((token: string) => ( + + )) + } +
+ ) +} diff --git a/modules/TokenGrid/actions/getTokens.action.ts b/modules/TokenGrid/actions/getTokens.action.ts new file mode 100644 index 0000000..984e313 --- /dev/null +++ b/modules/TokenGrid/actions/getTokens.action.ts @@ -0,0 +1,21 @@ +import { Token } from "@/common/types" + +export async function getTokens(): Promise { + // const mintedTokenTransactionsRequest = await fetch(`${process.env.HELIUS_API_URL}/v0/addresses/${process.env.WALLET_ADDRESS}/transactions?type=TOKEN_MINT&api-key=${process.env.HELIUS_API_KEY}`) + // const mintedTokenTransactions = await mintedTokenTransactionsRequest.json() + // const mintAccounts = mintedTokenTransactions.map((transaction: any) => { + // return transaction?.tokenTransfers[0]?.mint + // }) + + // const tokens = await fetch(`${process.env.HELIUS_API_URL}/v0/token-metadata?api-key=${process.env.HELIUS_API_KEY}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify({ mintAccounts: mintAccounts, includeOffChain: true }) + // }) + + // return tokens.json() + + return ['a7jpm5unxa9o5ecvbhcqrr6zlczzcrucxr8bvybsgeto', 'euwgfageap4uddybpwvwjzplcryzemgr2pxxw8bji7e5', '2a6cadqwxqszsywkdp2jjgf9bwwg42dh3xgoye7f512r', 'e7rpjcn8sckkzjynjcpjup48ho29khgksfrorjyqms5c'] +} diff --git a/modules/TokenGrid/index.ts b/modules/TokenGrid/index.ts new file mode 100644 index 0000000..ba53c8d --- /dev/null +++ b/modules/TokenGrid/index.ts @@ -0,0 +1,2 @@ +export { TokenGrid } from './TokenGrid' +export { getTokens } from './actions/getTokens.action' diff --git a/modules/TokenGrid/types b/modules/TokenGrid/types new file mode 100644 index 0000000..e69de29 diff --git a/modules/createToken/CreateToken.tsx b/modules/createToken/CreateToken.tsx new file mode 100644 index 0000000..cea2876 --- /dev/null +++ b/modules/createToken/CreateToken.tsx @@ -0,0 +1,142 @@ +'use client' + +import { CreateTokenResult } from '@/common/types' +import { dissoc } from 'ramda' +import React, { FC, useState } from 'react' +import { useForm } from "react-hook-form" + +interface CreateTokenProps { + createToken: (data: FormData) => Promise +} + +type TFormData = { + name: string + decimals: number + symbol: string + supply: number + image: FileList | null + description: string + revokeMint: boolean + revokeFreeze: boolean + revokeUpdate: boolean +} + +export const CreateToken:FC = ({ createToken }) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm() + const [isReady, setIsReady] = useState(false) + const [isLoading, setIsLoading] = useState(false) + + const onSubmit = handleSubmit(async (data) => { + setIsLoading(true) + + const formData = new FormData() + + if (data?.image?.length) { + formData.append('file', data.image[0]) + } + + formData.append('data', JSON.stringify(dissoc('file')(data))) + await createToken(formData) + setIsLoading(false) + }) + + return ( +
+
+
+
+
+ +
+ + {errors.name?.type === "required" && ( +

Name is required

+ )} +
+
+ {/*
+ +
+ + {errors.decimals?.type === "required" && ( +

Decimals is required

+ )} + {errors.supply?.type === "min" && ( +

Minimal decimals is 0

+ )} + {errors.supply?.type === "max" && ( +

Minimal decimals is 18

+ )} +
+
*/} +
+
+
+ +
+ + {errors.symbol?.type === "required" && ( +

Symbol is required

+ )} +
+
+
+ +
+ + {errors.supply?.type === "required" && ( +

Supply is required

+ )} + {errors.supply?.type === "min" && ( +

Minimal supply is 1

+ )} +
+
+
+
+ +
+ + {errors.image?.type === "required" && ( +

Image is required

+ )} +
+
+
+ +
+