diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d767148 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["crates/*"] +resolver = "2" diff --git a/README.md b/README.md index aa8d8a5..a734759 100644 --- a/README.md +++ b/README.md @@ -1 +1,57 @@ -# pumpfun-sdk \ No newline at end of file +# PumpFun Rust SDK + +A comprehensive Rust SDK for seamless interaction with the PumpFun Solana program. This SDK provides a robust set of tools and interfaces to integrate PumpFun functionality into your applications. + + +# Explanation +This repository is forked from [https://github.com/nhuxhr/pumpfun-rs](https://github.com/nhuxhr/pumpfun-rs). +Change `PumpFun<'a>` to `PumpFun`, and `payer: &'a Keypair` to `payer: Arc`. + +## Table of Contents + +- [Crates](#crates) +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [API Reference](#api-reference) +- [Contributing](#contributing) + +## Crates + +| Name | Description | Version | +| ------------------------------------- | ---------------------------------------------------------------------------------- | ------- | +| [`pumpfun`](./crates/pumpfun) | Main client library for interacting with the PumpFun program | 2.0.0 | +| [`pumpfun-cpi`](./crates/pumpfun-cpi) | CPI (Cross-Program Invocation) interfaces for integrating with the PumpFun program | 1.1.0 | + +## Features + +- **Easy-to-use API**: Simplified interfaces for interacting with the PumpFun Solana program. +- **Cross-Program Invocation**: Seamless integration with other Solana programs. +- **Comprehensive Documentation**: Detailed guides and API references for all functionalities. + +## Installation + +Add the following to your `Cargo.toml`: + +```toml +[dependencies] +mai3-pumpfun-sdk = "2.0.1" +``` + +## Usage + +For detailed usage instructions, please refer to the documentation of each crate. + +## API Reference + +For detailed API documentation, run: + +``` +cargo doc --open +``` + +This will generate and open the API documentation in your default web browser. + +## Contributing + +We welcome contributions to the PumpFun Rust SDK! Please see our [Contributing Guide](CONTRIBUTING.md) for more details on how to get started. diff --git a/crates/pumpfun-cpi/Cargo.toml b/crates/pumpfun-cpi/Cargo.toml new file mode 100644 index 0000000..c50f2df --- /dev/null +++ b/crates/pumpfun-cpi/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "pumpfun-cpi" +version = "1.1.0" +edition = "2021" +authors = ["Rapheal Bamidele "] +repository = "https://github.com/nhuxhr/pumpfun-rs" +description = "Autogenerated CPI client for the Pump.fun Solana program." +license = "Apache-2.0" +keywords = ["solana", "memecoins", "pumpfun", "cpi"] +readme = "README.md" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["cpi"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] + +[dependencies] +anchor-gen = "0.3.1" +anchor-lang = "0.30.1" + +[package.metadata.workspaces] +independent = true diff --git a/crates/pumpfun-cpi/LICENSE b/crates/pumpfun-cpi/LICENSE new file mode 100644 index 0000000..c9c4c83 --- /dev/null +++ b/crates/pumpfun-cpi/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Rapheal Bamidele + + 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. \ No newline at end of file diff --git a/crates/pumpfun-cpi/README.md b/crates/pumpfun-cpi/README.md new file mode 100644 index 0000000..cffc940 --- /dev/null +++ b/crates/pumpfun-cpi/README.md @@ -0,0 +1,14 @@ +# Pump.fun Solana Program CPI + +## Overview + +This crate provides CPI (Cross-Program Invocation) bindings for interacting with the Pump.fun Solana program. Pump.fun is a Solana-based marketplace enabling users to create and distribute their own tokens, primarily memecoins. + +The crate was automatically generated by [anchor-gen](https://github.com/saber-hq/anchor-gen) from the program's IDL (Interface Description Language) file, ensuring type-safe and reliable cross-program interactions. + +## Features + +- Type-safe CPI bindings for all Pump.fun program instructions +- Account validation and constraint checking +- Automatically generated account structs and instruction builders +- Full integration with Anchor's programming model diff --git a/crates/pumpfun-cpi/idl.classic.json b/crates/pumpfun-cpi/idl.classic.json new file mode 100644 index 0000000..8716a90 --- /dev/null +++ b/crates/pumpfun-cpi/idl.classic.json @@ -0,0 +1,1011 @@ +{ + "version": "0.1.0", + "name": "pump", + "instructions": [ + { + "name": "initialize", + "accounts": [ + { + "name": "global", + "isMut": true, + "isSigner": false + }, + { + "name": "user", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "address": "11111111111111111111111111111111" + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 175, + "bytes": [175, 175, 109, 31, 13, 152, 155, 237] + } + }, + { + "name": "setParams", + "accounts": [ + { + "name": "global", + "isMut": true, + "isSigner": false + }, + { + "name": "user", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "address": "11111111111111111111111111111111" + }, + { + "name": "eventAuthority", + "isMut": false, + "isSigner": false, + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "isMut": false, + "isSigner": false, + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "feeRecipient", + "type": "publicKey", + "attrs": [] + }, + { + "name": "initialVirtualTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialVirtualSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialRealTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "tokenTotalSupply", + "type": "u64", + "attrs": [] + }, + { + "name": "feeBasisPoints", + "type": "u64", + "attrs": [] + } + ], + "discriminant": { + "type": "u8", + "value": 165, + "bytes": [165, 31, 134, 53, 189, 180, 130, 255] + } + }, + { + "name": "create", + "accounts": [ + { + "name": "mint", + "isMut": true, + "isSigner": true + }, + { + "name": "mintAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "bondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "associatedBondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "global", + "isMut": false, + "isSigner": false + }, + { + "name": "mplTokenMetadata", + "isMut": false, + "isSigner": false, + "address": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" + }, + { + "name": "metadata", + "isMut": true, + "isSigner": false + }, + { + "name": "user", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false, + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "associatedTokenProgram", + "isMut": false, + "isSigner": false, + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "eventAuthority", + "isMut": false, + "isSigner": false, + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "isMut": false, + "isSigner": false, + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "name", + "type": "string", + "attrs": [] + }, + { + "name": "symbol", + "type": "string", + "attrs": [] + }, + { + "name": "uri", + "type": "string", + "attrs": [] + } + ], + "discriminant": { + "type": "u8", + "value": 24, + "bytes": [24, 30, 200, 40, 5, 28, 7, 119] + } + }, + { + "name": "buy", + "accounts": [ + { + "name": "global", + "isMut": false, + "isSigner": false + }, + { + "name": "feeRecipient", + "isMut": true, + "isSigner": false + }, + { + "name": "mint", + "isMut": false, + "isSigner": false + }, + { + "name": "bondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "associatedBondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "associatedUser", + "isMut": true, + "isSigner": false + }, + { + "name": "user", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false, + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "eventAuthority", + "isMut": false, + "isSigner": false, + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "isMut": false, + "isSigner": false, + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "amount", + "type": "u64", + "attrs": [] + }, + { + "name": "maxSolCost", + "type": "u64", + "attrs": [] + } + ], + "discriminant": { + "type": "u8", + "value": 102, + "bytes": [102, 6, 61, 18, 1, 218, 235, 234] + } + }, + { + "name": "sell", + "accounts": [ + { + "name": "global", + "isMut": false, + "isSigner": false + }, + { + "name": "feeRecipient", + "isMut": true, + "isSigner": false + }, + { + "name": "mint", + "isMut": false, + "isSigner": false + }, + { + "name": "bondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "associatedBondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "associatedUser", + "isMut": true, + "isSigner": false + }, + { + "name": "user", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "address": "11111111111111111111111111111111" + }, + { + "name": "associatedTokenProgram", + "isMut": false, + "isSigner": false, + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false, + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "eventAuthority", + "isMut": false, + "isSigner": false, + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "isMut": false, + "isSigner": false, + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [ + { + "name": "amount", + "type": "u64", + "attrs": [] + }, + { + "name": "minSolOutput", + "type": "u64", + "attrs": [] + } + ], + "discriminant": { + "type": "u8", + "value": 51, + "bytes": [51, 230, 133, 164, 1, 127, 131, 173] + } + }, + { + "name": "withdraw", + "accounts": [ + { + "name": "global", + "isMut": false, + "isSigner": false + }, + { + "name": "lastWithdraw", + "isMut": true, + "isSigner": false + }, + { + "name": "mint", + "isMut": false, + "isSigner": false + }, + { + "name": "bondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "associatedBondingCurve", + "isMut": true, + "isSigner": false + }, + { + "name": "associatedUser", + "isMut": true, + "isSigner": false + }, + { + "name": "user", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false, + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "eventAuthority", + "isMut": false, + "isSigner": false, + "address": "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" + }, + { + "name": "program", + "isMut": false, + "isSigner": false, + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 183, + "bytes": [183, 18, 70, 156, 148, 109, 161, 34] + } + } + ], + "accounts": [ + { + "name": "BondingCurve", + "type": { + "kind": "struct", + "fields": [ + { + "name": "virtualTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "virtualSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "realTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "realSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "tokenTotalSupply", + "type": "u64", + "attrs": [] + }, + { + "name": "complete", + "type": "bool", + "attrs": [] + } + ] + } + }, + { + "name": "Global", + "type": { + "kind": "struct", + "fields": [ + { + "name": "initialized", + "type": "bool", + "attrs": [] + }, + { + "name": "authority", + "type": "publicKey", + "attrs": [] + }, + { + "name": "feeRecipient", + "type": "publicKey", + "attrs": [] + }, + { + "name": "initialVirtualTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialVirtualSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialRealTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "tokenTotalSupply", + "type": "u64", + "attrs": [] + }, + { + "name": "feeBasisPoints", + "type": "u64", + "attrs": [] + } + ] + } + } + ], + "types": [ + { + "name": "Global", + "type": { + "kind": "struct", + "fields": [ + { + "name": "initialized", + "type": "bool", + "attrs": [] + }, + { + "name": "authority", + "type": "publicKey", + "attrs": [] + }, + { + "name": "feeRecipient", + "type": "publicKey", + "attrs": [] + }, + { + "name": "initialVirtualTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialVirtualSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialRealTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "tokenTotalSupply", + "type": "u64", + "attrs": [] + }, + { + "name": "feeBasisPoints", + "type": "u64", + "attrs": [] + } + ] + } + }, + { + "name": "LastWithdraw", + "type": { + "kind": "struct", + "fields": [ + { + "name": "lastWithdrawTimestamp", + "type": "i64", + "attrs": [] + } + ] + } + }, + { + "name": "BondingCurve", + "type": { + "kind": "struct", + "fields": [ + { + "name": "virtualTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "virtualSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "realTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "realSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "tokenTotalSupply", + "type": "u64", + "attrs": [] + }, + { + "name": "complete", + "type": "bool", + "attrs": [] + } + ] + } + }, + { + "name": "CreateEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "name", + "type": "string", + "attrs": [] + }, + { + "name": "symbol", + "type": "string", + "attrs": [] + }, + { + "name": "uri", + "type": "string", + "attrs": [] + }, + { + "name": "mint", + "type": "publicKey", + "attrs": [] + }, + { + "name": "bondingCurve", + "type": "publicKey", + "attrs": [] + }, + { + "name": "user", + "type": "publicKey", + "attrs": [] + } + ] + } + }, + { + "name": "TradeEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "mint", + "type": "publicKey", + "attrs": [] + }, + { + "name": "solAmount", + "type": "u64", + "attrs": [] + }, + { + "name": "tokenAmount", + "type": "u64", + "attrs": [] + }, + { + "name": "isBuy", + "type": "bool", + "attrs": [] + }, + { + "name": "user", + "type": "publicKey", + "attrs": [] + }, + { + "name": "timestamp", + "type": "i64", + "attrs": [] + }, + { + "name": "virtualSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "virtualTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "realSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "realTokenReserves", + "type": "u64", + "attrs": [] + } + ] + } + }, + { + "name": "CompleteEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "user", + "type": "publicKey", + "attrs": [] + }, + { + "name": "mint", + "type": "publicKey", + "attrs": [] + }, + { + "name": "bondingCurve", + "type": "publicKey", + "attrs": [] + }, + { + "name": "timestamp", + "type": "i64", + "attrs": [] + } + ] + } + }, + { + "name": "SetParamsEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "feeRecipient", + "type": "publicKey", + "attrs": [] + }, + { + "name": "initialVirtualTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialVirtualSolReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "initialRealTokenReserves", + "type": "u64", + "attrs": [] + }, + { + "name": "tokenTotalSupply", + "type": "u64", + "attrs": [] + }, + { + "name": "feeBasisPoints", + "type": "u64", + "attrs": [] + } + ] + } + } + ], + "events": [ + { + "name": "CreateEvent", + "fields": [ + { + "name": "name", + "type": "string", + "index": false + }, + { + "name": "symbol", + "type": "string", + "index": false + }, + { + "name": "uri", + "type": "string", + "index": false + }, + { + "name": "mint", + "type": "publicKey", + "index": false + }, + { + "name": "bondingCurve", + "type": "publicKey", + "index": false + }, + { + "name": "user", + "type": "publicKey", + "index": false + } + ] + }, + { + "name": "TradeEvent", + "fields": [ + { + "name": "mint", + "type": "publicKey", + "index": false + }, + { + "name": "solAmount", + "type": "u64", + "index": false + }, + { + "name": "tokenAmount", + "type": "u64", + "index": false + }, + { + "name": "isBuy", + "type": "bool", + "index": false + }, + { + "name": "user", + "type": "publicKey", + "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", + "fields": [ + { + "name": "user", + "type": "publicKey", + "index": false + }, + { + "name": "mint", + "type": "publicKey", + "index": false + }, + { + "name": "bondingCurve", + "type": "publicKey", + "index": false + }, + { + "name": "timestamp", + "type": "i64", + "index": false + } + ] + }, + { + "name": "SetParamsEvent", + "fields": [ + { + "name": "feeRecipient", + "type": "publicKey", + "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" + } + ], + "metadata": { + "origin": "anchor", + "address": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" + } +} diff --git a/crates/pumpfun-cpi/idl.json b/crates/pumpfun-cpi/idl.json new file mode 100644 index 0000000..5be7fda --- /dev/null +++ b/crates/pumpfun-cpi/idl.json @@ -0,0 +1,805 @@ +{ + "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/crates/pumpfun-cpi/src/lib.rs b/crates/pumpfun-cpi/src/lib.rs new file mode 100644 index 0000000..6f3fc8c --- /dev/null +++ b/crates/pumpfun-cpi/src/lib.rs @@ -0,0 +1,3 @@ +anchor_gen::generate_cpi_crate!("idl.classic.json"); + +declare_id!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); diff --git a/crates/pumpfun/Cargo.toml b/crates/pumpfun/Cargo.toml new file mode 100644 index 0000000..93eb18b --- /dev/null +++ b/crates/pumpfun/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "mai3-pumpfun-sdk" +version = "2.0.1" +edition = "2021" +authors = ["Rapheal Bamidele "] +repository = "https://github.com/MiracleAI-Labs/pumpfun-sdk" +description = "Rust SDK to interact with the Pump.fun Solana program." +license = "MIT OR Apache-2.0" +keywords = ["solana", "memecoins", "pumpfun", "pumpfun-sdk", "cpi"] +readme = "README.md" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +anchor-client = { version = "0.30.1", features = ["async"] } +anchor-spl = "0.30.1" +borsh = { version = "1.5.3", features = ["derive"] } +isahc = "1.7.2" +mpl-token-metadata = "5.1.0" +pumpfun-cpi = { path = "../pumpfun-cpi", version = "1.1.0" } +serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.132" +solana-sdk = "1.18.26" +tokio = "1.41.1" diff --git a/crates/pumpfun/LICENSE-APACHE b/crates/pumpfun/LICENSE-APACHE new file mode 100644 index 0000000..c9c4c83 --- /dev/null +++ b/crates/pumpfun/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Rapheal Bamidele + + 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. \ No newline at end of file diff --git a/crates/pumpfun/LICENSE-MIT b/crates/pumpfun/LICENSE-MIT new file mode 100644 index 0000000..1372510 --- /dev/null +++ b/crates/pumpfun/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Rapheal Bamidele + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/crates/pumpfun/README.md b/crates/pumpfun/README.md new file mode 100644 index 0000000..5329c6a --- /dev/null +++ b/crates/pumpfun/README.md @@ -0,0 +1,133 @@ +# Pump.fun Solana Program SDK + +## Overview + +The `Pump.fun Solana Program SDK` is a Rust library that provides an interface for interacting with the Pump.fun Solana program. Pump.fun is a Solana-based marketplace enabling users to create and distribute their own tokens, primarily memecoins. + +## Installation + +Add this crate to your project using cargo: + +```sh +cargo add pumpfun +``` + +## Usage + +The main entry point is the `PumpFun` struct which provides methods for interacting with the program: + +> **Note:** The SDK automatically creates Associated Token Accounts (ATAs) when needed during buy transactions. No manual ATA creation is required. + +```rust +use anchor_client::{ + solana_sdk::{ + native_token::LAMPORTS_PER_SOL, + signature::{Keypair, Signature}, + signer::Signer, + }, + Cluster, +}; +use pumpfun::{accounts::BondingCurveAccount, utils::CreateTokenMetadata, PriorityFee, PumpFun}; + +// Create a new PumpFun client +let payer: Keypair = Keypair::new(); +let client: PumpFun<'_> = PumpFun::new(Cluster::Mainnet, &payer, None, None); + +// Mint keypair +let mint: Keypair = Keypair::new(); + +// Token metadata +let metadata: CreateTokenMetadata = CreateTokenMetadata { + name: "Lorem ipsum".to_string(), + symbol: "LIP".to_string(), + description: "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quam, nisi.".to_string(), + file: "/path/to/image.png".to_string(), + twitter: None, + telegram: None, + website: Some("https://example.com".to_string()), +}; + +// Optional priority fee to expedite transaction processing (e.g., 100 LAMPORTS per compute unit, equivalent to a 0.01 SOL priority fee) +let fee: Option = Some(PriorityFee { + limit: Some(100_000), + price: Some(100_000_000), +}); + +// Create token with metadata +let signature: Signature = client.create(&mint, metadata.clone(), fee).await?; +println!("Created token: {}", signature); + +// Print amount of SOL and LAMPORTS +let amount_sol: u64 = 1; +let amount_lamports: u64 = LAMPORTS_PER_SOL * amount_sol; +println!("Amount in SOL: {}", amount_sol); +println!("Amount in LAMPORTS: {}", amount_lamports); + +// Create and buy tokens with metadata +let signature: Signature = client.create_and_buy(&mint, metadata.clone(), amount_lamports, None, fee).await?; +println!("Created and bought tokens: {}", signature); + +// Print the curve +let curve: BondingCurveAccount = client.get_bonding_curve_account(&mint.pubkey())?; +println!("{:?}", curve); + +// Buy tokens (ATA will be created automatically if needed) +let signature: Signature = client.buy(&mint.pubkey(), amount_lamports, None, fee).await?; +println!("Bought tokens: {}", signature); + +// Sell tokens (sell all tokens) +let signature: Signature = client.sell(&mint.pubkey(), None, None, fee).await?; +println!("Sold tokens: {}", signature); +``` + +## Features + +- Create new tokens with metadata and custom image +- Buy tokens using SOL with automatic ATA creation +- Sell tokens for SOL with slippage protection +- Query global and bonding curve state +- Calculate prices, fees and slippage +- Priority fee support for faster transactions +- IPFS metadata storage + +## Architecture + +The SDK is organized into several modules: + +- `cpi`: Cross-program invocation interfaces +- `accounts`: Account structs for deserializing on-chain state +- `constants`: Program constants like seeds and public keys +- `error`: Custom error types for error handling +- `instruction`: Transaction instruction builders +- `utils`: Helper functions and utilities + +The main `PumpFun` struct provides high-level methods that abstract away the complexity of: + +- Managing Program Derived Addresses (PDAs) +- Constructing and signing transactions +- Handling account lookups and deserialization +- Calculating prices, fees and slippage +- IPFS metadata uploads +- Priority fee configuration +- Associated Token Account management + +## Contributing + +We welcome contributions! Please submit a pull request or open an issue to discuss any changes. + +## License + +This project is licensed under either of the following licenses, at your option: + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) +- MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +## Disclaimer + +This software is provided "as is," without warranty of any kind, express or implied. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + +**Use at your own risk.** The authors take no responsibility for any harm or damage caused by the use of this software. Users are responsible for ensuring the suitability and safety of this software for their specific use cases. + +By using this software, you acknowledge that you have read, understood, and agree to this disclaimer. diff --git a/crates/pumpfun/RUSTDOC.md b/crates/pumpfun/RUSTDOC.md new file mode 100644 index 0000000..f64cfcf --- /dev/null +++ b/crates/pumpfun/RUSTDOC.md @@ -0,0 +1,111 @@ +# Pump.fun Solana Program SDK + +This library provides a Rust interface for interacting with the Pump.fun Solana program. +Pump.fun is a Solana-based marketplace that enables users to create and distribute their own tokens, primarily memecoins. + +## Getting Started + +Add this crate to your project using cargo: + +```sh +cargo add pumpfun +``` + +## Usage + +The main entry point is the `PumpFun` struct which provides methods for interacting with the program: + +> **Note:** The SDK automatically creates Associated Token Accounts (ATAs) when needed during buy transactions. No manual ATA creation is required. + +```rust,no_run +use anchor_client::{ + solana_sdk::{ + native_token::LAMPORTS_PER_SOL, + signature::{Keypair, Signature}, + signer::Signer, + }, + Cluster, +}; +use pumpfun::{accounts::BondingCurveAccount, utils::CreateTokenMetadata, PriorityFee, PumpFun}; + +// Create a new PumpFun client +let payer: Keypair = Keypair::new(); +let client: PumpFun<'_> = PumpFun::new(Cluster::Mainnet, &payer, None, None); + +// Mint keypair +let mint: Keypair = Keypair::new(); + +// Token metadata +let metadata: CreateTokenMetadata = CreateTokenMetadata { + name: "Lorem ipsum".to_string(), + symbol: "LIP".to_string(), + description: "Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quam, nisi.".to_string(), + file: "/path/to/image.png".to_string(), + twitter: None, + telegram: None, + website: Some("https://example.com".to_string()), +}; + +// Optional priority fee to expedite transaction processing (e.g., 100 LAMPORTS per compute unit, equivalent to a 0.01 SOL priority fee) +let fee: Option = Some(PriorityFee { + limit: Some(100_000), + price: Some(100_000_000), +}); + +// Create token with metadata +let signature: Signature = client.create(&mint, metadata.clone(), fee).await?; +println!("Created token: {}", signature); + +// Print amount of SOL and LAMPORTS +let amount_sol: u64 = 1; +let amount_lamports: u64 = LAMPORTS_PER_SOL * amount_sol; +println!("Amount in SOL: {}", amount_sol); +println!("Amount in LAMPORTS: {}", amount_lamports); + +// Create and buy tokens with metadata +let signature: Signature = client.create_and_buy(&mint, metadata.clone(), amount_lamports, None, fee).await?; +println!("Created and bought tokens: {}", signature); + +// Print the curve +let curve: BondingCurveAccount = client.get_bonding_curve_account(&mint.pubkey())?; +println!("{:?}", curve); + +// Buy tokens (ATA will be created automatically if needed) +let signature: Signature = client.buy(&mint.pubkey(), amount_lamports, None, fee).await?; +println!("Bought tokens: {}", signature); + +// Sell tokens (sell all tokens) +let signature: Signature = client.sell(&mint.pubkey(), None, None, fee).await?; +println!("Sold tokens: {}", signature); +``` + +## Features + +- Create new tokens with metadata and custom image +- Buy tokens using SOL with automatic ATA creation +- Sell tokens for SOL with slippage protection +- Query global and bonding curve state +- Calculate prices, fees and slippage +- Priority fee support for faster transactions +- IPFS metadata storage + +## Architecture + +The SDK is organized into several modules: + +- `cpi`: Cross-program invocation interfaces +- `accounts`: Account structs for deserializing on-chain state +- `constants`: Program constants like seeds and public keys +- `error`: Custom error types for error handling +- `instruction`: Transaction instruction builders +- `utils`: Helper functions and utilities + +The main `PumpFun` struct provides high-level methods that abstract away the complexity of: + +- Managing Program Derived Addresses (PDAs) +- Constructing and signing transactions +- Handling account lookups and deserialization +- Calculating prices, fees and slippage +- IPFS metadata uploads +- Priority fee configuration +- Associated Token Account management diff --git a/crates/pumpfun/src/accounts/bonding_curve.rs b/crates/pumpfun/src/accounts/bonding_curve.rs new file mode 100644 index 0000000..4d8ef81 --- /dev/null +++ b/crates/pumpfun/src/accounts/bonding_curve.rs @@ -0,0 +1,326 @@ +//! Bonding curve account for the Pump.fun Solana Program +//! +//! This module contains the definition for the bonding curve account. +//! +//! # Bonding Curve Account +//! +//! The bonding curve account is used to manage token pricing and liquidity. +//! +//! # Fields +//! +//! - `discriminator`: Unique identifier for the bonding curve +//! - `virtual_token_reserves`: Virtual token reserves used for price calculations +//! - `virtual_sol_reserves`: Virtual SOL reserves used for price calculations +//! - `real_token_reserves`: Actual token reserves available for trading +//! - `real_sol_reserves`: Actual SOL reserves available for trading +//! - `token_total_supply`: Total supply of tokens +//! - `complete`: Whether the bonding curve is complete/finalized +//! +//! # Methods +//! +//! - `new`: Creates a new bonding curve instance +//! - `get_buy_price`: Calculates the amount of tokens received for a given SOL amount +//! - `get_sell_price`: Calculates the amount of SOL received for selling tokens +//! - `get_market_cap_sol`: Calculates the current market cap in SOL +//! - `get_final_market_cap_sol`: Calculates the final market cap in SOL after all tokens are sold +//! - `get_buy_out_price`: Calculates the price to buy out all remaining tokens + +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Represents a bonding curve for token pricing and liquidity management +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct BondingCurveAccount { + /// Unique identifier for the bonding curve + pub discriminator: u64, + /// Virtual token reserves used for price calculations + pub virtual_token_reserves: u64, + /// Virtual SOL reserves used for price calculations + pub virtual_sol_reserves: u64, + /// Actual token reserves available for trading + pub real_token_reserves: u64, + /// Actual SOL reserves available for trading + pub real_sol_reserves: u64, + /// Total supply of tokens + pub token_total_supply: u64, + /// Whether the bonding curve is complete/finalized + pub complete: bool, +} + +impl BondingCurveAccount { + /// Creates a new bonding curve instance + /// + /// # Arguments + /// * `discriminator` - Unique identifier for the curve + /// * `virtual_token_reserves` - Virtual token reserves for price calculations + /// * `virtual_sol_reserves` - Virtual SOL reserves for price calculations + /// * `real_token_reserves` - Actual token reserves available + /// * `real_sol_reserves` - Actual SOL reserves available + /// * `token_total_supply` - Total supply of tokens + /// * `complete` - Whether the curve is complete + pub fn new( + discriminator: u64, + virtual_token_reserves: u64, + virtual_sol_reserves: u64, + real_token_reserves: u64, + real_sol_reserves: u64, + token_total_supply: u64, + complete: bool, + ) -> Self { + Self { + discriminator, + virtual_token_reserves, + virtual_sol_reserves, + real_token_reserves, + real_sol_reserves, + token_total_supply, + complete, + } + } + + /// Calculates the amount of tokens received for a given SOL amount + /// + /// # Arguments + /// * `amount` - Amount of SOL to spend + /// + /// # Returns + /// * `Ok(u64)` - Amount of tokens that would be received + /// * `Err(&str)` - Error message if curve is complete + pub fn get_buy_price(&self, amount: u64) -> Result { + if self.complete { + return Err("Curve is complete"); + } + + if amount == 0 { + return Ok(0); + } + + // Calculate the product of virtual reserves using u128 to avoid overflow + let n: u128 = (self.virtual_sol_reserves as u128) * (self.virtual_token_reserves as u128); + + // Calculate the new virtual sol reserves after the purchase + let i: u128 = (self.virtual_sol_reserves as u128) + (amount as u128); + + // Calculate the new virtual token reserves after the purchase + let r: u128 = n / i + 1; + + // Calculate the amount of tokens to be purchased + let s: u128 = (self.virtual_token_reserves as u128) - r; + + // Convert back to u64 and return the minimum of calculated tokens and real reserves + let s_u64 = s as u64; + Ok(if s_u64 < self.real_token_reserves { + s_u64 + } else { + self.real_token_reserves + }) + } + + /// Calculates the amount of SOL received for selling tokens + /// + /// # Arguments + /// * `amount` - Amount of tokens to sell + /// * `fee_basis_points` - Fee in basis points (1/100th of a percent) + /// + /// # Returns + /// * `Ok(u64)` - Amount of SOL that would be received after fees + /// * `Err(&str)` - Error message if curve is complete + pub fn get_sell_price(&self, amount: u64, fee_basis_points: u64) -> Result { + if self.complete { + return Err("Curve is complete"); + } + + if amount == 0 { + return Ok(0); + } + + // Calculate the proportional amount of virtual sol reserves to be received using u128 + let n: u128 = ((amount as u128) * (self.virtual_sol_reserves as u128)) + / ((self.virtual_token_reserves as u128) + (amount as u128)); + + // Calculate the fee amount in the same units + let a: u128 = (n * (fee_basis_points as u128)) / 10000; + + // Return the net amount after deducting the fee, converting back to u64 + Ok((n - a) as u64) + } + + /// Calculates the current market cap in SOL + pub fn get_market_cap_sol(&self) -> u64 { + if self.virtual_token_reserves == 0 { + return 0; + } + + ((self.token_total_supply as u128) * (self.virtual_sol_reserves as u128) + / (self.virtual_token_reserves as u128)) as u64 + } + + /// Calculates the final market cap in SOL after all tokens are sold + /// + /// # Arguments + /// * `fee_basis_points` - Fee in basis points (1/100th of a percent) + pub fn get_final_market_cap_sol(&self, fee_basis_points: u64) -> u64 { + let total_sell_value: u128 = + self.get_buy_out_price(self.real_token_reserves, fee_basis_points) as u128; + let total_virtual_value: u128 = (self.virtual_sol_reserves as u128) + total_sell_value; + let total_virtual_tokens: u128 = + (self.virtual_token_reserves as u128) - (self.real_token_reserves as u128); + + if total_virtual_tokens == 0 { + return 0; + } + + ((self.token_total_supply as u128) * total_virtual_value / total_virtual_tokens) as u64 + } + + /// Calculates the price to buy out all remaining tokens + /// + /// # Arguments + /// * `amount` - Amount of tokens to buy + /// * `fee_basis_points` - Fee in basis points (1/100th of a percent) + pub fn get_buy_out_price(&self, amount: u64, fee_basis_points: u64) -> u64 { + // Get the effective amount of sol tokens + let sol_tokens: u128 = if amount < self.real_sol_reserves { + self.real_sol_reserves as u128 + } else { + amount as u128 + }; + + // Calculate total sell value + let total_sell_value: u128 = (sol_tokens * (self.virtual_sol_reserves as u128)) + / ((self.virtual_token_reserves as u128) - sol_tokens) + + 1; + + // Calculate fee + let fee: u128 = (total_sell_value * (fee_basis_points as u128)) / 10000; + + // Return total including fee, converting back to u64 + (total_sell_value + fee) as u64 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_bonding_curve() -> BondingCurveAccount { + BondingCurveAccount::new( + 1, // discriminator + 1000, // virtual_token_reserves + 1000, // virtual_sol_reserves + 500, // real_token_reserves + 500, // real_sol_reserves + 1000, // token_total_supply + false, // complete + ) + } + + fn get_large_bonding_curve() -> BondingCurveAccount { + BondingCurveAccount::new( + 1, // discriminator + u64::MAX / 2, // virtual_token_reserves + u64::MAX / 2, // virtual_sol_reserves + u64::MAX / 4, // real_token_reserves + u64::MAX / 4, // real_sol_reserves + u64::MAX / 2, // token_total_supply + false, // complete + ) + } + + #[test] + fn test_bonding_curve_account() { + let bonding_curve: BondingCurveAccount = get_bonding_curve(); + + // Test buy price calculation + assert_eq!(bonding_curve.get_buy_price(0).unwrap(), 0); + + let buy_price = bonding_curve.get_buy_price(100).unwrap(); + assert!(buy_price > 0); + assert!(buy_price <= bonding_curve.real_token_reserves); + + // Test sell price calculation + assert_eq!(bonding_curve.get_sell_price(0, 250).unwrap(), 0); + + let sell_price = bonding_curve.get_sell_price(100, 250).unwrap(); + assert!(sell_price > 0); + } + + #[test] + fn test_bonding_curve_complete() { + let mut bonding_curve: BondingCurveAccount = get_bonding_curve(); + + // Test operations work when not complete + assert!(bonding_curve.get_buy_price(100).is_ok()); + assert!(bonding_curve.get_sell_price(100, 250).is_ok()); + + // Set curve to complete + bonding_curve.complete = true; + + // Test operations fail when complete + assert!(bonding_curve.get_buy_price(100).is_err()); + assert!(bonding_curve.get_sell_price(100, 250).is_err()); + } + + #[test] + fn test_market_cap_calculations() { + let bonding_curve: BondingCurveAccount = get_bonding_curve(); + + // Test market cap calculations + let market_cap = bonding_curve.get_market_cap_sol(); + assert!(market_cap > 0); + + let final_market_cap = bonding_curve.get_final_market_cap_sol(250); + assert!(final_market_cap > 0); + } + + #[test] + fn test_buy_out_price() { + let bonding_curve: BondingCurveAccount = get_bonding_curve(); + + let buy_out_price = bonding_curve.get_buy_out_price(100, 250); + assert!(buy_out_price > 0); + + // Test with amount less than real_sol_reserves + let small_buy_out = bonding_curve.get_buy_out_price(400, 250); + assert!(small_buy_out > 0); + } + + #[test] + fn test_overflow_buy_price() { + let bonding_curve = get_large_bonding_curve(); + + // Test buying with large SOL amount + let buy_price = bonding_curve.get_buy_price(u64::MAX).unwrap(); + assert!(buy_price > 0); + assert!(buy_price <= bonding_curve.real_token_reserves); + } + + #[test] + fn test_overflow_sell_price() { + let bonding_curve = get_large_bonding_curve(); + + // Test selling large token amount + let sell_price = bonding_curve.get_sell_price(u64::MAX / 4, 250).unwrap(); + assert!(sell_price > 0); + } + + #[test] + fn test_overflow_market_cap() { + let bonding_curve = get_large_bonding_curve(); + + // Test market cap with large values + let market_cap = bonding_curve.get_market_cap_sol(); + assert!(market_cap > 0); + + let final_market_cap = bonding_curve.get_final_market_cap_sol(250); + assert!(final_market_cap > 0); + } + + #[test] + fn test_overflow_buy_out_price() { + let bonding_curve = get_large_bonding_curve(); + + // Test buy out with large token amount + let buy_out_price = bonding_curve.get_buy_out_price(u64::MAX / 4, 250); + assert!(buy_out_price > 0); + } +} diff --git a/crates/pumpfun/src/accounts/global.rs b/crates/pumpfun/src/accounts/global.rs new file mode 100644 index 0000000..1357f0f --- /dev/null +++ b/crates/pumpfun/src/accounts/global.rs @@ -0,0 +1,201 @@ +//! Global account for the Pump.fun Solana Program +//! +//! This module contains the definition for the global configuration account. +//! +//! # Global Account +//! +//! The global account is used to store the global configuration for the Pump.fun program. +//! +//! # Fields +//! +//! - `discriminator`: Unique identifier for the global account +//! - `initialized`: Whether the global account has been initialized +//! - `authority`: Authority pubkey that can modify settings +//! - `fee_recipient`: Account that receives fees +//! - `initial_virtual_token_reserves`: Initial virtual token reserves for price calculations +//! - `initial_virtual_sol_reserves`: Initial virtual SOL reserves for price calculations +//! - `initial_real_token_reserves`: Initial actual token reserves available for trading +//! - `token_total_supply`: Total supply of tokens +//! - `fee_basis_points`: Fee in basis points (1/100th of a percent) +//! +//! # Methods +//! +//! - `new`: Creates a new global account instance +//! - `get_initial_buy_price`: Calculates the initial amount of tokens received for a given SOL amount + +use anchor_client::solana_sdk::pubkey::Pubkey; +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Represents the global configuration account for token pricing and fees +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct GlobalAccount { + /// Unique identifier for the global account + pub discriminator: u64, + /// Whether the global account has been initialized + pub initialized: bool, + /// Authority that can modify global settings + pub authority: Pubkey, + /// Account that receives fees + pub fee_recipient: Pubkey, + /// Initial virtual token reserves for price calculations + pub initial_virtual_token_reserves: u64, + /// Initial virtual SOL reserves for price calculations + pub initial_virtual_sol_reserves: u64, + /// Initial actual token reserves available for trading + pub initial_real_token_reserves: u64, + /// Total supply of tokens + pub token_total_supply: u64, + /// Fee in basis points (1/100th of a percent) + pub fee_basis_points: u64, +} + +impl GlobalAccount { + /// Creates a new global account instance + /// + /// # Arguments + /// * `discriminator` - Unique identifier for the account + /// * `initialized` - Whether the account is initialized + /// * `authority` - Authority pubkey that can modify settings + /// * `fee_recipient` - Account that receives fees + /// * `initial_virtual_token_reserves` - Initial virtual token reserves + /// * `initial_virtual_sol_reserves` - Initial virtual SOL reserves + /// * `initial_real_token_reserves` - Initial actual token reserves + /// * `token_total_supply` - Total supply of tokens + /// * `fee_basis_points` - Fee in basis points + #[allow(clippy::too_many_arguments)] + pub fn new( + discriminator: u64, + initialized: bool, + authority: Pubkey, + fee_recipient: Pubkey, + initial_virtual_token_reserves: u64, + initial_virtual_sol_reserves: u64, + initial_real_token_reserves: u64, + token_total_supply: u64, + fee_basis_points: u64, + ) -> Self { + Self { + discriminator, + initialized, + authority, + fee_recipient, + initial_virtual_token_reserves, + initial_virtual_sol_reserves, + initial_real_token_reserves, + token_total_supply, + fee_basis_points, + } + } + + /// Calculates the initial amount of tokens received for a given SOL amount + /// + /// # Arguments + /// * `amount` - Amount of SOL to spend + /// + /// # Returns + /// Amount of tokens that would be received + pub fn get_initial_buy_price(&self, amount: u64) -> u64 { + if amount == 0 { + return 0; + } + + let n: u128 = (self.initial_virtual_sol_reserves as u128) + * (self.initial_virtual_token_reserves as u128); + let i: u128 = (self.initial_virtual_sol_reserves as u128) + (amount as u128); + let r: u128 = n / i + 1; + let s: u128 = (self.initial_virtual_token_reserves as u128) - r; + + if s < (self.initial_real_token_reserves as u128) { + s as u64 + } else { + self.initial_real_token_reserves + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_global() -> GlobalAccount { + GlobalAccount::new( + 1, + true, + Pubkey::new_unique(), + Pubkey::new_unique(), + 1000, + 1000, + 500, + 1000, + 250, + ) + } + + fn get_large_global() -> GlobalAccount { + GlobalAccount::new( + 1, + true, + Pubkey::new_unique(), + Pubkey::new_unique(), + u64::MAX, + u64::MAX, + u64::MAX / 2, + u64::MAX, + 250, + ) + } + + #[test] + fn test_global_account() { + let global: GlobalAccount = get_global(); + + // Test initial buy price calculation + assert_eq!(global.get_initial_buy_price(0), 0); + + let price: u64 = global.get_initial_buy_price(100); + assert!(price > 0); + assert!(price <= global.initial_real_token_reserves); + } + + #[test] + fn test_global_account_max_reserves() { + let mut global: GlobalAccount = get_global(); + global.initial_real_token_reserves = 100; + + // Test that returned amount is capped by real_token_reserves + let price: u64 = global.get_initial_buy_price(1000); + assert_eq!(price, global.initial_real_token_reserves); + } + + #[test] + fn test_global_account_overflow() { + let global: GlobalAccount = get_large_global(); + + // Test with maximum possible SOL amount + let price: u64 = global.get_initial_buy_price(u64::MAX); + assert!(price > 0); + assert!(price <= global.initial_real_token_reserves); + + // Test with large but not maximum SOL amount + let price: u64 = global.get_initial_buy_price(u64::MAX / 2); + assert!(price > 0); + assert!(price <= global.initial_real_token_reserves); + } + + #[test] + fn test_global_account_overflow_edge_cases() { + let mut global: GlobalAccount = get_large_global(); + global.initial_virtual_sol_reserves = u64::MAX - 1000; + global.initial_virtual_token_reserves = u64::MAX - 1000; + global.initial_real_token_reserves = u64::MAX / 4; + + // Test with amounts near u64::MAX + let price: u64 = global.get_initial_buy_price(u64::MAX - 1); + assert!(price > 0); + assert!(price <= global.initial_real_token_reserves); + + let price: u64 = global.get_initial_buy_price(u64::MAX - 1000); + assert!(price > 0); + assert!(price <= global.initial_real_token_reserves); + } +} diff --git a/crates/pumpfun/src/accounts/mod.rs b/crates/pumpfun/src/accounts/mod.rs new file mode 100644 index 0000000..f77567c --- /dev/null +++ b/crates/pumpfun/src/accounts/mod.rs @@ -0,0 +1,14 @@ +//! Accounts for the Pump.fun Solana Program +//! +//! This module contains the definitions for the accounts used by the Pump.fun program. +//! +//! # Accounts +//! +//! - `BondingCurve`: Represents a bonding curve account. +//! - `Global`: Represents the global configuration account. + +mod bonding_curve; +mod global; + +pub use bonding_curve::*; +pub use global::*; diff --git a/crates/pumpfun/src/constants/mod.rs b/crates/pumpfun/src/constants/mod.rs new file mode 100644 index 0000000..e13c78b --- /dev/null +++ b/crates/pumpfun/src/constants/mod.rs @@ -0,0 +1,53 @@ +//! Constants used by the crate. +//! +//! This module contains various constants used throughout the crate, including: +//! +//! - Seeds for deriving Program Derived Addresses (PDAs) +//! - Program account addresses and public keys +//! +//! The constants are organized into submodules for better organization: +//! +//! - `seeds`: Contains seed values used for PDA derivation +//! - `accounts`: Contains important program account addresses + +/// Constants used as seeds for deriving PDAs (Program Derived Addresses) +pub mod seeds { + /// Seed for the global state PDA + pub const GLOBAL_SEED: &[u8] = b"global"; + + /// Seed for the mint authority PDA + pub const MINT_AUTHORITY_SEED: &[u8] = b"mint-authority"; + + /// Seed for bonding curve PDAs + pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve"; + + /// Seed for metadata PDAs + pub const METADATA_SEED: &[u8] = b"metadata"; +} + +/// Constants related to program accounts and authorities +pub mod accounts { + use solana_sdk::{pubkey, pubkey::Pubkey}; + + /// Public key for the Pump.fun program + pub const PUMPFUN: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); + + /// Public key for the MPL Token Metadata program + pub const MPL_TOKEN_METADATA: Pubkey = pubkey!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"); + + /// Authority for program events + pub const EVENT_AUTHORITY: Pubkey = pubkey!("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"); + + /// System Program ID + pub const SYSTEM_PROGRAM: Pubkey = pubkey!("11111111111111111111111111111111"); + + /// Token Program ID + pub const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + + /// Associated Token Program ID + pub const ASSOCIATED_TOKEN_PROGRAM: Pubkey = + pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + + /// Rent Sysvar ID + pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111"); +} diff --git a/crates/pumpfun/src/error/mod.rs b/crates/pumpfun/src/error/mod.rs new file mode 100644 index 0000000..ce65e1d --- /dev/null +++ b/crates/pumpfun/src/error/mod.rs @@ -0,0 +1,74 @@ +//! Error types for the Pump.fun SDK. +//! +//! This module defines the `ClientError` enum, which encompasses various error types that can occur when interacting with the Pump.fun program. +//! It includes specific error cases for bonding curve operations, metadata uploads, Solana client errors, and more. +//! +//! The `ClientError` enum provides a comprehensive set of error types to help developers handle and debug issues that may arise during interactions with the Pump.fun program. +//! +//! # Error Types +//! +//! - `BondingCurveNotFound`: The bonding curve account was not found. +//! - `BondingCurveError`: An error occurred while interacting with the bonding curve. +//! - `BorshError`: An error occurred while serializing or deserializing data using Borsh. +//! - `SolanaClientError`: An error occurred while interacting with the Solana RPC client. +//! - `UploadMetadataError`: An error occurred while uploading metadata to IPFS. +//! - `AnchorClientError`: An error occurred while interacting with the Anchor client. +//! - `InvalidInput`: Invalid input parameters were provided. +//! - `InsufficientFunds`: Insufficient funds for a transaction. +//! - `SimulationError`: Transaction simulation failed. +//! - `RateLimitExceeded`: Rate limit exceeded. + +use anchor_client::solana_client; + +#[derive(Debug)] +pub enum ClientError { + /// Bonding curve account was not found + BondingCurveNotFound, + /// Error related to bonding curve operations + BondingCurveError(&'static str), + /// Error deserializing data using Borsh + BorshError(std::io::Error), + /// Error from Solana RPC client + SolanaClientError(solana_client::client_error::ClientError), + /// Error uploading metadata + UploadMetadataError(Box), + /// Error from Anchor client + AnchorClientError(anchor_client::ClientError), + /// Invalid input parameters + InvalidInput(&'static str), + /// Insufficient funds for transaction + InsufficientFunds, + /// Transaction simulation failed + SimulationError(String), + /// Rate limit exceeded + RateLimitExceeded, +} + +impl std::fmt::Display for ClientError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BondingCurveNotFound => write!(f, "Bonding curve not found"), + Self::BondingCurveError(msg) => write!(f, "Bonding curve error: {}", msg), + Self::BorshError(err) => write!(f, "Borsh serialization error: {}", err), + Self::SolanaClientError(err) => write!(f, "Solana client error: {}", err), + Self::UploadMetadataError(err) => write!(f, "Metadata upload error: {}", err), + Self::AnchorClientError(err) => write!(f, "Anchor client error: {}", err), + Self::InvalidInput(msg) => write!(f, "Invalid input: {}", msg), + Self::InsufficientFunds => write!(f, "Insufficient funds for transaction"), + Self::SimulationError(msg) => write!(f, "Transaction simulation failed: {}", msg), + Self::RateLimitExceeded => write!(f, "Rate limit exceeded"), + } + } +} + +impl std::error::Error for ClientError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::BorshError(err) => Some(err), + Self::SolanaClientError(err) => Some(err), + Self::UploadMetadataError(err) => Some(err.as_ref()), + Self::AnchorClientError(err) => Some(err), + _ => None, + } + } +} diff --git a/crates/pumpfun/src/instruction/mod.rs b/crates/pumpfun/src/instruction/mod.rs new file mode 100644 index 0000000..596188c --- /dev/null +++ b/crates/pumpfun/src/instruction/mod.rs @@ -0,0 +1,148 @@ +//! Instructions for interacting with the Pump.fun program. +//! +//! This module contains instruction builders for creating Solana instructions to interact with the +//! Pump.fun program. Each function takes the required accounts and instruction data and returns a +//! properly formatted Solana instruction. +//! +//! # Instructions +//! +//! - `create`: Instruction to create a new token with an associated bonding curve. +//! - `buy`: Instruction to buy tokens from a bonding curve by providing SOL. +//! - `sell`: Instruction to sell tokens back to the bonding curve in exchange for SOL. + +use crate::{constants, PumpFun}; +use anchor_client::anchor_lang::InstructionData; +use anchor_spl::associated_token::get_associated_token_address; +use pumpfun_cpi as cpi; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, +}; + +/// Creates an instruction to create a new token with bonding curve +/// +/// Creates a new SPL token with an associated bonding curve that determines its price. +/// +/// # Arguments +/// +/// * `payer` - Keypair that will pay for account creation and transaction fees +/// * `mint` - Keypair for the new token mint account that will be created +/// * `args` - Create instruction data containing token name, symbol and metadata URI +/// +/// # Returns +/// +/// Returns a Solana instruction that when executed will create the token and its accounts +pub fn create(payer: &Keypair, mint: &Keypair, args: cpi::instruction::Create) -> Instruction { + let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(&mint.pubkey()).unwrap(); + Instruction::new_with_bytes( + constants::accounts::PUMPFUN, + &args.data(), + vec![ + AccountMeta::new(mint.pubkey(), true), + AccountMeta::new(PumpFun::get_mint_authority_pda(), false), + AccountMeta::new(bonding_curve, false), + AccountMeta::new( + get_associated_token_address(&bonding_curve, &mint.pubkey()), + false, + ), + AccountMeta::new_readonly(PumpFun::get_global_pda(), false), + AccountMeta::new_readonly(constants::accounts::MPL_TOKEN_METADATA, false), + AccountMeta::new(PumpFun::get_metadata_pda(&mint.pubkey()), false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::ASSOCIATED_TOKEN_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::RENT, false), + AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false), + AccountMeta::new_readonly(constants::accounts::PUMPFUN, false), + ], + ) +} + +/// Creates an instruction to buy tokens from a bonding curve +/// +/// Buys tokens by providing SOL. The amount of tokens received is calculated based on +/// the bonding curve formula. A portion of the SOL is taken as a fee and sent to the +/// fee recipient account. +/// +/// # Arguments +/// +/// * `payer` - Keypair that will provide the SOL to buy tokens +/// * `mint` - Public key of the token mint to buy +/// * `fee_recipient` - Public key of the account that will receive the transaction fee +/// * `args` - Buy instruction data containing the SOL amount and maximum acceptable token price +/// +/// # Returns +/// +/// Returns a Solana instruction that when executed will buy tokens from the bonding curve +pub fn buy( + payer: &Keypair, + mint: &Pubkey, + fee_recipient: &Pubkey, + args: cpi::instruction::Buy, +) -> Instruction { + let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap(); + Instruction::new_with_bytes( + constants::accounts::PUMPFUN, + &args.data(), + vec![ + AccountMeta::new_readonly(PumpFun::get_global_pda(), false), + AccountMeta::new(*fee_recipient, false), + AccountMeta::new_readonly(*mint, false), + AccountMeta::new(bonding_curve, false), + AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false), + AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::RENT, false), + AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false), + AccountMeta::new_readonly(constants::accounts::PUMPFUN, false), + ], + ) +} + +/// Creates an instruction to sell tokens back to a bonding curve +/// +/// Sells tokens back to the bonding curve in exchange for SOL. The amount of SOL received +/// is calculated based on the bonding curve formula. A portion of the SOL is taken as +/// a fee and sent to the fee recipient account. +/// +/// # Arguments +/// +/// * `payer` - Keypair that owns the tokens to sell +/// * `mint` - Public key of the token mint to sell +/// * `fee_recipient` - Public key of the account that will receive the transaction fee +/// * `args` - Sell instruction data containing token amount and minimum acceptable SOL output +/// +/// # Returns +/// +/// Returns a Solana instruction that when executed will sell tokens to the bonding curve +pub fn sell( + payer: &Keypair, + mint: &Pubkey, + fee_recipient: &Pubkey, + args: cpi::instruction::Sell, +) -> Instruction { + let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap(); + Instruction::new_with_bytes( + constants::accounts::PUMPFUN, + &args.data(), + vec![ + AccountMeta::new_readonly(PumpFun::get_global_pda(), false), + AccountMeta::new(*fee_recipient, false), + AccountMeta::new_readonly(*mint, false), + AccountMeta::new(bonding_curve, false), + AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false), + AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(constants::accounts::SYSTEM_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::ASSOCIATED_TOKEN_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::TOKEN_PROGRAM, false), + AccountMeta::new_readonly(constants::accounts::EVENT_AUTHORITY, false), + AccountMeta::new_readonly(constants::accounts::PUMPFUN, false), + ], + ) +} diff --git a/crates/pumpfun/src/lib.rs b/crates/pumpfun/src/lib.rs new file mode 100644 index 0000000..42bf38b --- /dev/null +++ b/crates/pumpfun/src/lib.rs @@ -0,0 +1,519 @@ +// #![doc = include_str!("../RUSTDOC.md")] + +pub mod accounts; +pub mod constants; +pub mod error; +pub mod instruction; +pub mod utils; + +use anchor_client::{ + solana_client::rpc_client::RpcClient, + solana_sdk::{ + commitment_config::CommitmentConfig, + pubkey::Pubkey, + signature::{Keypair, Signature}, + signer::Signer, + }, + Client, Cluster, Program, +}; +use anchor_spl::associated_token::{ + get_associated_token_address, + spl_associated_token_account::instruction::create_associated_token_account, +}; +use borsh::BorshDeserialize; +pub use pumpfun_cpi as cpi; +use solana_sdk::compute_budget::ComputeBudgetInstruction; +use std::sync::Arc; + +/// Configuration for priority fee compute unit parameters +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PriorityFee { + /// Maximum compute units that can be consumed by the transaction + pub limit: Option, + /// Price in micro-lamports per compute unit + pub price: Option, +} + +/// Main client for interacting with the Pump.fun program +pub struct PumpFun { + /// RPC client for Solana network requests + pub rpc: RpcClient, + /// Keypair used to sign transactions + pub payer: Arc, + /// Anchor client instance + pub client: Client>, + /// Anchor program instance + pub program: Program>, +} + +impl PumpFun { + /// Creates a new PumpFun client instance + /// + /// # Arguments + /// + /// * `cluster` - Solana cluster to connect to (e.g. devnet, mainnet-beta) + /// * `payer` - Keypair used to sign and pay for transactions + /// * `options` - Optional commitment config for transaction finality + /// * `ws` - Whether to use websocket connection instead of HTTP + /// + /// # Returns + /// + /// Returns a new PumpFun client instance configured with the provided parameters + pub fn new( + cluster: Cluster, + payer: Arc, + options: Option, + ws: Option, + ) -> Self { + // Create Solana RPC Client with either WS or HTTP endpoint + let rpc: RpcClient = RpcClient::new(if ws.unwrap_or(false) { + cluster.ws_url() + } else { + cluster.url() + }); + + // Create Anchor Client with optional commitment config + let client: Client> = if let Some(options) = options { + Client::new_with_options(cluster.clone(), payer.clone(), options) + } else { + Client::new(cluster.clone(), payer.clone()) + }; + + // Create Anchor Program instance for Pump.fun + let program: Program> = client.program(cpi::ID).unwrap(); + + // Return configured PumpFun client + Self { + rpc, + payer, + client, + program, + } + } + + /// Creates a new token with metadata by uploading metadata to IPFS and initializing on-chain accounts + /// + /// # Arguments + /// + /// * `mint` - Keypair for the new token mint account that will be created + /// * `metadata` - Token metadata including name, symbol, description and image file + /// * `priority_fee` - Optional priority fee configuration for compute units + /// + /// # Returns + /// + /// Returns the transaction signature if successful, or a ClientError if the operation fails + pub async fn create( + &self, + mint: &Keypair, + metadata: utils::CreateTokenMetadata, + priority_fee: Option, + ) -> Result { + // First upload metadata and image to IPFS + let ipfs: utils::TokenMetadataResponse = utils::create_token_metadata(metadata) + .await + .map_err(error::ClientError::UploadMetadataError)?; + + let mut request = self.program.request(); + + // Add priority fee if provided + if let Some(fee) = priority_fee { + if let Some(limit) = fee.limit { + let limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(limit); + request = request.instruction(limit_ix); + } + + if let Some(price) = fee.price { + let price_ix = ComputeBudgetInstruction::set_compute_unit_price(price); + request = request.instruction(price_ix); + } + } + + // Add create token instruction + request = request.instruction(instruction::create( + &self.payer.clone().as_ref(), + mint, + cpi::instruction::Create { + _name: ipfs.metadata.name, + _symbol: ipfs.metadata.symbol, + _uri: ipfs.metadata.image, + }, + )); + + // Add signers + request = request.signer(&self.payer).signer(mint); + + // Send transaction + let signature: Signature = request + .send() + .await + .map_err(error::ClientError::AnchorClientError)?; + + Ok(signature) + } + + /// Creates a new token and immediately buys an initial amount in a single atomic transaction + /// + /// # Arguments + /// + /// * `mint` - Keypair for the new token mint + /// * `metadata` - Token metadata to upload to IPFS + /// * `amount_sol` - Amount of SOL to spend on initial buy in lamports + /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%). Defaults to 500 + /// * `priority_fee` - Optional priority fee configuration for compute units + /// + /// # Returns + /// + /// Returns the transaction signature if successful, or a ClientError if the operation fails + pub async fn create_and_buy( + &self, + mint: &Keypair, + metadata: utils::CreateTokenMetadata, + amount_sol: u64, + slippage_basis_points: Option, + priority_fee: Option, + ) -> Result { + // Upload metadata to IPFS first + let ipfs: utils::TokenMetadataResponse = utils::create_token_metadata(metadata) + .await + .map_err(error::ClientError::UploadMetadataError)?; + + // Get accounts and calculate buy amounts + let global_account = self.get_global_account()?; + let buy_amount = global_account.get_initial_buy_price(amount_sol); + let buy_amount_with_slippage = + utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(500)); + + let mut request = self.program.request(); + + // Add priority fee if provided + if let Some(fee) = priority_fee { + if let Some(limit) = fee.limit { + let limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(limit); + request = request.instruction(limit_ix); + } + + if let Some(price) = fee.price { + let price_ix = ComputeBudgetInstruction::set_compute_unit_price(price); + request = request.instruction(price_ix); + } + } + + // Add create token instruction + request = request.instruction(instruction::create( + &self.payer.clone().as_ref(), + mint, + cpi::instruction::Create { + _name: ipfs.metadata.name, + _symbol: ipfs.metadata.symbol, + _uri: ipfs.metadata.image, + }, + )); + + // Create Associated Token Account if needed + let ata: Pubkey = get_associated_token_address(&self.payer.pubkey(), &mint.pubkey()); + if self.rpc.get_account(&ata).is_err() { + request = request.instruction(create_associated_token_account( + &self.payer.pubkey(), + &self.payer.pubkey(), + &mint.pubkey(), + &constants::accounts::TOKEN_PROGRAM, + )); + } + + // Add buy instruction + request = request.instruction(instruction::buy( + &self.payer.clone().as_ref(), + &mint.pubkey(), + &global_account.fee_recipient, + cpi::instruction::Buy { + _amount: buy_amount, + _max_sol_cost: buy_amount_with_slippage, + }, + )); + + // Add signers and send transaction + let signature: Signature = request + .signer(&self.payer) + .signer(mint) + .send() + .await + .map_err(error::ClientError::AnchorClientError)?; + + Ok(signature) + } + + /// Buys tokens from a bonding curve by spending SOL + /// + /// # Arguments + /// + /// * `mint` - Public key of the token mint to buy + /// * `amount_sol` - Amount of SOL to spend in lamports + /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%). Defaults to 500 + /// * `priority_fee` - Optional priority fee configuration for compute units + /// + /// # Returns + /// + /// Returns the transaction signature if successful, or a ClientError if the operation fails + pub async fn buy( + &self, + mint: &Pubkey, + amount_sol: u64, + slippage_basis_points: Option, + priority_fee: Option, + ) -> Result { + // Get accounts and calculate buy amounts + let global_account = self.get_global_account()?; + let bonding_curve_account = self.get_bonding_curve_account(mint)?; + let buy_amount = bonding_curve_account + .get_buy_price(amount_sol) + .map_err(error::ClientError::BondingCurveError)?; + let buy_amount_with_slippage = + utils::calculate_with_slippage_buy(amount_sol, slippage_basis_points.unwrap_or(500)); + + let mut request = self.program.request(); + + // Add priority fee if provided + if let Some(fee) = priority_fee { + if let Some(limit) = fee.limit { + let limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(limit); + request = request.instruction(limit_ix); + } + + if let Some(price) = fee.price { + let price_ix = ComputeBudgetInstruction::set_compute_unit_price(price); + request = request.instruction(price_ix); + } + } + + // Create Associated Token Account if needed + let ata: Pubkey = get_associated_token_address(&self.payer.pubkey(), mint); + if self.rpc.get_account(&ata).is_err() { + request = request.instruction(create_associated_token_account( + &self.payer.pubkey(), + &self.payer.pubkey(), + mint, + &constants::accounts::TOKEN_PROGRAM, + )); + } + + // Add buy instruction + request = request.instruction(instruction::buy( + &self.payer.clone().as_ref(), + mint, + &global_account.fee_recipient, + cpi::instruction::Buy { + _amount: buy_amount, + _max_sol_cost: buy_amount_with_slippage, + }, + )); + + // Add signer + request = request.signer(&self.payer); + + // Send transaction + let signature: Signature = request + .send() + .await + .map_err(error::ClientError::AnchorClientError)?; + + Ok(signature) + } + + /// Sells tokens back to the bonding curve in exchange for SOL + /// + /// # Arguments + /// + /// * `mint` - Public key of the token mint to sell + /// * `amount_token` - Optional amount of tokens to sell in base units. If None, sells entire balance + /// * `slippage_basis_points` - Optional maximum acceptable slippage in basis points (1 bp = 0.01%). Defaults to 500 + /// * `priority_fee` - Optional priority fee configuration for compute units + /// + /// # Returns + /// + /// Returns the transaction signature if successful, or a ClientError if the operation fails + pub async fn sell( + &self, + mint: &Pubkey, + amount_token: Option, + slippage_basis_points: Option, + priority_fee: Option, + ) -> Result { + // Get accounts and calculate sell amounts + let ata: Pubkey = get_associated_token_address(&self.payer.pubkey(), mint); + let balance = self.rpc.get_token_account_balance(&ata).unwrap(); + let balance_u64: u64 = balance.amount.parse::().unwrap(); + let _amount = amount_token.unwrap_or(balance_u64); + let global_account = self.get_global_account()?; + let bonding_curve_account = self.get_bonding_curve_account(mint)?; + let min_sol_output = bonding_curve_account + .get_sell_price(_amount, global_account.fee_basis_points) + .map_err(error::ClientError::BondingCurveError)?; + let _min_sol_output = utils::calculate_with_slippage_sell( + min_sol_output, + slippage_basis_points.unwrap_or(500), + ); + + let mut request = self.program.request(); + + // Add priority fee if provided + if let Some(fee) = priority_fee { + if let Some(limit) = fee.limit { + let limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(limit); + request = request.instruction(limit_ix); + } + + if let Some(price) = fee.price { + let price_ix = ComputeBudgetInstruction::set_compute_unit_price(price); + request = request.instruction(price_ix); + } + } + + // Add sell instruction + request = request.instruction(instruction::sell( + &self.payer.clone().as_ref(), + mint, + &global_account.fee_recipient, + cpi::instruction::Sell { + _amount, + _min_sol_output, + }, + )); + + // Add signer + request = request.signer(&self.payer); + + // Send transaction + let signature: Signature = request + .send() + .await + .map_err(error::ClientError::AnchorClientError)?; + + Ok(signature) + } + + /// Gets the Program Derived Address (PDA) for the global state account + /// + /// # Returns + /// + /// Returns the PDA public key derived from the GLOBAL_SEED + pub fn get_global_pda() -> Pubkey { + let seeds: &[&[u8]; 1] = &[constants::seeds::GLOBAL_SEED]; + let program_id: &Pubkey = &cpi::ID; + Pubkey::find_program_address(seeds, program_id).0 + } + + /// Gets the Program Derived Address (PDA) for the mint authority + /// + /// # Returns + /// + /// Returns the PDA public key derived from the MINT_AUTHORITY_SEED + pub fn get_mint_authority_pda() -> Pubkey { + let seeds: &[&[u8]; 1] = &[constants::seeds::MINT_AUTHORITY_SEED]; + let program_id: &Pubkey = &cpi::ID; + Pubkey::find_program_address(seeds, program_id).0 + } + + /// Gets the Program Derived Address (PDA) for a token's bonding curve account + /// + /// # Arguments + /// + /// * `mint` - Public key of the token mint + /// + /// # Returns + /// + /// Returns Some(PDA) if derivation succeeds, or None if it fails + pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option { + let seeds: &[&[u8]; 2] = &[constants::seeds::BONDING_CURVE_SEED, mint.as_ref()]; + let program_id: &Pubkey = &cpi::ID; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) + } + + /// Gets the Program Derived Address (PDA) for a token's metadata account + /// + /// # Arguments + /// + /// * `mint` - Public key of the token mint + /// + /// # Returns + /// + /// Returns the PDA public key for the token's metadata account + pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey { + let seeds: &[&[u8]; 3] = &[ + constants::seeds::METADATA_SEED, + constants::accounts::MPL_TOKEN_METADATA.as_ref(), + mint.as_ref(), + ]; + let program_id: &Pubkey = &constants::accounts::MPL_TOKEN_METADATA; + Pubkey::find_program_address(seeds, program_id).0 + } + + /// Gets the global state account data containing program-wide configuration + /// + /// # Returns + /// + /// Returns the deserialized GlobalAccount if successful, or a ClientError if the operation fails + pub fn get_global_account(&self) -> Result { + let global: Pubkey = Self::get_global_pda(); + + let account = self + .rpc + .get_account(&global) + .map_err(error::ClientError::SolanaClientError)?; + + accounts::GlobalAccount::try_from_slice(&account.data) + .map_err(error::ClientError::BorshError) + } + + /// Gets a token's bonding curve account data containing pricing parameters + /// + /// # Arguments + /// + /// * `mint` - Public key of the token mint + /// + /// # Returns + /// + /// Returns the deserialized BondingCurveAccount if successful, or a ClientError if the operation fails + pub fn get_bonding_curve_account( + &self, + mint: &Pubkey, + ) -> Result { + let bonding_curve_pda = + Self::get_bonding_curve_pda(mint).ok_or(error::ClientError::BondingCurveNotFound)?; + + let account = self + .rpc + .get_account(&bonding_curve_pda) + .map_err(error::ClientError::SolanaClientError)?; + + accounts::BondingCurveAccount::try_from_slice(&account.data) + .map_err(error::ClientError::BorshError) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anchor_client::solana_sdk::signer::keypair::Keypair; + + #[test] + fn test_new_client() { + let payer = Arc::new(Keypair::new()); + let client = PumpFun::new(Cluster::Devnet, Arc::clone(&payer), None, None); + assert_eq!(client.payer.pubkey(), payer.pubkey()); + } + + #[test] + fn test_get_pdas() { + let mint = Keypair::new(); + let global_pda = PumpFun::get_global_pda(); + let mint_authority_pda = PumpFun::get_mint_authority_pda(); + let bonding_curve_pda = PumpFun::get_bonding_curve_pda(&mint.pubkey()); + let metadata_pda = PumpFun::get_metadata_pda(&mint.pubkey()); + + assert!(global_pda != Pubkey::default()); + assert!(mint_authority_pda != Pubkey::default()); + assert!(bonding_curve_pda.is_some()); + assert!(metadata_pda != Pubkey::default()); + } +} diff --git a/crates/pumpfun/src/utils/mod.rs b/crates/pumpfun/src/utils/mod.rs new file mode 100644 index 0000000..f8af304 --- /dev/null +++ b/crates/pumpfun/src/utils/mod.rs @@ -0,0 +1,270 @@ +//! Utilities for working with token metadata and IPFS uploads. +//! +//! This module provides functionality for creating and managing token metadata, +//! including uploading image and metadata to IPFS via the Pump.fun API. + +use isahc::AsyncReadResponseExt; +use serde::{Deserialize, Serialize}; +use std::{fs::File, io::Read}; + +/// Metadata structure for a token, matching the format expected by Pump.fun. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenMetadata { + /// Name of the token + pub name: String, + /// Token symbol (e.g. "BTC") + pub symbol: String, + /// Description of the token + pub description: String, + /// IPFS URL of the token's image + pub image: String, + /// Whether to display the token's name + pub show_name: bool, + /// Creation timestamp/source + pub created_on: String, + /// Twitter handle + pub twitter: Option, + /// Telegram handle + pub telegram: Option, + /// Website URL + pub website: Option, +} + +/// Response received after successfully uploading token metadata. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenMetadataResponse { + /// The uploaded token metadata + pub metadata: TokenMetadata, + /// IPFS URI where the metadata is stored + pub metadata_uri: String, +} + +/// Parameters for creating new token metadata. +#[derive(Debug, Clone)] +pub struct CreateTokenMetadata { + /// Name of the token + pub name: String, + /// Token symbol (e.g. "BTC") + pub symbol: String, + /// Description of the token + pub description: String, + /// Path to the token's image file + pub file: String, + /// Optional Twitter handle + pub twitter: Option, + /// Optional Telegram group + pub telegram: Option, + /// Optional website URL + pub website: Option, +} + +/// Creates and uploads token metadata to IPFS via the Pump.fun API. +/// +/// This function takes token metadata and an image file, constructs a multipart form request, +/// and uploads it to the Pump.fun IPFS API endpoint. The metadata and image are stored on IPFS +/// and the function returns the IPFS locations. +/// +/// # Arguments +/// +/// * `metadata` - Token metadata and image file information +/// +/// # Returns +/// +/// Returns a `Result` containing the `TokenMetadataResponse` with IPFS locations on success, +/// or an error if the upload fails. +/// +/// # Examples +/// +/// ```rust,no_run +/// use pumpfun::utils::{CreateTokenMetadata, create_token_metadata}; +/// +/// # async fn run() -> Result<(), Box> { +/// let metadata = CreateTokenMetadata { +/// name: "My Token".to_string(), +/// symbol: "MT".to_string(), +/// description: "A test token".to_string(), +/// file: "path/to/image.png".to_string(), +/// twitter: None, +/// telegram: None, +/// website: Some("https://example.com".to_string()), +/// }; +/// +/// let response = create_token_metadata(metadata).await?; +/// println!("Metadata URI: {}", response.metadata_uri); +/// # Ok(()) +/// # } +/// ``` +pub async fn create_token_metadata( + metadata: CreateTokenMetadata, +) -> Result> { + let boundary = "------------------------f4d9c2e8b7a5310f"; + let mut body = Vec::new(); + + // Helper function to append form data + fn append_text_field(body: &mut Vec, boundary: &str, name: &str, value: &str) { + body.extend_from_slice(b"--"); + body.extend_from_slice(boundary.as_bytes()); + body.extend_from_slice(b"\r\n"); + body.extend_from_slice( + format!("Content-Disposition: form-data; name=\"{}\"\r\n\r\n", name).as_bytes(), + ); + body.extend_from_slice(value.as_bytes()); + body.extend_from_slice(b"\r\n"); + } + + // Append form fields + append_text_field(&mut body, boundary, "name", &metadata.name); + append_text_field(&mut body, boundary, "symbol", &metadata.symbol); + append_text_field(&mut body, boundary, "description", &metadata.description); + if let Some(twitter) = metadata.twitter { + append_text_field(&mut body, boundary, "twitter", &twitter); + } + if let Some(telegram) = metadata.telegram { + append_text_field(&mut body, boundary, "telegram", &telegram); + } + if let Some(website) = metadata.website { + append_text_field(&mut body, boundary, "website", &website); + } + append_text_field(&mut body, boundary, "showName", "true"); + + // Append file part + body.extend_from_slice(b"--"); + body.extend_from_slice(boundary.as_bytes()); + body.extend_from_slice(b"\r\n"); + body.extend_from_slice(b"Content-Disposition: form-data; name=\"file\"; filename=\"file\"\r\n"); + body.extend_from_slice(b"Content-Type: application/octet-stream\r\n\r\n"); + + // Read the file contents + let mut file = File::open(&metadata.file)?; + let mut file_contents = Vec::new(); + file.read_to_end(&mut file_contents)?; + body.extend_from_slice(&file_contents); + + // Close the boundary + body.extend_from_slice(b"\r\n--"); + body.extend_from_slice(boundary.as_bytes()); + body.extend_from_slice(b"--\r\n"); + + let client = isahc::HttpClient::new()?; + let request = isahc::Request::builder() + .method("POST") + .uri("https://pump.fun/api/ipfs") + .header( + "Content-Type", + format!("multipart/form-data; boundary={}", boundary), + ) + .header("Content-Length", body.len() as u64) + .body(isahc::AsyncBody::from(body))?; + + // Send request and print response + let mut response = client.send_async(request).await?; + let text = response.text().await?; + let json: TokenMetadataResponse = serde_json::from_str(&text)?; + + Ok(json) +} + +/// Calculates the maximum amount to pay when buying tokens, accounting for slippage tolerance +/// +/// # Arguments +/// * `amount` - The base amount in lamports (1 SOL = 1,000,000,000 lamports) +/// * `basis_points` - The slippage tolerance in basis points (1% = 100 basis points) +/// +/// # Returns +/// The maximum amount to pay, including slippage tolerance +/// +/// # Example +/// ```rust +/// use pumpfun::utils; +/// +/// let amount = 1_000_000_000; // 1 SOL in lamports +/// let slippage = 100; // 1% slippage tolerance +/// +/// let max_amount = utils::calculate_with_slippage_buy(amount, slippage); +/// assert_eq!(max_amount, 1_010_000_000); // 1.01 SOL +/// ``` +pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 { + amount + (amount * basis_points) / 10000 +} + +/// Calculates the minimum amount to receive when selling tokens, accounting for slippage tolerance +/// +/// # Arguments +/// * `amount` - The base amount in lamports (1 SOL = 1,000,000,000 lamports) +/// * `basis_points` - The slippage tolerance in basis points (1% = 100 basis points) +/// +/// # Returns +/// The minimum amount to receive, accounting for slippage tolerance +/// +/// # Example +/// ```rust +/// use pumpfun::utils; +/// +/// let amount = 1_000_000_000; // 1 SOL in lamports +/// let slippage = 100; // 1% slippage tolerance +/// +/// let min_amount = utils::calculate_with_slippage_sell(amount, slippage); +/// assert_eq!(min_amount, 990_000_000); // 0.99 SOL +/// ``` +pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 { + amount - (amount * basis_points) / 10000 +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::write; + + #[tokio::test] + async fn test_create_token_metadata() { + // Create a temporary file + let temp_dir = std::env::temp_dir(); + let file_path = temp_dir.join("test_image.png"); + write(&file_path, b"fake image data").unwrap(); + + // Create test metadata + let metadata = CreateTokenMetadata { + name: "Test Token".to_string(), + symbol: "TEST".to_string(), + description: "Test Description".to_string(), + file: file_path.to_str().unwrap().to_string(), + twitter: None, + telegram: None, + website: Some("https://example.com".to_string()), + }; + + // Call the function + let result = create_token_metadata(metadata).await; + + // Assert the result + assert!(result.is_ok()); + let response = result.unwrap(); + + // Verify response fields + assert_eq!(response.metadata.name, "Test Token"); + assert_eq!(response.metadata.symbol, "TEST"); + assert_eq!(response.metadata.description, "Test Description"); + assert!(response.metadata.image.starts_with("https://ipfs.io/ipfs/")); + assert!(response.metadata.show_name); + assert_eq!(response.metadata.created_on, "https://pump.fun"); + assert!(response.metadata_uri.starts_with("https://ipfs.io/ipfs/")); + } + + #[test] + fn test_calculate_with_slippage_buy() { + let amount = 1_000_000_000; // 1 SOL in lamports + let slippage = 100; // 1% slippage tolerance + let max_amount = calculate_with_slippage_buy(amount, slippage); + assert_eq!(max_amount, 1_010_000_000); // 1.01 SOL + } + + #[test] + fn test_calculate_with_slippage_sell() { + let amount = 1_000_000_000; // 1 SOL in lamports + let slippage = 100; // 1% slippage tolerance + let min_amount = calculate_with_slippage_sell(amount, slippage); + assert_eq!(min_amount, 990_000_000); // 0.99 SOL + } +}