name: godot-solana-sdk description: > Use this skill when building Solana-enabled games or apps in Godot 4 with the Godot Solana SDK. Triggers include: Solana transactions in GDScript, SPL tokens or NFTs in a Godot project, connecting browser or mobile wallets (Phantom, Solflare, Backpack) in a Godot game, calling Anchor smart contracts from GDScript, working with Candy Machine in Godot, or any mention of SolanaClient, Transaction, Pubkey, Keypair, WalletAdapter, or AnchorProgram in a Godot context. Do NOT use for non-Godot Solana SDKs (solana-py, solana-go, solana-web3.js).
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()
var response = await tx.transaction_response_received
if response.has("result"):
print("Signature:", response["result"])
# Await confirmation stages
await tx.confirmed
await tx.finalized
Other useful methods:
tx.set_instructions(instructions_array)
tx.set_signers([payer, another_keypair])
tx.sign() # signs without sending
tx.send() # sends without signing
tx.serialize() -> PackedByteArray
tx.get_message_hash() -> String
# Reconstruct from bytes (e.g. after passing serialized tx to wallet)
var new_tx = Transaction.new_from_bytes(tx.serialize())
new_tx.set_signers([payer]) # signers are not stored in bytes
new_tx.update_latest_blockhash()
new_tx.sign()
new_tx.send()
Handle simulation failures:
var data = await tx.transaction_simulation_failed
push_error("Simulation failed: " + data[0])
print("Logs:", data[1].get("logs", []))
SystemProgram
# Transfer SOL
SystemProgram.transfer(sender, receiver, lamports) -> Instruction
# Create a new on-chain account
SystemProgram.create_account(from_keypair, new_account_keypair, lamports, space_bytes, owner_program) -> Instruction
# Create account with seed
SystemProgram.create_account_with_seed(from, base, seed, lamports, space, owner) -> Instruction
# Well-known pubkeys
SystemProgram.get_pid()
SystemProgram.get_sysvar_clock_pubkey()
SystemProgram.get_sysvar_rent_pubkey()
# ... and all other sysvar pubkeys
TokenProgram (SPL Token)
Use
transfer_checked(requires mint + decimals), nottransfer.
# Initialize a new mint (MINT_SIZE = 82 bytes)
TokenProgram.initialize_mint(mint_keypair, mint_authority, freeze_authority, decimals) -> Instruction
# Initialize a token account (MINT_ACCOUNT_SIZE = 165 bytes)
TokenProgram.initialize_account(account_keypair, mint_keypair, owner) -> Instruction
# Mint tokens
TokenProgram.mint_to(mint, account, owner, mint_authority, amount) -> Instruction
# Transfer with decimals verification
TokenProgram.transfer_checked(source, mint, destination, source_authority, amount, decimals) -> Instruction
# Freeze a token account
TokenProgram.freeze_account(account, mint, freeze_authority) -> Instruction
# Change authority
TokenProgram.set_authority(mint, current_authority, new_authority, authority_type) -> Instruction
# authority_type: 1=MintTokens, 2=FreezeAccount, 3=AccountOwner, 4=CloseAccount
TokenProgram.get_pid() -> Pubkey
Create Mint + Initialize (full example)
const MINT_SIZE := 82
func create_token_mint(payer: Keypair) -> Keypair:
var client = SolanaClient.new()
add_child(client)
client.get_minimum_balance_for_rent_extemption(MINT_SIZE)
var lamports = (await client.http_request_completed)[1]["result"]
remove_child(client); client.queue_free()
var mint_kp: Keypair = Keypair.new_random()
var tx := Transaction.new()
add_child(tx)
tx.add_instruction(SystemProgram.create_account(payer, mint_kp, lamports, MINT_SIZE, TokenProgram.get_pid()))
tx.add_instruction(TokenProgram.initialize_mint(mint_kp, payer, payer, 0))
tx.set_payer(payer)
tx.update_latest_blockhash()
tx.sign_and_send()
await tx.transaction_response_received
await tx.confirmed
return mint_kp
AssociatedTokenAccountProgram
# Derive the ATA address (does not create it)
var ata: Pubkey = Pubkey.new_associated_token_address(owner, mint, TokenProgram.get_pid())
# Create the ATA (instruction — add to transaction)
AssociatedTokenAccountProgram.create_associated_token_account(payer, owner, mint, TokenProgram.get_pid()) -> Instruction
AssociatedTokenAccountProgram.get_pid() -> Pubkey
AssociatedTokenAccountProgramdoes not haveget_associated_token_address. UsePubkey.new_associated_token_addressto derive the address.
ComputeBudget
# Add to transaction before other instructions
tx.add_instruction(ComputeBudget.set_compute_unit_price(50_000)) # micro-lamports
tx.add_instruction(ComputeBudget.set_compute_unit_limit(200_000))
Transactions already have default
unit_limit = 800000andunit_price = 8000built in. UseComputeBudgetinstructions when you need explicit control over priority fees.
WalletAdapter (Browser / Mobile)
Primarily intended for web/browser platform exports. Desktop and mobile require tweaking.
# WalletAdapter node must be in the scene tree.
$WalletAdapter.set_wallet_type(WalletAdapter.PHANTOM) # PHANTOM, SOLFLARE, BACKPACK, LEDGER, ...
$WalletAdapter.connect_wallet()
await $WalletAdapter.connection_established
var pubkey: Pubkey = $WalletAdapter.get_connected_key()
# Use WalletAdapter as payer — triggers wallet popup on sign
$Transaction.set_payer($WalletAdapter)
$Transaction.add_instruction(SystemProgram.transfer($WalletAdapter, Pubkey.new_random(), 1_000_000))
$Transaction.update_latest_blockhash()
$Transaction.sign()
await $Transaction.fully_signed
$Transaction.send()
var response = await $Transaction.transaction_response_received
Sign a text message:
$WalletAdapter.sign_text_message("Hello Godot")
var signature: PackedByteArray = await $WalletAdapter.message_signed # 64 bytes
Supported wallets (enum values):
PHANTOM, SOLFLARE, BACKPACK, LEDGER, TREZOR, TRUST, WALLETCONNECT, COINBASE, NIGHTLY,
COIN98, ALPHA, AVANA, BITKEEP, BITPIE, CLOVER, COINHUB, FRACTAL, HUOBI, HYPERPAY,
KEYSTONE, KRYSTAL, MATH, NEKO, NUFI, ONTO, PARTICLE, SAFEPAL, SAIFU, SALMON, SKY,
SOLONG, SPOT, TOKENARY, TOKENPOCKET, TORUS, UNSAFEBURNER, XDEFI
AnchorProgram
Interact with any Anchor smart contract by loading its IDL.
Setup (prefer inspector over code):
- Add an
AnchorProgramnode to your scene. - Set
pidto the program's base58 address. - Assign the IDL JSON resource to
json_fileand settry_from_json_file = true, or fetch it from the chain withtry_from_pid = true.
Signals:
idl_fetched
account_fetched(account_data: Dictionary)
all_account_fetched
Call an instruction:
# Inspect anchor_program.idl in the Godot inspector to verify account order and arg types.
var program_key := Pubkey.new_from_string(PROGRAM_ID)
var level_pda := Pubkey.new_pda(["Level1"], program_key)
var vault_pda := Pubkey.new_pda(["Vault1"], program_key)
var ix: Instruction = $AnchorProgram.build_instruction(
"restartLevel", # exact instruction name from IDL
[
level_pda, # accounts in IDL order
vault_pda,
SolanaService.wallet.get_kp(), # signer
SystemProgram.get_pid() # system program
],
AnchorProgram.u64(int(prize_sol * pow(10, 9)))
)
var tx := Transaction.new()
add_child(tx)
tx.set_payer(SolanaService.wallet.get_kp())
tx.add_instruction(ix)
tx.update_latest_blockhash()
tx.sign_and_send()
var result = await tx.transaction_response_received
Typed argument helpers:
AnchorProgram.u8(val) AnchorProgram.u16(val) AnchorProgram.u32(val) AnchorProgram.u64(val)
AnchorProgram.s8(val) AnchorProgram.s16(val) AnchorProgram.s32(val) AnchorProgram.s64(val)
AnchorProgram.f32(val) AnchorProgram.f64(val) AnchorProgram.option(val)
Fetch an account:
$AnchorProgram.fetch_account("CandyMachine", "2kJKEGhqGXJJtoWPfxnKq1Y2bN4eF9GQ39SAcGu8TDZn")
var data: Dictionary = await $AnchorProgram.account_fetched
MplCandyMachine
# Initialize a Candy Machine account (InitializeV2 in Metaplex program)
MplCandyMachine.initialize(authority, candy_machine_account, collection_mint, candy_machine_data, pnft) -> Instruction
# Mint an NFT (MintV2 in Metaplex program)
MplCandyMachine.mint(payer, receiver, mint, collection_mint, collection_update_authority, candy_machine_key) -> Instruction
# Add config lines to hidden data section
MplCandyMachine.add_config_lines(candy_machine_key, authority, config_lines, index) -> Instruction
# Derive the candy machine authority PDA
MplCandyMachine.new_candy_machine_authority_pda(candy_machine_key) -> Pubkey
# Fetch candy machine account data (listens to http_request_completed)
candy_machine_node.get_candy_machine_info(candy_machine_key)
MplCandyMachine.get_pid() -> Pubkey
SolanaUtils
SolanaUtils.bs58_decode(str: String) -> PackedByteArray
SolanaUtils.bs58_encode(bytes: PackedByteArray) -> String
SolanaUtils.bs64_encode(bytes: PackedByteArray) -> String
SolanaUtils.bs64_decode(str: String) -> PackedByteArray
SolanaUtils.sha256_hash_array(contents: PackedStringArray) -> PackedByteArray
SolanaService (Helper Layer)
SolanaService is the autoload singleton added by the plugin. Its wallet child node is a
WalletService that abstracts keypair vs. wallet adapter login.
# Get the connected wallet's pubkey (works for both keypair and adapter)
var pubkey: Pubkey = SolanaService.wallet.get_pubkey()
# Get the keypair or WalletAdapter as a signer
var signer = SolanaService.wallet.get_kp() # Keypair or WalletAdapter
# Check login state
var logged_in: bool = SolanaService.wallet.is_logged_in()
# Request an airdrop via SolanaService helper
var balance = await SolanaService.get_balance(pubkey.to_string())
Error Handling
# HTTP request pattern
var params = await client.http_request_completed
var error: Error = params[0]
var response: Dictionary = params[1]
if error != OK:
push_error("Connection error: %d" % error)
return
if response.is_empty() or response.has("error"):
push_error("RPC error: " + str(response.get("error", {}).get("message", "unknown")))
return
var result = response["result"]
Common Pitfalls
SolanaClientmust be in the scene tree. Calladd_child(client)before making requests; remove andqueue_free()when done.TokenProgram.transfer_checkedis the correct method, nottransfer. It requires the mint address and decimals to prevent cross-mint transfers.Pubkey.new_associated_token_addressis how you derive ATAs — not a method onAssociatedTokenAccountProgram.- Signers are not stored in serialized transaction bytes. After
Transaction.new_from_bytes, callset_signers([...])again before signing. - AnchorProgram IDL loading: Set
pidandtry_from_json_file/try_from_pidin the Godot inspector, not from GDScript. Pubkey.new_pdareturnsnullif it cannot find a valid PDA. Always null-check.- WalletAdapter is primarily for Web exports. Use
Keypairfor desktop/server builds.
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.