mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-13 23:16:55 +00:00
Merge branch 'main' into rudy5348/main
This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
OPENAI_API_KEY=
|
||||
RPC_URL=
|
||||
SOLANA_PRIVATE_KEY=
|
||||
154
CONTRIBUTING.md
Normal file
154
CONTRIBUTING.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Contributing to Solana Agent Kit
|
||||
|
||||
First off, thank you for considering contributing to Solana Agent Kit! 🎉 Your contributions are **greatly appreciated**.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Contributing to Solana Agent Kit](#contributing-to-solana-agent-kit)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [How Can I Contribute?](#how-can-i-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||
- [Your First Code Contribution](#your-first-code-contribution)
|
||||
- [Pull Requests](#pull-requests)
|
||||
- [Style Guides](#style-guides)
|
||||
- [Code Style](#code-style)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Naming Conventions](#naming-conventions)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Building the Project](#building-the-project)
|
||||
- [Running Tests](#running-tests)
|
||||
- [Generating Documentation](#generating-documentation)
|
||||
- [Security](#security)
|
||||
- [License](#license)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/). By participating, you are expected to uphold this code. Please report unacceptable behavior to [aryan@sendai.fun](mailto:aryan@sendai.fun).
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
**Great**! Opening an issue is the best way to help us improve. Here's how you can report a bug:
|
||||
|
||||
1. **Search** the [existing issues](https://github.com/sendaifun/solana-agent-kit/issues) to make sure it hasn't been reported.
|
||||
2. **Open a new issue** and fill out the template with as much information as possible.
|
||||
3. **Provide reproduction steps** if applicable.
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
We welcome your ideas for improving Solana Agent Kit! To suggest an enhancement:
|
||||
|
||||
1. **Search** the [existing issues](https://github.com/sendaifun/solana-agent-kit/issues) to see if it's already been suggested.
|
||||
2. **Open a new issue** and describe your idea in detail.
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
Unsure where to start? You can help out by:
|
||||
|
||||
- Fixing simple bugs.
|
||||
- Improving documentation.
|
||||
- Adding tests.
|
||||
|
||||
Check out the [Good First Issues](https://github.com/sendaifun/solana-agent-kit/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) to get started!
|
||||
|
||||
### Pull Requests
|
||||
|
||||
1. **Fork** the repository.
|
||||
2. **Create** a new branch for your feature or bugfix.
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
3. **Commit** your changes with clear and descriptive messages.
|
||||
4. **Push** to your fork.
|
||||
```bash
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
5. **Open a Pull Request** against the `main` branch of this repository.
|
||||
|
||||
## Style Guides
|
||||
|
||||
### Code Style
|
||||
|
||||
- **Language**: TypeScript
|
||||
- **Formatting**: Follow the existing codebase formatting. Consider using [Prettier](https://prettier.io/) for consistent code formatting.
|
||||
- **Linting**: Adhere to the linting rules defined in `.eslintrc`. Ensure all linting checks pass before submitting a PR.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for your commit messages. Examples:
|
||||
|
||||
- `feat: add ability to deploy new SPL token`
|
||||
- `fix: handle edge case when deploying collection`
|
||||
- `docs: update README with new usage examples`
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- **Variables and Functions**: `camelCase`
|
||||
- **Classes and Types**: `PascalCase`
|
||||
- **Constants**: `UPPER_SNAKE_CASE`
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js**: v23.x or higher
|
||||
- **npm**: v10.x or higher
|
||||
- **Git**: Installed and configured
|
||||
|
||||
### Installation
|
||||
|
||||
1. **Clone** the repository:
|
||||
```bash
|
||||
git clone https://github.com/yourusername/solana-agent-kit.git
|
||||
```
|
||||
2. **Navigate** to the project directory:
|
||||
```bash
|
||||
cd solana-agent-kit
|
||||
```
|
||||
3. **Install** dependencies:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Building the Project
|
||||
|
||||
To compile the TypeScript code:
|
||||
|
||||
```bash
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
To execute the test suite:
|
||||
|
||||
```bash
|
||||
pnpm run test
|
||||
```
|
||||
|
||||
### Generating Documentation
|
||||
|
||||
To generate the project documentation using TypeDoc:
|
||||
|
||||
```bash
|
||||
npm run docs
|
||||
```
|
||||
|
||||
The documentation will be available in the `docs/` directory.
|
||||
|
||||
## Security
|
||||
|
||||
This toolkit handles sensitive information such as private keys and API keys. **Ensure you never commit `.env` files or any sensitive data**. Review the `.gitignore` to confirm that sensitive files are excluded.
|
||||
|
||||
For security vulnerabilities, please follow the [responsible disclosure](mailto:aryan@sendai.fun) process.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [ISC License](LICENSE).
|
||||
|
||||
---
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 SendAI
|
||||
|
||||
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.
|
||||
115
README.md
115
README.md
@@ -5,21 +5,26 @@ A powerful toolkit for interacting with the Solana blockchain, providing easy-to
|
||||
## Features
|
||||
|
||||
- 🪙 Token Operations
|
||||
|
||||
- Deploy new SPL tokens
|
||||
- Transfer SOL and SPL tokens
|
||||
- Check token balances
|
||||
- Stake SOL
|
||||
|
||||
- 🖼️ NFT Management
|
||||
|
||||
- Deploy NFT collections
|
||||
- Mint NFTs to collections
|
||||
- Manage metadata and royalties
|
||||
|
||||
- 💱 Trading
|
||||
|
||||
- Integrated Jupiter Exchange support
|
||||
- Token swaps with customizable slippage
|
||||
- Direct routing options
|
||||
|
||||
- 🏦 Yield Farming
|
||||
|
||||
- Lend idle assets to earn interest with Lulo
|
||||
|
||||
- 🔗 LangChain Integration
|
||||
@@ -35,12 +40,13 @@ npm install solana-agent-kit
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { SolanaAgentKit, createSolanaTools } from 'solana-agent-kit';
|
||||
import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";
|
||||
|
||||
// Initialize with private key and optional RPC URL
|
||||
const agent = new SolanaAgentKit(
|
||||
'your-private-key',
|
||||
'https://api.mainnet-beta.solana.com'
|
||||
"your-wallet-private-key-as-base58",
|
||||
"https://api.mainnet-beta.solana.com",
|
||||
"your-openai-api-key"
|
||||
);
|
||||
|
||||
// Create LangChain tools
|
||||
@@ -52,7 +58,7 @@ const tools = createSolanaTools(agent);
|
||||
### Deploy a New Token
|
||||
|
||||
```typescript
|
||||
import { deploy_token } from 'solana-agent-kit';
|
||||
import { deploy_token } from "solana-agent-kit";
|
||||
|
||||
const result = await deploy_token(
|
||||
agent,
|
||||
@@ -60,13 +66,13 @@ const result = await deploy_token(
|
||||
1000000 // initial supply
|
||||
);
|
||||
|
||||
console.log('Token Mint Address:', result.mint.toString());
|
||||
console.log("Token Mint Address:", result.mint.toString());
|
||||
```
|
||||
|
||||
### Create NFT Collection
|
||||
|
||||
```typescript
|
||||
import { deploy_collection } from 'solana-agent-kit';
|
||||
import { deploy_collection } from "solana-agent-kit";
|
||||
|
||||
const collection = await deploy_collection(agent, {
|
||||
name: "My NFT Collection",
|
||||
@@ -75,23 +81,23 @@ const collection = await deploy_collection(agent, {
|
||||
creators: [
|
||||
{
|
||||
address: "creator-wallet-address",
|
||||
percentage: 100
|
||||
}
|
||||
]
|
||||
percentage: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Swap Tokens
|
||||
|
||||
```typescript
|
||||
import { trade } from 'solana-agent-kit';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { trade } from "solana-agent-kit";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
const signature = await trade(
|
||||
agent,
|
||||
new PublicKey('target-token-mint'),
|
||||
new PublicKey("target-token-mint"),
|
||||
100, // amount
|
||||
new PublicKey('source-token-mint'),
|
||||
new PublicKey("source-token-mint"),
|
||||
300 // 3% slippage
|
||||
);
|
||||
```
|
||||
@@ -99,40 +105,110 @@ const signature = await trade(
|
||||
### Lend Tokens
|
||||
|
||||
```typescript
|
||||
import { lendAsset } from 'solana-agent-kit';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { lendAsset } from "solana-agent-kit";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
const signature = await lendAsset(
|
||||
agent,
|
||||
100, // amount
|
||||
100 // amount
|
||||
);
|
||||
```
|
||||
|
||||
### Stake SOL
|
||||
|
||||
```typescript
|
||||
import { stakeWithJup } from "solana-agent-kit";
|
||||
|
||||
const signature = await stakeWithJup(
|
||||
agent,
|
||||
1 // amount in SOL
|
||||
);
|
||||
```
|
||||
|
||||
### Fetch Token Price
|
||||
|
||||
```typescript
|
||||
import { fetchPrice } from "solana-agent-kit";
|
||||
|
||||
const price = await fetchPrice(
|
||||
agent,
|
||||
"JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" // Token mint address
|
||||
);
|
||||
|
||||
console.log("Price in USDC:", price);
|
||||
```
|
||||
|
||||
### Send an SPL Token Airdrop via ZK Compression
|
||||
|
||||
```typescript
|
||||
import {
|
||||
sendCompressedAirdrop,
|
||||
getAirdropCostEstimate,
|
||||
} from "solana-agent-kit";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
(async () => {
|
||||
console.log(
|
||||
"~Airdrop cost estimate:",
|
||||
getAirdropCostEstimate(
|
||||
1000, // recipients
|
||||
30_000 // priority fee in lamports
|
||||
)
|
||||
);
|
||||
|
||||
const signature = await sendCompressedAirdrop(
|
||||
agent,
|
||||
new PublicKey("JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"), // mint
|
||||
42, // amount per recipient
|
||||
[
|
||||
new PublicKey("1nc1nerator11111111111111111111111111111111"),
|
||||
// ... add more recipients
|
||||
],
|
||||
30_000 // priority fee in lamports
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core Functions
|
||||
|
||||
#### `deploy_token(agent, decimals?, initialSupply?)`
|
||||
Deploy a new SPL token with optional initial supply.
|
||||
#### `deploy_token(agent, decimals?, name, uri, symbol, initialSupply?)`
|
||||
|
||||
Deploy a new SPL token with optional initial supply. If not specified, decimals default to 9.
|
||||
|
||||
#### `deploy_collection(agent, options)`
|
||||
|
||||
Create a new NFT collection with customizable metadata and royalties.
|
||||
|
||||
#### `mintCollectionNFT(agent, collectionMint, metadata, recipient?)`
|
||||
|
||||
Mint a new NFT as part of an existing collection.
|
||||
|
||||
#### `transfer(agent, to, amount, mint?)`
|
||||
|
||||
Transfer SOL or SPL tokens to a recipient.
|
||||
|
||||
#### `trade(agent, outputMint, inputAmount, inputMint?, slippageBps?)`
|
||||
|
||||
Swap tokens using Jupiter Exchange integration.
|
||||
|
||||
#### `get_balance(agent, token_address)`
|
||||
|
||||
Check SOL or token balance for the agent's wallet.
|
||||
|
||||
#### `lendAsset(agent, assetMint, amount, apiKey)`
|
||||
|
||||
Lend idle assets to earn interest with Lulo.
|
||||
|
||||
#### `stakeWithJup(agent, amount)`
|
||||
|
||||
Stake SOL with Jupiter to earn rewards.
|
||||
|
||||
#### `sendCompressedAirdrop(agent, mintAddress, amount, recipients, priorityFeeInLamports?, shouldLog?)`
|
||||
|
||||
Send an SPL token airdrop to many recipients at low cost via ZK Compression.
|
||||
|
||||
## Dependencies
|
||||
|
||||
The toolkit relies on several key Solana and Metaplex libraries:
|
||||
@@ -140,7 +216,10 @@ The toolkit relies on several key Solana and Metaplex libraries:
|
||||
- @solana/web3.js
|
||||
- @solana/spl-token
|
||||
- @metaplex-foundation/mpl-token-metadata
|
||||
- @metaplex-foundation/mpl-core
|
||||
- @metaplex-foundation/umi
|
||||
- @lightprotocol/compressed-token
|
||||
- @lightprotocol/stateless.js
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
solanaagentkit.xyz
|
||||
@@ -1 +1 @@
|
||||
window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE4XSTUsDQQwG4P+S8+Ji0SJ7K5ZerB/o3sTDMGbdodlk2GRAkf53qRXt4nR6mUveeUJInj/B8N2ggSchx27xhmw3waCC6KyHBjw5VdR6Wj/rbSCoYBP4FZrz2dW2+pWuhQi9BeElRpKPAfnAC2w4ds6j1rngFJ5dzrPwfdy9ekL9SRXJEZ3JmIf2tdL3dSJZeC+JbYnmAukjahRWzIrH46Umt4Htb6i7VVvscTRdavGQhtglXrvEvi/y2eQpepW4lQ0W15bJlVi/Ww7uj7IVoQO0S/w9u9b/QlNxfrF9+QJeMbLbAQMAAA=="
|
||||
window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE4XTQUvDMBTA8e+Sc7E4dEhvw9KDzm1ob+IhxNc1LH0vJC+gyL67bBNdXfZ26SX//l4T0tcvxfDBqlIv5DTq2RqQHy2rQnnNvaqUcTpGiOV4/arnwalCbSy+q+p6crctfqV7cg4MW8IavKPPAfDIs8gQOm0glrlwDE9up1l46XfPeEH9qUQygGYKeeiwJr3eAJt+FayBZ4ieMEJWOs0k9CF5yxBa2gDWmnWW/B9J4Dw5mhlDCbkG1tZF8WvP59KQJ4v8d/SLphVnnK2lEas0+C7hXCc0vchny0t0k3B/nNLlynQSa3ZXCA6/TkvkjtAu4X7vsTyJxuL0Zvv2DYU9ByOnAwAA"
|
||||
@@ -1 +1 @@
|
||||
window.searchData = "data:application/octet-stream;base64,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";
|
||||
window.searchData = "data:application/octet-stream;base64,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";
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
docs/interfaces/FetchPriceResponse.html
Normal file
6
docs/interfaces/FetchPriceResponse.html
Normal file
File diff suppressed because one or more lines are too long
12
docs/interfaces/JupiterTokenData.html
Normal file
12
docs/interfaces/JupiterTokenData.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,6 +2,8 @@
|
||||
</div></section><section class="tsd-index-section"><h3 class="tsd-index-heading">Interfaces</h3><div class="tsd-index-list"><a href="interfaces/CollectionDeployment.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Collection<wbr/>Deployment</span></a>
|
||||
<a href="interfaces/CollectionOptions.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Collection<wbr/>Options</span></a>
|
||||
<a href="interfaces/Creator.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Creator</span></a>
|
||||
<a href="interfaces/FetchPriceResponse.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Fetch<wbr/>Price<wbr/>Response</span></a>
|
||||
<a href="interfaces/JupiterTokenData.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Jupiter<wbr/>Token<wbr/>Data</span></a>
|
||||
<a href="interfaces/LuloAccountDetailsResponse.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Lulo<wbr/>Account<wbr/>Details<wbr/>Response</span></a>
|
||||
<a href="interfaces/MintCollectionNFTResponse.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Mint<wbr/>CollectionNFTResponse</span></a>
|
||||
<a href="interfaces/PumpfunLaunchResponse.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Pumpfun<wbr/>Launch<wbr/>Response</span></a>
|
||||
|
||||
179
guides/add_your_own_tool.md
Normal file
179
guides/add_your_own_tool.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# How to Add Your Own Tool
|
||||
|
||||
Extending the **Solana Agent Kit** with custom tools allows you to add specialized functionalities tailored to your needs. This guide walks you through creating and integrating a new tool into the existing framework.
|
||||
|
||||
## Overview
|
||||
|
||||
1. Create a new tool file
|
||||
2. Implement the tool class
|
||||
3. Implement supporting functions in SolanaAgentKit
|
||||
4. Export the new tool
|
||||
5. Integrate the tool into the agent
|
||||
6. Use the custom tool
|
||||
|
||||
## Implementation Guide
|
||||
|
||||
### 1. Create a New Tool File
|
||||
|
||||
Create a new TypeScript file in the `src/tools/` directory for your tool (e.g., `custom_tool.ts`).
|
||||
|
||||
### 2. Implement the Tool Class
|
||||
|
||||
```typescript:src/tools/custom_tool.ts
|
||||
import { Tool } from "langchain/tools";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
|
||||
export class CustomTool extends Tool {
|
||||
name = "custom_tool";
|
||||
description = "Description of what the custom tool does.";
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const result = await this.solanaKit.customFunction(input);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Custom tool executed successfully",
|
||||
data: result,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add Supporting Functions to SolanaAgentKit
|
||||
|
||||
```typescript:src/agent/index.ts
|
||||
export class SolanaAgentKit {
|
||||
// ... existing code ...
|
||||
|
||||
async customFunction(input: string): Promise<string> {
|
||||
// Implement your custom functionality
|
||||
return `Processed input: ${input}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Export the Tool
|
||||
|
||||
```typescript:src/tools/index.ts
|
||||
export * from "./request_faucet_funds";
|
||||
export * from "./deploy_token";
|
||||
export * from "./custom_tool"; // Add your new tool
|
||||
```
|
||||
|
||||
### 5. Integrate with Agent
|
||||
|
||||
```typescript:src/langchain/index.ts
|
||||
import { CustomTool } from "../tools/custom_tool";
|
||||
|
||||
export function createSolanaTools(agent: SolanaAgentKit) {
|
||||
return [
|
||||
// ... existing tools ...
|
||||
new CustomTool(agent),
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Usage Example
|
||||
|
||||
```typescript
|
||||
import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit";
|
||||
|
||||
const agent = new SolanaAgentKit(
|
||||
"your-wallet-private-key-as-base58",
|
||||
"https://api.mainnet-beta.solana.com",
|
||||
"your-openai-api-key"
|
||||
);
|
||||
|
||||
const tools = createSolanaTools(agent);
|
||||
const customTool = tools.find(tool => tool.name === "custom_tool");
|
||||
|
||||
if (customTool) {
|
||||
const result = await customTool._call("your-input");
|
||||
console.log(result);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Implement robust error handling
|
||||
- Add security checks for sensitive operations
|
||||
- Document your tool's purpose and usage
|
||||
- Write tests for reliability
|
||||
- Keep tools focused on single responsibilities
|
||||
|
||||
## Example: Token Price Fetching Tool
|
||||
|
||||
Here's a complete example of implementing a tool to fetch token prices:
|
||||
|
||||
```typescript:src/tools/fetch_token_price.ts
|
||||
import { Tool } from "langchain/tools";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
|
||||
export class FetchTokenPriceTool extends Tool {
|
||||
name = "fetch_token_price";
|
||||
description = "Fetches the current price of a specified token.";
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(tokenSymbol: string): Promise<string> {
|
||||
try {
|
||||
const price = await this.solanaKit.getTokenPrice(tokenSymbol);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `Price fetched successfully for ${tokenSymbol}.`,
|
||||
data: { token: tokenSymbol, price },
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add the supporting function to SolanaAgentKit:
|
||||
|
||||
```typescript:src/agent/index.ts
|
||||
export class SolanaAgentKit {
|
||||
async getTokenPrice(tokenSymbol: string): Promise<number> {
|
||||
const mockPrices: { [key: string]: number } = {
|
||||
SOL: 150,
|
||||
USDC: 1,
|
||||
USDT: 1,
|
||||
BONK: 0.5,
|
||||
};
|
||||
|
||||
if (!mockPrices[tokenSymbol.toUpperCase()]) {
|
||||
throw new Error(`Price for token symbol ${tokenSymbol} not found.`);
|
||||
}
|
||||
|
||||
return mockPrices[tokenSymbol.toUpperCase()];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Need Help?
|
||||
|
||||
If you encounter any issues while implementing your custom tool:
|
||||
|
||||
- Open an issue in the repository
|
||||
- Contact the maintainer
|
||||
- Check existing tools for implementation examples
|
||||
|
||||
---
|
||||
83
guides/setup_locally.md
Normal file
83
guides/setup_locally.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# How to Setup Locally
|
||||
|
||||
Setting up the **Solana Agent Kit** on your local machine involves cloning the repository, installing dependencies, configuring environment variables, and building the project. Follow the steps below to get started.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node**: Ensure you have Node version 23.x or higher installed. You can download it from [Node Official Website](https://nodejs.org/).
|
||||
- **Package Manager**: Node's package manager comes bundled with Node. Verify installation by running `npm -v`.
|
||||
- **Git**: Ensure Git is installed and configured. Download from [Git Official Website](https://git-scm.com/).
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
1. **Clone the Repository**
|
||||
```bash
|
||||
git clone https://github.com/yourusername/solana-agent-kit.git
|
||||
```
|
||||
|
||||
2. **Navigate to the Project Directory**
|
||||
```bash
|
||||
cd solana-agent-kit
|
||||
```
|
||||
|
||||
3. **Install Dependencies**
|
||||
|
||||
The project uses `pnpm` for package management. Install all necessary dependencies by running:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
4. **Configure Environment Variables**
|
||||
|
||||
Create a `.env` file in the root directory of the project to store your environment variables securely. This file should include the following variables:
|
||||
```env
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
RPC_URL=your_rpc_url
|
||||
SOLANA_PRIVATE_KEY=your_solana_private_key_here
|
||||
```
|
||||
|
||||
- **OPENAI_API_KEY**: Your OpenAI API key for generating images and interacting with OpenAI services.
|
||||
- **RPC_URL**: Your RPC_URL for Solana blockchain interactions.
|
||||
- **SOLANA_PRIVATE_KEY**: Your Solana wallet's private key in base58 format.
|
||||
|
||||
**Note:** Ensure that the `.env` file is added to `.gitignore` to prevent exposing sensitive information.
|
||||
|
||||
5. **Build the Project**
|
||||
|
||||
Compile the TypeScript code to JavaScript using the build script:
|
||||
```bash
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
This will generate the compiled files in the `dist/` directory.
|
||||
|
||||
6. **Generate Documentation (Optional)**
|
||||
|
||||
If you wish to generate the project documentation, use the following command:
|
||||
```bash
|
||||
pnpm run docs
|
||||
```
|
||||
|
||||
The documentation will be available in the `docs/` directory.
|
||||
|
||||
---
|
||||
|
||||
**Additional Information:**
|
||||
|
||||
- **Git Configuration:** Ensure that Git is properly configured with your user name and email. You can set them using:
|
||||
```bash
|
||||
git config --global user.name "Your Name"
|
||||
git config --global user.email "your.email@example.com"
|
||||
```
|
||||
|
||||
- **Verifying Installation:**
|
||||
|
||||
After installing dependencies and building the project, you can verify the installation by running:
|
||||
```bash
|
||||
pnpm run build
|
||||
pnpm run test
|
||||
```
|
||||
|
||||
Ensure that all tests pass successfully.
|
||||
|
||||
---
|
||||
107
guides/test_it_out.md
Normal file
107
guides/test_it_out.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# How to Test It Out
|
||||
|
||||
Testing the **Solana Agent Kit** ensures that all functionalities are working as expected. You can run automated tests or interact with the agent in different modes to verify its operations.
|
||||
|
||||
## Running Automated Tests
|
||||
|
||||
The project includes a test script located at `test/index.ts`. To execute the tests:
|
||||
|
||||
1. **Ensure Dependencies are Installed**
|
||||
- If you haven't installed the dependencies yet, refer to the [Setup Locally](./setup_locally.md) guide.
|
||||
|
||||
2. **Run the Test Script**
|
||||
```bash
|
||||
pnpm run test
|
||||
```
|
||||
This will run the `test/index.ts` script using `ts-node`. Ensure that your environment variables are correctly set in the `.env` file before running the tests.
|
||||
|
||||
## Interactive Modes
|
||||
|
||||
### Available Modes
|
||||
1. **Chat Mode**
|
||||
- Allows you to interact with the agent in a conversational manner.
|
||||
|
||||
2. **Autonomous Mode**
|
||||
- Enables the agent to perform actions on the blockchain autonomously at regular intervals.
|
||||
|
||||
### Starting the Agent
|
||||
|
||||
1. **Launch the Agent**
|
||||
```bash
|
||||
pnpm start
|
||||
```
|
||||
|
||||
2. **Select Your Mode**
|
||||
- For Chat Mode: Enter `1` or `chat`
|
||||
- For Autonomous Mode: Enter `2` or `auto`
|
||||
|
||||
### Using Each Mode
|
||||
|
||||
#### Chat Mode
|
||||
- Start chatting by entering prompts after the `Prompt:` indicator
|
||||
- Type `exit` to end the chat session
|
||||
|
||||
#### Autonomous Mode
|
||||
- The agent executes predefined actions every 10 seconds
|
||||
- Actions and outputs are displayed in the console
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Token Deployment
|
||||
```typescript
|
||||
import { deploy_token } from "solana-agent-kit";
|
||||
|
||||
const result = await deploy_token(
|
||||
agent,
|
||||
9, // decimals
|
||||
1000000 // initial supply
|
||||
);
|
||||
|
||||
console.log("Token Mint Address:", result.mint.toString());
|
||||
```
|
||||
|
||||
### NFT Collection Creation
|
||||
```typescript
|
||||
import { deploy_collection } from "solana-agent-kit";
|
||||
|
||||
const collection = await deploy_collection(agent, {
|
||||
name: "My NFT Collection",
|
||||
uri: "https://arweave.net/metadata.json",
|
||||
royaltyBasisPoints: 500, // 5%
|
||||
creators: [
|
||||
{
|
||||
address: "creator-wallet-address",
|
||||
percentage: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Environment Setup
|
||||
- Verify `.env` file contains correct and secure values
|
||||
- Ensure all required environment variables are set
|
||||
|
||||
### Testing
|
||||
- Maintain comprehensive test coverage
|
||||
- Monitor console logs during testing
|
||||
- Clean up test assets after deployment
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Test Failures
|
||||
|
||||
#### Missing Environment Variables
|
||||
- **Issue:** Tests fail due to missing environment variables
|
||||
- **Solution:** Check `.env` file for all required variables
|
||||
|
||||
#### Network Problems
|
||||
- **Issue:** Network-related errors
|
||||
- **Solution:** Verify internet connection and Solana RPC endpoint accessibility
|
||||
|
||||
### Agent Issues
|
||||
|
||||
#### Startup Problems
|
||||
- **Issue:** Agent doesn't prompt for mode selection
|
||||
- **Solution:** Verify successful build and dependency installation
|
||||
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "solana-agent-kit",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"description": "A toolkit for interacting with the Solana blockchain using LangChain",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -10,26 +10,35 @@
|
||||
"test": "ts-node test/index.ts",
|
||||
"generate": "ts-node src/utils/keypair.ts"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=23.1.0",
|
||||
"pnpm": ">=8.0.0"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@raydium-io/raydium-sdk-v2": "0.1.95-alpha",
|
||||
"@bonfida/spl-name-service": "^3.0.7",
|
||||
"@coral-xyz/anchor": "0.29",
|
||||
"@langchain/core": "^0.3.18",
|
||||
"@langchain/groq": "^0.1.2",
|
||||
"@langchain/langgraph": "^0.2.27",
|
||||
"@langchain/openai": "^0.3.13",
|
||||
"@lightprotocol/compressed-token": "^0.17.1",
|
||||
"@lightprotocol/stateless.js": "^0.17.1",
|
||||
"@metaplex-foundation/mpl-core": "^1.1.1",
|
||||
"@metaplex-foundation/mpl-token-metadata": "^3.3.0",
|
||||
"@metaplex-foundation/umi": "^0.9.2",
|
||||
"@metaplex-foundation/umi-bundle-defaults": "^0.9.2",
|
||||
"@metaplex-foundation/umi-web3js-adapters": "^0.9.2",
|
||||
"@orca-so/common-sdk": "0.6.4",
|
||||
"@orca-so/whirlpools-sdk": "^0.13.12",
|
||||
"@solana/spl-token": "^0.4.9",
|
||||
"@solana/web3.js": "^1.95.4",
|
||||
"bs58": "^6.0.0",
|
||||
"bn.js": "^5.2.1",
|
||||
"decimal.js": "^10.4.3",
|
||||
"bn.js": "^5.2.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"form-data": "^4.0.1",
|
||||
"langchain": "^0.3.6",
|
||||
@@ -39,6 +48,7 @@
|
||||
"devDependencies": {
|
||||
"@types/bn.js": "^5.1.5",
|
||||
"@types/node": "^22.9.0",
|
||||
"ts-node": "^10.9.2"
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
3141
pnpm-lock.yaml
generated
3141
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { Connection, Keypair, PublicKey } from "@solana/web3.js";;
|
||||
import bs58 from "bs58";
|
||||
import Decimal from "decimal.js";
|
||||
import { DEFAULT_OPTIONS } from "../constants";
|
||||
@@ -8,6 +7,8 @@ import {
|
||||
deploy_token,
|
||||
get_balance,
|
||||
getTPS,
|
||||
resolveSolDomain,
|
||||
getPrimaryDomain,
|
||||
launchPumpFunToken,
|
||||
lendAsset,
|
||||
mintCollectionNFT,
|
||||
@@ -19,8 +20,15 @@ import {
|
||||
request_faucet_funds,
|
||||
trade,
|
||||
transfer,
|
||||
getTokenDataByAddress,
|
||||
getTokenDataByTicker,
|
||||
stakeWithJup,
|
||||
sendCompressedAirdrop,
|
||||
createOrcaSingleSidedWhirlpool,
|
||||
FEE_TIERS
|
||||
} from "../tools";
|
||||
import { CollectionOptions, PumpFunTokenOptions } from "../types";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
|
||||
/**
|
||||
* Main class for interacting with Solana blockchain
|
||||
@@ -40,7 +48,7 @@ export class SolanaAgentKit {
|
||||
constructor(
|
||||
private_key: string,
|
||||
rpc_url = "https://api.mainnet-beta.solana.com",
|
||||
openai_api_key: string,
|
||||
openai_api_key: string
|
||||
) {
|
||||
this.connection = new Connection(rpc_url);
|
||||
this.wallet = Keypair.fromSecretKey(bs58.decode(private_key));
|
||||
@@ -54,10 +62,13 @@ export class SolanaAgentKit {
|
||||
}
|
||||
|
||||
async deployToken(
|
||||
name: string,
|
||||
uri: string,
|
||||
symbol: string,
|
||||
decimals: number = DEFAULT_OPTIONS.TOKEN_DECIMALS,
|
||||
// initialSupply?: number
|
||||
initialSupply?: number
|
||||
) {
|
||||
return deploy_token(this, decimals);
|
||||
return deploy_token(this, name, uri, symbol, decimals, initialSupply);
|
||||
}
|
||||
|
||||
async deployCollection(options: CollectionOptions) {
|
||||
@@ -71,7 +82,7 @@ export class SolanaAgentKit {
|
||||
async mintNFT(
|
||||
collectionMint: PublicKey,
|
||||
metadata: Parameters<typeof mintCollectionNFT>[2],
|
||||
recipient?: PublicKey,
|
||||
recipient?: PublicKey
|
||||
) {
|
||||
return mintCollectionNFT(this, collectionMint, metadata, recipient);
|
||||
}
|
||||
@@ -84,11 +95,19 @@ export class SolanaAgentKit {
|
||||
return registerDomain(this, name, spaceKB);
|
||||
}
|
||||
|
||||
async resolveSolDomain(domain: string) {
|
||||
return resolveSolDomain(this, domain);
|
||||
}
|
||||
|
||||
async getPrimaryDomain(account: PublicKey) {
|
||||
return getPrimaryDomain(this, account);
|
||||
}
|
||||
|
||||
async trade(
|
||||
outputMint: PublicKey,
|
||||
inputAmount: number,
|
||||
inputMint?: PublicKey,
|
||||
slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS,
|
||||
slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS
|
||||
) {
|
||||
return trade(this, outputMint, inputAmount, inputMint, slippageBps);
|
||||
}
|
||||
@@ -101,12 +120,20 @@ export class SolanaAgentKit {
|
||||
return getTPS(this);
|
||||
}
|
||||
|
||||
async getTokenDataByAddress(mint: string) {
|
||||
return getTokenDataByAddress(new PublicKey(mint));
|
||||
}
|
||||
|
||||
async getTokenDataByTicker(ticker: string) {
|
||||
return getTokenDataByTicker(ticker);
|
||||
}
|
||||
|
||||
async launchPumpFunToken(
|
||||
tokenName: string,
|
||||
tokenTicker: string,
|
||||
description: string,
|
||||
imageUrl: string,
|
||||
options?: PumpFunTokenOptions,
|
||||
options?: PumpFunTokenOptions
|
||||
) {
|
||||
return launchPumpFunToken(
|
||||
this,
|
||||
@@ -114,10 +141,52 @@ export class SolanaAgentKit {
|
||||
tokenTicker,
|
||||
description,
|
||||
imageUrl,
|
||||
options,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
async stake(amount: number) {
|
||||
return stakeWithJup(this, amount);
|
||||
}
|
||||
|
||||
async sendCompressedAirdrop(
|
||||
mintAddress: string,
|
||||
amount: number,
|
||||
decimals: number,
|
||||
recipients: string[],
|
||||
priorityFeeInLamports: number,
|
||||
shouldLog: boolean
|
||||
): Promise<string[]> {
|
||||
return await sendCompressedAirdrop(
|
||||
this,
|
||||
new PublicKey(mintAddress),
|
||||
amount,
|
||||
decimals,
|
||||
recipients.map((recipient) => new PublicKey(recipient)),
|
||||
priorityFeeInLamports,
|
||||
shouldLog
|
||||
);
|
||||
}
|
||||
|
||||
async createOrcaSingleSidedWhirlpool(
|
||||
depositTokenAmount: BN,
|
||||
depositTokenMint: PublicKey,
|
||||
otherTokenMint: PublicKey,
|
||||
initialPrice: Decimal,
|
||||
maxPrice: Decimal,
|
||||
feeTier: keyof typeof FEE_TIERS,
|
||||
) {
|
||||
return createOrcaSingleSidedWhirlpool(
|
||||
this,
|
||||
depositTokenAmount,
|
||||
depositTokenMint,
|
||||
otherTokenMint,
|
||||
initialPrice,
|
||||
maxPrice,
|
||||
feeTier
|
||||
)
|
||||
}
|
||||
|
||||
async raydiumCreateAmmV4(
|
||||
marketId: PublicKey,
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { BN } from "bn.js";
|
||||
import Decimal from "decimal.js";
|
||||
import { Tool } from "langchain/tools";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { create_image } from "../tools/create_image";
|
||||
import { fetchPrice } from "../tools/fetch_price";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { FEE_TIERS } from "../tools";
|
||||
import { toJSON } from "../utils/toJSON";
|
||||
|
||||
export class SolanaBalanceTool extends Tool {
|
||||
@@ -56,7 +58,6 @@ export class SolanaTransferTool extends Tool {
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
console.log(parsedInput);
|
||||
|
||||
const recipient = new PublicKey(parsedInput.to);
|
||||
const mintAddress = parsedInput.mint
|
||||
@@ -89,38 +90,30 @@ export class SolanaTransferTool extends Tool {
|
||||
|
||||
export class SolanaDeployTokenTool extends Tool {
|
||||
name = "solana_deploy_token";
|
||||
description =
|
||||
"Deploy a new SPL token. Input should be JSON string with: {decimals?: number, initialSupply?: number}";
|
||||
description = `Deploy a new token on Solana blockchain.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
name: string, eg "My Token" (required)
|
||||
uri: string, eg "https://example.com/token.json" (required)
|
||||
symbol: string, eg "MTK" (required)
|
||||
decimals?: number, eg 9 (optional, defaults to 9)
|
||||
initialSupply?: number, eg 1000000 (optional)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
private validateInput(input: any): void {
|
||||
if (
|
||||
input.decimals !== undefined &&
|
||||
(typeof input.decimals !== "number" ||
|
||||
input.decimals < 0 ||
|
||||
input.decimals > 9)
|
||||
) {
|
||||
throw new Error(
|
||||
"decimals must be a number between 0 and 9 when provided"
|
||||
);
|
||||
}
|
||||
if (
|
||||
input.initialSupply !== undefined &&
|
||||
(typeof input.initialSupply !== "number" || input.initialSupply <= 0)
|
||||
) {
|
||||
throw new Error("initialSupply must be a positive number when provided");
|
||||
}
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = toJSON(input);
|
||||
this.validateInput(parsedInput);
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const result = await this.solanaKit.deployToken(parsedInput.decimals);
|
||||
const result = await this.solanaKit.deployToken(
|
||||
parsedInput.name,
|
||||
parsedInput.uri,
|
||||
parsedInput.symbol,
|
||||
parsedInput.decimals,
|
||||
parsedInput.initialSupply
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
@@ -140,57 +133,20 @@ export class SolanaDeployTokenTool extends Tool {
|
||||
|
||||
export class SolanaDeployCollectionTool extends Tool {
|
||||
name = "solana_deploy_collection";
|
||||
description =
|
||||
"Deploy a new NFT collection. Input should be JSON with: {name: string, uri: string, royaltyBasisPoints?: number, creators?: Array<{address: string, percentage: number}>}";
|
||||
description = `Deploy a new NFT collection on Solana blockchain.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
name: string, eg "My Collection" (required)
|
||||
uri: string, eg "https://example.com/collection.json" (required)
|
||||
royaltyBasisPoints?: number, eg 500 for 5% (optional)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
private validateInput(input: any): void {
|
||||
if (!input.name || typeof input.name !== "string") {
|
||||
throw new Error("name is required and must be a string");
|
||||
}
|
||||
if (!input.uri || typeof input.uri !== "string") {
|
||||
throw new Error("uri is required and must be a string");
|
||||
}
|
||||
if (
|
||||
input.royaltyBasisPoints !== undefined &&
|
||||
(typeof input.royaltyBasisPoints !== "number" ||
|
||||
input.royaltyBasisPoints < 0 ||
|
||||
input.royaltyBasisPoints > 10000)
|
||||
) {
|
||||
throw new Error(
|
||||
"royaltyBasisPoints must be a number between 0 and 10000 when provided"
|
||||
);
|
||||
}
|
||||
if (input.creators) {
|
||||
if (!Array.isArray(input.creators)) {
|
||||
throw new Error("creators must be an array when provided");
|
||||
}
|
||||
input.creators.forEach((creator: any, index: number) => {
|
||||
if (!creator.address || typeof creator.address !== "string") {
|
||||
throw new Error(
|
||||
`creator[${index}].address is required and must be a string`
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof creator.percentage !== "number" ||
|
||||
creator.percentage < 0 ||
|
||||
creator.percentage > 100
|
||||
) {
|
||||
throw new Error(
|
||||
`creator[${index}].percentage must be a number between 0 and 100`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = toJSON(input);
|
||||
this.validateInput(parsedInput);
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const result = await this.solanaKit.deployCollection(parsedInput);
|
||||
|
||||
@@ -212,50 +168,42 @@ export class SolanaDeployCollectionTool extends Tool {
|
||||
|
||||
export class SolanaMintNFTTool extends Tool {
|
||||
name = "solana_mint_nft";
|
||||
description =
|
||||
"Mint a new NFT in a collection. Input should be JSON with: {collectionMint: string, metadata: {name: string, symbol: string, uri: string}, recipient?: string}";
|
||||
description = `Mint a new NFT in a collection on Solana blockchain.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
collectionMint: string, eg "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w" (required) - The address of the collection to mint into
|
||||
name: string, eg "My NFT" (required)
|
||||
uri: string, eg "https://example.com/nft.json" (required)
|
||||
recipient?: string, eg "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u" (optional) - The wallet to receive the NFT, defaults to agent's wallet which is ${this.solanaKit.wallet_address.toString()}`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
private validateInput(input: any): void {
|
||||
if (!input.collectionMint || typeof input.collectionMint !== "string") {
|
||||
throw new Error("collectionMint is required and must be a string");
|
||||
}
|
||||
if (!input.metadata || typeof input.metadata !== "object") {
|
||||
throw new Error("metadata is required and must be an object");
|
||||
}
|
||||
if (!input.metadata.name || typeof input.metadata.name !== "string") {
|
||||
throw new Error("metadata.name is required and must be a string");
|
||||
}
|
||||
if (!input.metadata.symbol || typeof input.metadata.symbol !== "string") {
|
||||
throw new Error("metadata.symbol is required and must be a string");
|
||||
}
|
||||
if (!input.metadata.uri || typeof input.metadata.uri !== "string") {
|
||||
throw new Error("metadata.uri is required and must be a string");
|
||||
}
|
||||
if (input.recipient !== undefined && typeof input.recipient !== "string") {
|
||||
throw new Error("recipient must be a string when provided");
|
||||
}
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = toJSON(input);
|
||||
this.validateInput(parsedInput);
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const result = await this.solanaKit.mintNFT(
|
||||
new PublicKey(parsedInput.collectionMint),
|
||||
parsedInput.metadata,
|
||||
parsedInput.recipient ? new PublicKey(parsedInput.recipient) : undefined
|
||||
{
|
||||
name: parsedInput.name,
|
||||
uri: parsedInput.uri,
|
||||
},
|
||||
parsedInput.recipient
|
||||
? new PublicKey(parsedInput.recipient)
|
||||
: this.solanaKit.wallet_address
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: result.mint.toString(),
|
||||
name: parsedInput.metadata.name,
|
||||
metadata: {
|
||||
name: parsedInput.name,
|
||||
symbol: parsedInput.symbol,
|
||||
uri: parsedInput.uri,
|
||||
},
|
||||
recipient: parsedInput.recipient || result.mint.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -304,7 +252,6 @@ export class SolanaTradeTool extends Tool {
|
||||
outputToken: parsedInput.outputMint,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
@@ -393,6 +340,70 @@ export class SolanaRegisterDomainTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaResolveDomainTool extends Tool {
|
||||
name = "solana_resolve_domain";
|
||||
description = `Resolve a .sol domain to a Solana PublicKey.
|
||||
|
||||
Inputs:
|
||||
domain: string, eg "pumpfun.sol" or "pumpfun"(required)
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const domain = input.trim();
|
||||
const publicKey = await this.solanaKit.resolveSolDomain(domain);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Domain resolved successfully",
|
||||
publicKey: publicKey.toBase58(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaGetDomainTool extends Tool {
|
||||
name = "solana_get_domain";
|
||||
description = `Retrieve the .sol domain associated for a given account address.
|
||||
|
||||
Inputs:
|
||||
account: string, eg "4Be9CvxqHW6BYiRAxW9Q3xu1ycTMWaL5z8NX4HR3ha7t" (required)
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const account = new PublicKey(input.trim());
|
||||
const domain = await this.solanaKit.getPrimaryDomain(account);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Primary domain retrieved successfully",
|
||||
domain,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaGetWalletAddressTool extends Tool {
|
||||
name = "solana_get_wallet_address";
|
||||
description = `Get the wallet address of the agent`;
|
||||
@@ -425,7 +436,6 @@ export class SolanaPumpfunTokenLaunchTool extends Tool {
|
||||
}
|
||||
|
||||
private validateInput(input: any): void {
|
||||
console.log(input);
|
||||
if (!input.tokenName || typeof input.tokenName !== "string") {
|
||||
throw new Error("tokenName is required and must be a string");
|
||||
}
|
||||
@@ -570,6 +580,230 @@ export class SolanaTPSCalculatorTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaStakeTool extends Tool {
|
||||
name = "solana_stake";
|
||||
description = `This tool can be used to stake your SOL (Solana), also called as SOL staking or liquid staking.
|
||||
|
||||
Inputs ( input is a JSON string ):
|
||||
amount: number, eg 1 or 0.01 (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input) || Number(input);
|
||||
|
||||
const tx = await this.solanaKit.stake(parsedInput.amount);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Staked successfully",
|
||||
transaction: tx,
|
||||
amount: parsedInput.amount,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool to fetch the price of a token in USDC
|
||||
*/
|
||||
export class SolanaFetchPriceTool extends Tool {
|
||||
name = "solana_fetch_price";
|
||||
description = `Fetch the price of a given token in USDC.
|
||||
|
||||
Inputs:
|
||||
- tokenId: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const price = await fetchPrice(this.solanaKit, input.trim());
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
tokenId: input.trim(),
|
||||
priceInUSDC: price,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaTokenDataTool extends Tool {
|
||||
name = "solana_token_data";
|
||||
description = `Get the token data for a given token mint address
|
||||
|
||||
Inputs: mintAddress is required.
|
||||
mintAddress: string, eg "So11111111111111111111111111111111111111112" (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = input.trim();
|
||||
|
||||
const tokenData = await this.solanaKit.getTokenDataByAddress(parsedInput);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
tokenData: tokenData,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaTokenDataByTickerTool extends Tool {
|
||||
name = "solana_token_data_by_ticker";
|
||||
description = `Get the token data for a given token ticker
|
||||
|
||||
Inputs: ticker is required.
|
||||
ticker: string, eg "USDC" (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const ticker = input.trim();
|
||||
const tokenData = await this.solanaKit.getTokenDataByTicker(ticker);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
tokenData: tokenData,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCompressedAirdropTool extends Tool {
|
||||
name = "solana_compressed_airdrop";
|
||||
description = `Airdrop SPL tokens with ZK Compression (also called as airdropping tokens)
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
mintAddress: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" (required)
|
||||
amount: number, the amount of tokens to airdrop per recipient, e.g., 42 (required)
|
||||
decimals: number, the decimals of the token, e.g., 6 (required)
|
||||
recipients: string[], the recipient addresses, e.g., ["1nc1nerator11111111111111111111111111111111"] (required)
|
||||
priorityFeeInLamports: number, the priority fee in lamports. Default is 30_000. (optional)
|
||||
shouldLog: boolean, whether to log progress to stdout. Default is false. (optional)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const txs = await this.solanaKit.sendCompressedAirdrop(
|
||||
parsedInput.mintAddress,
|
||||
parsedInput.amount,
|
||||
parsedInput.decimals,
|
||||
parsedInput.recipients,
|
||||
parsedInput.priorityFeeInLamports || 30_000,
|
||||
parsedInput.shouldLog || false
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `Airdropped ${parsedInput.amount} tokens to ${parsedInput.recipients.length} recipients.`,
|
||||
transactionHashes: txs,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
|
||||
name = "create_orca_single_sided_whirlpool";
|
||||
description = `Create a single-sided Whirlpool with liquidity.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
- depositTokenAmount: number, eg: 1000000000 (required, in units of deposit token including decimals)
|
||||
- depositTokenMint: string, eg: "DepositTokenMintAddress" (required, mint address of deposit token)
|
||||
- otherTokenMint: string, eg: "OtherTokenMintAddress" (required, mint address of other token)
|
||||
- initialPrice: number, eg: 0.001 (required, initial price of deposit token in terms of other token)
|
||||
- maxPrice: number, eg: 5.0 (required, maximum price at which liquidity is added)
|
||||
- feeTier: number, eg: 0.30 (required, fee tier for the pool)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const depositTokenAmount = new BN(inputFormat.depositTokenAmount);
|
||||
const depositTokenMint = new PublicKey(inputFormat.depositTokenMint);
|
||||
const otherTokenMint = new PublicKey(inputFormat.otherTokenMint);
|
||||
const initialPrice = new Decimal(inputFormat.initialPrice);
|
||||
const maxPrice = new Decimal(inputFormat.maxPrice);
|
||||
const feeTier = inputFormat.feeTier;
|
||||
|
||||
if (!feeTier || !(feeTier in FEE_TIERS)) {
|
||||
throw new Error(`Invalid feeTier. Available options: ${Object.keys(FEE_TIERS).join(", ")}`);
|
||||
}
|
||||
|
||||
const txId = await this.solanaKit.createOrcaSingleSidedWhirlpool(
|
||||
depositTokenAmount,
|
||||
depositTokenMint,
|
||||
otherTokenMint,
|
||||
initialPrice,
|
||||
maxPrice,
|
||||
feeTier,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Single-sided Whirlpool created successfully",
|
||||
transaction: txId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SolanaRaydiumCreateAmmV4 extends Tool {
|
||||
name = "raydium_create_ammV4";
|
||||
description = `Raydium's Legacy AMM that requiers an OpenBook marketID
|
||||
@@ -761,9 +995,17 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaCreateImageTool(solanaKit),
|
||||
new SolanaLendAssetTool(solanaKit),
|
||||
new SolanaTPSCalculatorTool(solanaKit),
|
||||
new SolanaStakeTool(solanaKit),
|
||||
new SolanaFetchPriceTool(solanaKit),
|
||||
new SolanaResolveDomainTool(solanaKit),
|
||||
new SolanaGetDomainTool(solanaKit),
|
||||
new SolanaTokenDataTool(solanaKit),
|
||||
new SolanaTokenDataByTickerTool(solanaKit),
|
||||
new SolanaCompressedAirdropTool(solanaKit),
|
||||
new SolanaRaydiumCreateAmmV4(solanaKit),
|
||||
new SolanaRaydiumCreateClmm(solanaKit),
|
||||
new SolanaRaydiumCreateCpmm(solanaKit),
|
||||
new SolanaOpenbookCreateMarket(solanaKit),
|
||||
new SolanaCreateSingleSidedWhirlpoolTool(solanaKit),
|
||||
];
|
||||
}
|
||||
|
||||
385
src/tools/create_orca_single_sided_whirlpool.ts
Normal file
385
src/tools/create_orca_single_sided_whirlpool.ts
Normal file
@@ -0,0 +1,385 @@
|
||||
import { Keypair, PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { BN, Wallet } from "@coral-xyz/anchor";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
PDAUtil,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
WhirlpoolContext,
|
||||
TickUtil,
|
||||
PriceMath,
|
||||
PoolUtil,
|
||||
TokenExtensionContextForPool,
|
||||
NO_TOKEN_EXTENSION_CONTEXT,
|
||||
TokenExtensionUtil,
|
||||
WhirlpoolIx,
|
||||
IncreaseLiquidityQuoteParam,
|
||||
increaseLiquidityQuoteByInputTokenWithParams,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import {
|
||||
Percentage,
|
||||
resolveOrCreateATAs,
|
||||
TransactionBuilder,
|
||||
} from "@orca-so/common-sdk";
|
||||
import {
|
||||
increaseLiquidityIx,
|
||||
increaseLiquidityV2Ix,
|
||||
initTickArrayIx,
|
||||
openPositionWithTokenExtensionsIx,
|
||||
} from "@orca-so/whirlpools-sdk/dist/instructions";
|
||||
import {
|
||||
getAssociatedTokenAddressSync,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
|
||||
/**
|
||||
* Maps fee tier percentages to their corresponding tick spacing values in the Orca Whirlpool protocol.
|
||||
*
|
||||
* @remarks
|
||||
* Fee tiers determine the percentage of fees collected on swaps, while tick spacing affects
|
||||
* the granularity of price ranges for liquidity positions.
|
||||
*
|
||||
* For more details, refer to:
|
||||
* - [Whirlpool Fees](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Fees)
|
||||
* - [Whirlpool Parameters](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Parameters)
|
||||
*
|
||||
* @example
|
||||
* const tickSpacing = FEE_TIERS[0.30]; // Returns 64
|
||||
*/
|
||||
export const FEE_TIERS = {
|
||||
0.01: 1,
|
||||
0.02: 2,
|
||||
0.04: 4,
|
||||
0.05: 8,
|
||||
0.16: 16,
|
||||
0.30: 64,
|
||||
0.65: 96,
|
||||
1.00: 128,
|
||||
2.00: 256,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* # Creates a single-sided Whirlpool.
|
||||
*
|
||||
* This function initializes a new Whirlpool (liquidity pool) on Orca and seeds it with liquidity from a single token.
|
||||
*
|
||||
* ## Example Usage:
|
||||
* You created a new token called SHARK, and you want to set the initial price to 0.001 USDC.
|
||||
* You set `depositTokenMint` to SHARK's mint address and `otherTokenMint` to USDC's mint address.
|
||||
* You can minimize price impact for buyers in a few ways:
|
||||
* 1. Increase the amount of tokens you deposit
|
||||
* 2. Set the initial price very low
|
||||
* 3. Set the maximum price closer to the initial price
|
||||
*
|
||||
* ### Note for experts:
|
||||
* The Wrhirlpool program initializes the Whirlpool with the in a specific order. This might not be
|
||||
* the order you expect, so the function checks the order and adjusts the inverts the prices. This means that
|
||||
* on-chain the Whirlpool might be configured as USDC/SHARK instead of SHARK/USDC, and the on-chain price will
|
||||
* be 1/`initialPrice`. This will not affect the price of the token as you intended it to be.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection details.
|
||||
* @param depositTokenAmount - The amount of the deposit token (including the decimals) to contribute to the pool.
|
||||
* @param depositTokenMint - The mint address of the token being deposited into the pool, eg. SHARK.
|
||||
* @param otherTokenMint - The mint address of the other token in the pool, eg. USDC.
|
||||
* @param initialPrice - The initial price of the deposit token in terms of the other token.
|
||||
* @param maxPrice - The maximum price at which liquidity is added.
|
||||
* @param feeTier - The fee tier percentage for the pool, determining tick spacing and fee collection rates.
|
||||
*
|
||||
* @returns A promise that resolves to a transaction ID (`string`) of the transaction creating the pool.
|
||||
*
|
||||
* @throws Will throw an error if:
|
||||
* - Mint accounts for the tokens cannot be fetched.
|
||||
* - Prices are out of bounds.
|
||||
*
|
||||
* @remarks
|
||||
* This function is designed for single-sided deposits where users only contribute one type of token,
|
||||
* and the function manages mint order and necessary calculations.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { SolanaAgentKit } from "your-sdk";
|
||||
* import { PublicKey } from "@solana/web3.js";
|
||||
* import { BN } from "@coral-xyz/anchor";
|
||||
* import Decimal from "decimal.js";
|
||||
*
|
||||
* const agent = new SolanaAgentKit(wallet, connection);
|
||||
* const depositAmount = new BN(1_000_000_000_000); // 1 million SHARK if SHARK has 6 decimals
|
||||
* const depositTokenMint = new PublicKey("DEPOSTI_TOKEN_ADDRESS");
|
||||
* const otherTokenMint = new PublicKey("OTHER_TOKEN_ADDRESS");
|
||||
* const initialPrice = new Decimal(0.001);
|
||||
* const maxPrice = new Decimal(5.0);
|
||||
* const feeTier = 0.30;
|
||||
*
|
||||
* const txId = await createOrcaSingleSidedWhirlpool(
|
||||
* agent,
|
||||
* depositAmount,
|
||||
* depositTokenMint,
|
||||
* otherTokenMint,
|
||||
* initialPrice,
|
||||
* maxPrice,
|
||||
* feeTier,
|
||||
* );
|
||||
* console.log(`Single sided whirlpool created in transaction: ${txId}`);
|
||||
* ```
|
||||
*/
|
||||
export async function createOrcaSingleSidedWhirlpool(
|
||||
agent: SolanaAgentKit,
|
||||
depositTokenAmount: BN,
|
||||
depositTokenMint: PublicKey,
|
||||
otherTokenMint: PublicKey,
|
||||
initialPrice: Decimal,
|
||||
maxPrice: Decimal,
|
||||
feeTier: keyof typeof FEE_TIERS,
|
||||
): Promise<string> {
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(agent.connection, wallet, ORCA_WHIRLPOOL_PROGRAM_ID);
|
||||
const fetcher = ctx.fetcher;
|
||||
|
||||
const correctTokenOrder = PoolUtil.orderMints(otherTokenMint, depositTokenMint).map(
|
||||
(addr) => addr.toString(),
|
||||
);
|
||||
const isCorrectMintOrder = correctTokenOrder[0] === depositTokenMint.toString();
|
||||
let mintA, mintB;
|
||||
if (isCorrectMintOrder) {
|
||||
[mintA, mintB] = [depositTokenMint, otherTokenMint];
|
||||
} else {
|
||||
[mintA, mintB] = [otherTokenMint, depositTokenMint];
|
||||
initialPrice = new Decimal(1 / initialPrice.toNumber());
|
||||
maxPrice = new Decimal(1 / maxPrice.toNumber());
|
||||
}
|
||||
const mintAAccount = await fetcher.getMintInfo(mintA);
|
||||
const mintBAccount = await fetcher.getMintInfo(mintB);
|
||||
if (mintAAccount === null || mintBAccount === null) throw Error('Mint account not found');
|
||||
const tickSpacing = FEE_TIERS[feeTier];
|
||||
const tickIndex = PriceMath.priceToTickIndex(initialPrice, mintAAccount.decimals, mintBAccount.decimals);
|
||||
const initialTick = TickUtil.getInitializableTickIndex(tickIndex, tickSpacing);
|
||||
|
||||
const tokenExtensionCtx: TokenExtensionContextForPool = {
|
||||
...NO_TOKEN_EXTENSION_CONTEXT,
|
||||
tokenMintWithProgramA: mintAAccount,
|
||||
tokenMintWithProgramB: mintBAccount,
|
||||
};
|
||||
const feeTierKey = PDAUtil.getFeeTier(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
tickSpacing,
|
||||
).publicKey;
|
||||
const initSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(initialTick);
|
||||
const tokenVaultAKeypair = Keypair.generate();
|
||||
const tokenVaultBKeypair = Keypair.generate();
|
||||
const whirlpoolPda = PDAUtil.getWhirlpool(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
mintA,
|
||||
mintB,
|
||||
FEE_TIERS[feeTier],
|
||||
);
|
||||
const tokenBadgeA = PDAUtil.getTokenBadge(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
mintA,
|
||||
).publicKey;
|
||||
const tokenBadgeB = PDAUtil.getTokenBadge(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
mintB,
|
||||
).publicKey;
|
||||
const baseParamsPool = {
|
||||
initSqrtPrice,
|
||||
whirlpoolsConfig: ORCA_WHIRLPOOLS_CONFIG,
|
||||
whirlpoolPda,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tokenVaultAKeypair,
|
||||
tokenVaultBKeypair,
|
||||
feeTierKey,
|
||||
tickSpacing: tickSpacing,
|
||||
funder: wallet.publicKey
|
||||
};
|
||||
const initPoolIx = !TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx)
|
||||
? WhirlpoolIx.initializePoolIx(ctx.program, baseParamsPool)
|
||||
: WhirlpoolIx.initializePoolV2Ix(ctx.program, {
|
||||
...baseParamsPool,
|
||||
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
|
||||
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
|
||||
tokenBadgeA,
|
||||
tokenBadgeB,
|
||||
});
|
||||
const initialTickArrayStartTick = TickUtil.getStartTickIndex(
|
||||
initialTick,
|
||||
tickSpacing,
|
||||
);
|
||||
const initialTickArrayPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
initialTickArrayStartTick,
|
||||
);
|
||||
|
||||
const txBuilder = new TransactionBuilder(
|
||||
ctx.provider.connection,
|
||||
ctx.provider.wallet,
|
||||
ctx.txBuilderOpts,
|
||||
);
|
||||
txBuilder.addInstruction(initPoolIx);
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: initialTickArrayStartTick,
|
||||
tickArrayPda: initialTickArrayPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
|
||||
let tickLowerIndex, tickUpperIndex;
|
||||
if (isCorrectMintOrder) {
|
||||
tickLowerIndex = initialTick;
|
||||
tickUpperIndex = PriceMath.priceToTickIndex(maxPrice, mintAAccount.decimals, mintBAccount.decimals);
|
||||
} else {
|
||||
tickLowerIndex = PriceMath.priceToTickIndex(maxPrice, mintAAccount.decimals, mintBAccount.decimals);
|
||||
tickUpperIndex = initialTick;
|
||||
}
|
||||
const tickLowerInitializableIndex = TickUtil.getInitializableTickIndex(tickLowerIndex, tickSpacing);
|
||||
const tickUpperInitializableIndex = TickUtil.getInitializableTickIndex(tickUpperIndex, tickSpacing);
|
||||
if (!TickUtil.checkTickInBounds(tickLowerInitializableIndex) || !TickUtil.checkTickInBounds(tickUpperInitializableIndex)) throw Error('Prices out of bounds');
|
||||
const increasLiquidityQuoteParam: IncreaseLiquidityQuoteParam = {
|
||||
inputTokenAmount: new BN(depositTokenAmount),
|
||||
inputTokenMint: depositTokenMint,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tickCurrentIndex: initialTick,
|
||||
sqrtPrice: initSqrtPrice,
|
||||
tickLowerIndex: tickLowerInitializableIndex,
|
||||
tickUpperIndex: tickUpperInitializableIndex,
|
||||
tokenExtensionCtx: tokenExtensionCtx,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100)
|
||||
}
|
||||
const liquidityInput = increaseLiquidityQuoteByInputTokenWithParams(
|
||||
increasLiquidityQuoteParam
|
||||
)
|
||||
const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput;
|
||||
|
||||
const positionMintKeypair = Keypair.generate();
|
||||
const positionMintPubkey = positionMintKeypair.publicKey;
|
||||
const positionPda = PDAUtil.getPosition(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
positionMintPubkey,
|
||||
);
|
||||
const positionTokenAccountAddress = getAssociatedTokenAddressSync(
|
||||
positionMintPubkey,
|
||||
wallet.publicKey,
|
||||
ctx.accountResolverOpts.allowPDAOwnerAddress,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
const params = {
|
||||
funder: wallet.publicKey,
|
||||
owner: wallet.publicKey,
|
||||
positionPda,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tickLowerIndex: tickLowerInitializableIndex,
|
||||
tickUpperIndex: tickUpperInitializableIndex,
|
||||
};
|
||||
const positionIx = openPositionWithTokenExtensionsIx(ctx.program, {
|
||||
...params,
|
||||
positionMint: positionMintPubkey,
|
||||
withTokenMetadataExtension: true,
|
||||
})
|
||||
|
||||
txBuilder.addInstruction(positionIx);
|
||||
txBuilder.addSigner(positionMintKeypair);
|
||||
|
||||
const [ataA, ataB] = await resolveOrCreateATAs(
|
||||
ctx.connection,
|
||||
wallet.publicKey,
|
||||
[
|
||||
{ tokenMint: mintA, wrappedSolAmountIn: tokenMaxA },
|
||||
{ tokenMint: mintB, wrappedSolAmountIn: tokenMaxB },
|
||||
],
|
||||
() => ctx.fetcher.getAccountRentExempt(),
|
||||
wallet.publicKey,
|
||||
undefined,
|
||||
ctx.accountResolverOpts.allowPDAOwnerAddress,
|
||||
ctx.accountResolverOpts.createWrappedSolAccountMethod,
|
||||
);
|
||||
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
|
||||
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
|
||||
|
||||
txBuilder.addInstruction(tokenOwnerAccountAIx);
|
||||
txBuilder.addInstruction(tokenOwnerAccountBIx);
|
||||
|
||||
const tickArrayLowerStartIndex = TickUtil.getStartTickIndex(
|
||||
tickLowerInitializableIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickArrayUpperStartIndex = TickUtil.getStartTickIndex(
|
||||
tickUpperInitializableIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickArrayLowerPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
tickArrayLowerStartIndex,
|
||||
);
|
||||
const tickArrayUpperPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
tickArrayUpperStartIndex,
|
||||
);
|
||||
if (tickArrayUpperStartIndex !== tickArrayLowerStartIndex) {
|
||||
if (isCorrectMintOrder) {
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: tickArrayUpperStartIndex,
|
||||
tickArrayPda: tickArrayUpperPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: tickArrayLowerStartIndex,
|
||||
tickArrayPda: tickArrayLowerPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const baseParamsLiquidity = {
|
||||
liquidityAmount: liquidity,
|
||||
tokenMaxA,
|
||||
tokenMaxB,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: wallet.publicKey,
|
||||
position: positionPda.publicKey,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
tokenOwnerAccountA,
|
||||
tokenOwnerAccountB,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
};
|
||||
|
||||
const liquidityIx = !TokenExtensionUtil.isV2IxRequiredPool(
|
||||
tokenExtensionCtx,
|
||||
)
|
||||
? increaseLiquidityIx(ctx.program, baseParamsLiquidity)
|
||||
: increaseLiquidityV2Ix(ctx.program, {
|
||||
...baseParamsLiquidity,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
|
||||
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
|
||||
});
|
||||
txBuilder.addInstruction(liquidityIx);
|
||||
|
||||
const txId = await txBuilder.buildAndExecute({
|
||||
maxSupportedTransactionVersion: "legacy"
|
||||
});
|
||||
|
||||
return txId;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { createUmi, generateSigner, publicKey } from "@metaplex-foundation/umi";
|
||||
import { createCollection, ruleSet } from "@metaplex-foundation/mpl-core";
|
||||
import { mplTokenMetadata } from "@metaplex-foundation/mpl-token-metadata";
|
||||
import { generateSigner, keypairIdentity, publicKey } from "@metaplex-foundation/umi";
|
||||
import { createCollection, mplCore, ruleSet } from "@metaplex-foundation/mpl-core";
|
||||
import { CollectionOptions, CollectionDeployment } from "../types";
|
||||
import { toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { fromWeb3JsKeypair, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
|
||||
|
||||
/**
|
||||
* Deploy a new NFT collection
|
||||
@@ -17,7 +17,8 @@ export async function deploy_collection(
|
||||
): Promise<CollectionDeployment> {
|
||||
try {
|
||||
// Initialize Umi
|
||||
const umi = createUmi().use(mplTokenMetadata());
|
||||
const umi = createUmi(agent.connection.rpcEndpoint).use(mplCore());
|
||||
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
|
||||
|
||||
// Generate collection signer
|
||||
const collectionSigner = generateSigner(umi);
|
||||
@@ -27,11 +28,11 @@ export async function deploy_collection(
|
||||
address: publicKey(creator.address),
|
||||
percentage: creator.percentage,
|
||||
})) || [
|
||||
{
|
||||
address: publicKey(agent.wallet_address.toString()),
|
||||
percentage: 100,
|
||||
},
|
||||
];
|
||||
{
|
||||
address: publicKey(agent.wallet_address.toString()),
|
||||
percentage: 100,
|
||||
},
|
||||
];
|
||||
|
||||
// Create collection
|
||||
const tx = await createCollection(umi, {
|
||||
|
||||
@@ -1,68 +1,66 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
createInitializeMint2Instruction,
|
||||
MINT_SIZE,
|
||||
getMinimumBalanceForRentExemptAccount,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { Keypair, SystemProgram, Transaction } from "@solana/web3.js";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
|
||||
import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi";
|
||||
import { createFungible, mintV1, TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
|
||||
import { fromWeb3JsKeypair, fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
|
||||
/**
|
||||
* Deploy a new SPL token
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param name Name of the token
|
||||
* @param uri URI for the token metadata
|
||||
* @param symbol Symbol of the token
|
||||
* @param decimals Number of decimals for the token (default: 9)
|
||||
* @param initialSupply Initial supply to mint (optional)
|
||||
* @returns Object containing token mint address and initial account (if supply was minted)
|
||||
*/
|
||||
export async function deploy_token(
|
||||
agent: SolanaAgentKit,
|
||||
decimals: number = 9
|
||||
// initialSupply?: number
|
||||
) {
|
||||
name: string,
|
||||
uri: string,
|
||||
symbol: string,
|
||||
decimals: number = 9,
|
||||
initialSupply?: number
|
||||
): Promise<{ mint: PublicKey }> {
|
||||
try {
|
||||
// Create UMI instance from agent
|
||||
const umi = createUmi(agent.connection.rpcEndpoint)
|
||||
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
|
||||
|
||||
// Create new token mint
|
||||
const lamports = await getMinimumBalanceForRentExemptAccount(
|
||||
agent.connection
|
||||
);
|
||||
const mint = generateSigner(umi);
|
||||
|
||||
const mint = Keypair.generate();
|
||||
|
||||
console.log("Mint address: ", mint.publicKey.toString());
|
||||
console.log("Agent address: ", agent.wallet_address.toString());
|
||||
|
||||
let account_create_ix = SystemProgram.createAccount({
|
||||
fromPubkey: agent.wallet_address,
|
||||
newAccountPubkey: mint.publicKey,
|
||||
lamports,
|
||||
space: MINT_SIZE,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
let builder = createFungible(umi, {
|
||||
name,
|
||||
uri,
|
||||
symbol,
|
||||
sellerFeeBasisPoints: {
|
||||
basisPoints: 0n,
|
||||
identifier: "%",
|
||||
decimals: 2,
|
||||
},
|
||||
decimals,
|
||||
mint,
|
||||
});
|
||||
|
||||
let create_mint_ix = createInitializeMint2Instruction(
|
||||
mint.publicKey,
|
||||
decimals,
|
||||
agent.wallet_address,
|
||||
agent.wallet_address,
|
||||
TOKEN_PROGRAM_ID
|
||||
);
|
||||
if (initialSupply) {
|
||||
builder = builder.add(
|
||||
mintV1(umi, {
|
||||
mint: mint.publicKey,
|
||||
tokenStandard: TokenStandard.Fungible,
|
||||
tokenOwner: fromWeb3JsPublicKey(agent.wallet_address),
|
||||
amount: initialSupply,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let tx = new Transaction().add(account_create_ix, create_mint_ix);
|
||||
|
||||
let hash = await sendTx(agent, tx, [mint]);
|
||||
|
||||
console.log("Transaction hash: ", hash);
|
||||
|
||||
console.log(
|
||||
"Token deployed successfully. Mint address: ",
|
||||
mint.publicKey.toString()
|
||||
);
|
||||
builder.sendAndConfirm(umi, { confirm: { commitment: 'finalized' } });
|
||||
|
||||
return {
|
||||
mint: mint.publicKey,
|
||||
mint: toWeb3JsPublicKey(mint.publicKey),
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
throw new Error(`Token deployment failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
35
src/tools/fetch_price.ts
Normal file
35
src/tools/fetch_price.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { Tool } from "langchain/tools";
|
||||
|
||||
/**
|
||||
* Fetch the price of a given token in USDC using Jupiter API
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param tokenId The token mint address
|
||||
* @returns The price of the token in USDC
|
||||
*/
|
||||
export async function fetchPrice(
|
||||
agent: SolanaAgentKit,
|
||||
tokenId: string
|
||||
): Promise<string> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.jup.ag/price/v2?ids=${tokenId}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch price: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const price = data.data[tokenId]?.price;
|
||||
|
||||
if (!price) {
|
||||
throw new Error("Price data not available for the given token.");
|
||||
}
|
||||
|
||||
return price;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Price fetch failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
37
src/tools/get_primary_domain.ts
Normal file
37
src/tools/get_primary_domain.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { getPrimaryDomain as _getPrimaryDomain } from "@bonfida/spl-name-service";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
/**
|
||||
* Retrieves the primary .sol domain associated with a given Solana public key.
|
||||
*
|
||||
* This function queries the Bonfida SPL Name Service to get the primary .sol domain for
|
||||
* a specified Solana public key. If the primary domain is stale or an error occurs during
|
||||
* the resolution, it throws an error.
|
||||
*
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param account The Solana public key for which to retrieve the primary domain
|
||||
* @returns A promise that resolves to the primary .sol domain as a string
|
||||
* @throws Error if the domain is stale or if the domain resolution fails
|
||||
*/
|
||||
export async function getPrimaryDomain(
|
||||
agent: SolanaAgentKit,
|
||||
account: PublicKey
|
||||
): Promise<string> {
|
||||
try {
|
||||
const { reverse, stale } = await _getPrimaryDomain(
|
||||
agent.connection,
|
||||
account
|
||||
);
|
||||
if (stale) {
|
||||
throw new Error(
|
||||
`Primary domain is stale for account: ${account.toBase58()}`
|
||||
);
|
||||
}
|
||||
return reverse;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to get primary domain for account: ${account.toBase58()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
68
src/tools/get_token_data.ts
Normal file
68
src/tools/get_token_data.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { JupiterTokenData } from "../types";
|
||||
|
||||
export async function getTokenDataByAddress(
|
||||
mint: PublicKey,
|
||||
): Promise<JupiterTokenData | undefined> {
|
||||
try {
|
||||
if (!mint) {
|
||||
throw new Error("Mint address is required");
|
||||
}
|
||||
|
||||
const response = await fetch("https://tokens.jup.ag/tokens?tags=verified", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = (await response.json()) as JupiterTokenData[];
|
||||
const token = data.find((token: JupiterTokenData) => {
|
||||
return token.address === mint.toBase58();
|
||||
});
|
||||
return token;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error fetching token data: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokenAddressFromTicker(
|
||||
ticker: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.dexscreener.com/latest/dex/search?q=${ticker}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.pairs || data.pairs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Filter for Solana pairs only and sort by FDV
|
||||
let solanaPairs = data.pairs
|
||||
.filter((pair: any) => pair.chainId === "solana")
|
||||
.sort((a: any, b: any) => (b.fdv || 0) - (a.fdv || 0));
|
||||
|
||||
solanaPairs = solanaPairs.filter(
|
||||
(pair: any) =>
|
||||
pair.baseToken.symbol.toLowerCase() === ticker.toLowerCase()
|
||||
);
|
||||
|
||||
// Return the address of the highest FDV Solana pair
|
||||
return solanaPairs[0].baseToken.address;
|
||||
} catch (error) {
|
||||
console.error("Error fetching token address from DexScreener:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokenDataByTicker(
|
||||
ticker: string
|
||||
): Promise<JupiterTokenData | undefined> {
|
||||
const address = await getTokenAddressFromTicker(ticker);
|
||||
if (!address) {
|
||||
throw new Error(`Token address not found for ticker: ${ticker}`);
|
||||
}
|
||||
return getTokenDataByAddress(new PublicKey(address));
|
||||
}
|
||||
@@ -6,10 +6,17 @@ export * from "./mint_nft";
|
||||
export * from "./transfer";
|
||||
export * from "./trade";
|
||||
export * from "./register_domain";
|
||||
export * from "./resolve_sol_domain";
|
||||
export * from "./get_primary_domain";
|
||||
export * from "./launch_pumpfun_token";
|
||||
export * from "./lend";
|
||||
export * from "./get_tps";
|
||||
export * from "./raydium_create_ammV4";
|
||||
export * from "./get_token_data";
|
||||
export * from "./stake_with_jup";
|
||||
export * from "./fetch_price";
|
||||
export * from "./send_compressed_airdrop";
|
||||
|
||||
export * from "./create_orca_single_sided_whirlpool";export * from "./raydium_create_ammV4";
|
||||
export * from "./raydium_create_clmm";
|
||||
export * from "./raydium_create_cpmm";
|
||||
export * from "./openbook_create_market";
|
||||
@@ -38,7 +38,6 @@ async function uploadMetadata(
|
||||
finalFormData.append('file', files.file);
|
||||
}
|
||||
|
||||
console.log("Final form data:", finalFormData);
|
||||
|
||||
const metadataResponse = await fetch("https://pump.fun/api/ipfs", {
|
||||
method: "POST",
|
||||
@@ -46,7 +45,6 @@ async function uploadMetadata(
|
||||
});
|
||||
|
||||
if (!metadataResponse.ok) {
|
||||
console.log("Metadata response:", await metadataResponse.json());
|
||||
throw new Error(`Metadata upload failed: ${metadataResponse.statusText}`);
|
||||
}
|
||||
|
||||
@@ -152,30 +150,14 @@ export async function launchPumpFunToken(
|
||||
options?: PumpFunTokenOptions
|
||||
) {
|
||||
try {
|
||||
// TBD : Remove clgs after approval
|
||||
console.log("Starting token launch process...");
|
||||
|
||||
// Generate mint keypair
|
||||
const mintKeypair = Keypair.generate();
|
||||
console.log("Mint public key:", mintKeypair.publicKey.toBase58());
|
||||
|
||||
// Upload metadata
|
||||
console.log("Uploading metadata to IPFS...");
|
||||
const metadataResponse = await uploadMetadata(tokenName, tokenTicker, description, imageUrl, options);
|
||||
console.log("Metadata response:", metadataResponse);
|
||||
|
||||
// Create token transaction
|
||||
console.log("Creating token transaction...");
|
||||
const response = await createTokenTransaction(agent, mintKeypair, metadataResponse, options);
|
||||
|
||||
const transactionData = await response.arrayBuffer();
|
||||
const tx = VersionedTransaction.deserialize(new Uint8Array(transactionData));
|
||||
|
||||
// Send transaction with proper blockhash handling
|
||||
console.log("Sending transaction...");
|
||||
const signature = await signAndSendTransaction(agent, tx, mintKeypair);
|
||||
|
||||
console.log("Token launch successful!");
|
||||
return {
|
||||
signature,
|
||||
mint: mintKeypair.publicKey.toBase58(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { generateSigner } from '@metaplex-foundation/umi';
|
||||
import { create } from '@metaplex-foundation/mpl-core';
|
||||
import { generateSigner, keypairIdentity } from '@metaplex-foundation/umi';
|
||||
import { create, mplCore } from '@metaplex-foundation/mpl-core';
|
||||
import { fetchCollection } from '@metaplex-foundation/mpl-core';
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { fromWeb3JsKeypair, fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
|
||||
import { MintCollectionNFTResponse } from '../types';
|
||||
|
||||
@@ -20,7 +20,6 @@ export async function mintCollectionNFT(
|
||||
collectionMint: PublicKey,
|
||||
metadata: {
|
||||
name: string;
|
||||
symbol: string;
|
||||
uri: string;
|
||||
sellerFeeBasisPoints?: number;
|
||||
creators?: Array<{
|
||||
@@ -32,11 +31,12 @@ export async function mintCollectionNFT(
|
||||
): Promise<MintCollectionNFTResponse> {
|
||||
try {
|
||||
// Create UMI instance from agent
|
||||
const umi = createUmi(agent.connection)
|
||||
const umi = createUmi(agent.connection.rpcEndpoint).use(mplCore());
|
||||
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
|
||||
|
||||
// Convert collection mint to UMI format
|
||||
const umiCollectionMint = fromWeb3JsPublicKey(collectionMint);
|
||||
|
||||
|
||||
// Fetch the existing collection
|
||||
const collection = await fetchCollection(umi, umiCollectionMint);
|
||||
|
||||
@@ -48,8 +48,8 @@ export async function mintCollectionNFT(
|
||||
asset: assetSigner,
|
||||
collection: collection,
|
||||
name: metadata.name,
|
||||
uri: metadata.uri,
|
||||
owner: fromWeb3JsPublicKey(recipient!)
|
||||
uri: metadata.uri,
|
||||
owner: fromWeb3JsPublicKey(recipient ?? agent.wallet.publicKey)
|
||||
}).sendAndConfirm(umi);
|
||||
|
||||
return {
|
||||
|
||||
30
src/tools/resolve_sol_domain.ts
Normal file
30
src/tools/resolve_sol_domain.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { resolve } from "@bonfida/spl-name-service";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
/**
|
||||
* Resolves a .sol domain to a Solana PublicKey.
|
||||
*
|
||||
* This function uses the Bonfida SPL Name Service to resolve a given .sol domain
|
||||
* to the corresponding Solana PublicKey. The domain can be provided with or without
|
||||
* the .sol suffix.
|
||||
*
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param domain The .sol domain to resolve. This can be provided with or without the .sol TLD suffix
|
||||
* @returns A promise that resolves to the corresponding Solana PublicKey
|
||||
* @throws Error if the domain resolution fails
|
||||
*/
|
||||
export async function resolveSolDomain(
|
||||
agent: SolanaAgentKit,
|
||||
domain: string
|
||||
): Promise<PublicKey> {
|
||||
if (!domain || typeof domain !== "string") {
|
||||
throw new Error("Invalid domain. Expected a non-empty string.");
|
||||
}
|
||||
|
||||
try {
|
||||
return await resolve(agent.connection, domain);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to resolve domain: ${domain}`);
|
||||
}
|
||||
}
|
||||
306
src/tools/send_compressed_airdrop.ts
Normal file
306
src/tools/send_compressed_airdrop.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import {
|
||||
AddressLookupTableAccount,
|
||||
ComputeBudgetProgram,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent/index.js";
|
||||
import {
|
||||
buildAndSignTx,
|
||||
calculateComputeUnitPrice,
|
||||
createRpc,
|
||||
Rpc,
|
||||
sendAndConfirmTx,
|
||||
sleep,
|
||||
} from "@lightprotocol/stateless.js";
|
||||
import {
|
||||
CompressedTokenProgram,
|
||||
createTokenPool,
|
||||
} from "@lightprotocol/compressed-token";
|
||||
import { Account, getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
|
||||
|
||||
// arbitrary
|
||||
const MAX_AIRDROP_RECIPIENTS = 1000;
|
||||
const MAX_CONCURRENT_TXS = 30;
|
||||
|
||||
/**
|
||||
* Estimate the cost of an airdrop in lamports.
|
||||
* @param numberOfRecipients Number of recipients
|
||||
* @param priorityFeeInLamports Priority fee in lamports
|
||||
* @returns Estimated cost in lamports
|
||||
*/
|
||||
export const getAirdropCostEstimate = (
|
||||
numberOfRecipients: number,
|
||||
priorityFeeInLamports: number
|
||||
) => {
|
||||
const baseFee = 5000;
|
||||
const perRecipientCompressedStateFee = 300;
|
||||
|
||||
const txsNeeded = Math.ceil(numberOfRecipients / 15);
|
||||
const totalPriorityFees = txsNeeded * (baseFee + priorityFeeInLamports);
|
||||
|
||||
return (
|
||||
perRecipientCompressedStateFee * numberOfRecipients + totalPriorityFees
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send airdrop with ZK Compressed Tokens.
|
||||
* @param agent Agent
|
||||
* @param mintAddress SPL Mint address
|
||||
* @param amount Amount to send per recipient
|
||||
* @param decimals Decimals of the token
|
||||
* @param recipients Recipient wallet addresses (no ATAs)
|
||||
* @param priorityFeeInLamports Priority fee in lamports
|
||||
* @param shouldLog Whether to log progress to stdout. Defaults to false.
|
||||
*/
|
||||
export async function sendCompressedAirdrop(
|
||||
agent: SolanaAgentKit,
|
||||
mintAddress: PublicKey,
|
||||
amount: number,
|
||||
decimals: number,
|
||||
recipients: PublicKey[],
|
||||
priorityFeeInLamports: number,
|
||||
shouldLog: boolean = false
|
||||
): Promise<string[]> {
|
||||
if (recipients.length > MAX_AIRDROP_RECIPIENTS) {
|
||||
throw new Error(
|
||||
`Max airdrop can be ${MAX_AIRDROP_RECIPIENTS} recipients at a time. For more scale, use open source ZK Compression airdrop tools such as https://github.com/helius-labs/airship.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const url = agent.connection.rpcEndpoint;
|
||||
if (url.includes("devnet")) {
|
||||
throw new Error("Devnet is not supported for airdrop. Please use mainnet.");
|
||||
}
|
||||
if (!url.includes("helius")) {
|
||||
console.warn(
|
||||
"Warning: Must use RPC with ZK Compression support. Double check with your RPC provider if in doubt."
|
||||
);
|
||||
}
|
||||
|
||||
let sourceTokenAccount: Account;
|
||||
try {
|
||||
sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
|
||||
agent.connection,
|
||||
agent.wallet,
|
||||
mintAddress,
|
||||
agent.wallet.publicKey
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
"Source token account not found and failed to create it. Please add funds to your wallet and try again."
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await createTokenPool(
|
||||
agent.connection as unknown as Rpc,
|
||||
agent.wallet,
|
||||
mintAddress
|
||||
);
|
||||
} catch (error: any) {
|
||||
if (error.message.includes("already in use")) {
|
||||
// skip
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return await processAll(
|
||||
agent,
|
||||
amount * 10 ** decimals,
|
||||
mintAddress,
|
||||
recipients,
|
||||
priorityFeeInLamports,
|
||||
shouldLog
|
||||
);
|
||||
}
|
||||
|
||||
async function processAll(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
mint: PublicKey,
|
||||
recipients: PublicKey[],
|
||||
priorityFeeInLamports: number,
|
||||
shouldLog: boolean
|
||||
): Promise<string[]> {
|
||||
const mintAddress = mint;
|
||||
const payer = agent.wallet;
|
||||
|
||||
const sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
|
||||
agent.connection,
|
||||
agent.wallet,
|
||||
mintAddress,
|
||||
agent.wallet.publicKey
|
||||
);
|
||||
|
||||
const maxRecipientsPerInstruction = 5;
|
||||
const maxIxs = 3; // empirically determined (as of 12/15/2024)
|
||||
const lookupTableAddress = new PublicKey(
|
||||
"9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"
|
||||
);
|
||||
|
||||
const lookupTableAccount = (
|
||||
await agent.connection.getAddressLookupTable(lookupTableAddress)
|
||||
).value!;
|
||||
|
||||
const batches: PublicKey[][] = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < recipients.length;
|
||||
i += maxRecipientsPerInstruction * maxIxs
|
||||
) {
|
||||
batches.push(recipients.slice(i, i + maxRecipientsPerInstruction * maxIxs));
|
||||
}
|
||||
|
||||
const instructionSets = await Promise.all(
|
||||
batches.map(async (recipientBatch) => {
|
||||
const instructions: TransactionInstruction[] = [
|
||||
ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }),
|
||||
ComputeBudgetProgram.setComputeUnitPrice({
|
||||
microLamports: calculateComputeUnitPrice(
|
||||
priorityFeeInLamports,
|
||||
500_000
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
const compressIxPromises = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < recipientBatch.length;
|
||||
i += maxRecipientsPerInstruction
|
||||
) {
|
||||
const batch = recipientBatch.slice(i, i + maxRecipientsPerInstruction);
|
||||
compressIxPromises.push(
|
||||
CompressedTokenProgram.compress({
|
||||
payer: payer.publicKey,
|
||||
owner: payer.publicKey,
|
||||
source: sourceTokenAccount.address,
|
||||
toAddress: batch,
|
||||
amount: batch.map(() => amount),
|
||||
mint: mintAddress,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const compressIxs = await Promise.all(compressIxPromises);
|
||||
return [...instructions, ...compressIxs];
|
||||
})
|
||||
);
|
||||
|
||||
const url = agent.connection.rpcEndpoint;
|
||||
const rpc = createRpc(url, url, url);
|
||||
|
||||
const results = [];
|
||||
let confirmedCount = 0;
|
||||
const totalBatches = instructionSets.length;
|
||||
|
||||
const renderProgressBar = (current: number, total: number) => {
|
||||
const percentage = Math.floor((current / total) * 100);
|
||||
const filled = Math.floor((percentage / 100) * 20);
|
||||
const empty = 20 - filled;
|
||||
const bar = "█".repeat(filled) + "░".repeat(empty);
|
||||
return `Airdropped to ${Math.min(current * 15, recipients.length)}/${
|
||||
recipients.length
|
||||
} recipients [${bar}] ${percentage}%`;
|
||||
};
|
||||
|
||||
const log = (message: string) => {
|
||||
if (shouldLog && typeof process !== "undefined" && process.stdout) {
|
||||
process.stdout.write(message);
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < instructionSets.length; i += MAX_CONCURRENT_TXS) {
|
||||
const batchPromises = instructionSets
|
||||
.slice(i, i + MAX_CONCURRENT_TXS)
|
||||
.map((instructions, idx) =>
|
||||
sendTransactionWithRetry(
|
||||
rpc,
|
||||
instructions,
|
||||
payer,
|
||||
lookupTableAccount,
|
||||
i + idx
|
||||
).then((signature) => {
|
||||
confirmedCount++;
|
||||
log("\r" + renderProgressBar(confirmedCount, totalBatches));
|
||||
return signature;
|
||||
})
|
||||
);
|
||||
|
||||
const batchResults = await Promise.allSettled(batchPromises);
|
||||
results.push(...batchResults);
|
||||
}
|
||||
|
||||
log("\n");
|
||||
|
||||
const failures = results
|
||||
.filter((r) => r.status === "rejected")
|
||||
.map((r, idx) => ({
|
||||
index: idx,
|
||||
error: (r as PromiseRejectedResult).reason,
|
||||
}));
|
||||
|
||||
if (failures.length > 0) {
|
||||
throw new Error(
|
||||
`Failed to process ${failures.length} batches: ${failures
|
||||
.map((f) => f.error)
|
||||
.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
return results.map((r) => (r as PromiseFulfilledResult<string>).value);
|
||||
}
|
||||
|
||||
async function sendTransactionWithRetry(
|
||||
connection: Rpc,
|
||||
instructions: TransactionInstruction[],
|
||||
payer: Keypair,
|
||||
lookupTableAccount: AddressLookupTableAccount,
|
||||
batchIndex: number
|
||||
): Promise<string> {
|
||||
const MAX_RETRIES = 3;
|
||||
const INITIAL_BACKOFF = 500; // ms
|
||||
|
||||
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const { blockhash } = await connection.getLatestBlockhash();
|
||||
const tx = buildAndSignTx(
|
||||
instructions,
|
||||
payer,
|
||||
blockhash,
|
||||
[],
|
||||
[lookupTableAccount]
|
||||
);
|
||||
|
||||
const signature = await sendAndConfirmTx(connection, tx);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
const isRetryable =
|
||||
error.message?.includes("blockhash not found") ||
|
||||
error.message?.includes("timeout") ||
|
||||
error.message?.includes("rate limit") ||
|
||||
error.message?.includes("too many requests");
|
||||
|
||||
if (!isRetryable || attempt === MAX_RETRIES - 1) {
|
||||
throw new Error(
|
||||
`Batch ${batchIndex} failed after ${attempt + 1} attempts: ${
|
||||
error.message
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
const backoff =
|
||||
INITIAL_BACKOFF * Math.pow(2, attempt) * (0.5 + Math.random());
|
||||
await sleep(backoff);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
56
src/tools/stake_with_jup.ts
Normal file
56
src/tools/stake_with_jup.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { VersionedTransaction } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
|
||||
/**
|
||||
* Stake SOL with Jup validator
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param amount Amount of SOL to stake
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function stakeWithJup(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://worker.jup.ag/blinks/swap/So11111111111111111111111111111111111111112/jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v/${amount}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
account: agent.wallet.publicKey.toBase58(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
const txn = VersionedTransaction.deserialize(
|
||||
Buffer.from(data.transaction, "base64"),
|
||||
);
|
||||
|
||||
const { blockhash } = await agent.connection.getLatestBlockhash();
|
||||
txn.message.recentBlockhash = blockhash;
|
||||
|
||||
// Sign and send transaction
|
||||
txn.sign([agent.wallet]);
|
||||
const signature = await agent.connection.sendTransaction(txn, {
|
||||
preflightCommitment: "confirmed",
|
||||
maxRetries: 3,
|
||||
});
|
||||
|
||||
const latestBlockhash = await agent.connection.getLatestBlockhash();
|
||||
await agent.connection.confirmTransaction({
|
||||
signature,
|
||||
blockhash: latestBlockhash.blockhash,
|
||||
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
||||
});
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
throw new Error(`jupSOL staking failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,6 @@ export async function trade(
|
||||
slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS,
|
||||
): Promise<string> {
|
||||
try {
|
||||
// Get quote for the swap
|
||||
console.log(inputMint.toString(), outputMint.toString(), inputAmount, slippageBps);
|
||||
const quoteResponse = await (
|
||||
await fetch(
|
||||
`${JUP_API}/quote?` +
|
||||
|
||||
@@ -39,7 +39,6 @@ export interface PumpfunLaunchResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lulo Account Details response format
|
||||
*/
|
||||
@@ -54,3 +53,27 @@ export interface LuloAccountDetailsResponse {
|
||||
minimumRate: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface JupiterTokenData {
|
||||
address: string;
|
||||
name: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
tags: string[];
|
||||
logoURI: string;
|
||||
daily_volume: number;
|
||||
freeze_authority: string | null;
|
||||
mint_authority: string | null;
|
||||
permanent_delegate: string | null;
|
||||
extensions: {
|
||||
coingeckoId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FetchPriceResponse {
|
||||
status: "success" | "error";
|
||||
tokenId?: string;
|
||||
priceInUSDC?: string;
|
||||
message?: string;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function getPriorityFees(connection: Connection): Promise<{
|
||||
const median =
|
||||
sortedFees.length % 2 === 0
|
||||
? ((sortedFees[mid - 1] ?? 0) + (sortedFees[mid] ?? 0)) / 2
|
||||
: (sortedFees[mid] ?? 0);
|
||||
: sortedFees[mid] ?? 0;
|
||||
|
||||
// Helper to create priority fee IX based on chosen strategy
|
||||
const createPriorityFeeIx = (fee: number) => {
|
||||
@@ -76,7 +76,7 @@ export async function getPriorityFees(connection: Connection): Promise<{
|
||||
export async function sendTx(
|
||||
agent: SolanaAgentKit,
|
||||
tx: Transaction,
|
||||
otherKeypairs?: Keypair[],
|
||||
otherKeypairs?: Keypair[]
|
||||
) {
|
||||
tx.recentBlockhash = (await agent.connection.getLatestBlockhash()).blockhash;
|
||||
tx.feePayer = agent.wallet_address;
|
||||
@@ -90,8 +90,9 @@ export async function sendTx(
|
||||
await agent.connection.confirmTransaction({
|
||||
signature: txid,
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
lastValidBlockHeight: (await agent.connection.getLatestBlockhash())
|
||||
.lastValidBlockHeight,
|
||||
lastValidBlockHeight: (
|
||||
await agent.connection.getLatestBlockhash()
|
||||
).lastValidBlockHeight,
|
||||
});
|
||||
return txid;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ dotenv.config();
|
||||
|
||||
function validateEnvironment(): void {
|
||||
const missingVars: string[] = [];
|
||||
const requiredVars = ["OPENAI_API_KEY", "HELIUS_API_KEY", "SOLANA_PRIVATE_KEY"];
|
||||
|
||||
const requiredVars = ["OPENAI_API_KEY", "RPC_URL", "SOLANA_PRIVATE_KEY"];
|
||||
|
||||
requiredVars.forEach(varName => {
|
||||
if (!process.env[varName]) {
|
||||
missingVars.push(varName);
|
||||
@@ -52,7 +52,7 @@ async function initializeAgent() {
|
||||
|
||||
const solanaKit = new SolanaAgentKit(
|
||||
process.env.SOLANA_PRIVATE_KEY!,
|
||||
`https://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}`,
|
||||
process.env.RPC_URL,
|
||||
process.env.OPENAI_API_KEY!
|
||||
);
|
||||
|
||||
@@ -176,7 +176,7 @@ async function chooseMode(): Promise<"chat" | "auto"> {
|
||||
.trim();
|
||||
|
||||
rl.close();
|
||||
|
||||
|
||||
if (choice === "1" || choice === "chat") {
|
||||
return "chat";
|
||||
} else if (choice === "2" || choice === "auto") {
|
||||
|
||||
Reference in New Issue
Block a user