Skip to main content

Deploy Smart Contracts (Go)

This guide shows the core steps to deploy a compiled ULVM smart contract using the ULedger Go SDK.

You will:

  1. Load a wallet (the signer)
  2. Read your compiled contract (.wat or .wasm)
  3. Create a UL_TransactionSession to a node
  4. Submit a DEPLOY_SMART_CONTRACT transaction
info

Before deploying contracts on most networks, your wallet must be registered on-chain (TX_CREATE_WALLET).
If deployment fails with authorization errors, register your wallet first.


1. Prerequisites

You need:

  • A node endpoint (you can use a testnet endpoint: https://tn-w-3.uledger.net/)
  • A blockchainId
  • A wallet you can sign with
  • A compiled contract file:
    • contract.wat (text), or
    • contract.wasm (binary)

Recommended: load from a .ukey file created by the SDK wallet flow.

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

Avoid hardcoding private keys in examples. Use .ukey files or environment variables.


3. Read the contract file

If you deploy .wat (text)

watBytes, err := os.ReadFile("./auction.wat")
if err != nil {
return fmt.Errorf("read contract file: %w", err)
}
sourceCode := string(watBytes)

If you deploy .wasm (binary)

Most transaction payloads are strings, so a common approach is base64:

wasmBytes, err := os.ReadFile("./auction.wasm")
if err != nil {
return fmt.Errorf("read contract file: %w", err)
}
sourceCode := base64.StdEncoding.EncodeToString(wasmBytes)

Whether your network expects WAT text or base64 WASM depends on node configuration.


4. Choose a contract address

Many chains treat the contract address as an explicit value (passed as To).

A simple, safe default is a random 32-byte hex string:

func randomHex32() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
contractAddress, err := randomHex32()
if err != nil {
return fmt.Errorf("generate contract address: %w", err)
}

5. Create a transaction session

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

NewUL_TransactionSession typically performs initial node calls (like /health) and prepares the session to sign and submit transactions.


6. Build and submit the deploy transaction

This is the “core logic”:

input := transaction.ULTransactionInput{
BlockchainId: blockchainId,
From: w.Address, // signer wallet address
To: contractAddress, // where the contract will live
Payload: sourceCode, // WAT text or base64 WASM
PayloadType: transaction.DEPLOY_SMART_CONTRACT.String(),
}

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

if tx.TransactionId == "" {
return fmt.Errorf("deploy returned empty transaction id")
}

fmt.Println("Deploy TX:", tx.TransactionId)
fmt.Println("Status:", tx.Status)
fmt.Println("Output:", tx.Output)
fmt.Println("Contract address:", contractAddress)

What to expect back

  • tx.TransactionId = transaction identifier
  • tx.Status = SUBMITTED, ACCEPTED, or REJECTED
  • tx.Output = SUCCESS or a rejection reason

7. Next step: Invoke the contract

Once deployed, you can call the contract with an INVOKE_SMART_CONTRACT transaction.

Continue to: **Invoke Smart Contracts (Go).