Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ hex = "0.4"
rand = "0.8"
async-trait = "0.1"
url = "2"

solana-client = "2.1.0"
solana-sdk = "2.1.0"
spl-token = "6.0.0"
[dev-dependencies]
tokio-test = "0.4"
solana-program-test = "2.1.0"
solana-test-validator = "2.1.0"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async fn main() -> Result<()> {

## Features

- **Multi-chain** — EVM (Ethereum, Arbitrum, Optimism, Base, Avalanche, Tempo) from one agent. Solana and Cosmos planned.
- **Multi-chain** — EVM (Ethereum, Arbitrum, Optimism, Base, Avalanche, Tempo) and Solana from one agent. Cosmos planned.
- **Wallet management** — Generate, import, derive. Sign transactions. Manage multiple wallets.
- **DEX interaction** — Swap, add/remove liquidity, read pool state. Uniswap V3, Aerodrome, Trader Joe.
- **MPP payments** — Native support for Machine Payments Protocol. Agent pays for APIs, services, compute.
Expand All @@ -84,7 +84,7 @@ async fn main() -> Result<()> {
| `arka::dex` | 🚧 WIP | DEX swap execution, routing |
| `arka::mpp` | 🚧 WIP | Machine Payments Protocol client |
| `arka::oracle` | 🚧 WIP | Price feeds, TWAP |
| `arka::solana` | 📋 Planned | Solana chain connector |
| `arka::solana` | ✅ MVP | Solana chain connector |
| `arka::cosmos` | 📋 Planned | Cosmos chain connector |

## Quick Start
Expand Down
6 changes: 6 additions & 0 deletions examples/multi_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,11 @@ async fn main() -> Result<()> {
);
}

// Solana example
let solana = arka::chain::solana::SolanaChain::new()?;
let sol_keypair = solana_sdk::signature::Keypair::new();
let sol_balance = solana.balance(&sol_keypair.pubkey())?;
println!("{:12} | balance: {} lamports | gas: native", Chain::Solana, sol_balance);

Ok(())
}
8 changes: 8 additions & 0 deletions src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::fmt;
mod connector;
pub use connector::ChainConnector;

pub mod solana;

/// Supported blockchain networks.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Chain {
Expand All @@ -18,6 +20,7 @@ pub enum Chain {
Bsc,
Tempo,
TempoTestnet,
Solana,
}

impl Chain {
Expand All @@ -33,6 +36,7 @@ impl Chain {
Chain::Bsc => 56,
Chain::Tempo => 4217,
Chain::TempoTestnet => 42429,
Chain::Solana => 0, // Solana doesn't use EVM chain ID
}
}

Expand All @@ -48,6 +52,7 @@ impl Chain {
Chain::Bsc => "https://bsc-dataseed.binance.org",
Chain::Tempo => "https://rpc.tempo.xyz",
Chain::TempoTestnet => "https://rpc.testnet.tempo.xyz",
Chain::Solana => "https://api.mainnet-beta.solana.com",
}
}

Expand All @@ -59,6 +64,7 @@ impl Chain {
Chain::Polygon => "MATIC",
Chain::Bsc => "BNB",
Chain::Tempo | Chain::TempoTestnet => "USDC", // Tempo has no native gas token
Chain::Solana => "SOL",
}
}

Expand All @@ -79,6 +85,7 @@ impl Chain {
Chain::Bsc => "https://bscscan.com",
Chain::Tempo => "https://explorer.tempo.xyz",
Chain::TempoTestnet => "https://explorer.testnet.tempo.xyz",
Chain::Solana => "https://solscan.io",
}
}
}
Expand All @@ -95,6 +102,7 @@ impl fmt::Display for Chain {
Chain::Bsc => write!(f, "bsc"),
Chain::Tempo => write!(f, "tempo"),
Chain::TempoTestnet => write!(f, "tempo-testnet"),
Chain::Solana => write!(f, "solana"),
}
}
}
142 changes: 142 additions & 0 deletions src/chain/solana.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signature, Signer},
system_instruction,
transaction::Transaction,
};

use crate::error::{ArkaError, Result};
use super::Chain;

/// Manages connection and transactions for Solana network.
pub struct SolanaChain {
chain: Chain,
rpc_url: String,
client: RpcClient,
}

impl SolanaChain {
/// Create a new connector with the default RPC.
pub fn new() -> Result<Self> {
Self::with_rpc(Chain::Solana.default_rpc())
}

/// Create a new connector with a custom RPC URL.
pub fn with_rpc(rpc_url: &str) -> Result<Self> {
let client = RpcClient::new(rpc_url.to_string());

Ok(Self {
chain: Chain::Solana,
rpc_url: rpc_url.to_string(),
client,
})
}

/// Get native SOL balance for a pubkey (in lamports).
pub fn balance(&self, pubkey: &Pubkey) -> Result<u64> {
self.client
.get_balance(pubkey)
.map_err(|e| ArkaError::Rpc(format!("Failed to get balance: {e}")))
}

/// Transfer SOL from one account to another.
pub fn transfer_sol(
&self,
sender: &Keypair,
receiver: &Pubkey,
lamports: u64,
) -> Result<Signature> {
let ix = system_instruction::transfer(&sender.pubkey(), receiver, lamports);
let recent_blockhash = self.client
.get_latest_blockhash()
.map_err(|e| ArkaError::Rpc(format!("Failed to get blockhash: {e}")))?;

let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&sender.pubkey()),
&[sender],
recent_blockhash,
);

self.client
.send_and_confirm_transaction(&tx)
.map_err(|e| ArkaError::Rpc(format!("Failed to transfer SOL: {e}")))
}

/// Transfer SPL token.
pub fn transfer_spl(
&self,
sender: &Keypair,
source: &Pubkey,
destination: &Pubkey,
authority: &Keypair,
amount: u64,
) -> Result<Signature> {
let ix = spl_token::instruction::transfer(
&spl_token::id(),
source,
destination,
&authority.pubkey(),
&[&authority.pubkey()],
amount,
)
.map_err(|e| ArkaError::Rpc(format!("Failed to create SPL transfer instruction: {e}")))?;

let recent_blockhash = self.client
.get_latest_blockhash()
.map_err(|e| ArkaError::Rpc(format!("Failed to get blockhash: {e}")))?;

let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&sender.pubkey()),
&[sender, authority],
recent_blockhash,
);

self.client
.send_and_confirm_transaction(&tx)
.map_err(|e| ArkaError::Rpc(format!("Failed to transfer SPL token: {e}")))
}

/// Get the chain this connector is for.
pub fn chain(&self) -> Chain {
self.chain
}

/// Get the RPC URL.
pub fn rpc_url(&self) -> &str {
&self.rpc_url
}
}

#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::signature::Keypair;
use solana_test_validator::TestValidatorGenesis;

#[test]
fn test_solana_chain_sol_transfer() {
// Start a local test validator
let (test_validator, payer) = TestValidatorGenesis::default().start();
let rpc_url = test_validator.rpc_url();

let solana = SolanaChain::with_rpc(&rpc_url).unwrap();

// Check initial balance
let balance = solana.balance(&payer.pubkey()).unwrap();
assert!(balance > 0);

// Generate a new receiver
let receiver = Keypair::new();

// Transfer 1 SOL (1_000_000_000 lamports)
let amount = 1_000_000_000;
let _sig = solana.transfer_sol(&payer, &receiver.pubkey(), amount).unwrap();

// Check receiver balance
let receiver_balance = solana.balance(&receiver.pubkey()).unwrap();
assert_eq!(receiver_balance, amount);
}
}