Saltar al contenido principal

Tokens ERC-20 (Go)

Esta guía muestra los pasos principales para usar las transacciones de tokens ERC-20 nativas de ULedger con el ULedger Go SDK.

Los tokens ERC-20 son tokens fungibles: cada unidad es intercambiable, como la moneda en el sentido convencional. Se usan comúnmente para pagos, balances dentro de aplicaciones, tokens de gobernanza/staking, y activos tokenizados donde "1 unidad" es equivalente a cualquier otra unidad.

En ULedger, los tokens de tipo ERC-20 están soportados como transacciones nativas del protocolo. Esto significa que puedes gestionar el ciclo de vida de un token usando tipos de transacción estándar como:

  • CREATE_TOKEN — define los metadatos del token (name, symbol, decimals) y opcionalmente acuña un suministro inicial
  • TRANSFER_TOKEN — envía tokens entre wallets
  • APPROVE_TOKEN — otorga una asignación a un gastador
  • TRANSFER_TOKEN (con payload From) — transfiere en nombre de un propietario usando una asignación/aprobación
  • BURN_TOKEN — destruye tokens para reducir el suministro (si es quemable)

Dado que estas acciones son transacciones normales de ULedger, las operaciones con tokens heredan las capacidades principales de ULedger: identidad basada en wallet, firmas criptográficas, historial a prueba de manipulaciones y un rastro auditable de eventos de tokens en la cadena.

En las próximas guías, cubriremos los otros estándares de tokens nativos de ULedger:

  • ERC-721 (NFTs) — tokens no fungibles donde cada token tiene un tokenId único (coleccionables, certificados, activos únicos).
  • ERC-1155 (multi-tokens) — un único contrato/dirección puede gestionar tanto IDs de tokens fungibles como no fungibles, incluyendo acuñación/transferencia por lotes (objetos de juego, sistemas de inventario, catálogos de activos mixtos).
info

Tu wallet debe estar registrada en la cadena (CREATE_WALLET) antes de poder enviar transacciones de tokens. Asegúrate de que tu wallet esté registrada.


1) Prerrequisitos

Necesitas:

  • Un endpoint de node (puedes usar un endpoint de testnet: https://tn-w-1.uledger.net/)
  • Un blockchainId (Testnet: 08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2)
  • Una wallet con la que puedas firmar (recomendado: cargar desde .ukey)
  • Una comprensión básica de que:
    • Token Address = el TransactionId devuelto por CREATE_TOKEN

2) Cargar una wallet

Carga desde un archivo .ukey en lugar de codificar las claves directamente:

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

3) Crear una sesión de transacción

Crea una sesión vinculada a un node y una wallet:

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

4) Crear un token ERC-20 (CREATE_TOKEN)

4.1 Construir el payload

func buildCreateERC20Payload() ([]byte, error) {
return json.Marshal(transaction.CreateTokenPayload{
TokenType: transaction.ERC20_TOKEN_TYPE,
Name: "Sotoken",
Symbol: "SOTOK",
Decimals: 18,
InitialSupply: 1000000000000000000, // 1 token if decimals=18 (in wei-like units)
Mintable: true,
Burnable: true,
})
}

4.2 Enviar la transacción

payloadBytes, err := buildCreateERC20Payload()
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 token tx: %w", err)
}

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

✅ El tx.TransactionId devuelto se usa como la "dirección" del token en llamadas futuras.


5) Transferir tokens (TRANSFER_TOKEN)

5.1 Construir el payload

func buildTransferERC20Payload(tokenAddress, to string, amount uint64) ([]byte, error) {
return json.Marshal(transaction.TransferTokenPayload{
TokenAddress: tokenAddress,
To: to,
Amount: amount,
})
}

5.2 Enviar la transacción

payloadBytes, err := buildTransferERC20Payload(tokenAddress, toAddress, 5000)
if err != nil {
return err
}

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: w.Address,
PayloadType: transaction.TRANSFER_TOKEN.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)

6) Aprobar un gastador (APPROVE_TOKEN)

Las aprobaciones permiten que un gastador transfiera tokens en nombre del propietario del token (estilo asignación).

6.1 Construir el payload

func buildApproveERC20Payload(tokenAddress, spender string, amount uint64) ([]byte, error) {
return json.Marshal(transaction.ApproveTokenPayload{
TokenAddress: tokenAddress,
Spender: spender,
Amount: amount,
})
}

6.2 Enviar la transacción

payloadBytes, err := buildApproveERC20Payload(tokenAddress, spenderAddress, 5000)
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)

7) Transferir usando aprobación (TRANSFER_TOKEN con payload From)

Este patrón envía una transferencia donde:

  • el firmante de la transacción (input.From) es el gastador
  • el payload From es el propietario del token cuyo balance está siendo movido

7.1 Construir el payload

func buildTransferApprovalERC20Payload(tokenAddress, to, owner string, amount uint64) ([]byte, error) {
return json.Marshal(transaction.TransferTokenPayload{
TokenAddress: tokenAddress,
To: to,
From: owner, // owner whose funds will be moved
Amount: amount, // amount to spend from allowance
})
}

7.2 Enviar la transacción

payloadBytes, err := buildTransferApprovalERC20Payload(tokenAddress, finalRecipient, ownerAddress, 3000)
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)

8) Quemar tokens (BURN_TOKEN)

8.1 Construir el payload

func buildBurnERC20Payload(tokenAddress string, amount uint64) ([]byte, error) {
return json.Marshal(transaction.BurnTokenPayload{
TokenAddress: tokenAddress,
Amount: amount,
})
}

8.2 Enviar la transacción

payloadBytes, err := buildBurnERC20Payload(tokenAddress, 5000)
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)

9) Ejemplo completo

Este ejemplo único muestra:

  1. Crear token
  2. Transferir
  3. Aprobar
  4. Transferir usando aprobación
  5. Quemar
package main

import (
"encoding/json"
"fmt"

"github.com/ULedgerInc/go-sdk/pkg/crypto"
"github.com/ULedgerInc/go-sdk/pkg/transaction"
"github.com/ULedgerInc/go-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
createPayload, err := json.Marshal(transaction.CreateTokenPayload{
TokenType: transaction.ERC20_TOKEN_TYPE,
Name: "Sotoken",
Symbol: "SOTOK",
Decimals: 18,
InitialSupply: 1000000000000000000,
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),
KeyType: crypto.KeyTypeSecp256k1,
})
if err != nil { panic(err) }

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

// 2) TRANSFER_TOKEN (owner -> someone)
transferPayload, _ := json.Marshal(transaction.TransferTokenPayload{
TokenAddress: tokenAddress,
To: spenderWallet.Address,
Amount: 5000,
})
transferTx, err := ownerSession.GenerateTransaction(transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: ownerWallet.Address,
PayloadType: transaction.TRANSFER_TOKEN.String(),
Payload: string(transferPayload),
})
if err != nil { panic(err) }
fmt.Println("Transfer tx:", transferTx.TransactionId)

// 3) APPROVE_TOKEN (owner approves spender)
approvePayload, _ := json.Marshal(transaction.ApproveTokenPayload{
TokenAddress: tokenAddress,
Spender: spenderWallet.Address,
Amount: 5000,
})
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)

// 4) TRANSFER_TOKEN using approval (spender spends owner's allowance)
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
Amount: 3000,
})

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)

// 5) BURN_TOKEN
burnPayload, _ := json.Marshal(transaction.BurnTokenPayload{
TokenAddress: tokenAddress,
Amount: 1000,
})

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)
}

Próximos pasos

  • Crear una guía de ERC-721 (patrones de acuñación/transferencia/aprobación de NFTs)
  • Crear una guía de ERC-1155 (multi-token + patrones de transferencia por lotes)