diff --git a/docs/guides/chain-fusion/solana.md b/docs/guides/chain-fusion/solana.md
deleted file mode 100644
index 3da58a8d..00000000
--- a/docs/guides/chain-fusion/solana.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: "Solana Integration"
-description: "Interact with Solana from ICP canisters using the Sol RPC canister"
-sidebar:
- order: 3
----
-
-TODO: Write content for this page.
-
-
-Interact with Solana from ICP canisters. Cover the Sol RPC canister, transaction signing with threshold Ed25519, SPL token reads, and current status/limitations of the integration. Note that Solana support is newer and evolving -- document what is stable and link to the sol-rpc-canister repo for latest updates.
-
-
-- Portal: building-apps/chain-fusion/solana/overview.mdx
-- Examples: basic_solana (Rust, redirects to sol-rpc-canister repo)
-- Learn Hub: [SOL RPC Canister](https://learn.internetcomputer.org/hc/en-us/articles/46782465439764)
-
-
-- concepts/chain-fusion -- chain fusion overview
-- guides/chain-fusion/ethereum -- similar EVM RPC pattern
-- guides/chain-fusion/bitcoin -- comparison with Bitcoin integration
diff --git a/docs/guides/chain-fusion/solana.mdx b/docs/guides/chain-fusion/solana.mdx
new file mode 100644
index 00000000..775d72c4
--- /dev/null
+++ b/docs/guides/chain-fusion/solana.mdx
@@ -0,0 +1,351 @@
+---
+title: "Solana Integration"
+description: "Interact with Solana from ICP canisters using the SOL RPC canister and threshold Ed25519 signatures"
+sidebar:
+ order: 3
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components';
+
+ICP canisters can interact directly with the Solana network: read account balances, query transaction history, and sign and submit transactions — all without bridges, oracles, or external signers. This guide covers the SOL RPC canister for querying Solana and threshold Ed25519 signatures for signing Solana transactions.
+
+For a conceptual overview of how ICP connects to other blockchains, see [Chain Fusion](../../concepts/chain-fusion.md).
+
+## How it works
+
+Two ICP features enable Solana integration:
+
+- **[HTTPS outcalls](../backends/https-outcalls.md)** — canisters can make HTTP requests to external services. The SOL RPC canister uses HTTPS outcalls to reach Solana JSON-RPC providers and aggregates their responses for consensus.
+- **Threshold Ed25519** — Solana uses Ed25519 signatures for authorizing transactions. ICP provides a threshold signature scheme where a canister can sign messages using a key that no single node holds outright. This lets canisters sign valid Solana transactions without ever exposing a private key.
+
+## SOL RPC canister
+
+The SOL RPC canister (`2xib7-jqaaa-aaaar-qai6q-cai`) is deployed on ICP mainnet and handles Solana JSON-RPC calls on your behalf. When your canister calls it:
+
+1. Your canister sends a JSON-RPC request with cycles attached.
+2. The SOL RPC canister fans the request out to multiple Solana RPC providers via HTTPS outcalls.
+3. Responses are aggregated — the canister returns the result once providers agree.
+4. Unused cycles are refunded.
+
+No API keys are required. The SOL RPC canister is controlled by the [Network Nervous System](../../concepts/governance.md), so any change to it requires an NNS proposal.
+
+The SOL RPC canister contacts these JSON-RPC providers:
+- [Helius](https://www.helius.dev/)
+- [Alchemy](https://www.alchemy.com/)
+- [Ankr](https://www.ankr.com/)
+- [dRPC](https://drpc.org/)
+- [Public Node](https://www.publicnode.com/)
+
+## Querying Solana
+
+Use the SOL RPC canister's `request` method to send any Solana JSON-RPC call. Pass cycles to cover the HTTPS outcall cost; unused cycles are refunded.
+
+### Get an account balance
+
+The following example queries the SOL balance of a Solana public key using `getBalance`.
+
+
+
+
+```motoko
+import Runtime "mo:core/Runtime";
+
+persistent actor {
+
+ type SolRpc = actor {
+ request : (Text, Nat64) -> async { #Ok : Text; #Err : Text };
+ };
+
+ transient let solRpc : SolRpc = actor ("2xib7-jqaaa-aaaar-qai6q-cai");
+
+ public func getSolBalance(pubkey : Text) : async Text {
+ let json = "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getBalance\","
+ # "\"params\":[\"" # pubkey # "\"]}";
+
+ let result = await (with cycles = 10_000_000_000)
+ solRpc.request(json, 1000);
+
+ switch (result) {
+ case (#Ok response) { response };
+ case (#Err err) {
+ Runtime.trap("RPC error: " # err);
+ };
+ };
+ };
+};
+```
+
+
+
+
+```rust
+use candid::Principal;
+use ic_cdk::call::Call;
+use ic_cdk::update;
+
+const SOL_RPC_CANISTER: &str = "2xib7-jqaaa-aaaar-qai6q-cai";
+
+fn sol_rpc_id() -> Principal {
+ Principal::from_text(SOL_RPC_CANISTER).unwrap()
+}
+
+#[update]
+async fn get_sol_balance(pubkey: String) -> String {
+ let json = format!(
+ r#"{{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["{}"]}}"#,
+ pubkey
+ );
+
+ let (result,): (Result,) =
+ Call::unbounded_wait(sol_rpc_id(), "request")
+ .with_args(&(json, 1000_u64))
+ .with_cycles(10_000_000_000_u128)
+ .await
+ .expect("Failed to call SOL RPC canister")
+ .candid_tuple()
+ .expect("Failed to decode response");
+
+ match result {
+ Ok(response) => response,
+ Err(err) => ic_cdk::trap(&format!("RPC error: {}", err)),
+ }
+}
+```
+
+
+
+
+The response is the raw JSON-RPC response string. The `getBalance` result contains a `value` field with the balance in lamports (1 SOL = 1,000,000,000 lamports). Parse the JSON string to extract the value your canister needs.
+
+### Other common queries
+
+Any Solana JSON-RPC method works the same way — pass the JSON payload as the first argument to `request` and set the second argument (`max_response_bytes`) to the expected response size. Larger values cost more cycles; set it to the minimum needed:
+
+```rust
+// Get latest slot
+let json = r#"{"jsonrpc":"2.0","id":1,"method":"getSlot"}"#;
+
+// Get account information
+let json = format!(
+ r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo",
+ "params":["{}",{{"encoding":"base64"}}]}}"#,
+ pubkey
+);
+
+// Get recent transaction signatures for an address
+let json = format!(
+ r#"{{"jsonrpc":"2.0","id":1,"method":"getSignaturesForAddress",
+ "params":["{}"]}}"#,
+ pubkey
+);
+```
+
+For the full list of supported methods, see the [Solana JSON-RPC documentation](https://solana.com/docs/rpc/http).
+
+## Signing Solana transactions
+
+Solana uses Ed25519 signatures for all transactions. ICP supports threshold Ed25519 via the management canister's `sign_with_schnorr` method (using the `ed25519` algorithm variant). The key is distributed across ICP subnet nodes — no single node ever holds the full private key.
+
+The signing flow for a Solana transaction:
+1. Get your canister's Ed25519 public key from the management canister.
+2. Derive the Solana address (base58-encode the 32-byte public key).
+3. Build the Solana transaction message.
+4. Sign the serialized message bytes with `sign_with_schnorr`.
+5. Submit the signed transaction via the SOL RPC canister's `sendTransaction` method.
+
+### Get an Ed25519 public key
+
+
+
+
+```motoko
+import Principal "mo:core/Principal";
+import Blob "mo:core/Blob";
+
+persistent actor {
+
+ type IC = actor {
+ schnorr_public_key : ({
+ canister_id : ?Principal;
+ derivation_path : [Blob];
+ key_id : { algorithm : { #ed25519 }; name : Text };
+ }) -> async ({ public_key : Blob; chain_code : Blob });
+ };
+
+ transient let ic : IC = actor ("aaaaa-aa");
+
+ public func getEd25519PublicKey() : async Blob {
+ let { public_key } = await ic.schnorr_public_key({
+ canister_id = null;
+ derivation_path = [];
+ key_id = {
+ algorithm = #ed25519;
+ name = "test_key_1"; // Use "key_1" for production
+ };
+ });
+ public_key;
+ };
+};
+```
+
+
+
+
+```rust
+use ic_cdk::management_canister::{
+ schnorr_public_key, SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgs,
+};
+use ic_cdk::update;
+
+#[update]
+async fn get_ed25519_public_key() -> Vec {
+ let args = SchnorrPublicKeyArgs {
+ canister_id: None,
+ derivation_path: vec![],
+ key_id: SchnorrKeyId {
+ algorithm: SchnorrAlgorithm::Ed25519,
+ name: "test_key_1".to_string(), // Use "key_1" for production
+ },
+ };
+
+ let result = schnorr_public_key(&args)
+ .await
+ .expect("schnorr_public_key failed");
+
+ result.public_key
+}
+```
+
+
+
+
+The returned `public_key` is the raw 32-byte Ed25519 public key. To use it as a Solana address, base58-encode these 32 bytes. For a complete implementation of this step, see [`solana_helpers.rs`](https://github.com/dfinity/sol-rpc-canister/blob/main/examples/basic_solana/src/basic_solana_backend/src/solana_helpers.rs) in the `basic_solana` example.
+
+### Sign a transaction message
+
+`sign_with_schnorr` takes the full message bytes — not a hash. For Solana transactions, pass the serialized transaction message bytes directly.
+
+
+
+
+```motoko
+import Blob "mo:core/Blob";
+
+persistent actor {
+
+ type IC = actor {
+ sign_with_schnorr : ({
+ message : Blob;
+ derivation_path : [Blob];
+ key_id : { algorithm : { #ed25519 }; name : Text };
+ aux : ?{ #bip341 : { merkle_root_hash : Blob } };
+ }) -> async ({ signature : Blob });
+ };
+
+ transient let ic : IC = actor ("aaaaa-aa");
+
+ public func signSolanaMessage(message : Blob) : async Blob {
+ let { signature } = await (with cycles = 30_000_000_000)
+ ic.sign_with_schnorr({
+ message;
+ derivation_path = [];
+ key_id = {
+ algorithm = #ed25519;
+ name = "test_key_1"; // Use "key_1" for production
+ };
+ aux = null;
+ });
+ signature;
+ };
+};
+```
+
+{/* Note: 30B cycles is an intentional buffer above the ~26.15B fee (26_153_846_153 cycles, per cdk-rs SIGN_WITH_SCHNORR_FEE). Unused cycles are refunded. The Rust cdk-rs attaches the exact fee automatically; Motoko requires an explicit `with cycles` attachment. */}
+
+
+
+
+```rust
+use ic_cdk::management_canister::{
+ sign_with_schnorr, SchnorrAlgorithm, SchnorrKeyId, SignWithSchnorrArgs,
+};
+use ic_cdk::update;
+
+#[update]
+async fn sign_solana_message(message: Vec) -> Vec {
+ let args = SignWithSchnorrArgs {
+ message,
+ derivation_path: vec![],
+ key_id: SchnorrKeyId {
+ algorithm: SchnorrAlgorithm::Ed25519,
+ name: "test_key_1".to_string(), // Use "key_1" for production
+ },
+ aux: None,
+ };
+
+ // sign_with_schnorr attaches the required cycles automatically
+ let result = sign_with_schnorr(&args)
+ .await
+ .expect("sign_with_schnorr failed");
+
+ result.signature
+}
+```
+
+
+
+
+The returned 64-byte signature is a valid Ed25519 signature that Solana accepts for transactions signed by this canister's key.
+
+### Key IDs
+
+| Key ID | Environment |
+|---|---|
+| `test_key_1` | ICP mainnet — test key, reduced security. Use for development and testing only. |
+| `key_1` | ICP mainnet — production key. Use for production deployments. |
+
+Ed25519 does not have a local development key — unlike ECDSA (which has `dfx_test_key` for local replica testing), there is no Ed25519 equivalent. All Ed25519 signing must be tested on ICP mainnet using `test_key_1`. Plan your test workflow accordingly: local replica development is not possible for the signing steps.
+
+## Complete transaction example
+
+Constructing a full Solana transaction requires:
+1. Fetching a recent blockhash via `getLatestBlockhash`
+2. Building the transaction structure (account keys, instructions, message header)
+3. Serializing the transaction message
+4. Signing the serialized bytes with `sign_with_schnorr`
+5. Submitting the signed transaction via `sendTransaction`
+
+For a complete end-to-end Rust implementation, see the [basic_solana example](https://github.com/dfinity/sol-rpc-canister/tree/main/examples/basic_solana) in the SOL RPC canister repository. It demonstrates a SOL transfer, including blockhash fetching, transaction serialization, signing, and submission.
+
+## Cycle costs
+
+Every SOL RPC call requires cycles to cover HTTPS outcall costs. The `sign_with_schnorr` management canister call also requires cycles.
+
+| Operation | Approximate cost |
+|---|---|
+| SOL RPC `request` (small response, 1–2 providers) | ~1–5B cycles |
+| `sign_with_schnorr` (Ed25519, Rust cdk auto-attached) | ~26.15B cycles |
+
+Send 10B cycles per RPC call as a starting budget — unused cycles are refunded. Set `max_response_bytes` to the minimum needed; smaller values reduce costs.
+
+## Current status and limitations
+
+The Solana integration is newer than the Bitcoin and Ethereum integrations:
+
+- **SOL RPC canister is live on mainnet** — deployed and functional, with the API surface still evolving.
+- **Threshold Ed25519 is available** — both test (`test_key_1`) and production (`key_1`) keys are live on ICP mainnet.
+- **No SPL token helpers** — SPL token operations (reading token accounts, transferring tokens) require constructing JSON-RPC calls and transaction instructions manually.
+- **No ckSOL token** — unlike Bitcoin (ckBTC) and Ethereum (ckETH), there is no chain-key SOL token yet.
+- **Transaction construction is manual** — there is no official ICP library for building Solana transactions. See the [basic_solana example](https://github.com/dfinity/sol-rpc-canister/tree/main/examples/basic_solana) for a reference implementation.
+
+Follow the [SOL RPC canister repository](https://github.com/dfinity/sol-rpc-canister/blob/main/README.md) for the latest updates.
+
+## Next steps
+
+- [SOL RPC canister README](https://github.com/dfinity/sol-rpc-canister/blob/main/README.md) — full documentation and the `basic_solana` end-to-end example
+- [Bitcoin integration](bitcoin.md) — direct protocol-level BTC integration
+- [Ethereum integration](ethereum.md) — EVM RPC canister, similar JSON-RPC pattern
+- [HTTPS outcalls](../backends/https-outcalls.md) — the mechanism underlying the SOL RPC canister
+- [Chain Fusion concepts](../../concepts/chain-fusion.md) — how ICP connects to other blockchains
+
+{/* Upstream: informed by dfinity/portal — docs/building-apps/chain-fusion/solana/overview.mdx; dfinity/cdk-rs — ic-cdk/src/management_canister.rs, ic-management-canister-types/src/lib.rs; dfinity/examples — rust/basic_solana/README.md */}