Artemis Solana SDK
Artemis is a mobile-first Kotlin Solana SDK that consolidates app-side Solana client dependencies.
one Kotlin Multiplatform dependency graph. JSON-RPC, WebSocket subs with reconnect and replay, legacy + v0 transactions, MWA 2.1, Seed Vault, Token-2022, compressed NFTs with DAS failover, Solana Pay, Jupiter, Anchor, Actions. plus a real reliability layer wrapped around all of it.

Why Artemis
the kotlin/android solana story is fragmented. you ship mobile-wallet-adapter-clientlib-ktx for MWA, seedvault-wallet-sdk for Seed Vault access on Saga and Seeker, solana-kmp or Sol4k for RPC and transactions, Metaplex KMM for NFT metadata, and you still hand-roll a websocket layer, a retry pipeline, and a DAS client.
five dependencies. five opinions about coroutines. five version matrices. zero shared reliability story.
Artemis is one dependency graph, one coroutine-first surface, one ArtemisMobile.create() call for the full stack. every module pulls weight. artemis-rpc ships 92 typed JSON-RPC methods with a batch DSL and a real circuit breaker. artemis-ws runs a real websocket with deterministic resubscribe on reconnect. artemis-vtx drives a simulate + retry + priority-fee transaction pipeline. artemis-wallet-mwa-android speaks MWA 2.x end to end with P-256 association, AES-128-GCM session crypto, HKDF-SHA256 key derivation, and Sign-In With Solana.
Artemis is the client SDK layer above Solana Mobile Stack primitives. It does not replace MWA, Seed Vault, wallet approval UX, or the Solana Mobile platform. If you can't rewrite call sites, the source-compatible interop/artemis-*-compat shims let you swap Maven coordinates and keep your imports. one dep, not five.
What you get
Install
current published version is 2.3.2. the version field in gradle.properties is the source of truth.
compatibility and claim boundaries live in docs/PARITY_MATRIX.md, docs/compatibility/replacement-matrix.md, docs/drop-in-compatibility.md, and docs/claims.md. Solana Mobile-specific guidance is in docs/solana-mobile.md. A compile-checked Android migration sample lives at samples/mwa-compat-migration/README.md.
Quick start
One call, full stack
ArtemisMobile.create() builds RPC + MWA wallet + TxEngine + session manager + realtime + DAS + marketplace from a single Activity. that's it.
val artemis = ArtemisMobile.create(
activity = this,
identityUri = Uri.parse("https://myapp.example.com"),
iconPath = "https://myapp.example.com/favicon.ico",
identityName = "My App",
rpcUrl = "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",
wsUrl = "wss://atlas-mainnet.helius-rpc.com/?api-key=YOUR_KEY",
dasUrl = "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY"
)
sig = artemis.sessionManager.withWallet { session ->
session.sendSol(recipient, )
}
constructor lives at ArtemisMobile.kt:85.
Realtime account and signature subscriptions
artemis.realtime.connect()
val handle = artemis.realtime.subscribeAccount(
pubkey = artemis.session.publicKey.toBase58(),
commitment = "confirmed"
) { info ->
println("lamports: ${info.lamports}, slot: ${info.slot}")
}
artemis.realtime.subscribeSignature(txSignature) { confirmed ->
println("confirmed: $confirmed")
}
handle.close()
watch the transport directly:
artemis.realtime.state
.onEach { state ->
when (state) {
is ConnectionState.Connected -> hideOfflineBanner()
is ConnectionState.Reconnecting -> showBanner("reconnecting...")
is ConnectionState.Closed -> showBanner("offline")
else -> Unit
}
}
.launchIn(scope)
ConnectionState is at ConnectionState.kt. every transition carries a monotonic epoch, so a fresh connect and a reconnect that lands back on Connected are not the same event. yes we built that because we got tired of pretending RPC failures don't happen.
Framework event bus
every subsystem publishes through one Flow<ArtemisEvent>. no per-module listener wiring.
ArtemisEventBus.events
.onEach { event ->
when (event) {
is ArtemisEvent.Wallet.Connected -> analytics.track("wallet_connected", event.publicKey)
is ArtemisEvent.Tx.Confirmed -> refreshBalances()
is ArtemisEvent.Realtime.StateChanged -> banner.update(event.stateName)
is ArtemisEvent.Das.ProviderFailover -> log.warn("DAS failover: ${event.reason}")
else -> Unit
}
}
.launchIn(scope)
want one slice:
ArtemisEventBus.wallet().onEach { ... }.launchIn(scope)
ArtemisEventBus.tx().onEach { ... }.launchIn(scope)
ArtemisEventBus.realtime().onEach { ... }.launchIn(scope)
bus is wired into WalletSessionManager, RealtimeEngine, TxEngine, and CompositeDas automatically. source at ArtemisEvent.kt.
NFT queries with automatic fallback
val das: ArtemisDas = CompositeDas(
primary = HeliusDas(rpcUrl = "https://mainnet.helius-rpc.com/?api-key=$KEY"),
fallback = RpcFallbackDas(rpc)
)
val nfts: List<DigitalAsset> = das.assetsByOwner(walletPubkey)
val asset: DigitalAsset? = das.asset("GdR7...")
val collection = das.assetsByCollection("CollMint...")
when Helius times out, gets rate-limited, or errors, CompositeDas re-issues against RpcFallbackDas. fallback synthesizes the same DigitalAsset view from getTokenAccountsByOwner plus the Metaplex metadata PDA. after a failure the primary stays cooled off for 30 seconds, so a burst of calls does not pay the timeout repeatedly. source at CompositeDas.kt.
ATA ensurer for token transfers
val ensurer = AtaEnsurer(rpc)
val resolution = ensurer.resolve(
payer = wallet.publicKey,
owner = recipient,
mint = usdcMint
)
val instructions = buildList {
resolution.createIx?.let(::add)
add(tokenTransfer(source, resolution.ata, amount))
}
batched variant for airdrops uses a single getMultipleAccounts call for N destinations. cache invalidates on a 10 second TTL. source at AtaEnsurer.kt.
Compressed NFT transfer
val result = artemis.marketplace.transferCnft(
wallet = artemis.wallet,
dasClient = myDasClient,
assetId = "GdR7...",
merkleTree = Pubkey.fromBase58("tree..."),
newOwner = recipientPubkey
)
preflight runs by default. validates ownership, frozen state, and (for standard NFTs) destination ATA presence. source at MarketplaceEngine.kt and MarketplacePreflight.kt.
Plain transactions
val ix = SystemProgram.transfer(
from = wallet.publicKey,
to = recipient,
lamports = 500_000_000L
)
val engine = TxEngine(rpc)
val result = engine.execute(
instructions = listOf(ix),
signer = keypair,
config = TxConfig(retries = 3, simulate = true, computeUnitPrice = 1000L)
)
pipeline runs prepare → simulate → sign → send → confirm, refreshes the blockhash on retry, and emits ArtemisEvent.Tx.Sent / Confirmed / Failed / Retrying to the event bus on the way through. source at TxEngine.kt.
Module rings
strict downward-only deps. Foundation never depends on anything above it. Mobile depends only on Foundation. Ecosystem depends on Foundation. Advanced never leaks into the core path.
Ring 1 Foundation core, rpc, ws, tx, vtx, programs, errors, logging, compute [KMP]
Ring 2 Mobile wallet [KMP], wallet-mwa-android [Android], wallet-mwa-walletlib-android,
seed-vault [Android]
Ring 3 Ecosystem token2022, metaplex, mplcore, cnft, candy-machine,
solana-pay, anchor, jupiter, actions [KMP]
Ring 4 Advanced privacy, streaming, simulation, batch, scheduler, offline,
portfolio, replay, gaming, depin, nlp, intent, universal, preview
Ring 5 Presets discriminators, nft-compat, tx-presets,
candy-machine-presets, presets
Ring 6 Interop mwa-compat, mwa-clientlib-compat, mwa-walletlib-compat,
mwa-common-compat, seedvault-compat, sol4k-compat,
solana-kmp-compat, metaplex-android-compat,
rpc-core-compat, web3-solana-compat
full ring map and dep rules are in docs/ARCHITECTURE_OVERVIEW.md and docs/DEPENDENCY_RULES.md.
Foundation
Mobile
Ecosystem
Advanced (status varies)
these ship in the repo but maturity varies. gaming, intent, privacy, portfolio, and offline have substantial implementations. the rest define interfaces and primitive helpers, intended as opt-in starting points, not drop-in production layers. none of these are required for the core mobile path.
if a module says "Helpers", it exists for app teams that want a starting point and intend to extend it. Foundation, Mobile, and Ecosystem are the production surface.
Interop (source-compatible shims)
source-compatible shims that publish the upstream package + class FQNs. swap your Maven coordinates and existing import lines keep compiling. each module pins the upstream version it targets (see extra["upstream.version"] in the module's build.gradle.kts). CI's verifyApiSnapshots task fails the build when the public surface drifts from the committed snapshot.
Client SDK layers Artemis consolidates
two adoption modes. pick one up front.
Artemis-native ready
the native surface (ArtemisMobile.create, WalletSession, TxEngine, RpcApi, RealtimeEngine, ArtemisDas, ) is ready to use today. every capability marked in is backed by a test that fails if the feature regresses.
SMS-client-compat ready
keep your existing imports. swap the Maven coordinates to the Artemis compat artifacts under interop/artemis-*-compat. this track is Verified for the MWA client (ktx + non-ktx), MWA walletlib source compatibility (chain-gated reauthorize, wallet-driven deauthorize, sign-messages address check, typed exceptions), Seed Vault static surface, sol4k 0.7.0 surface (incl. Token-2022 instructions and the upstream RpcException data-class shape), and typed result classes. the rest is Partial or In Progress. matrix in PARITY_MATRIX.md is the source of truth per capability. MWA and Seed Vault remain the underlying protocol and custody boundaries.
Client packages covered by compat shims:
each compat module pins the exact upstream version it targets in its build.gradle.kts (extra["upstream.version"]). CI's verifyApiSnapshots job runs ./gradlew dumpApi and fails when the public surface drifts. intentional changes update both the snapshot under interop/<module>/api/<module>.api and the upstream pin together.
status vocabulary (Verified / In Progress / Partial / Experimental / Planned) is defined at the top of docs/PARITY_MATRIX.md. migration walkthrough with both tracks: docs/ADOPTING_ARTEMIS_WITH_SOLANA_MOBILE.md.
Reliability features
these are why this SDK is meant for production mobile work, not just a wrapper around RPC. yeah we built a circuit breaker because we got tired of pretending RPC failures don't happen.
Build and test
./gradlew build
./gradlew test
run a single module:
./gradlew :artemis-vtx:jvmTest
./gradlew :artemis-ws:jvmTest
./gradlew :artemis-cnft:jvmTest
Android library modules need the Android SDK on the build machine (set ANDROID_HOME or sdk.dir in local.properties). pure JVM modules build without it.
the optional Android sample is excluded from the default build:
./gradlew -PenableAndroidSamples=true :samples:solana-mobile-compose-mint-app:assembleDebug
devnet test runner is at run-devnet-tests.sh. it exercises the JVM test path against a real devnet keypair via the integration test module at testing/artemis-devnet-tests/.
Documentation
License
Apache License 2.0. see LICENSE.
Maintained by Bluefoot Labs.