Write, Test and Deploy a Smart Contract
This is a brief overview of VSC smart contract development with Go.
Environment Setup
Section titled “Environment Setup”Prerequisites
Section titled “Prerequisites”Install Dependencies
Section titled “Install Dependencies”git clone https://github.com/vsc-eco/go-vsc-nodecd go-vsc-nodego mod downloadgo build -buildvcs=false -o vsc-contract-deploy vsc-node/cmd/contract-deployer
This will generate an executable named vsc-contract-deploy
. You may move it to your PATH
directory (i.e. /usr/bin
for Linux or /usr/local/bin
for macOS).
Writing Your First Contract
Section titled “Writing Your First Contract”Clone the Go contract template:
git clone https://github.com/vsc-eco/go-contract-template
Project Structure
Section titled “Project Structure”Directorycontract your contract code goes here
- main.go
Directoryruntime
- gc_leaking_exported.go
Directorysdk
- address.go
- env.go
- sdk.go
Directorytest
- contract_test.go
- .gitignore
- go.mod
- go.sum
Hello World Contract
Section titled “Hello World Contract”The below contract demonstrates calling SDK methods to interact with the contract database and defining WASM exports to mark a function as callable by VSC accounts or other contracts.
Find out more about the Go contract SDK methods here.
package main
import "contract-template/sdk"
//go:wasmexport hello_worldfunc HelloWorld(a *string) *string { ret := "Hello world" return &ret}
//go:wasmexport setStringfunc SetString(a *string) *string { sdk.StateSetObject("myString", *a) return a}
//go:wasmexport getStringfunc GetString(a *string) *string { return sdk.StateGetObject("myString")}
Compile Contract
Section titled “Compile Contract”To compile your contract:
tinygo build -gc=custom -scheduler=none -panic=trap -no-debug -target=wasm-unknown -o artifacts/main.wasm contract/main.go
To remove metadata from the output WASM binary to reduce file size:
wasm-strip -o artifacts/main-striped.wasm artifacts/main.wasm
wasm-tools strip -o artifacts/main-striped.wasm artifacts/main.wasm
To inspect the output WASM assembly:
wasm2wat artifacts/main.wasm
wasm-tools print artifacts/main.wasm
Test and Debug Contract
Section titled “Test and Debug Contract”We provide a contract test environment for you to execute contract calls on a state engine that resembles the live network. Your contract test code will look something like this:
package contract_test
import ( "encoding/json" "testing"
"github.com/stretchr/testify/assert" "github.com/vsc-eco/go-vsc-node/lib/test_utils" "github.com/vsc-eco/go-vsc-node/modules/db/vsc/contracts" ledgerDb "github.com/vsc-eco/go-vsc-node/modules/db/vsc/ledger" stateEngine "github.com/vsc-eco/go-vsc-node/modules/state-processing")
//go:embed artifacts/main.wasmvar ContractWasm []byte
func TestContract(t *testing.T) { ct := test_utils.NewContractTest() ct.RegisterContract("vsccontractid", ContractWasm) ct.Deposit("hive:someone", 1000, ledgerDb.AssetHive) // deposit 1 HIVE ct.Deposit("hive:someone", 1000, ledgerDb.AssetHbd) // deposit 1 HBD
result, gasUsed, logs := ct.Call(stateEngine.TxVscCallContract{ Self: stateEngine.TxSelf{ TxId: "sometxid", BlockId: "abcdef", Index: 69, OpIndex: 0, Timestamp: "2025-09-03T00:00:00", RequiredAuths: []string{"hive:someone"}, RequiredPostingAuths: []string{}, }, ContractId: contractId, Action: "yourMethodName", Payload: json.RawMessage([]byte("1000")), RcLimit: 1000, Intents: []contracts.Intent{{ Type: "transfer.allow", Args: map[string]string{ "limit": "1.000", "token": "hive", }, }}, }) assert.True(t, result.Success) // assert contract execution success assert.LessOrEqual(t, gasUsed, uint(10000000)) // assert this call uses no more than 10M WASM gas assert.GreaterOrEqual(t, len(logs), 1) // assert at least 1 log emitted}
Find out more about methods provided by the contract test utils here.
Deploy Contract
Section titled “Deploy Contract”Firstly, initialize the deployer configuration:
vsc-contract-deploy -init
This will generate some config files in data/config
folder in your current directory. Insert your Hive username and active key of the deployer account in identityConfig.json
:
{ "BlsPrivKeySeed": "9e264692ced37...", "HiveActiveKey": "ADD_YOUR_PRIVATE_WIF", "HiveUsername": "ADD_YOUR_USERNAME", "Libp2pPrivKey": "125cd98aed75d..."}
Then deploy your contract to VSC:
vsc-contract-deploy -wasmPath artifacts/main-striped.wasm -name "my first contract"
You should see an output similar to this:
WASM_CODE: 1130 [0 97 115 109 1 0 0 0 1 9] ...peer ID: 12D3KooWS7N7zmrkMHxGX9ibNXbKk4byfkx8ckEhfgXM8eQcgBtKNAT Status {Private}12D3KooWS7N7zmrkMHxGX9ibNXbKk4byfkx8ckEhfgXM8eQcgBtK pubsub /vsc/mainnet/data-availability/v1 peers: 20pubsub handling error: message did not add any signaturespubsub handling error: message did not add any signaturespubsub handling error: message did not add any signaturespubsub handling error: message did not add any signaturespubsub handling error: message did not add any signaturespubsub handling error: message did not add any signaturespubsub handling error: message did not add any signaturespubsub handling error: message did not add any signatures{bafkreibwwj3fypek5uz6l3scacy47yw3b2adgbxmfab2ybmkarljaggbey {i_TLUMJVJb6n6X-5_lKvBibiLvP0RUVz2tn20KJTw5Q_lQ-tcVt9jrZr4s1WVga1CR7q0Ldnrc-EjNpqgaY7GpnhE2d3pC3nfNhUKkmN1za6h9SwCEUuo3CByNnmnqYE __f9}}{"__v":"0.1","code":"bafkreibwwj3fypek5uz6l3scacy47yw3b2adgbxmfab2ybmkarljaggbey","description":"","name":"go test","net_id":"vsc-mainnet","owner":"techcoderx.vsc","runtime":"go","storage_proof":{"hash":"bafkreibwwj3fypek5uz6l3scacy47yw3b2adgbxmfab2ybmkarljaggbey","signature":{"sig":"i_TLUMJVJb6n6X-5_lKvBibiLvP0RUVz2tn20KJTw5Q_lQ-tcVt9jrZr4s1WVga1CR7q0Ldnrc-EjNpqgaY7GpnhE2d3pC3nfNhUKkmN1za6h9SwCEUuo3CByNnmnqYE","bv":"__f9"}}}pubsub handling error: message did not add any signaturespubsub handling error: message did not add any signaturestx id: bef70add6d21cd812cf68da2caee72da05de48b4contract id: vsc1Bem8RnoLgGPP7E2MBN52ekrdVqy2LNpSqFError in subscription: subscription cancelled
Call Contract
Section titled “Call Contract”Contracts may be called from L1 using Hive accounts or on VSC using offchain accounts.
Call From L1
Section titled “Call From L1”Contract invokation from L1 involves sending a custom_json_operation
with vsc.call
as its ID and the following JSON format:
{ "net_id": "vsc-mainnet", "contract_id": "vsc1...", "action": "methodName", "payload": "your payload string here", "rc_limit": 2000, "intents": []}
An example transaction may be seen here.
Intents
Section titled “Intents”A contract call transaction may include a list of intents
which authorizes the contract to spend up to a specific amount of assets from your account. For example, an intent of 1 HIVE allowance may be specified as follows:
{ "type": "transfer.allow", "args": { "limit": "1.000", "token": "hive" }}