Skip to main content

ERC-721 Tokens (Go)

This guide shows the core steps to use ULedger’s native ERC-721-style token transactions with the ULedger Go SDK.

ERC-721 tokens are non-fungible tokens (NFTs): each token is unique and is identified by a tokenId. They’re commonly used for collectibles, certificates, tickets, digital art, memberships, and any scenario where each asset needs a unique identity.

In ULedger, ERC-721-style tokens are supported as native protocol transactions. That means you can manage an NFT collection and its tokens using standard transaction types such as:

  • CREATE_TOKEN — define token collection metadata (name, symbol, baseURI)
  • MINT_NFT — mint a new NFT (tokenId) to a wallet address
  • TRANSFER_NFT — transfer an NFT (tokenId) between wallets
  • APPROVE_TOKEN — approve another wallet to transfer a token (spender pattern)
  • TRANSFER_TOKEN (with payload From) — transfer on behalf of an owner using an approval (if supported by your network)
  • BURN_TOKEN — destroy an NFT (if burnable)

Because these actions are normal ULedger transactions, token operations inherit ULedger’s core capabilities: wallet-based identity, cryptographic signatures, tamper-evident history, and an auditable trail of token events across the chain.

In the next guide, we’ll cover ULedger’s other native token standard:

  • ERC-1155 (multi-tokens) — a single contract/address can manage both fungible and non-fungible token IDs, including batch mint/transfer.
info

Your wallet must be registered on-chain (CREATE_WALLET) before it can submit token transactions. Make sure your wallet is registered.


1) Prerequisites

You need:

  • A node endpoint (you can use a testnet endpoint: https://tn-w-1.uledger.net/)
  • A blockchainId (Testnet: 08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2)
  • A wallet you can sign with (recommended: load from .ukey)
  • A basic understanding that:
    • Token Address = the TransactionId returned by CREATE_TOKEN

2) Load a wallet

Load from a .ukey file instead of hardcoding keys:

w, err := wallet.LoadFromFile("./wallets/my_wallet.ukey", "")
if err != nil {
return fmt.Errorf("load wallet: %w", err)
}

3) Create a transaction session

Create a session bound to a node and wallet:

session, err := transaction.NewUL_TransactionSession(nodeEndpoint, w)
if err != nil {
return fmt.Errorf("create session: %w", err)
}

4) Create an ERC-721 collection (CREATE_TOKEN)

4.1 Build the payload

func buildCreateERC721Payload() ([]byte, error) {
return json.Marshal(transaction.CreateTokenPayload{
TokenType: transaction.ERC721_TOKEN_TYPE,
Name: "Collectible Token",
Symbol: "CTK",
BaseURI: "https://api.collectibletoken.com/token/",
Mintable: true,
Burnable: true,
})
}

4.2 Submit the transaction

payloadBytes, err := buildCreateERC721Payload()
if err != nil {
return err
}

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: w.Address,
PayloadType: transaction.CREATE_TOKEN.String(),
Payload: string(payloadBytes),
}

tx, err := session.GenerateTransaction(input)
if err != nil {
return fmt.Errorf("create collection tx: %w", err)
}

tokenAddress := tx.TransactionId // ✅ Save this
fmt.Println("Token address:", tokenAddress)
fmt.Println("Status:", tx.Status, "Output:", tx.Output)

✅ The returned tx.TransactionId is used as the collection’s “token address” in future calls.


5) Mint an NFT (MINT_NFT)

Mint creates a new token in the collection and assigns it to a wallet address.

5.1 Build the payload

func buildMintERC721Payload(tokenAddress, to string, tokenId uint64, tokenURI string) ([]byte, error) {
return json.Marshal(transaction.MintTokenPayload{
TokenAddress: tokenAddress,
To: to,
TokenId: tokenId,
TokenURI: tokenURI,
})
}

5.2 Submit the transaction

payloadBytes, err := buildMintERC721Payload(
tokenAddress,
toAddress,
1, // tokenId
"https://api.collectibletoken.com/token/1",
)
if err != nil {
return err
}

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: w.Address,
PayloadType: transaction.MINT_NFT.String(),
Payload: string(payloadBytes),
}

tx, err := session.GenerateTransaction(input)
if err != nil {
return fmt.Errorf("mint tx: %w", err)
}

fmt.Println("Mint tx:", tx.TransactionId)
fmt.Println("Status:", tx.Status, "Output:", tx.Output)

6) Transfer an NFT (TRANSFER_NFT)

Transfers a specific tokenId to another wallet address.

6.1 Build the payload

func buildTransferERC721Payload(tokenAddress, to string, tokenId uint64) ([]byte, error) {
return json.Marshal(transaction.TransferTokenPayload{
TokenAddress: tokenAddress,
To: to,
TokenId: tokenId,
})
}

6.2 Submit the transaction

payloadBytes, err := buildTransferERC721Payload(tokenAddress, toAddress, 1)
if err != nil {
return err
}

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: w.Address, // current owner/sender
PayloadType: transaction.TRANSFER_NFT.String(),
Payload: string(payloadBytes),
}

tx, err := session.GenerateTransaction(input)
if err != nil {
return fmt.Errorf("transfer tx: %w", err)
}

fmt.Println("Transfer tx:", tx.TransactionId)
fmt.Println("Status:", tx.Status, "Output:", tx.Output)

7) Approve a spender (APPROVE_TOKEN)

Approvals allow a spender to transfer NFTs on behalf of the owner.

7.1 Build the payload

Most ERC-721 implementations approve a spender for a specific tokenId:

func buildApproveERC721Payload(tokenAddress, spender string, tokenId uint64) ([]byte, error) {
return json.Marshal(transaction.ApproveTokenPayload{
TokenAddress: tokenAddress,
Spender: spender,
TokenId: tokenId,
})
}

7.2 Submit the transaction

payloadBytes, err := buildApproveERC721Payload(tokenAddress, spenderAddress, 1)
if err != nil {
return err
}

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: w.Address, // token owner
PayloadType: transaction.APPROVE_TOKEN.String(),
Payload: string(payloadBytes),
}

tx, err := session.GenerateTransaction(input)
if err != nil {
return fmt.Errorf("approve tx: %w", err)
}

fmt.Println("Approve tx:", tx.TransactionId)
fmt.Println("Status:", tx.Status, "Output:", tx.Output)

8) Transfer using approval (TRANSFER_TOKEN with payload From)

This pattern submits a transfer where:

  • the transaction signer (input.From) is the spender
  • the payload From is the token owner whose NFT is being moved

8.1 Build the payload

func buildTransferApprovalERC721Payload(tokenAddress, to, owner string, tokenId uint64) ([]byte, error) {
return json.Marshal(transaction.TransferTokenPayload{
TokenAddress: tokenAddress,
To: to,
From: owner, // owner whose NFT will be moved
TokenId: tokenId, // token being moved
})
}

8.2 Submit the transaction

payloadBytes, err := buildTransferApprovalERC721Payload(tokenAddress, finalRecipient, ownerAddress, 1)
if err != nil {
return err
}

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: spenderWallet.Address, // signer is spender
PayloadType: transaction.TRANSFER_TOKEN.String(),
Payload: string(payloadBytes),
}

spenderSession, err := transaction.NewUL_TransactionSession(nodeEndpoint, spenderWallet)
if err != nil {
return err
}

tx, err := spenderSession.GenerateTransaction(input)
if err != nil {
return fmt.Errorf("transfer_approval tx: %w", err)
}

fmt.Println("Transfer (approval) tx:", tx.TransactionId)
fmt.Println("Status:", tx.Status, "Output:", tx.Output)

9) Burn an NFT (BURN_TOKEN)

9.1 Build the payload

func buildBurnERC721Payload(tokenAddress string, tokenId uint64) ([]byte, error) {
return json.Marshal(transaction.BurnTokenPayload{
TokenAddress: tokenAddress,
TokenId: tokenId,
})
}

9.2 Submit the transaction

payloadBytes, err := buildBurnERC721Payload(tokenAddress, 1)
if err != nil {
return err
}

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: w.Address,
PayloadType: transaction.BURN_TOKEN.String(),
Payload: string(payloadBytes),
}

tx, err := session.GenerateTransaction(input)
if err != nil {
return fmt.Errorf("burn tx: %w", err)
}

fmt.Println("Burn tx:", tx.TransactionId)
fmt.Println("Status:", tx.Status, "Output:", tx.Output)

10) Full example

This single example shows:

  1. Create collection
  2. Mint tokenId=1
  3. Transfer tokenId=1
  4. Approve spender for tokenId=1
  5. Transfer using approval (spender moves tokenId=1)
  6. Burn tokenId=1
package main

import (
"encoding/json"
"fmt"

"github.com/ULedgerInc/golang-sdk/pkg/transaction"
"github.com/ULedgerInc/golang-sdk/pkg/wallet"
)

func main() {
nodeEndpoint := "https://tn-w-1.uledger.net/"
blockchainId := "08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2"

// Load two wallets (owner + spender) for demonstration
ownerWallet, err := wallet.LoadFromFile("./wallets/owner.ukey", "")
if err != nil { panic(err) }

spenderWallet, err := wallet.LoadFromFile("./wallets/spender.ukey", "")
if err != nil { panic(err) }

ownerSession, err := transaction.NewUL_TransactionSession(nodeEndpoint, ownerWallet)
if err != nil { panic(err) }

// 1) CREATE_TOKEN (ERC721)
createPayload, err := json.Marshal(transaction.CreateTokenPayload{
TokenType: transaction.ERC721_TOKEN_TYPE,
Name: "Collectible Token",
Symbol: "CTK",
BaseURI: "https://api.collectibletoken.com/token/",
Mintable: true,
Burnable: true,
})
if err != nil { panic(err) }

createTx, err := ownerSession.GenerateTransaction(transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: ownerWallet.Address,
PayloadType: transaction.CREATE_TOKEN.String(),
Payload: string(createPayload),
})
if err != nil { panic(err) }

tokenAddress := createTx.TransactionId
fmt.Println("Token address:", tokenAddress)

// 2) MINT_NFT (mint tokenId=1 to owner)
mintPayload, _ := json.Marshal(transaction.MintTokenPayload{
TokenAddress: tokenAddress,
To: ownerWallet.Address,
TokenId: 1,
TokenURI: "https://api.collectibletoken.com/token/1",
})

mintTx, err := ownerSession.GenerateTransaction(transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: ownerWallet.Address,
PayloadType: transaction.MINT_NFT.String(),
Payload: string(mintPayload),
})
if err != nil { panic(err) }
fmt.Println("Mint tx:", mintTx.TransactionId)

// 3) TRANSFER_NFT (owner -> spender)
transferPayload, _ := json.Marshal(transaction.TransferTokenPayload{
TokenAddress: tokenAddress,
To: spenderWallet.Address,
TokenId: 1,
})

transferTx, err := ownerSession.GenerateTransaction(transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: ownerWallet.Address,
PayloadType: transaction.TRANSFER_NFT.String(),
Payload: string(transferPayload),
})
if err != nil { panic(err) }
fmt.Println("Transfer tx:", transferTx.TransactionId)

// 4) APPROVE_TOKEN (spender approved for tokenId=1)
approvePayload, _ := json.Marshal(transaction.ApproveTokenPayload{
TokenAddress: tokenAddress,
Spender: spenderWallet.Address,
TokenId: 1,
})

approveTx, err := ownerSession.GenerateTransaction(transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: ownerWallet.Address,
PayloadType: transaction.APPROVE_TOKEN.String(),
Payload: string(approvePayload),
})
if err != nil { panic(err) }
fmt.Println("Approve tx:", approveTx.TransactionId)

// 5) TRANSFER_TOKEN using approval (spender moves owner's tokenId=1 to final recipient)
spenderSession, err := transaction.NewUL_TransactionSession(nodeEndpoint, spenderWallet)
if err != nil { panic(err) }

transferApprovalPayload, _ := json.Marshal(transaction.TransferTokenPayload{
TokenAddress: tokenAddress,
From: ownerWallet.Address, // owner
To: "DESTINATION_ADDRESS", // replace
TokenId: 1,
})

transferApprovalTx, err := spenderSession.GenerateTransaction(transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: spenderWallet.Address, // signer = spender
PayloadType: transaction.TRANSFER_TOKEN.String(),
Payload: string(transferApprovalPayload),
})
if err != nil { panic(err) }
fmt.Println("Transfer (approval) tx:", transferApprovalTx.TransactionId)

// 6) BURN_TOKEN (burn tokenId=1)
burnPayload, _ := json.Marshal(transaction.BurnTokenPayload{
TokenAddress: tokenAddress,
TokenId: 1,
})

burnTx, err := ownerSession.GenerateTransaction(transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: ownerWallet.Address,
PayloadType: transaction.BURN_TOKEN.String(),
Payload: string(burnPayload),
})
if err != nil { panic(err) }
fmt.Println("Burn tx:", burnTx.TransactionId)
}

Next steps

  • Follow the ERC-1155 token guide.