add yellowstone grpc

This commit is contained in:
William
2025-01-23 17:48:10 +08:00
parent 429fc78ecf
commit 42b7267c2c
36 changed files with 897 additions and 3356 deletions
+44 -3
View File
@@ -1,3 +1,44 @@
[workspace] [package]
members = ["crates/*"] name = "mai3-pumpfun-sdk"
resolver = "2" version = "2.4.5"
edition = "2021"
authors = ["William <william@mai3.io>"]
repository = "https://github.com/MiracleAI-Labs/pumpfun-sdk"
description = "Rust SDK to interact with the Pump.fun Solana program."
license = "MIT"
keywords = ["solana", "memecoins", "pumpfun", "pumpfun-sdk"]
readme = "README.md"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
solana-sdk = "2.1.7"
solana-client = "2.1.7"
solana-transaction-status = "2.1.7"
spl-token = "7.0.0"
spl-associated-token-account = "6.0.0"
borsh = { version = "1.5.3", features = ["derive"] }
isahc = "1.7.2"
mpl-token-metadata = "5.1.0"
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.134"
futures = "0.3.31"
futures-util = "0.3.31"
base64 = "0.22.1"
bs58 = "0.5.1"
rand = "0.8.5"
bincode = "1.3.3"
anyhow = "1.0.90"
reqwest = { version = "0.11.27", features = ["json"] }
tonic = { version = "0.12.3", features = ["tls", "tls-webpki-roots", "tls-roots"] }
tokio = { version = "1.42.0" , features = ["full", "rt-multi-thread"]}
yellowstone-grpc-client = { version = "4.1.0" }
yellowstone-grpc-proto = { version = "4.1.1" }
rustls = { version = "0.23.20", features = ["ring"] }
dotenvy = "0.15.7"
pretty_env_logger = "0.5.0"
log = "0.4.22"
chrono = "0.4.39"
regex = "1"
+6 -8
View File
@@ -28,7 +28,7 @@ use mai3_pumpfun_sdk::instruction::{
logs_events::DexEvent, logs_events::DexEvent,
logs_subscribe::{tokens_subscription, stop_subscription} logs_subscribe::{tokens_subscription, stop_subscription}
}; };
use anchor_client::solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::commitment_config::CommitmentConfig;
println!("Starting token subscription..."); println!("Starting token subscription...");
@@ -74,14 +74,12 @@ println!("Ended token subscription.");
### pumpfun Create, Buy, Sell ### pumpfun Create, Buy, Sell
```rust ```rust
use anchor_client::{ use solana_sdk::{
solana_sdk::{ native_token::LAMPORTS_PER_SOL,
native_token::LAMPORTS_PER_SOL, signature::{Keypair, Signature},
signature::{Keypair, Signature}, signer::Signer,
signer::Signer,
},
Cluster,
}; };
use mai3_pumpfun_sdk::{accounts::BondingCurveAccount, utils::CreateTokenMetadata, PriorityFee, PumpFun}; use mai3_pumpfun_sdk::{accounts::BondingCurveAccount, utils::CreateTokenMetadata, PriorityFee, PumpFun};
// Create a new PumpFun client // Create a new PumpFun client
-27
View File
@@ -1,27 +0,0 @@
[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
@@ -1,201 +0,0 @@
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
@@ -1,14 +0,0 @@
# 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
@@ -1,805 +0,0 @@
{
"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
@@ -1,3 +0,0 @@
anchor_gen::generate_cpi_crate!("idl.classic.json");
declare_id!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
-34
View File
@@ -1,34 +0,0 @@
[package]
name = "mai3-pumpfun-sdk"
version = "2.4.4"
edition = "2021"
authors = ["William <william@mai3.io>"]
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.42.0"
futures = "0.3.31"
futures-util = "0.3.31"
base64 = "0.22.1"
bs58 = "0.5.1"
rand = "0.8.5"
bincode = "1.3.3"
anyhow = "1.0.90"
reqwest = { version = "0.11.27", features = ["json"] }
-201
View File
@@ -1,201 +0,0 @@
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
@@ -1,21 +0,0 @@
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
@@ -1,133 +0,0 @@
# 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 mai3-pumpfun-sdk
```
## 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 mai3_pumpfun_sdk::{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
@@ -1,111 +0,0 @@
# 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 mai3_pumpfun_sdk::{accounts::BondingCurveAccount, utils::CreateTokenMetadata, PriorityFee, PumpFun};
// Create a new PumpFun client
let payer: Keypair = Keypair::new();
let client: PumpFun = PumpFun::new(Cluster::Mainnet, None, &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
-730
View File
@@ -1,730 +0,0 @@
use {
super::jito::JitoClient, crate::{InfrastructureError, InfrastructureResult}, borsh::{BorshDeserialize, BorshSerialize}, solana_client::rpc_client::RpcClient, solana_sdk::{
commitment_config::CommitmentConfig, compute_budget::ComputeBudgetInstruction, instruction::{AccountMeta, Instruction}, pubkey::Pubkey, signature::{Keypair, Signature, Signer}, system_instruction, system_program, sysvar, transaction::Transaction
}, spl_associated_token_account::{get_associated_token_address, instruction::create_associated_token_account}, spl_token, std::str::FromStr
};
const PROGRAM_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
const DEFAULT_SLIPPAGE: u64 = 500; // 10%
const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 68_000;
const DEFAULT_COMPUTE_UNIT_PRICE: u64 = 400_000;
pub struct PumpFunSDK {
client: RpcClient,
jito_client: Option<JitoClient>,
}
impl Clone for PumpFunSDK {
fn clone(&self) -> Self {
Self {
client: RpcClient::new_with_commitment(
self.client.url().to_string(),
self.client.commitment()
),
jito_client: self.jito_client.clone(),
}
}
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct GlobalAccount {
pub discriminator: u64,
pub initialized: bool,
pub authority: Pubkey,
pub fee_recipient: Pubkey,
pub initial_virtual_token_reserves: u64,
pub initial_virtual_sol_reserves: u64,
pub initial_real_token_reserves: u64,
pub token_total_supply: u64,
pub fee_basis_points: u64,
}
impl GlobalAccount {
pub fn get_initial_buy_price(&self, amount: u64) -> u64 {
if amount == 0 {
return 0;
}
let n = self.initial_virtual_sol_reserves
.checked_mul(self.initial_virtual_token_reserves)
.unwrap_or(0);
let i = self.initial_virtual_sol_reserves
.checked_add(amount)
.unwrap_or(u64::MAX);
let r = n.checked_div(i)
.map(|x| x.checked_add(1).unwrap_or(u64::MAX))
.unwrap_or(0);
let s = self.initial_virtual_token_reserves
.checked_sub(r)
.unwrap_or(0);
if s < self.initial_real_token_reserves {
s
} else {
self.initial_real_token_reserves
}
}
pub fn from_buffer(buffer: &[u8]) -> InfrastructureResult<Self> {
Self::try_from_slice(buffer)
.map_err(|e| InfrastructureError::Other(e.to_string()))
}
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct BondingCurveAccount {
pub discriminator: u64,
pub virtual_token_reserves: u64,
pub virtual_sol_reserves: u64,
pub real_token_reserves: u64,
pub real_sol_reserves: u64,
pub token_total_supply: u64,
pub complete: bool,
}
impl BondingCurveAccount {
pub fn get_buy_price(&self, amount: u64) -> InfrastructureResult<u64> {
if self.complete {
return Err(InfrastructureError::Other("Curve is complete".to_string()));
}
if amount == 0 {
return Ok(0);
}
// 使用 u128 进行计算,模仿 TypeScript 中的 bigint 计算
let virtual_sol = self.virtual_sol_reserves as u128;
let virtual_token = self.virtual_token_reserves as u128;
let amount = amount as u128;
// n = virtualSolReserves * virtualTokenReserves
let n = virtual_sol
.checked_mul(virtual_token)
.ok_or_else(|| InfrastructureError::Other("Overflow in virtual reserves multiplication".to_string()))?;
// i = virtualSolReserves + amount
let i = virtual_sol
.checked_add(amount)
.ok_or_else(|| InfrastructureError::Other("Overflow in sol reserves addition".to_string()))?;
// r = n / i + 1
let r = n.checked_div(i)
.ok_or_else(|| InfrastructureError::Other("Division by zero".to_string()))?
.checked_add(1)
.ok_or_else(|| InfrastructureError::Other("Overflow in token calculation".to_string()))?;
// s = virtualTokenReserves - r
let s = virtual_token
.checked_sub(r)
.ok_or_else(|| InfrastructureError::Other("Underflow in token calculation".to_string()))?;
// 确保结果不超过 u64
if s > u64::MAX as u128 {
return Err(InfrastructureError::Other("Result exceeds u64 max value".to_string()));
}
let result = s as u64;
// Return the minimum of s and realTokenReserves
Ok(if result < self.real_token_reserves {
result
} else {
self.real_token_reserves
})
}
pub fn get_sell_price(&self, amount: u64, fee_basis_points: u64) -> InfrastructureResult<u64> {
if self.complete {
return Err(InfrastructureError::Other("Curve is complete".to_string()));
}
if amount == 0 {
return Ok(0);
}
let amount = amount as u128;
let virtual_sol = self.virtual_sol_reserves as u128;
let virtual_token = self.virtual_token_reserves as u128;
let fee_basis_points = fee_basis_points as u128;
// n = (amount * virtualSolReserves) / (virtualTokenReserves + amount)
let n = amount
.checked_mul(virtual_sol)
.ok_or_else(|| InfrastructureError::Other("Overflow in sol calculation".to_string()))?
.checked_div(
virtual_token
.checked_add(amount)
.ok_or_else(|| InfrastructureError::Other("Overflow in token addition".to_string()))?
)
.ok_or_else(|| InfrastructureError::Other("Division by zero".to_string()))?;
// a = (n * feeBasisPoints) / 10000
let a = n
.checked_mul(fee_basis_points)
.ok_or_else(|| InfrastructureError::Other("Overflow in fee calculation".to_string()))?
.checked_div(10000)
.ok_or_else(|| InfrastructureError::Other("Division by zero in fee calculation".to_string()))?;
// result = n - a
let result = n
.checked_sub(a)
.ok_or_else(|| InfrastructureError::Other("Underflow in final calculation".to_string()))?;
if result > u64::MAX as u128 {
return Err(InfrastructureError::Other("Result exceeds u64 max value".to_string()));
}
Ok(result as u64)
}
pub fn get_market_cap_sol(&self) -> u64 {
if self.virtual_token_reserves == 0 {
return 0;
}
self.token_total_supply
.checked_mul(self.virtual_sol_reserves)
.and_then(|n| n.checked_div(self.virtual_token_reserves))
.unwrap_or(0)
}
pub fn get_final_market_cap_sol(&self, fee_basis_points: u64) -> Result<u64, Box<dyn std::error::Error>> {
let total_sell_value = self.get_buy_out_price(self.real_token_reserves, fee_basis_points)?;
let total_virtual_value = self.virtual_sol_reserves
.checked_add(total_sell_value)
.ok_or("Overflow in virtual value calculation")?;
let total_virtual_tokens = self.virtual_token_reserves
.checked_sub(self.real_token_reserves)
.ok_or("Underflow in virtual tokens calculation")?;
if total_virtual_tokens == 0 {
return Ok(0);
}
self.token_total_supply
.checked_mul(total_virtual_value)
.ok_or("Overflow in market cap calculation")?
.checked_div(total_virtual_tokens)
.ok_or("Division by zero in market cap calculation".into())
}
pub fn get_buy_out_price(&self, amount: u64, fee_basis_points: u64) -> Result<u64, Box<dyn std::error::Error>> {
let sol_tokens = if amount < self.real_sol_reserves {
self.real_sol_reserves
} else {
amount
};
let total_sell_value = sol_tokens
.checked_mul(self.virtual_sol_reserves)
.ok_or("Overflow in sell value calculation")?
.checked_div(
self.virtual_token_reserves
.checked_sub(sol_tokens)
.ok_or("Underflow in token reserves calculation")?
)
.ok_or("Division by zero")?
.checked_add(1)
.ok_or("Overflow in sell value calculation")?;
let fee = total_sell_value
.checked_mul(fee_basis_points)
.ok_or("Overflow in fee calculation")?
.checked_div(10000)
.ok_or("Division by zero in fee calculation")?;
total_sell_value
.checked_add(fee)
.ok_or("Overflow in final price calculation".into())
}
pub fn from_buffer(buffer: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
Self::try_from_slice(buffer).map_err(|e| e.into())
}
}
impl PumpFunSDK {
pub fn new(rpc_url: &str) -> Self {
Self {
client: RpcClient::new_with_commitment(
rpc_url.to_string(),
CommitmentConfig::processed(),
),
jito_client: None,
}
}
pub fn with_jito(mut self, jito_endpoint: &str) -> Self {
self.jito_client = Some(JitoClient::new(jito_endpoint));
self
}
// 添加公共方法来获取代币余额
pub async fn get_token_balance(&self, wallet_address: &Pubkey, mint: &Pubkey) -> InfrastructureResult<u64> {
// 获取用户代币账户地址
let user_token_account = get_associated_token_address(&wallet_address, &mint);
// 首先检查账户是否存在
match self.client.get_token_account_balance(&user_token_account) {
Ok(balance) => {
balance.amount
.parse::<u64>()
.map_err(|e| InfrastructureError::Other(e.to_string()))
},
Err(_) => Ok(0) // 如果账户不存在,返回 None
}
}
// 添加公共方法来获取 SOL 余额
pub async fn get_sol_balance(&self, address: &Pubkey) -> Result<u64, Box<dyn std::error::Error>> {
Ok(self.client.get_balance(address)?)
}
// Buy token with SOL
pub async fn buy(
&self,
buyer: &Keypair,
mint: Pubkey,
buy_amount_sol: u64,
slippage_basis_points: Option<u64>,
) -> InfrastructureResult<Signature> {
let start_time = std::time::Instant::now();
println!("Starting buy operation...");
// 1. 获取 bonding curve 账户
let bonding_curve_start = std::time::Instant::now();
let bonding_curve_address = self.get_bonding_curve_pda(&mint)?;
let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?;
println!("Bonding curve virtual sol reserves: {}", bonding_curve.virtual_sol_reserves);
println!("Bonding curve real sol reserves: {}", bonding_curve.real_sol_reserves);
println!("Got bonding curve account: {:?}ms", bonding_curve_start.elapsed().as_millis());
// 2. 计算买入数量和滑点
let buy_amount = bonding_curve.get_buy_price(buy_amount_sol)?;
let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE);
let max_sol_cost = calculate_with_slippage_buy(buy_amount_sol, slippage);
// 3. 获取全局账户
let global_start = std::time::Instant::now();
let global_account = self.get_global_account().await?;
println!("Got global account: {:?}ms", global_start.elapsed().as_millis());
// 4. 准备所有指令
let mut instructions = vec![];
instructions.push(
ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT)
);
// 5. 添加 ATA 创建指令
instructions.push(
create_associated_token_account(
&buyer.pubkey(), // payer
&buyer.pubkey(), // wallet
&mint, // mint
&spl_token::id(), // token program
),
);
// 6. 添加买入指令
instructions.push(
self.create_buy_instruction(
&buyer.pubkey(),
mint,
global_account.fee_recipient,
buy_amount,
max_sol_cost,
bonding_curve_address,
)?,
);
// 7. 创建并发送交易
let tx_start = std::time::Instant::now();
let recent_blockhash = self.client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&buyer.pubkey()),
&[buyer],
recent_blockhash,
);
let signature = self.client
.send_transaction(&transaction)
.map_err(|e| InfrastructureError::Other(format!("Transaction failed: {}", e)))?;
println!("Transaction sent and confirmed: {:?}ms", tx_start.elapsed().as_millis());
println!("Total buy operation time: {:?}ms", start_time.elapsed().as_millis());
Ok(signature)
}
// Sell token for SOL
pub async fn sell(
&self,
seller: &Keypair,
mint: Pubkey,
sell_amount: u64,
slippage_basis_points: Option<u64>,
) -> Result<Signature, Box<dyn std::error::Error>> {
let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE);
// 1. 获取账户信息
let bonding_curve_address = self.get_bonding_curve_pda(&mint)?;
let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?;
let global_account = self.get_global_account().await?;
// 2. 计算卖出价格和滑点
let sell_price = bonding_curve.get_sell_price(sell_amount, global_account.fee_basis_points)?;
let min_out_amount = calculate_with_slippage_sell(sell_price, slippage);
// 3. 构建交易指令
let instructions = vec![
// // 设置计算预算
// ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT),
// ComputeBudgetInstruction::set_compute_unit_price(DEFAULT_COMPUTE_UNIT_PRICE),
// 卖出指令
self.create_sell_instruction(
&seller.pubkey(),
mint,
global_account.fee_recipient,
sell_amount,
min_out_amount,
bonding_curve_address,
)?,
];
// 5. 发送交易
let recent_blockhash = self.client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&seller.pubkey()),
&[seller],
recent_blockhash,
);
let signature = self.client.send_transaction(&transaction)?;
println!("Transaction sent: {}", signature);
Ok(signature)
}
// 辅助函数:获取 bonding curve PDA
fn get_bonding_curve_pda(&self, mint: &Pubkey) -> InfrastructureResult<Pubkey> {
let seeds: &[&[u8]] = &[b"bonding-curve", mint.as_ref()];
let (pda, _) = Pubkey::find_program_address(
seeds,
&Pubkey::from_str(PROGRAM_ID).unwrap(),
);
Ok(pda)
}
// Helper functions
fn create_buy_instruction(
&self,
buyer: &Pubkey,
mint: Pubkey,
fee_recipient: Pubkey,
amount: u64,
max_sol_cost: u64,
bonding_curve_address: Pubkey,
) -> InfrastructureResult<Instruction> {
let associated_bonding_curve = get_associated_token_address(&bonding_curve_address, &mint);
let associated_user = get_associated_token_address(buyer, &mint);
let global_seeds: &[&[u8]] = &[b"global"];
let (global_pda, _) = Pubkey::find_program_address(
global_seeds,
&Pubkey::from_str(PROGRAM_ID).unwrap(),
);
let accounts = vec![
AccountMeta::new_readonly(global_pda, false),
AccountMeta::new(fee_recipient, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new(bonding_curve_address, false),
AccountMeta::new(associated_bonding_curve, false),
AccountMeta::new(associated_user, false),
AccountMeta::new(*buyer, true),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(Pubkey::from_str("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1").unwrap(), false),
AccountMeta::new_readonly(Pubkey::from_str(PROGRAM_ID).unwrap(), false),
];
let mut data = Vec::with_capacity(8 + 8 + 8);
data.extend_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // discriminator
data.extend_from_slice(&amount.to_le_bytes());
data.extend_from_slice(&max_sol_cost.to_le_bytes());
Ok(Instruction {
program_id: Pubkey::from_str(PROGRAM_ID).unwrap(),
accounts,
data,
})
}
fn create_sell_instruction(
&self,
seller: &Pubkey,
mint: Pubkey,
fee_recipient: Pubkey,
amount: u64,
min_sol_output: u64,
bonding_curve_address: Pubkey,
) -> InfrastructureResult<Instruction> {
let associated_bonding_curve = get_associated_token_address(&bonding_curve_address, &mint);
let associated_user = get_associated_token_address(seller, &mint);
let global_seeds: &[&[u8]] = &[b"global"];
let (global_pda, _) = Pubkey::find_program_address(
global_seeds,
&Pubkey::from_str(PROGRAM_ID).unwrap(),
);
let accounts = vec![
AccountMeta::new_readonly(global_pda, false),
AccountMeta::new(fee_recipient, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new(bonding_curve_address, false),
AccountMeta::new(associated_bonding_curve, false),
AccountMeta::new(associated_user, false),
AccountMeta::new(*seller, true),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(spl_associated_token_account::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(Pubkey::from_str("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1").unwrap(), false),
AccountMeta::new_readonly(Pubkey::from_str(PROGRAM_ID).unwrap(), false),
];
let mut data = Vec::with_capacity(8 + 8 + 8);
data.extend_from_slice(&[51, 230, 133, 164, 1, 127, 131, 173]); // discriminator
data.extend_from_slice(&amount.to_le_bytes());
data.extend_from_slice(&min_sol_output.to_le_bytes());
Ok(Instruction {
program_id: Pubkey::from_str(PROGRAM_ID).unwrap(),
accounts,
data,
})
}
pub async fn get_global_account(&self) -> InfrastructureResult<GlobalAccount> {
let seeds: &[&[u8]] = &[b"global"];
let (global_account_pda, _) = Pubkey::find_program_address(
seeds,
&Pubkey::from_str(PROGRAM_ID).unwrap(),
);
let account = self.client.get_account(&global_account_pda)
.map_err(|e| InfrastructureError::Other(e.to_string()))?;
GlobalAccount::from_buffer(&account.data)
.map_err(|e| InfrastructureError::Other(e.to_string()))
}
pub async fn get_bonding_curve_account(&self, bonding_curve_address: &Pubkey) -> InfrastructureResult<BondingCurveAccount> {
let account = self.client.get_account(&bonding_curve_address)
.map_err(|e| InfrastructureError::Other(e.to_string()))?;
BondingCurveAccount::from_buffer(&account.data)
.map_err(|e| InfrastructureError::Other(e.to_string()))
}
/// 使用 Jito MEV 发送买入交易
pub async fn buy_via_jito(
&self,
buyer: &Keypair,
mint: Pubkey,
buy_amount_sol: u64,
slippage_basis_points: Option<u64>,
) -> InfrastructureResult<Signature> {
let jito_client = self.jito_client.as_ref()
.ok_or_else(|| InfrastructureError::Other("Jito client not configured".to_string()))?;
let start_time = std::time::Instant::now();
println!("Starting Jito buy operation...");
// 1. 获取 bonding curve 账户
let bonding_curve_address = self.get_bonding_curve_pda(&mint)?;
let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?;
// 2. 计算买入数量和滑点
let buy_amount = bonding_curve.get_buy_price(buy_amount_sol)?;
let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE);
let max_sol_cost = calculate_with_slippage_buy(buy_amount_sol, slippage);
// 3. 获取全局账户
let global_account = self.get_global_account().await?;
// 4. 获取优先费用估算
let priority_fees = jito_client.estimate_priority_fees(&bonding_curve_address).await?;
// 计算每计算单元的优先费用(使用 Extreme 级别)
let priority_fee_per_cu = priority_fees.per_compute_unit.extreme;
// 完整的单位转换过程
let total_priority_fee_microlamports = priority_fee_per_cu as u128 * DEFAULT_COMPUTE_UNIT_LIMIT as u128;
let total_priority_fee_lamports = total_priority_fee_microlamports / 1_000_000;
let total_priority_fee_sol = total_priority_fee_lamports as f64 / 1_000_000_000.0;
println!("Priority fee details:");
println!(" Per CU (microlamports): {}", priority_fee_per_cu);
println!(" Total (lamports): {}", total_priority_fee_lamports);
println!(" Total (SOL): {:.9}", total_priority_fee_sol);
// 5. 获取 tip account
let tip_account = jito_client.get_tip_account().await?;
// 6. 准备所有指令
let mut instructions = vec![];
// 添加计算预算指令,包括优先费用
instructions.push(
ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT)
);
// 添加 ATA 创建指令
instructions.push(
create_associated_token_account(
&buyer.pubkey(),
&buyer.pubkey(),
&mint,
&spl_token::id(),
),
);
// 添加买入指令
instructions.push(
self.create_buy_instruction(
&buyer.pubkey(),
mint,
global_account.fee_recipient,
buy_amount,
max_sol_cost,
bonding_curve_address,
)?,
);
// 添加 tip 指令
instructions.push(
system_instruction::transfer(
&buyer.pubkey(),
&tip_account,
total_priority_fee_lamports as u64,
),
);
// 7. 创建并发送交易
let recent_blockhash = self.client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&buyer.pubkey()),
&[buyer],
recent_blockhash,
);
// 8. 通过 Jito 发送交易
let signature = jito_client.send_transaction(&transaction).await?;
println!("Total Jito buy operation time: {:?}ms", start_time.elapsed().as_millis());
Ok(signature)
}
pub fn get_jito_client(&self) -> Option<&JitoClient> {
self.jito_client.as_ref()
}
// 添加基于 Jito 的卖出方法
pub async fn sell_via_jito(
&self,
seller: &Keypair,
mint: Pubkey,
sell_amount: u64,
slippage_basis_points: Option<u64>,
) -> InfrastructureResult<Signature> {
let jito_client = self.jito_client.as_ref()
.ok_or_else(|| InfrastructureError::Other("Jito client not configured".to_string()))?;
let start_time = std::time::Instant::now();
println!("Starting Jito sell operation...");
// 1. 获取账户信息
let bonding_curve_address = self.get_bonding_curve_pda(&mint)?;
let bonding_curve = self.get_bonding_curve_account(&bonding_curve_address).await?;
let global_account = self.get_global_account().await?;
// 2. 计算卖出价格和滑点
let sell_price = bonding_curve.get_sell_price(sell_amount, global_account.fee_basis_points)?;
let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE);
let min_out_amount = calculate_with_slippage_sell(sell_price, slippage);
// 3. 获取优先费用估算
let priority_fees = jito_client.estimate_priority_fees(&bonding_curve_address).await?;
let priority_fee_per_cu = priority_fees.per_compute_unit.extreme;
// 完整的单位转换过程
let total_priority_fee_microlamports = priority_fee_per_cu as u128 * DEFAULT_COMPUTE_UNIT_LIMIT as u128;
let total_priority_fee_lamports = total_priority_fee_microlamports / 1_000_000;
let total_priority_fee_sol = total_priority_fee_lamports as f64 / 1_000_000_000.0;
println!("Priority fee details:");
println!(" Per CU (microlamports): {}", priority_fee_per_cu);
println!(" Total (lamports): {}", total_priority_fee_lamports);
println!(" Total (SOL): {:.9}", total_priority_fee_sol);
// 4. 获取 tip account
let tip_account = jito_client.get_tip_account().await?;
// 5. 准备所有指令
let mut instructions = vec![
// 添加计算预算指令
ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT_LIMIT),
// 卖出指令
self.create_sell_instruction(
&seller.pubkey(),
mint,
global_account.fee_recipient,
sell_amount,
min_out_amount,
bonding_curve_address,
)?,
// 添加 tip 指令
system_instruction::transfer(
&seller.pubkey(),
&tip_account,
total_priority_fee_lamports as u64,
),
];
// 6. 创建并发送交易
let recent_blockhash = self.client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&seller.pubkey()),
&[seller],
recent_blockhash,
);
// 7. 通过 Jito 发送交易
let signature = jito_client.send_transaction(&transaction).await?;
println!("Total Jito sell operation time: {:?}ms", start_time.elapsed().as_millis());
Ok(signature)
}
}
fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
amount.saturating_sub(
amount.saturating_mul(basis_points)
.saturating_div(10000)
)
}
// 添加滑点计算函数
fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount.saturating_add(
amount.saturating_mul(basis_points)
.saturating_div(10000)
)
}
@@ -23,7 +23,7 @@
//! - `new`: Creates a new global account instance //! - `new`: Creates a new global account instance
//! - `get_initial_buy_price`: Calculates the initial amount of tokens received for a given SOL amount //! - `get_initial_buy_price`: Calculates the initial amount of tokens received for a given SOL amount
use anchor_client::solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
/// Represents the global configuration account for token pricing and fees /// Represents the global configuration account for token pricing and fees
@@ -12,15 +12,13 @@
//! - `BorshError`: An error occurred while serializing or deserializing data using Borsh. //! - `BorshError`: An error occurred while serializing or deserializing data using Borsh.
//! - `SolanaClientError`: An error occurred while interacting with the Solana RPC client. //! - `SolanaClientError`: An error occurred while interacting with the Solana RPC client.
//! - `UploadMetadataError`: An error occurred while uploading metadata to IPFS. //! - `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. //! - `InvalidInput`: Invalid input parameters were provided.
//! - `InsufficientFunds`: Insufficient funds for a transaction. //! - `InsufficientFunds`: Insufficient funds for a transaction.
//! - `SimulationError`: Transaction simulation failed. //! - `SimulationError`: Transaction simulation failed.
//! - `RateLimitExceeded`: Rate limit exceeded. //! - `RateLimitExceeded`: Rate limit exceeded.
use anchor_client::solana_client;
use serde_json::Error; use serde_json::Error;
use anchor_client::solana_client::{ use solana_client::{
client_error::ClientError as SolanaClientError, client_error::ClientError as SolanaClientError,
pubsub_client::PubsubClientError pubsub_client::PubsubClientError
}; };
@@ -38,8 +36,6 @@ pub enum ClientError {
SolanaClientError(solana_client::client_error::ClientError), SolanaClientError(solana_client::client_error::ClientError),
/// Error uploading metadata /// Error uploading metadata
UploadMetadataError(Box<dyn std::error::Error>), UploadMetadataError(Box<dyn std::error::Error>),
/// Error from Anchor client
AnchorClientError(anchor_client::ClientError),
/// Invalid input parameters /// Invalid input parameters
InvalidInput(&'static str), InvalidInput(&'static str),
/// Insufficient funds for transaction /// Insufficient funds for transaction
@@ -94,7 +90,6 @@ impl std::fmt::Display for ClientError {
Self::BorshError(err) => write!(f, "Borsh serialization error: {}", err), Self::BorshError(err) => write!(f, "Borsh serialization error: {}", err),
Self::SolanaClientError(err) => write!(f, "Solana client error: {}", err), Self::SolanaClientError(err) => write!(f, "Solana client error: {}", err),
Self::UploadMetadataError(err) => write!(f, "Metadata upload 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::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
Self::InsufficientFunds => write!(f, "Insufficient funds for transaction"), Self::InsufficientFunds => write!(f, "Insufficient funds for transaction"),
Self::SimulationError(msg) => write!(f, "Transaction simulation failed: {}", msg), Self::SimulationError(msg) => write!(f, "Transaction simulation failed: {}", msg),
@@ -126,7 +121,6 @@ impl std::error::Error for ClientError {
Self::BorshError(err) => Some(err), Self::BorshError(err) => Some(err),
Self::SolanaClientError(err) => Some(err), Self::SolanaClientError(err) => Some(err),
Self::UploadMetadataError(err) => Some(err.as_ref()), Self::UploadMetadataError(err) => Some(err.as_ref()),
Self::AnchorClientError(err) => Some(err),
Self::ExternalService(_) => None, Self::ExternalService(_) => None,
Self::Redis(_, _) => None, Self::Redis(_, _) => None,
Self::Solana(_, _) => None, Self::Solana(_, _) => None,
+123
View File
@@ -0,0 +1,123 @@
use anyhow::anyhow;
use base64::engine::general_purpose;
use base64::Engine;
use borsh::{BorshDeserialize, BorshSerialize};
use regex::Regex;
use solana_sdk::pubkey::Pubkey;
use super::myerror::AppError;
pub const PROGRAM_DATA: &str = "Program data: ";
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct TradeEvent {
pub mint: Pubkey,
pub sol_amount: u64,
pub token_amount: u64,
pub is_buy: bool,
pub user: Pubkey,
pub timestamp: u64,
pub virtual_sol_reserves: u64,
pub virtual_token_reserves: u64,
pub real_sol_reserves: u64,
pub real_token_reserves: u64,
}
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct CompleteEvent {
pub user: Pubkey,
pub mint: Pubkey,
pub bonding_curve: Pubkey,
pub timestamp: u64,
}
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct SwapBaseInLog {
pub log_type: u8,
// input
pub amount_in: u64,
pub minimum_out: u64,
pub direction: u64,
// user info
pub user_source: u64,
// pool info
pub pool_coin: u64,
pub pool_pc: u64,
// calc result
pub out_amount: u64,
}
pub trait EventTrait: Sized + std::fmt::Debug {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError>;
}
impl EventTrait for TradeEvent {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
TradeEvent::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
}
}
impl EventTrait for CompleteEvent {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
CompleteEvent::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
}
}
impl EventTrait for SwapBaseInLog {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
SwapBaseInLog::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
}
}
#[derive(Debug, Clone, Copy)]
pub struct PumpEvent {}
impl PumpEvent {
pub fn parse_logs<T: EventTrait + Clone>(logs: &Vec<String>) -> Option<T> {
let mut event: Option<T> = None;
if !logs.is_empty() {
let logs_iter = logs.iter().peekable();
for l in logs_iter.rev() {
if let Some(log) = l.strip_prefix(PROGRAM_DATA) {
let borsh_bytes = general_purpose::STANDARD.decode(log).unwrap();
let slice: &[u8] = &borsh_bytes[8..];
if let Ok(e) = T::from_bytes(slice) {
event = Some(e);
}
}
}
}
event
}
}
#[derive(Debug, Clone, Copy)]
pub struct RaydiumEvent {}
impl RaydiumEvent {
pub fn parse_logs<T: EventTrait + Clone>(logs: &Vec<String>) -> Option<T> {
let mut event: Option<T> = None;
if !logs.is_empty() {
let logs_iter = logs.iter().peekable();
for l in logs_iter.rev() {
let re = Regex::new(r"ray_log: (?P<base64>[A-Za-z0-9+/=]+)").unwrap();
if let Some(caps) = re.captures(l) {
if let Some(base64) = caps.name("base64") {
let bytes = general_purpose::STANDARD.decode(base64.as_str()).unwrap();
if let Ok(e) = T::from_bytes(&bytes) {
event = Some(e);
}
}
}
}
}
event
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod event;
pub mod myerror;
// pub mod subscribe_logs;
// pub mod subscribe_tx;
pub mod yellowstone_grpc;
+13
View File
@@ -0,0 +1,13 @@
use anyhow::Error;
#[derive(Debug)]
#[allow(dead_code)]
pub struct AppError(Error);
impl<E> From<E> for AppError
where
E: Into<Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}
+136
View File
@@ -0,0 +1,136 @@
#[cfg(test)]
mod subscribe_tx_tests {
use crate::common::{
event::{PumpEvent, RaydiumEvent, SwapBaseInLog, TradeEvent},
myerror::AppError,
yellowstone_grpc::{TransactionPretty, YellowstoneGrpc},
};
use anyhow::anyhow;
use chrono::Local;
use dotenvy::dotenv;
use futures::{channel::mpsc, sink::SinkExt, stream::StreamExt};
use log::{error, info};
use solana_sdk::pubkey;
use solana_sdk::pubkey::Pubkey;
use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedTransactionWithStatusMeta,
};
use std::env;
use tokio::test;
use yellowstone_grpc_proto::geyser::{
subscribe_update::UpdateOneof, SubscribeRequest, SubscribeRequestPing,
};
const AMM_V4: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
const PUMP_PROGRAM_ID: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
pub enum SwapType {
Pump,
Raydium,
}
#[test]
async fn test_subscribe_tx() -> Result<(), AppError> {
dotenv().ok();
pretty_env_logger::init_custom_env("RUST_LOG");
let yellowstone_url = env::var("YELLOWSTONE_URL")?;
info!("::: {:?}", yellowstone_url);
let yellowstone_grpc = YellowstoneGrpc::new(yellowstone_url);
let addrs = vec![
"Aa4QWNkS3RLUv7DA9BM1a2Hzm4HDQo5PyRefqDJnpump".to_string(),
"BnDssYyGDF9aj5j2N5BwsJFk9YMneQ8P7LQkoYkrpump".to_string(),
];
let transactions = yellowstone_grpc.subscribe_transaction(addrs, vec![], vec![]);
let (mut subscribe_tx, mut stream) = yellowstone_grpc.connect(transactions).await??;
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(1000);
tokio::spawn(async move {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
match msg.update_oneof {
Some(UpdateOneof::Transaction(sut)) => {
let transaction_pretty: TransactionPretty = sut.into();
let _ = tx.try_send(transaction_pretty);
}
Some(UpdateOneof::Ping(_)) => {
// This is necessary to keep load balancers that expect client pings alive. If your load balancer doesn't
// require periodic client pings then this is unnecessary
let _ = subscribe_tx
.send(SubscribeRequest {
ping: Some(SubscribeRequestPing { id: 1 }),
..Default::default()
})
.await;
info!("service is ping: {}", Local::now());
}
Some(UpdateOneof::Pong(_)) => {
info!("service is pong: {}", Local::now());
}
_ => {}
}
continue;
}
Err(error) => {
error!("error: {error:?}");
break;
}
}
}
});
while let Some(transaction_pretty) = rx.next().await {
let trade_raw = transaction_pretty.tx.clone();
let meta = &trade_raw.meta.clone().unwrap();
if meta.err.is_some() {
continue;
}
let logs = if let OptionSerializer::Some(logs) = &meta.log_messages {
logs
} else {
&vec![]
};
if let Ok(swap_type) = get_swap_type(&trade_raw) {
match swap_type {
SwapType::Raydium => {
let event = RaydiumEvent::parse_logs::<SwapBaseInLog>(logs);
info!("RaydiumEvent {:#?}", event);
}
SwapType::Pump => {
let event = PumpEvent::parse_logs::<TradeEvent>(logs);
info!("PumpEvent {:#?}", event);
}
}
};
}
Ok(())
}
#[allow(dead_code)]
pub fn get_swap_type(
trade_raw: &EncodedTransactionWithStatusMeta,
) -> Result<SwapType, AppError> {
let transaction = &trade_raw.transaction.decode();
if let Some(transaction) = transaction {
let account_keys = transaction.message.static_account_keys();
let program_index = account_keys
.iter()
.position(|item| item == &AMM_V4 || item == &PUMP_PROGRAM_ID)
.ok_or(anyhow!("swap type program_id not found"))?;
let program_id = account_keys[program_index];
let _type = match program_id {
AMM_V4 => Ok(SwapType::Raydium),
PUMP_PROGRAM_ID => Ok(SwapType::Pump),
_ => Err(AppError::from(anyhow!("program_id ix not found"))),
};
return _type;
}
Err(AppError::from(anyhow!("program_id ix not found")))
}
}
+73
View File
@@ -0,0 +1,73 @@
#[cfg(test)]
mod subscribe_tx_tests {
use crate::common::{
myerror::AppError,
yellowstone_grpc::{TransactionPretty, YellowstoneGrpc},
};
use chrono::Local;
use dotenvy::dotenv;
use futures::{channel::mpsc, sink::SinkExt, stream::StreamExt};
use log::{error, info};
use std::env;
use tokio::test;
use yellowstone_grpc_proto::geyser::{
subscribe_update::UpdateOneof, SubscribeRequest, SubscribeRequestPing,
};
#[test]
async fn test_subscribe_tx() -> Result<(), AppError> {
dotenv().ok();
pretty_env_logger::init_custom_env("RUST_LOG");
let yellowstone_url = env::var("YELLOWSTONE_URL")?;
info!("::: {:?}", yellowstone_url);
let yellowstone_grpc = YellowstoneGrpc::new(yellowstone_url);
let addrs = vec!["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P".to_string()];
let transactions = yellowstone_grpc.subscribe_transaction(addrs, vec![], vec![]);
let (mut subscribe_tx, mut stream) = yellowstone_grpc.connect(transactions).await??;
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(1000);
tokio::spawn(async move {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
match msg.update_oneof {
Some(UpdateOneof::Transaction(sut)) => {
let transaction_pretty: TransactionPretty = sut.into();
let _ = tx.try_send(transaction_pretty);
}
Some(UpdateOneof::Ping(_)) => {
// This is necessary to keep load balancers that expect client pings alive. If your load balancer doesn't
// require periodic client pings then this is unnecessary
let _ = subscribe_tx
.send(SubscribeRequest {
ping: Some(SubscribeRequestPing { id: 1 }),
..Default::default()
})
.await;
info!("service is ping: {}", Local::now());
}
Some(UpdateOneof::Pong(_)) => {
info!("service is pong: {}", Local::now());
}
_ => {}
}
continue;
}
Err(error) => {
error!("error: {error:?}");
break;
}
}
}
});
while let Some(event) = rx.next().await {
info!("TransactionPretty {:#?}", event);
}
Ok(())
}
}
+284
View File
@@ -0,0 +1,284 @@
use std::{collections::HashMap, fmt, time::Duration};
use futures::{channel::mpsc, sink::Sink, Stream, StreamExt, SinkExt};
use rustls::crypto::{ring::default_provider, CryptoProvider};
use tonic::{transport::channel::ClientTlsConfig, Status};
use yellowstone_grpc_client::{GeyserGrpcClient, GeyserGrpcClientResult};
use yellowstone_grpc_proto::geyser::{
CommitmentLevel, SubscribeRequest, SubscribeRequestFilterTransactions, SubscribeUpdate,
SubscribeUpdateTransaction, subscribe_update::UpdateOneof, SubscribeRequestPing,
};
use log::{error, info};
use chrono::Local;
use solana_sdk::{pubkey, pubkey::Pubkey, signature::Signature};
use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedTransactionWithStatusMeta, UiTransactionEncoding,
};
use anyhow::anyhow;
use crate::grpc::event::{PumpEvent, RaydiumEvent, SwapBaseInLog, TradeEvent};
use crate::grpc::myerror::AppError;
// 类型别名定义
type TransactionsFilterMap = HashMap<String, SubscribeRequestFilterTransactions>;
type GrpcStreamResult = GeyserGrpcClientResult<(
Box<dyn Sink<SubscribeRequest, Error = mpsc::SendError> + Unpin + Send>,
Box<dyn Stream<Item = Result<SubscribeUpdate, Status>> + Unpin + Send>,
)>;
// 常量定义
const AMM_V4: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");
const PUMP_PROGRAM_ID: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
const CONNECT_TIMEOUT: u64 = 10;
const REQUEST_TIMEOUT: u64 = 60;
const CHANNEL_SIZE: usize = 1000;
// 枚举定义
#[derive(Debug)]
pub enum SwapType {
Pump,
Raydium,
}
// 结构体定义
#[allow(dead_code)]
pub struct TransactionPretty {
pub slot: u64,
pub signature: Signature,
pub is_vote: bool,
pub tx: EncodedTransactionWithStatusMeta,
}
impl fmt::Debug for TransactionPretty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct TxWrap<'a>(&'a EncodedTransactionWithStatusMeta);
impl<'a> fmt::Debug for TxWrap<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let serialized = serde_json::to_string(self.0).expect("failed to serialize");
fmt::Display::fmt(&serialized, f)
}
}
f.debug_struct("TransactionPretty")
.field("slot", &self.slot)
.field("signature", &self.signature)
.field("is_vote", &self.is_vote)
.field("tx", &TxWrap(&self.tx))
.finish()
}
}
impl From<SubscribeUpdateTransaction> for TransactionPretty {
fn from(SubscribeUpdateTransaction { transaction, slot }: SubscribeUpdateTransaction) -> Self {
let tx = transaction.expect("should be defined");
Self {
slot,
signature: Signature::try_from(tx.signature.as_slice()).expect("valid signature"),
is_vote: tx.is_vote,
tx: yellowstone_grpc_proto::convert_from::create_tx_with_meta(tx)
.expect("valid tx with meta")
.encode(UiTransactionEncoding::Base64, Some(u8::MAX), true)
.expect("failed to encode"),
}
}
}
pub struct YellowstoneGrpc {
endpoint: String,
}
impl YellowstoneGrpc {
pub fn new(endpoint: String) -> Self {
Self { endpoint }
}
pub async fn connect(
&self,
transactions: TransactionsFilterMap,
) -> Result<
GeyserGrpcClientResult<(
impl Sink<SubscribeRequest, Error = mpsc::SendError>,
impl Stream<Item = Result<SubscribeUpdate, Status>>,
)>,
AppError,
> {
if CryptoProvider::get_default().is_none() {
default_provider()
.install_default()
.map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e))?;
}
let mut client = GeyserGrpcClient::build_from_shared(self.endpoint.clone())?
.tls_config(ClientTlsConfig::new().with_native_roots())?
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(60))
.connect()
.await?;
let subscribe_request = SubscribeRequest {
transactions,
commitment: Some(CommitmentLevel::Processed.into()),
..Default::default()
};
Ok(client.subscribe_with_request(Some(subscribe_request)).await)
}
pub async fn subscribe_accounts(&self, accounts: Vec<String>) -> Result<(), AppError> {
let transactions = self.get_subscribe_request_filter(accounts, vec![], vec![]);
let (mut subscribe_tx, mut stream) = self.connect(transactions).await??;
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(CHANNEL_SIZE);
tokio::spawn(async move {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
if let Err(e) = Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await {
error!("Error handling message: {:?}", e);
break;
}
}
Err(error) => {
error!("Stream error: {error:?}");
break;
}
}
}
});
while let Some(transaction_pretty) = rx.next().await {
if let Err(e) = Self::process_transaction(transaction_pretty).await {
error!("Error processing transaction: {:?}", e);
}
}
Ok(())
}
pub async fn subscribe_pumpfun(&self) -> Result<(), AppError> {
let addrs = vec![PUMP_PROGRAM_ID.to_string()];
let transactions = self.get_subscribe_request_filter(addrs, vec![], vec![]);
let (mut subscribe_tx, mut stream) = self.connect(transactions).await??;
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(CHANNEL_SIZE);
tokio::spawn(async move {
while let Some(message) = stream.next().await {
match message {
Ok(msg) => {
if let Err(e) = Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await {
error!("Error handling message: {:?}", e);
break;
}
}
Err(error) => {
error!("Stream error: {error:?}");
break;
}
}
}
});
while let Some(transaction_pretty) = rx.next().await {
if let Err(e) = Self::process_transaction(transaction_pretty).await {
error!("Error processing transaction: {:?}", e);
}
}
Ok(())
}
pub fn get_subscribe_request_filter(
&self,
account_include: Vec<String>,
account_exclude: Vec<String>,
account_required: Vec<String>,
) -> TransactionsFilterMap {
let mut transactions = HashMap::new();
transactions.insert(
"client".to_string(),
SubscribeRequestFilterTransactions {
vote: Some(false),
failed: Some(false),
signature: None,
account_include,
account_exclude,
account_required,
},
);
transactions
}
async fn handle_stream_message(
msg: SubscribeUpdate,
tx: &mut mpsc::Sender<TransactionPretty>,
subscribe_tx: &mut (impl Sink<SubscribeRequest, Error = mpsc::SendError> + Unpin),
) -> Result<(), AppError> {
match msg.update_oneof {
Some(UpdateOneof::Transaction(sut)) => {
let transaction_pretty = TransactionPretty::from(sut);
tx.try_send(transaction_pretty).map_err(|e| AppError::from(anyhow!("Send error: {:?}", e)))?;
}
Some(UpdateOneof::Ping(_)) => {
subscribe_tx
.send(SubscribeRequest {
ping: Some(SubscribeRequestPing { id: 1 }),
..Default::default()
})
.await
.map_err(|e| AppError::from(anyhow!("Ping error: {:?}", e)))?;
info!("service is ping: {}", Local::now());
}
Some(UpdateOneof::Pong(_)) => {
info!("service is pong: {}", Local::now());
}
_ => {}
}
Ok(())
}
async fn process_transaction(transaction_pretty: TransactionPretty) -> Result<(), AppError> {
let trade_raw = transaction_pretty.tx;
let meta = trade_raw.meta.as_ref()
.ok_or_else(|| AppError::from(anyhow!("Missing transaction metadata")))?;
if meta.err.is_some() {
return Ok(());
}
let logs = if let OptionSerializer::Some(logs) = &meta.log_messages {
logs
} else {
&vec![]
};
if let Ok(swap_type) = Self::get_swap_type(&trade_raw) {
match swap_type {
SwapType::Raydium => {
let event = RaydiumEvent::parse_logs::<SwapBaseInLog>(logs);
info!("RaydiumEvent {:#?}", event);
}
SwapType::Pump => {
let event = PumpEvent::parse_logs::<TradeEvent>(logs);
info!("PumpEvent {:#?}", event);
}
}
}
Ok(())
}
pub fn get_swap_type(trade_raw: &EncodedTransactionWithStatusMeta) -> Result<SwapType, AppError> {
let transaction = trade_raw.transaction.decode()
.ok_or_else(|| AppError::from(anyhow!("Failed to decode transaction")))?;
let account_keys = transaction.message.static_account_keys();
let program_index = account_keys
.iter()
.position(|item| item == &AMM_V4 || item == &PUMP_PROGRAM_ID)
.ok_or_else(|| AppError::from(anyhow!("swap type program_id not found")))?;
match account_keys[program_index] {
AMM_V4 => Ok(SwapType::Raydium),
PUMP_PROGRAM_ID => Ok(SwapType::Pump),
_ => Err(AppError::from(anyhow!("Invalid program_id")))
}
}
}
+123
View File
@@ -0,0 +1,123 @@
use anyhow::anyhow;
use base64::engine::general_purpose;
use base64::Engine;
use borsh::{BorshDeserialize, BorshSerialize};
use regex::Regex;
use solana_sdk::pubkey::Pubkey;
use super::myerror::AppError;
pub const PROGRAM_DATA: &str = "Program data: ";
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct TradeEvent {
pub mint: Pubkey,
pub sol_amount: u64,
pub token_amount: u64,
pub is_buy: bool,
pub user: Pubkey,
pub timestamp: u64,
pub virtual_sol_reserves: u64,
pub virtual_token_reserves: u64,
pub real_sol_reserves: u64,
pub real_token_reserves: u64,
}
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct CompleteEvent {
pub user: Pubkey,
pub mint: Pubkey,
pub bonding_curve: Pubkey,
pub timestamp: u64,
}
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)]
pub struct SwapBaseInLog {
pub log_type: u8,
// input
pub amount_in: u64,
pub minimum_out: u64,
pub direction: u64,
// user info
pub user_source: u64,
// pool info
pub pool_coin: u64,
pub pool_pc: u64,
// calc result
pub out_amount: u64,
}
pub trait EventTrait: Sized + std::fmt::Debug {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError>;
}
impl EventTrait for TradeEvent {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
TradeEvent::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
}
}
impl EventTrait for CompleteEvent {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
CompleteEvent::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
}
}
impl EventTrait for SwapBaseInLog {
fn from_bytes(bytes: &[u8]) -> Result<Self, AppError> {
SwapBaseInLog::try_from_slice(bytes).map_err(|e| AppError::from(anyhow!(e.to_string())))
}
}
#[derive(Debug, Clone, Copy)]
pub struct PumpEvent {}
impl PumpEvent {
pub fn parse_logs<T: EventTrait + Clone>(logs: &Vec<String>) -> Option<T> {
let mut event: Option<T> = None;
if !logs.is_empty() {
let logs_iter = logs.iter().peekable();
for l in logs_iter.rev() {
if let Some(log) = l.strip_prefix(PROGRAM_DATA) {
let borsh_bytes = general_purpose::STANDARD.decode(log).unwrap();
let slice: &[u8] = &borsh_bytes[8..];
if let Ok(e) = T::from_bytes(slice) {
event = Some(e);
}
}
}
}
event
}
}
#[derive(Debug, Clone, Copy)]
pub struct RaydiumEvent {}
impl RaydiumEvent {
pub fn parse_logs<T: EventTrait + Clone>(logs: &Vec<String>) -> Option<T> {
let mut event: Option<T> = None;
if !logs.is_empty() {
let logs_iter = logs.iter().peekable();
for l in logs_iter.rev() {
let re = Regex::new(r"ray_log: (?P<base64>[A-Za-z0-9+/=]+)").unwrap();
if let Some(caps) = re.captures(l) {
if let Some(base64) = caps.name("base64") {
let bytes = general_purpose::STANDARD.decode(base64.as_str()).unwrap();
if let Ok(e) = T::from_bytes(&bytes) {
event = Some(e);
}
}
}
}
}
event
}
}
@@ -1,7 +1,7 @@
use crate::instruction::logs_data::DexInstruction; use crate::instruction::logs_data::DexInstruction;
use crate::instruction::logs_parser::{parse_create_token_data, parse_trade_data}; use crate::instruction::logs_parser::{parse_create_token_data, parse_trade_data};
use crate::error::ClientResult; use crate::error::ClientResult;
use anchor_client::solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
pub struct LogFilter; pub struct LogFilter;
impl LogFilter { impl LogFilter {
@@ -6,7 +6,7 @@ use crate::instruction::{
logs_filters::LogFilter logs_filters::LogFilter
}; };
use anchor_client::solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
pub async fn process_logs<F>( pub async fn process_logs<F>(
signature: &str, signature: &str,
@@ -1,9 +1,9 @@
use anchor_client::solana_client::{ use solana_client::{
nonblocking::pubsub_client::PubsubClient, nonblocking::pubsub_client::PubsubClient,
rpc_config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter} rpc_config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter}
}; };
use anchor_client::solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey}; use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@@ -23,9 +23,8 @@ pub use logs_events::*;
pub use logs_subscribe::*; pub use logs_subscribe::*;
use crate::{constants, PumpFun}; use crate::{constants, PumpFun};
use anchor_client::anchor_lang::InstructionData; use spl_associated_token_account::get_associated_token_address;
use anchor_spl::associated_token::get_associated_token_address;
use pumpfun_cpi as cpi;
use solana_sdk::{ use solana_sdk::{
instruction::{AccountMeta, Instruction}, instruction::{AccountMeta, Instruction},
pubkey::Pubkey, pubkey::Pubkey,
@@ -33,6 +32,54 @@ use solana_sdk::{
signer::Signer, signer::Signer,
}; };
pub struct Create {
pub _name: String,
pub _symbol: String,
pub _uri: String,
}
impl Create {
pub fn data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(8 + 8 + 8);
data.extend_from_slice(&[24, 30, 200, 40, 5, 28, 7, 119]); // discriminator
data.extend_from_slice(&self._name.as_bytes());
data.extend_from_slice(&self._symbol.as_bytes());
data.extend_from_slice(&self._uri.as_bytes());
data
}
}
pub struct Buy {
pub _amount: u64,
pub _max_sol_cost: u64,
}
impl Buy {
pub fn data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(8 + 8 + 8);
data.extend_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // discriminator
data.extend_from_slice(&self._amount.to_le_bytes());
data.extend_from_slice(&self._max_sol_cost.to_le_bytes());
data
}
}
pub struct Sell {
pub _amount: u64,
pub _min_sol_output: u64,
}
impl Sell {
pub fn data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(8 + 8 + 8);
data.extend_from_slice(&[51, 230, 133, 164, 1, 127, 131, 173]); // discriminator
data.extend_from_slice(&self._amount.to_le_bytes());
data.extend_from_slice(&self._min_sol_output.to_le_bytes());
data
}
}
/// Creates an instruction to create a new token with bonding curve /// 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. /// Creates a new SPL token with an associated bonding curve that determines its price.
@@ -46,7 +93,7 @@ use solana_sdk::{
/// # Returns /// # Returns
/// ///
/// Returns a Solana instruction that when executed will create the token and its accounts /// 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 { pub fn create(payer: &Keypair, mint: &Keypair, args: Create) -> Instruction {
let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(&mint.pubkey()).unwrap(); let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(&mint.pubkey()).unwrap();
Instruction::new_with_bytes( Instruction::new_with_bytes(
constants::accounts::PUMPFUN, constants::accounts::PUMPFUN,
@@ -93,7 +140,7 @@ pub fn buy(
payer: &Keypair, payer: &Keypair,
mint: &Pubkey, mint: &Pubkey,
fee_recipient: &Pubkey, fee_recipient: &Pubkey,
args: cpi::instruction::Buy, args: Buy,
) -> Instruction { ) -> Instruction {
let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap(); let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap();
Instruction::new_with_bytes( Instruction::new_with_bytes(
@@ -136,7 +183,7 @@ pub fn sell(
payer: &Keypair, payer: &Keypair,
mint: &Pubkey, mint: &Pubkey,
fee_recipient: &Pubkey, fee_recipient: &Pubkey,
args: cpi::instruction::Sell, args: Sell,
) -> Instruction { ) -> Instruction {
let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap(); let bonding_curve: Pubkey = PumpFun::get_bonding_curve_pda(mint).unwrap();
Instruction::new_with_bytes( Instruction::new_with_bytes(
@@ -4,7 +4,7 @@ use rand::seq::SliceRandom;
use reqwest::Client; use reqwest::Client;
use serde_json::{json, Value}; use serde_json::{json, Value};
use anchor_client::solana_sdk::{ use solana_sdk::{
pubkey::Pubkey, pubkey::Pubkey,
transaction::Transaction, transaction::Transaction,
}; };
+27 -34
View File
@@ -1,29 +1,25 @@
#![doc = include_str!("../RUSTDOC.md")]
pub mod accounts; pub mod accounts;
pub mod constants; pub mod constants;
pub mod error; pub mod error;
pub mod instruction; pub mod instruction;
pub mod utils; pub mod utils;
pub mod jito; pub mod jito;
pub mod grpc;
use anchor_client::{ use solana_client::rpc_client::RpcClient;
solana_client::rpc_client::RpcClient, use solana_sdk::{
solana_sdk::{ commitment_config::CommitmentConfig,
commitment_config::CommitmentConfig, pubkey::Pubkey,
pubkey::Pubkey, signature::{Keypair, Signature},
signature::{Keypair, Signature}, signer::Signer,
signer::Signer, instruction::Instruction,
instruction::Instruction, system_instruction,
system_instruction, compute_budget::ComputeBudgetInstruction,
compute_budget::ComputeBudgetInstruction, transaction::Transaction,
transaction::Transaction,
},
}; };
use anchor_client::Cluster; use spl_associated_token_account::{
use anchor_spl::associated_token::{
get_associated_token_address, get_associated_token_address,
spl_associated_token_account::instruction::create_associated_token_account, create_associated_token_account,
}; };
use instruction::logs_subscribe; use instruction::logs_subscribe;
@@ -31,13 +27,13 @@ use instruction::logs_subscribe::SubscriptionHandle;
use instruction::logs_events::DexEvent; use instruction::logs_events::DexEvent;
use std::sync::Arc; use std::sync::Arc;
use borsh::BorshDeserialize;
use std::time::Instant; use std::time::Instant;
pub use pumpfun_cpi as cpi;
use crate::jito::JitoClient; use crate::jito::JitoClient;
use crate::error::ClientError; use crate::error::ClientError;
use borsh::BorshDeserialize;
// Constants // Constants
const DEFAULT_SLIPPAGE: u64 = 1000; // 10% const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 10_000_000; const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 10_000_000;
@@ -79,13 +75,13 @@ impl Clone for PumpFun {
impl PumpFun { impl PumpFun {
/// Create a new PumpFun client instance /// Create a new PumpFun client instance
pub fn new( pub fn new(
cluster: Cluster, rpc_url: String,
commitment: Option<CommitmentConfig>, commitment: Option<CommitmentConfig>,
payer: Arc<Keypair>, payer: Arc<Keypair>,
jito_url: Option<String>, jito_url: Option<String>,
) -> Self { ) -> Self {
let rpc = RpcClient::new_with_commitment( let rpc = RpcClient::new_with_commitment(
cluster.url(), rpc_url,
commitment.unwrap_or(CommitmentConfig::confirmed()) commitment.unwrap_or(CommitmentConfig::confirmed())
); );
@@ -114,7 +110,7 @@ impl PumpFun {
instructions.push(instruction::create( instructions.push(instruction::create(
&self.payer.clone(), &self.payer.clone(),
mint, mint,
cpi::instruction::Create { instruction::Create {
_name: ipfs.metadata.name, _name: ipfs.metadata.name,
_symbol: ipfs.metadata.symbol, _symbol: ipfs.metadata.symbol,
_uri: ipfs.metadata.image, _uri: ipfs.metadata.image,
@@ -157,7 +153,7 @@ impl PumpFun {
instructions.push(instruction::create( instructions.push(instruction::create(
&self.payer.clone(), &self.payer.clone(),
mint, mint,
cpi::instruction::Create { instruction::Create {
_name: ipfs.metadata.name, _name: ipfs.metadata.name,
_symbol: ipfs.metadata.symbol, _symbol: ipfs.metadata.symbol,
_uri: ipfs.metadata.image, _uri: ipfs.metadata.image,
@@ -170,7 +166,6 @@ impl PumpFun {
&self.payer.pubkey(), &self.payer.pubkey(),
&self.payer.pubkey(), &self.payer.pubkey(),
&mint.pubkey(), &mint.pubkey(),
&constants::accounts::TOKEN_PROGRAM,
)); ));
} }
@@ -178,7 +173,7 @@ impl PumpFun {
&self.payer.clone(), &self.payer.clone(),
&mint.pubkey(), &mint.pubkey(),
&global_account.fee_recipient, &global_account.fee_recipient,
cpi::instruction::Buy { instruction::Buy {
_amount: buy_amount, _amount: buy_amount,
_max_sol_cost: buy_amount_with_slippage, _max_sol_cost: buy_amount_with_slippage,
}, },
@@ -221,7 +216,6 @@ impl PumpFun {
&self.payer.pubkey(), &self.payer.pubkey(),
&self.payer.pubkey(), &self.payer.pubkey(),
mint, mint,
&constants::accounts::TOKEN_PROGRAM,
)); ));
} }
@@ -229,7 +223,7 @@ impl PumpFun {
&self.payer.clone(), &self.payer.clone(),
mint, mint,
&global_account.fee_recipient, &global_account.fee_recipient,
cpi::instruction::Buy { instruction::Buy {
_amount: buy_amount, _amount: buy_amount,
_max_sol_cost: buy_amount_with_slippage, _max_sol_cost: buy_amount_with_slippage,
}, },
@@ -276,7 +270,6 @@ impl PumpFun {
&self.payer.pubkey(), &self.payer.pubkey(),
&self.payer.pubkey(), &self.payer.pubkey(),
mint, mint,
&constants::accounts::TOKEN_PROGRAM,
)); ));
} }
@@ -284,7 +277,7 @@ impl PumpFun {
self.payer.as_ref(), self.payer.as_ref(),
mint, mint,
&global_account.fee_recipient, &global_account.fee_recipient,
cpi::instruction::Buy { instruction::Buy {
_amount: buy_amount, _amount: buy_amount,
_max_sol_cost: buy_amount_with_slippage, _max_sol_cost: buy_amount_with_slippage,
}, },
@@ -346,7 +339,7 @@ impl PumpFun {
&self.payer.clone(), &self.payer.clone(),
mint, mint,
&global_account.fee_recipient, &global_account.fee_recipient,
cpi::instruction::Sell { instruction::Sell {
_amount: amount, _amount: amount,
_min_sol_output: min_sol_output_with_slippage, _min_sol_output: min_sol_output_with_slippage,
}, },
@@ -451,7 +444,7 @@ impl PumpFun {
&self.payer.clone(), &self.payer.clone(),
mint, mint,
&global_account.fee_recipient, &global_account.fee_recipient,
cpi::instruction::Sell { instruction::Sell {
_amount: amount, _amount: amount,
_min_sol_output: min_sol_output_with_slippage, _min_sol_output: min_sol_output_with_slippage,
}, },
@@ -523,17 +516,17 @@ impl PumpFun {
// PDA related methods // PDA related methods
pub fn get_global_pda() -> Pubkey { pub fn get_global_pda() -> Pubkey {
Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &cpi::ID).0 Pubkey::find_program_address(&[constants::seeds::GLOBAL_SEED], &constants::accounts::PUMPFUN).0
} }
pub fn get_mint_authority_pda() -> Pubkey { pub fn get_mint_authority_pda() -> Pubkey {
Pubkey::find_program_address(&[constants::seeds::MINT_AUTHORITY_SEED], &cpi::ID).0 Pubkey::find_program_address(&[constants::seeds::MINT_AUTHORITY_SEED], &constants::accounts::PUMPFUN).0
} }
pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> { pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> {
Pubkey::try_find_program_address( Pubkey::try_find_program_address(
&[constants::seeds::BONDING_CURVE_SEED, mint.as_ref()], &[constants::seeds::BONDING_CURVE_SEED, mint.as_ref()],
&cpi::ID &constants::accounts::PUMPFUN
).map(|(pubkey, _)| pubkey) ).map(|(pubkey, _)| pubkey)
} }
+1 -1
View File
@@ -2,7 +2,7 @@ use mai3_pumpfun_sdk::instruction::{
logs_events::DexEvent, logs_events::DexEvent,
logs_subscribe::{tokens_subscription, stop_subscription} logs_subscribe::{tokens_subscription, stop_subscription}
}; };
use anchor_client::solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::commitment_config::CommitmentConfig;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {