first commit

This commit is contained in:
William
2024-12-31 16:51:58 +08:00
parent 7d022ed626
commit 120b085119
21 changed files with 4217 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
[workspace]
members = ["crates/*"]
resolver = "2"
+57 -1
View File
@@ -1 +1,57 @@
# pumpfun-sdk
# 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<Keypair>`.
## 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.
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "pumpfun-cpi"
version = "1.1.0"
edition = "2021"
authors = ["Rapheal Bamidele <nhuxhr@gmail.com>"]
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
+201
View File
@@ -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.
+14
View File
@@ -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
File diff suppressed because it is too large Load Diff
+805
View File
@@ -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"
}
]
}
+3
View File
@@ -0,0 +1,3 @@
anchor_gen::generate_cpi_crate!("idl.classic.json");
declare_id!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "mai3-pumpfun-sdk"
version = "2.0.1"
edition = "2021"
authors = ["Rapheal Bamidele <nhuxhr@gmail.com>"]
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"
+201
View File
@@ -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.
+21
View File
@@ -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.
+133
View File
@@ -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<PriorityFee> = 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.
+111
View File
@@ -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<PriorityFee> = 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
@@ -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<u64, &'static str> {
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<u64, &'static str> {
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);
}
}
+201
View File
@@ -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);
}
}
+14
View File
@@ -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::*;
+53
View File
@@ -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");
}
+74
View File
@@ -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<dyn std::error::Error>),
/// 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,
}
}
}
+148
View File
@@ -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),
],
)
}
+519
View File
@@ -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<u32>,
/// Price in micro-lamports per compute unit
pub price: Option<u64>,
}
/// 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<Keypair>,
/// Anchor client instance
pub client: Client<Arc<Keypair>>,
/// Anchor program instance
pub program: Program<Arc<Keypair>>,
}
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<Keypair>,
options: Option<CommitmentConfig>,
ws: Option<bool>,
) -> 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<Arc<Keypair>> = 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<Arc<Keypair>> = 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<PriorityFee>,
) -> Result<Signature, error::ClientError> {
// 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<u64>,
priority_fee: Option<PriorityFee>,
) -> Result<Signature, error::ClientError> {
// 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<u64>,
priority_fee: Option<PriorityFee>,
) -> Result<Signature, error::ClientError> {
// 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<u64>,
slippage_basis_points: Option<u64>,
priority_fee: Option<PriorityFee>,
) -> Result<Signature, error::ClientError> {
// 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::<u64>().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<Pubkey> {
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<accounts::GlobalAccount, error::ClientError> {
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<accounts::BondingCurveAccount, error::ClientError> {
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());
}
}
+270
View File
@@ -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<String>,
/// Telegram handle
pub telegram: Option<String>,
/// Website URL
pub website: Option<String>,
}
/// 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<String>,
/// Optional Telegram group
pub telegram: Option<String>,
/// Optional website URL
pub website: Option<String>,
}
/// 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<dyn std::error::Error>> {
/// 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<TokenMetadataResponse, Box<dyn std::error::Error>> {
let boundary = "------------------------f4d9c2e8b7a5310f";
let mut body = Vec::new();
// Helper function to append form data
fn append_text_field(body: &mut Vec<u8>, 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
}
}