Godot Solana SDK
A GDExtension (C++) plugin for Godot 4 that brings full Solana blockchain support to game development. Write GDScript to send transactions, manage wallets, mint NFTs, and interact with Anchor programs — on Windows, Linux, macOS, Web, Android, and iOS.
- Repo: https://github.com/Virus-Axel/godot-solana-sdk
- Docs: https://zenwiki.gitbook.io/solana-godot-sdk-docs
- Demo: https://zenrepublic.github.io/GodotSolanaSDKDemos/
- Asset Library: https://godotengine.org/asset-library/asset/3232
- Discord: https://discord.gg/9aFDCvqPgt
Security: Mainnet use is not yet security-audited. Crypto can be stolen. Use with care.
Installation
Via Godot Asset Library (recommended)
- Search for "Solana" in the Godot Asset Library and install.
- Install must go into the
addons/folder at the project root. - Go to Project → Project Settings → Plugins and enable SolanaSDK.
- Verify
SolanaServiceappears under Project Settings → Autoload.
Via GitHub Releases (manual)
Download binaries from the Releases page,
place them under res://bin/, and reload the project.
Configuration
After enabling the plugin, SolanaService is automatically added as an autoload singleton.
- RPC cluster: Select Mainnet or Devnet in the SolanaService inspector.
Provide a custom RPC URL (e.g. from Helius) for better performance.
The URL is also read from
Project Settings → solana_sdk/client/default_url. - Wallet: The
WalletServicechild node handles auth.- Use Generated — deterministic wallet seeded from your machine ID (testing only).
- Custom Wallet — path to a JSON file with the private key as a 64-byte array.
Core Nodes
| Node | Inherits | Purpose |
|---|---|---|
SolanaClient | Node | Low-level RPC calls to the Solana network |
Transaction | SolanaClient | Build, sign, and send transactions |
WalletAdapter | Node | Browser / mobile wallet integration |
Account | Node | Mirror of an on-chain account, auto-syncs |
AccountFetcher | SolanaClient | Bulk-fetch a list of Account nodes |
SystemProgram | Node | SOL transfers and account creation |
TokenProgram | Node | SPL Token instructions |
AssociatedTokenAccountProgram | Node | Create Associated Token Accounts |
ComputeBudget | Node | Set compute unit limits and priority fees |
MplCandyMachine | Node | Candy Machine v3 minting |
MplCandyGuard | Node | Candy Guard configuration |
MplTokenMetadata | Node | Metaplex token metadata |
AnchorProgram | Node | Generic interface for any Anchor program |
SolanaUtils | Node | Base58/Base64 encoding, hashing utilities |
Core Resources
| Resource | Purpose |
|---|---|
Pubkey | 32-byte Solana public key / address |
Keypair | ed25519 keypair for signing |
Instruction | A single transaction instruction |
AccountMeta | Account metadata entry in an instruction |
CandyMachineData | Candy Machine configuration |
Pubkey
# Create from base58 string
var pk: Pubkey = Pubkey.new_from_string("78GVwUb8ojcJVrEVkwCU5tfUKTfJuiazRrysGwgjqsif")
# Create from bytes
var pk: Pubkey = Pubkey.new_from_bytes(some_packed_byte_array)
# Derive a PDA (searches for valid bump, returns null on failure)
var program_key := Pubkey.new_from_string("CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR")
var pda: Pubkey = Pubkey.new_pda(["Level1"], program_key)
# Derive a PDA from raw byte seeds
var pda: Pubkey = Pubkey.new_pda_bytes([some_pubkey.to_bytes()], program_key)
# Derive Associated Token Address
var ata: Pubkey = Pubkey.new_associated_token_address(owner, mint, TokenProgram.get_pid())
# Random pubkey (for testing)
var pk: Pubkey = Pubkey.new_random()
# Convert
pk.to_string() # base58 String
pk.to_bytes() # PackedByteArray (32 bytes)
Pubkey.new_pdafinds the canonical valid PDA (searches for bump).Pubkey.new_program_addressdoes not search — it constructs from exact seeds. Usenew_pdafor normal PDA derivation.
Keypair
# Generate random keypair
var kp: Keypair = Keypair.new_random()
# From a deterministic seed
var seed = PackedByteArray(); seed.resize(32)
var kp: Keypair = Keypair.new_from_seed(seed)
# From Phantom private key (base58 string of 64 bytes)
var kp: Keypair = Keypair.new_from_bytes("3wUbDHMtMVQ...")
# From CLI-style JSON file
var kp: Keypair = Keypair.new_from_file("res://payer.json")
# Save to JSON file
kp.save_to_file("keypair.json")
# Inspect keys
kp.get_public_string() # base58 String
kp.get_public_bytes() # PackedByteArray (32 bytes)
kp.get_private_bytes() # PackedByteArray (64 bytes)
kp.to_pubkey() # Pubkey resource
# Sign and verify
var sig: PackedByteArray = kp.sign_message("hello".to_ascii_buffer())
var ok: bool = kp.verify_signature(sig, "hello".to_ascii_buffer())
SolanaClient (RPC)
Important:
SolanaClientmust be added as a child node (add_child(client)) for async HTTP requests to work. Remove and free it when done.
Signals:
http_request_completed(error: Error, response: Dictionary)
socket_response_received
var client: SolanaClient = SolanaClient.new()
add_child(client)
# Configure (optional — reads from Project Settings by default)
client.set_url_override("https://api.devnet.solana.com")
client.set_commitment("confirmed") # "confirmed" | "finalized" | "processed"
# Make an RPC call, then await the signal
client.get_account_info("4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn")
var response: Dictionary = (await client.http_request_completed)[1]
if response.has("result"):
print(response["result"])
remove_child(client)
client.queue_free()
RPC methods (all void — listen to http_request_completed):
get_account_info(account)
get_balance(account)
get_latest_blockhash()
get_minimum_balance_for_rent_extemption(data_size)
get_transaction(signature)
get_signature_statuses(signatures, search_history)
get_signature_for_address(address, before, until)
get_token_account_balance(token_account)
get_token_accounts_by_owner(owner, mint, program_id)
get_token_supply(token_mint)
get_program_accounts(program_address, filters, with_context)
get_multiple_accounts(accounts)
request_airdrop(address, lamports)
send_transaction(encoded_transaction, max_retries, skip_preflight)
simulate_transaction(encoded_transaction, sig_verify, replace_blockhash, ...)
DAS (Digital Asset Standard) methods:
get_asset(asset_id)
get_asset_proof(asset_id)
get_assets_by_owner(owner, page, limit, show_fungible)
get_assets_by_authority(authority, page, limit)
get_assets_by_creator_address(creator, only_verified, page, limit)
get_assets_by_group(group_key, group_value, page, limit)
WebSocket subscriptions:
client.account_subscribe(account_key, Callable(self, "_on_account_changed"))
client.signature_subscribe(signature, Callable(self, "_on_confirmed"), "finalized")
client.program_subscribe(program_id, Callable(self, "_on_program_change"))
client.unsubscribe_all(callback) # call when done; keep client in tree while subscribed
Transaction
Workflow: set_payer → add_instruction → update_latest_blockhash → sign_and_send
Signals:
transaction_response_received(response: Dictionary)
transaction_simulation_failed(message_and_data: Array)
processed
confirmed
finalized
fully_signed
blockhash_updated
var payer: Keypair = Keypair.new_from_file("res://payer.json")
var receiver: Pubkey = Pubkey.new_from_string("78GVwUb8ojcJVrEVkwCU5tfUKTfJuiazRrysGwgjqsif")
var tx = Transaction.new()
add_child(tx) # must be in scene tree
tx.set_payer(payer)
tx.add_instruction(SystemProgram.transfer(payer, receiver, 500_000)) # 0.0005 SOL
tx.update_latest_blockhash()
tx.sign_and_send