https://github.com/phantasma-io/phantasma-sdk-go
A Go SDK for the Phantasma blockchain
https://github.com/phantasma-io/phantasma-sdk-go
blockchain crypto cryptocurrency go layer1 phantasma phantasmachain phantasmaio sdk smartnft smartnfts
Last synced: 11 days ago
JSON representation
A Go SDK for the Phantasma blockchain
- Host: GitHub
- URL: https://github.com/phantasma-io/phantasma-sdk-go
- Owner: phantasma-io
- License: mit
- Created: 2024-03-11T11:38:57.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2026-05-28T16:35:02.000Z (15 days ago)
- Last Synced: 2026-05-28T18:17:21.096Z (15 days ago)
- Topics: blockchain, crypto, cryptocurrency, go, layer1, phantasma, phantasmachain, phantasmaio, sdk, smartnft, smartnfts
- Language: Go
- Homepage:
- Size: 1010 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
- Roadmap: ROADMAP.md
Awesome Lists containing this project
README
Go SDK for the Phantasma blockchain.

# Overview
This project aims to be an easy to use SDK for the Phantasma blockchain.
# Documentation
## Installation
Requires Go 1.25 or newer. The module is developed with the Go 1.26 toolchain.
`phantasma-sdk-go` is distributed as a library that includes all SDK functionality.
```
go get -u github.com/phantasma-io/phantasma-sdk-go
```
## Getting started
To start interacting with Phantasma blockchain you need to choose the network and create an RPC client. All typed RPC methods take `context.Context` as their first argument so callers can set deadlines and cancellation.
```go
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client := rpc.NewRPCTestnet()
// client := rpc.NewRPCMainnet()
```
To create a new key pair structure from private key in WIF format use following code:
```go
keyPair, err := cryptography.FromWIF("put WIF here")
if err != nil {
log.Fatalf("creating key pair: %v", err)
}
```
To get detailed description of tokens deployed on the chain and read token characteristics you can use following code:
```go
chainTokens, err := client.GetTokens(ctx, false)
if err != nil {
log.Fatal(err)
}
for _, token := range chainTokens {
if token.Symbol == "SOUL" && token.IsFungible() {
fmt.Println("Token SOUL is fungible")
break
}
}
```
Code samples in the following sections of this documentation use `client` and `keyPair` structures which should be initialized in advance.
## Carbon transactions and token builders
The SDK includes `pkg/carbon` for Phantasma Phoenix Carbon transaction serialization and token-module calls. This package mirrors the C#/TS/C++ SDK model:
- fixed-width Carbon types: `Bytes16`, `Bytes32`, `Bytes64`, `SmallString`, `IntX`;
- `TxMsg`, `SignedTxMsg`, witnesses, call sections, and trade payloads;
- VM schema structures used by token metadata;
- token-module argument/result blobs for create token, create series, mint, transfer, burn and metadata update calls;
- high-level helpers for token metadata, NFT ROM/RAM, standard NFT schemas, transaction signing, and NFT address/instance-id conversion.
Example: build and sign a Carbon NFT mint transaction:
```go
signer, err := cryptography.FromWIF("put WIF here")
if err != nil {
log.Fatal(err)
}
receiver, err := carbon.Bytes32FromPhantasmaAddressText("put receiver address here")
if err != nil {
log.Fatal(err)
}
schemas := carbon.PrepareStandardTokenSchemas(false)
rom, err := carbon.BuildNFTRom(schemas.ROM, big.NewInt(1), []carbon.MetadataField{
{Name: "name", Value: "Example NFT"},
{Name: "description", Value: "Minted with phantasma-sdk-go Carbon helpers"},
{Name: "imageURL", Value: "https://example.com/nft.png"},
{Name: "infoURL", Value: "https://example.com/nft"},
{Name: "royalties", Value: int32(0)},
})
if err != nil {
log.Fatal(err)
}
signedTx, err := carbon.BuildMintNonFungibleTxAndSignHex(
42, // Carbon token id
1, // Carbon series id
signer,
receiver,
rom,
nil, // RAM
carbon.DefaultMintNFTFeeOptions(),
100_000_000,
time.Now().UTC().Add(20*time.Minute).UnixMilli(),
)
if err != nil {
log.Fatal(err)
}
txHash, err := client.SendCarbonTransaction(ctx, signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Println("Carbon tx hash:", txHash)
```
See `docs/carbon.md` for the package-level API map.
Carbon `Build...` helpers return validation errors for user input; `MustBuild...` variants are available only when panic-on-invalid-input is intentional.
## Carbon RPC wrappers
The RPC client now exposes the Carbon endpoints used by the current C#/TS SDKs. Important additions include:
- chain/block lookup: `GetChains`, `GetChain`, `GetNexus`, `GetBlockByHash`, `GetLatestBlock`, `GetTransactionByBlockHashAndIndex`;
- contract/organization lookup: `GetContracts`, `GetContractByName`, `GetContractByAddress`, `GetOrganization`, `GetOrganizations`, `GetOrganizationMembers`, `GetOrganizationMember`;
- Carbon token views: `GetTokensByOwner`, `GetTokenWithID`, `GetTokenSeries`, `GetTokenSeriesByID`, `GetTokenNFTs`, `GetAccountFungibleTokens`, `GetAccountNFTs`, `GetAccountOwnedTokens`, `GetAccountOwnedTokenSeries`;
- archive and auction helpers: `GetArchive`, `ReadArchive`, `WriteArchive`, `GetAuctionsCount`, `GetAuctions`, `GetAuction`;
- transaction broadcast: `SendCarbonTransaction`, `SignAndSendCarbonTransaction`.
Cursor-paginated endpoints return `response.CursorPaginatedResult[T]`.
Carbon token id filters use `uint64`, Carbon series id filters use `uint32`, and `0` means no Carbon id filter. `GetTokenNFTsWithSeriesID` accepts the Phantasma Series ID string filter exposed by current RPC nodes.
Use `client.Call(ctx, method, params...)` for low-level calls that need request cancellation or deadlines.
## Public package surface
Prefer the high-level packages for application code:
- `pkg/rpc` and `pkg/rpc/response` for node calls and typed RPC results;
- `pkg/carbon` for Carbon transactions, serialization, token builders, and signing;
- `pkg/vm/script_builder` for classic VM scripts;
- `pkg/cryptography` for keys, addresses, signatures, and WIF handling;
- `pkg/blockchain` for classic VM transaction objects.
Lower-level packages such as `pkg/io`, `pkg/jsonrpc`, `pkg/domain`, and `pkg/vm` remain available for advanced serialization and migration work, but application code should avoid depending on their internals unless it needs that wire-level control.
## Script Builder
Building a script is the main entry point for transaction and contract interactions. The script must match the target contract or interop ABI.
The `CallContract` and `CallInterop` methods are the primary entry points for creating scripts.
```
func (s ScriptBuilder) CallContract(contractName, method string, args ...interface{}) ScriptBuilder
```
```
func (s ScriptBuilder) CallInterop(method string, args ...interface{}) ScriptBuilder
```
The available `CallInterop` functions are listed below.
For `CallContract`, inspect the ABIs of the smart contracts currently deployed on Phantasma mainnet: [Explorer contract list](https://explorer.phantasma.info/en/nexus?tab=contracts). To see all methods of a contract, for example `stake`, check the contract methods view: [stake contract methods](https://explorer.phantasma.info/en/contract?id=stake&tab=methods).
The Go builder now resolves labels per instance, emits integer values as VM `Number` payloads, supports array arguments, and matches the C#/TS/C++ shared script vectors.
Address arguments are intentionally typed as `cryptography.Address` in high-level helpers. Raw `string` arguments passed to `CallContract` or `CallInterop` are emitted as VM strings; use `cryptography.MustAddressFromString(text)` or the `*Text` helpers when the ABI expects a Phantasma address.
### Examples
Following code generates script to transfer `tokenAmount` amount of token `tokenSymbol` from wallet `from` to wallet `to`
```
from := cryptography.MustAddressFromString("put sender address here") // Phantasma address, starting with capital 'P'
to := cryptography.MustAddressFromString("put recipient address here") // Phantasma address, starting with capital 'P'
tokenAmount := big.NewInt(1000000000) // Token amount in the form of big integer
tokenSymbol := "SOUL"
sb := scriptbuilder.BeginScript()
script := sb.CallContract("gas", "AllowGas", from, cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
CallInterop("Runtime.TransferTokens", from, to, tokenSymbol, tokenAmount).
CallContract("gas", "SpendGas", from).
EndScript()
```
And here we generate script to make a call which does not require transaction, for this we use `CallContract` method:
```
address := cryptography.MustAddressFromString("put caller address here") // Phantasma address, starting with capital 'P'
tokenAmount := big.NewInt(1000000000) // Token amount in the form of big integer
sb := scriptbuilder.BeginScript().
CallContract("gas", "AllowGas", address, cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
CallContract("stake", "Stake", address, tokenAmount).
CallContract("gas", "SpendGas", address)
script := sb.EndScript()
```
## Script Builder Extensions
For some widely used contract calls SDK has special extension methods which make code more compact. The typed-address helpers are `AllowGas`, `SpendGas`, `MintTokens`, `Stake`, `Unstake`, `TransferTokens`, `TransferBalance`, `TransferNFT`, `CrossTransferToken`, `CrossTransferNFT`, and `CallNFT`.
String-address convenience helpers are `AllowGasText`, `SpendGasText`, `MintTokensText`, `StakeText`, `UnstakeText`, `TransferTokensText`, `TransferBalanceText`, `TransferNFTText`, `CrossTransferTokenText`, and `CrossTransferNFTText`.
The ordinary `*Text` helpers parse Phantasma address text before emitting VM address bytes. Cross-chain text helpers parse the Phantasma destination-chain/sender addresses and keep the destination account as a VM string for non-Phantasma destination formats.
### Examples
We can rewrite examples from previous section using `AllowGas()` and `SpendGas()` extensions:
```
sb := scriptbuilder.BeginScript()
script := sb.AllowGas(from, cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
CallInterop("Runtime.TransferTokens", from, to, tokenSymbol, tokenAmount).
SpendGas(from).
EndScript()
```
```
sb := scriptbuilder.BeginScript().
AllowGas(address, cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
CallContract("stake", "Stake", address, tokenAmount).
SpendGas(address)
script := sb.EndScript()
```
We can also rewrite main contract calls in these examples:
```
sb := scriptbuilder.BeginScript()
script := sb.AllowGas(from, cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
TransferTokens(tokenSymbol, from, to, tokenAmount).
SpendGas(from).
EndScript()
```
```
sb := scriptbuilder.BeginScript().
AllowGas(address, cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
Stake(address, tokenAmount).
SpendGas(address)
script := sb.EndScript()
```
## InvokeRawScript and decoding the result
Scripts that do not require a transaction can be sent to the chain directly using `InvokeRawScript()`.
Here's a read-only example that gets SOUL token decimals from the chain:
```go
// Build script
sb := scriptbuilder.BeginScript().
CallInterop("Runtime.GetTokenDecimals", "SOUL")
script := sb.EndScript()
// Before sending script to the chain we need to encode it into Base16 encoding (HEX)
encodedScript := fmt.Sprintf("%x", script)
// Make the call itself
result, err := client.InvokeRawScript(ctx, "main", encodedScript)
if err != nil {
log.Fatalf("script invocation failed: %v", err)
}
value, err := result.DecodeResultWithError()
if err != nil {
log.Fatalf("script result decoding failed: %v", err)
}
fmt.Println("SOUL decimals:", value.AsNumber().String())
```
## Building and sending transaction
### Building transaction
To build a transaction you will first need to build a script.
Note, building a transaction is for transactional scripts only. Non transactional scripts should use the RPC function `InvokeRawScript()`.
```
// Build script
sb := scriptbuilder.BeginScript()
script := sb.AllowGas(keyPair.Address(), cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
TransferTokens(tokenSymbol, keyPair.Address(), to, tokenAmount).
SpendGas(keyPair.Address()).
EndScript()
// Build transaction
expire := time.Now().UTC().Add(time.Second * time.Duration(30)).Unix()
tx := blockchain.NewTransaction(netSelected, "main", script, uint32(expire), domain.SDKPayload)
// Sign transaction
if err := tx.Sign(keyPair); err != nil {
log.Fatal(err)
}
// Before sending script to the chain we need to encode it into Base16 encoding (HEX)
txHex := hex.EncodeToString(tx.Bytes())
```
### Sending transaction
Broadcasting requires a funded key and sends a transaction to the selected network. Keep broadcast examples separate from read-only examples and only run them intentionally.
```
txHash, err := client.SendRawTransaction(ctx, txHex)
if err != nil {
log.Fatalf("broadcasting tx failed: %v", err)
} else {
if util.ErrorDetect(txHash) {
log.Fatalf("broadcasting tx failed: %s", txHash)
} else {
fmt.Println("Tx successfully broadcasted! Tx hash: " + txHash)
}
}
```
### Waiting for transaction execution result
We need to wait for transaction to be minted on the chain to get its status:
```
for {
txResult, _ := client.GetTransaction(ctx, txHash)
if txResult.StateIsSuccess() {
fmt.Println("Transaction was successfully minted, tx hash: " + fmt.Sprint(txResult.Hash))
break // Funds were transferred successfully
}
if txResult.StateIsFault() {
fmt.Println("Transaction failed, tx hash: " + fmt.Sprint(txResult.Hash))
break // Funds were not transferred, transaction failed
}
time.Sleep(200 * time.Millisecond)
}
```
## Staking SOUL token
Following code shows how to stake SOUL token:
```
// Build script
sb := scriptbuilder.BeginScript().
AllowGas(address, cryptography.NullAddress(), big.NewInt(100000), big.NewInt(21000)).
Stake(address, tokenAmount).
SpendGas(address)
script := sb.EndScript()
// Build transaction
expire := time.Now().UTC().Add(time.Second * time.Duration(30)).Unix()
tx := chain.NewTransaction(netSelected, "main", script, uint32(expire), domain.SDKPayload)
// Sign transaction
if err := tx.Sign(keyPair); err != nil {
log.Fatal(err)
}
// Before sending script to the chain we need to encode it into Base16 encoding (HEX)
txHex := hex.EncodeToString(tx.Bytes())
txHash, err := client.SendRawTransaction(ctx, txHex)
if err != nil {
log.Fatalf("broadcasting tx failed: %v", err)
} else {
if util.ErrorDetect(txHash) {
log.Fatalf("broadcasting tx failed: %s", txHash)
} else {
fmt.Println("Tx successfully broadcasted! Tx hash: " + txHash)
}
}
for {
txResult, _ := client.GetTransaction(ctx, txHash)
if txResult.StateIsSuccess() {
fmt.Println("Transaction was successfully minted, tx hash: " + fmt.Sprint(txResult.Hash))
break // Funds were transferred successfully
}
if txResult.StateIsFault() {
fmt.Println("Transaction failed, tx hash: " + fmt.Sprint(txResult.Hash))
break // Funds were not transferred, transaction failed
}
time.Sleep(200 * time.Millisecond)
}
```
## Scanning the blockchain for incoming transactions
In the following code we monitor the blockchain by checking all the new blocks minted on the blockchain and waiting for `TokenReceive` event for given address. This event for address means that address has received some tokens.
```
func onTransactionReceived(address, symbol, amount string) {
fmt.Printf("Address %s received %s %s\n", address, amount, symbol)
}
func waitForIncomingTransfers(address string) {
// Get current block height
height, _ := client.GetBlockHeight(ctx, "main")
for {
// Get block's data by its height
block, err := client.GetBlockByHeight(ctx, "main", height.String())
if err != nil {
log.Fatalf("GetBlockByHeight failed: %v", err)
}
// Iterate throough all transactions in the block
for _, tx := range block.Txs {
// Skip failed trasactions
if !tx.StateIsSuccess() {
continue
}
// Iterate throough all events in the transaction
for _, e := range tx.Events {
if e.Kind == event.TokenReceive.String() && e.Address == address {
// We found TokenReceive event for given address
// Decode event data into event.TokenEventData structure
decoded, _ := hex.DecodeString(e.Data)
data := io.Deserialize[*event.TokenEventData](decoded, &event.TokenEventData{})
// Apply decimals to the token amount
t, ok := getChainToken(data.Symbol)
if !ok {
log.Fatalf("token %s not found", data.Symbol)
}
tokenAmount := util.ConvertDecimals(data.Value, int(t.Decimals))
// Call our callback function
onTransactionReceived(e.Address, data.Symbol, tokenAmount)
}
}
}
// Wait for next block to appear on the blockchain
for {
newHeight, _ := client.GetBlockHeight(ctx, "main")
if newHeight.Cmp(height) == 1 {
// New block was minted (at least 1 new block)
height = height.Add(height, big.NewInt(1))
break
}
// Wait 200 milliseconds before making next RPC call
time.Sleep(200 * time.Millisecond)
}
}
}
```
## Examples
This repository has `examples` folder with some code which can be easily reused. Examples are grouped into a single console application.
To run this application switch to `examples` folder and run:
```
go run .
```
or
```
sh run.sh
```
Application entry point is `main()` function in `main.go` source file. Once launched it will display the following menu:

Wallet submenu:

Chain stats submenu:

# Contributing
Feel free to contribute to this project after reading the
[contributing guidelines](CONTRIBUTING.md).
Before starting to work on a certain topic, create an new issue first,
describing the feature/topic you are going to implement.
# Contact
- Get in contact with us on the [Phantasma Discord](https://discord.gg/JzSnmFZCcD)
# License
- Open-source [MIT](LICENSE.md)