Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0722520
Add Solana support to Across swapper and update contracts
0xh3rman Dec 27, 2025
c118b37
fix fee decimals for solana
0xh3rman Dec 27, 2025
f98e910
Merge branch 'main' into across-solana2
0xh3rman Jan 7, 2026
0afe301
Merge branch 'main' into across-solana2
0xh3rman Jan 8, 2026
d4827b3
Merge branch 'main' into across-solana2
0xh3rman Jan 13, 2026
fbd1fbd
Merge branch 'main' into across-solana2
0xh3rman Jan 15, 2026
1aec6a0
Merge branch 'main' into across-solana2
0xh3rman Jan 16, 2026
03bda89
Merge remote-tracking branch 'origin/main' into across-solana2
0xh3rman Jan 18, 2026
6801821
impl transfer from solana side
0xh3rman Jan 18, 2026
8061712
code cleanup
0xh3rman Jan 18, 2026
051e23d
add usdt on solana and deposit_status for across, use fallback recipi…
0xh3rman Jan 18, 2026
1b44052
pass token account instead of account itself for transfer msg
0xh3rman Jan 18, 2026
34c42dc
temp disable fee for bridging to solana for across
0xh3rman Jan 19, 2026
212ef4f
disable fee if from or to is solana
0xh3rman Jan 19, 2026
f5f1d6c
fix instruction index, replace spl_transfer_checked with spl_transfe…
0xh3rman Jan 20, 2026
e7cd74d
Merge branch 'main' into across-solana2
0xh3rman Jan 20, 2026
ddfa938
Merge branch 'main' into across-solana2
0xh3rman Jan 20, 2026
a3dcebc
add size check
0xh3rman Jan 20, 2026
ad46646
Merge branch 'main' into across-solana2
0xh3rman Jan 24, 2026
81fe33b
Merge branch 'main' into across-solana2
0xh3rman Jan 28, 2026
8b44488
Merge branch 'main' into across-solana2
0xh3rman Feb 1, 2026
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 20 additions & 16 deletions crates/gem_evm/src/across/contracts/spoke_pool.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use alloy_sol_types::sol;

// https://docs.across.to/reference/selected-contract-functions
// https://github.com/across-protocol/contracts/blob/master/contracts/interfaces/SpokePoolInterface.sol
// https://github.com/across-protocol/contracts/blob/master/contracts/interfaces/V3SpokePoolInterface.sol
sol! {
// Contains structs and functions used by SpokePool contracts to facilitate universal settlement.
interface V3SpokePoolInterface {
Expand All @@ -10,24 +10,24 @@ sol! {
// replay attacks on other chains. If any portion of this data differs, the relay is considered to be
// completely distinct.
struct V3RelayData {
// The address that made the deposit on the origin chain.
address depositor;
// The recipient address on the destination chain.
address recipient;
// The bytes32 that made the deposit on the origin chain.
bytes32 depositor;
// The recipient bytes32 on the destination chain.
bytes32 recipient;
// This is the exclusive relayer who can fill the deposit before the exclusivity deadline.
address exclusiveRelayer;
bytes32 exclusiveRelayer;
// Token that is deposited on origin chain by depositor.
address inputToken;
bytes32 inputToken;
// Token that is received on destination chain by recipient.
address outputToken;
bytes32 outputToken;
// The amount of input token deposited by depositor.
uint256 inputAmount;
// The amount of output token to be received by recipient.
uint256 outputAmount;
// Origin chain id.
uint256 originChainId;
// The id uniquely identifying this deposit on the origin chain.
uint32 depositId;
uint256 depositId;
// The timestamp on the destination chain after which this deposit can no longer be filled.
uint32 fillDeadline;
// The timestamp on the destination chain after which any relayer can fill the deposit.
Expand All @@ -38,21 +38,25 @@ sol! {

function getCurrentTime() public view virtual returns (uint256);

function depositV3(
address depositor,
address recipient,
address inputToken,
address outputToken,
function deposit(
bytes32 depositor,
bytes32 recipient,
bytes32 inputToken,
bytes32 outputToken,
uint256 inputAmount,
uint256 outputAmount,
uint256 destinationChainId,
address exclusiveRelayer,
bytes32 exclusiveRelayer,
uint32 quoteTimestamp,
uint32 fillDeadline,
uint32 exclusivityDeadline,
bytes calldata message
) external payable;

function fillV3Relay(V3RelayData calldata relayData, uint256 repaymentChainId) external;
function fillRelay(
V3RelayData calldata relayData,
uint256 repaymentChainId,
bytes32 repaymentAddress
) external;
}
}
45 changes: 37 additions & 8 deletions crates/gem_evm/src/across/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ use super::fees::CapitalCostConfig;
use crate::ether_conv::EtherConv;
use alloy_primitives::map::HashSet;
use num_bigint::BigInt;
use primitives::{AssetId, Chain, asset_constants::*};
use primitives::{AssetId, Chain, ChainType, asset_constants::*};
use std::{collections::HashMap, vec};

pub const ACROSS_CONFIG_STORE: &str = "0x3B03509645713718B78951126E0A6de6f10043f5";
pub const ACROSS_HUBPOOL: &str = "0xc186fA914353c44b2E33eBE05f21846F1048bEda";
pub const MULTICALL_HANDLER: &str = "0x924a9f036260DdD5808007E1AA95f08eD08aA569";
static SOLANA_CHAIN_ID: u64 = 34268394551451_u64;

/// https://docs.across.to/developer-docs/developers/contract-addresses
pub struct AcrossDeployment {
pub chain_id: u32,
pub chain_id: u64,
pub chain_type: ChainType,
pub spoke_pool: &'static str,
}

Expand All @@ -23,84 +25,108 @@ pub struct AssetMapping {

impl AcrossDeployment {
pub fn deployment_by_chain(chain: &Chain) -> Option<Self> {
let chain_id: u32 = chain.network_id().parse().unwrap();
let chain_id: u64 = if chain.chain_type() == ChainType::Solana {
SOLANA_CHAIN_ID
} else {
chain.network_id().parse().unwrap()
};
match chain {
Chain::Ethereum => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5",
}),
Chain::Arbitrum => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0xe35e9842fceaca96570b734083f4a58e8f7c5f2a",
}),
Chain::Base => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64",
}),
Chain::Blast => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x2D509190Ed0172ba588407D4c2df918F955Cc6E1",
}),
Chain::Linea => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x7E63A5f1a8F0B4d0934B2f2327DAED3F6bb2ee75",
}),
Chain::Optimism => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x6f26Bf09B1C792e3228e5467807a900A503c0281",
}),
Chain::Polygon => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x9295ee1d8C5b022Be115A2AD3c30C72E34e7F096",
}),
Chain::World => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64",
}),
Chain::ZkSync => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0xE0B015E54d54fc84a6cB9B666099c46adE9335FF",
}),
Chain::Ink => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0xeF684C38F94F48775959ECf2012D7E864ffb9dd4",
}),
Chain::Unichain => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64",
}),
Chain::Monad => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0xd2ecb3afe598b746F8123CaE365a598DA831A449",
}),
Chain::SmartChain => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x4e8E101924eDE233C13e2D8622DC8aED2872d505",
}),
Chain::Hyperliquid => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x35E63eA3eb0fb7A3bc543C71FB66412e1F6B0E04",
}),
Chain::Plasma => Some(Self {
chain_id,
chain_type: ChainType::Ethereum,
spoke_pool: "0x50039fAEfebef707cFD94D6d462fE6D10B39207a",
}),
Chain::Solana => Some(Self {
chain_id: SOLANA_CHAIN_ID,
chain_type: ChainType::Solana,
spoke_pool: "DLv3NggMiSaef97YCkew5xKUHDh13tVGZ7tydt3ZeAru",
}),
_ => None,
}
}

pub fn multicall_handler(&self) -> String {
match self.chain_id {
// Linea
59144 => "0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB".into(),
59144_u64 => "0x1015c58894961F4F7Dd7D68ba033e28Ed3ee1cDB".into(),
// zkSync
324 => "0x863859ef502F0Ee9676626ED5B418037252eFeb2".into(),
324_u64 => "0x863859ef502F0Ee9676626ED5B418037252eFeb2".into(),
// SmartChain
56 => "0xAC537C12fE8f544D712d71ED4376a502EEa944d7".into(),
56_u64 => "0xAC537C12fE8f544D712d71ED4376a502EEa944d7".into(),
// Monad
143 => "0xeC41F75c686e376Ab2a4F18bde263ab5822c4511".into(),
143_u64 => "0xeC41F75c686e376Ab2a4F18bde263ab5822c4511".into(),
// HyperEvm | Plasma
999 | 9745 => "0x5E7840E06fAcCb6d1c3b5F5E0d1d3d07F2829bba".into(),
999_u64 | 9745_u64 => "0x5E7840E06fAcCb6d1c3b5F5E0d1d3d07F2829bba".into(),
_ => MULTICALL_HANDLER.into(),
}
}
Expand Down Expand Up @@ -141,6 +167,7 @@ impl AcrossDeployment {
(Chain::Monad, vec![USDC_MONAD_ASSET_ID.into(), USDT_MONAD_ASSET_ID.into()]),
(Chain::SmartChain, vec![ETH_SMARTCHAIN_ASSET_ID.into()]),
(Chain::Plasma, vec![USDT_PLASMA_ASSET_ID.into()]),
(Chain::Solana, vec![USDC_SOLANA_ASSET_ID.into(), USDT_SOLANA_ASSET_ID.into()]),
])
}

Expand Down Expand Up @@ -184,6 +211,7 @@ impl AcrossDeployment {
USDC_UNICHAIN_ASSET_ID.into(),
USDC_HYPEREVM_ASSET_ID.into(),
USDC_MONAD_ASSET_ID.into(),
USDC_SOLANA_ASSET_ID.into(),
]),
},
// USDC on BSC decimals are 18
Expand Down Expand Up @@ -214,6 +242,7 @@ impl AcrossDeployment {
USDT_HYPEREVM_ASSET_ID.into(),
USDT_PLASMA_ASSET_ID.into(),
USDT_MONAD_ASSET_ID.into(),
USDT_SOLANA_ASSET_ID.into(),
]),
},
// USDT on BSC decimals are 18
Expand Down
1 change: 1 addition & 0 deletions crates/gem_evm/src/chainlink/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ sol! {

pub const CHAINLINK_ETH_USD_FEED: &str = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419";
pub const CHAINLINK_MON_USD_FEED: &str = "0xBcD78f76005B7515837af6b50c7C52BCf73822fb";
pub const CHAINLINK_SOL_USD_FEED: &str = "0x4ffC43a60e009B551865A93d232E33Fce9f01507";
7 changes: 6 additions & 1 deletion crates/gem_solana/src/jsonrpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ pub enum SolanaRpc {
GetProgramAccounts(String, Vec<Filter>),
GetAccountInfo(String),
GetMultipleAccounts(Vec<String>),
GetTransaction(String),
GetEpochInfo,
GetLatestBlockhash,
GetRecentPrioritizationFees,
}

impl Display for SolanaRpc {
Expand All @@ -21,8 +23,10 @@ impl Display for SolanaRpc {
SolanaRpc::GetProgramAccounts(_, _) => write!(f, "getProgramAccounts"),
SolanaRpc::GetAccountInfo(_) => write!(f, "getAccountInfo"),
SolanaRpc::GetMultipleAccounts(_) => write!(f, "getMultipleAccounts"),
SolanaRpc::GetTransaction(_) => write!(f, "getTransaction"),
SolanaRpc::GetEpochInfo => write!(f, "getEpochInfo"),
SolanaRpc::GetLatestBlockhash => write!(f, "getLatestBlockhash"),
SolanaRpc::GetRecentPrioritizationFees => write!(f, "getRecentPrioritizationFees"),
}
}
}
Expand All @@ -40,7 +44,8 @@ impl JsonRpcRequestConvert for SolanaRpc {
Value::Array(accounts.iter().map(|x| serde_json::to_value(x).unwrap()).collect()),
serde_json::to_value(default_config).unwrap(),
],
SolanaRpc::GetEpochInfo | SolanaRpc::GetLatestBlockhash => vec![],
SolanaRpc::GetTransaction(signature) => vec![Value::String(signature.into()), serde_json::json!({ "maxSupportedTransactionVersion": 0 })],
SolanaRpc::GetEpochInfo | SolanaRpc::GetLatestBlockhash | SolanaRpc::GetRecentPrioritizationFees => vec![],
};

JsonRpcRequest::new(id, &method, params.into())
Expand Down
14 changes: 14 additions & 0 deletions crates/gem_solana/src/models/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,17 @@ pub struct SingleTransaction {
pub meta: Meta,
pub transaction: Transaction,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionMetaWithLogs {
pub err: Option<serde_json::Value>,
pub log_messages: Option<Vec<String>>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionWithLogs {
pub slot: u64,
pub meta: Option<TransactionMetaWithLogs>,
}
2 changes: 1 addition & 1 deletion crates/primitives/src/asset_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub const USDT_POLYGON_ASSET_ID: &str = "polygon_0xc2132D05D31c914a87C6611C10748
pub const USDT_ZKSYNC_ASSET_ID: &str = "zksync_0x493257fD37EDB34451f62EDf8D2a0C418852bA4C";
pub const USDT_SMARTCHAIN_ASSET_ID: &str = "smartchain_0x55d398326f99059fF775485246999027B3197955";
pub const USDT_AVAX_ASSET_ID: &str = "avalanchec_0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7";
pub const USDT_SOLANA_ASSET_ID: &str = "solana_Es9vMFrzaCERmJFRf4H2Fyd4KCoNkY11McCe8BEnwNYB";
pub const USDT_SOLANA_ASSET_ID: &str = "solana_Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";
pub const USDT_TRON_ASSET_ID: &str = "tron_TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
pub const USDT_TON_ASSET_ID: &str = "ton_EQcxE6MuTQJkfNGfAARoTKOVt1LZBADiix1KCixRv7NW2id_sDs";
pub const USDT_NEAR_ASSET_ID: &str = "near_usdt.tether-token.near";
Expand Down
2 changes: 2 additions & 0 deletions crates/swapper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ serde_serializers = { path = "../serde_serializers" }
number_formatter = { path = "../number_formatter" }

reqwest = { workspace = true, optional = true }
borsh.workspace = true
typeshare = { version = "1.0.4" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why swapper needs typeshare?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


strum = { workspace = true }
Expand All @@ -37,6 +38,7 @@ async-trait.workspace = true
chrono.workspace = true
alloy-primitives.workspace = true
alloy-sol-types.workspace = true
sha2.workspace = true
hex.workspace = true
num-bigint.workspace = true
num-integer.workspace = true
Expand Down
25 changes: 21 additions & 4 deletions crates/swapper/src/across/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
alien::{RpcProvider, Target},
client_factory::create_eth_client,
};
use gem_evm::across::deployment::AcrossDeployment;
use primitives::{Chain, swap::SwapStatus};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
Expand Down Expand Up @@ -47,6 +48,13 @@ impl DepositStatus {

impl AcrossApi {
pub async fn deposit_status(&self, chain: Chain, tx_hash: &str) -> Result<DepositStatus, SwapperError> {
if chain == Chain::Solana {
return self.deposit_status_solana(tx_hash).await;
}
self.deposit_status_evm(chain, tx_hash).await
}

async fn deposit_status_evm(&self, chain: Chain, tx_hash: &str) -> Result<DepositStatus, SwapperError> {
let receipt = create_eth_client(self.provider.clone(), chain)?
.get_transaction_receipt(tx_hash)
.await
Expand All @@ -73,11 +81,20 @@ impl AcrossApi {
deposit_id_hex
};

let url = format!("{}/api/deposit/status?originChainId={}&depositId={}", self.url, chain.network_id(), &deposit_id);
let query = format!("originChainId={}&depositId={}", chain.network_id(), deposit_id);
self.fetch_deposit_status(&query).await
}

async fn deposit_status_solana(&self, tx_hash: &str) -> Result<DepositStatus, SwapperError> {
let deployment = AcrossDeployment::deployment_by_chain(&Chain::Solana).ok_or(SwapperError::NotSupportedChain)?;
let query = format!("originChainId={}&depositTxHash={}", deployment.chain_id, tx_hash);
self.fetch_deposit_status(&query).await
}

async fn fetch_deposit_status(&self, query: &str) -> Result<DepositStatus, SwapperError> {
let url = format!("{}/api/deposit/status?{}", self.url, query);
let target = Target::get(&url);
let response = self.provider.request(target).await?;
let status: DepositStatus = serde_json::from_slice(&response.data).map_err(SwapperError::from)?;

Ok(status)
serde_json::from_slice(&response.data).map_err(SwapperError::from)
}
}
4 changes: 4 additions & 0 deletions crates/swapper/src/across/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ pub use provider::Across;
pub mod api;
pub mod config_store;
pub mod hubpool;
pub mod models;
pub mod solana;
mod solana_tx;

const DEFAULT_FILL_TIMEOUT: u32 = 60 * 60 * 6; // 6 hours
const DEFAULT_DEPOSIT_GAS_LIMIT: u64 = 180_000; // gwei
const DEFAULT_FILL_GAS_LIMIT: u64 = 120_000; // gwei
const MESSAGE_GAS_MULTIPLIER: u64 = 10;
Loading
Loading