77
19
In review
Synced today
What it does
>
Skill profile
Claude Code · Codex · OpenClaw
Keep exploring
More options in Dev Tools.
C++
Updated 5/6/2026
Agent compatibility
Compatibility has not been reviewed for this listing yet. Check the publisher documentation before installing.
Installation
npx skills add https://github.com/Virus-Axel/godot-solana-sdk --skill rootReview source code and installation permissions before adding third-party tools to an agent.
godot-solana-sdk is organized in the Dev Tools category. Compare its source, install method, and compatibility before adding it to your workflow.
Third-party agent tools may access source code, credentials, or browser sessions. Read the source documentation and use the minimum permissions needed.
npx skills add https://github.com/Virus-Axel/godot-solana-sdk --skill rootSKILL.md
---
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)**
1. Search for **"Solana"** in the Godot Asset Library and install.
2. Install must go into the **`addons/`** folder at the project root.
3. Go to **Project → Project Settings → Plugins** and enable **SolanaSDK**.
4. Verify **`SolanaService`** appears under **Project Settings → Autoload**.
**Via GitHub Releases (manual)**
Download binaries from the [Releases page](https://github.com/Virus-Axel/godot-solana-sdk/releases),
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](https://www.helius.dev)) for better performance.
The URL is also read from `Project Settings → solana_sdk/client/default_url`.
- **Wallet:** The `WalletService` child 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
```gdscript
# 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_pda` finds the canonical valid PDA (searches for bump). `Pubkey.new_program_address`
> does not search — it constructs from exact seeds. Use `new_pda` for normal PDA derivation.
---
## Keypair
```gdscript
# 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:** `SolanaClient` must 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
```
```gdscript
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`):**
```gdscript
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:**
```gdscript
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:**
```gdscript
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
```
```gdscript
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:**
```gdscript
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:**
```gdscript
var data = await tx.transaction_simulation_failed
push_error("Simulation failed: " + data[0])
print("Logs:", data[1].get("logs", []))
```
---
## SystemProgram
```gdscript
# 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), not `transfer`.
```gdscript
# 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)
```gdscript
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
```gdscript
# 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
```
> `AssociatedTokenAccountProgram` does **not** have `get_associated_token_address`.
> Use `Pubkey.new_associated_token_address` to derive the address.
---
## ComputeBudget
```gdscript
# 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 = 800000` and `unit_price = 8000` built in.
> Use `ComputeBudget` instructions when you need explicit control over priority fees.
---
## WalletAdapter (Browser / Mobile)
> Primarily intended for **web/browser** platform exports. Desktop and mobile require tweaking.
```gdscript
# 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:**
```gdscript
$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 `AnchorProgram` node to your scene.
- Set `pid` to the program's base58 address.
- Assign the IDL JSON resource to `json_file` and set `try_from_json_file = true`,
or fetch it from the chain with `try_from_pid = true`.
**Signals:**
```
idl_fetched
account_fetched(account_data: Dictionary)
all_account_fetched
```
**Call an instruction:**
```gdscript
# 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:**
```gdscript
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:**
```gdscript
$AnchorProgram.fetch_account("CandyMachine", "2kJKEGhqGXJJtoWPfxnKq1Y2bN4eF9GQ39SAcGu8TDZn")
var data: Dictionary = await $AnchorProgram.account_fetched
```
---
## MplCandyMachine
```gdscript
# 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
```gdscript
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.
```gdscript
# 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
```gdscript
# 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
- **`SolanaClient` must be in the scene tree.** Call `add_child(client)` before making requests;
remove and `queue_free()` when done.
- **`TokenProgram.transfer_checked`** is the correct method, not `transfer`. It requires the mint
address and decimals to prevent cross-mint transfers.
- **`Pubkey.new_associated_token_address`** is how you derive ATAs — not a method on
`AssociatedTokenAccountProgram`.
- **Signers are not stored in serialized transaction bytes.** After `Transaction.new_from_bytes`,
call `set_signers([...])` again before signing.
- **AnchorProgram IDL loading:** Set `pid` and `try_from_json_file`/`try_from_pid` in the Godot
inspector, not from GDScript.
- **`Pubkey.new_pda` returns `null`** if it cannot find a valid PDA. Always null-check.
- **WalletAdapter is primarily for Web exports.** Use `Keypair` for desktop/server builds.
skill
mattpocock
A tool that automates setting up Husky pre-commit hooks with lint-staged, Prettier formatting, type checking, and tests in a code repository.