Dero Virtual Machine
SC Tutorial

Create, Deploy & Use a Smart Contract

This is a hands-on tutorial that takes you from a blank file to a working smart contract on the DERO blockchain. By the end, you will have written a contract, deployed it, called its functions, and queried its state — all from the command line.

New to DERO contracts? Read Smart Contract Fundamentals first for the concepts behind everything in this tutorial.


Prerequisites

You need three things running:

ComponentPurposeDefault Port
DERO Daemon (derod)Connects to the blockchain10102 (mainnet)
DERO Wallet CLISigns and broadcasts transactions10103 (mainnet)
Some DEROGas fees for deployment and calls~0.01 DERO minimum
# Terminal 1: Start the daemon
./derod-darwin  # or derod-linux, derod-windows.exe
 
# Terminal 2: Start the wallet with RPC enabled
./dero-wallet-cli --rpc-server --rpc-bind=127.0.0.1:10103 --wallet-file=your_wallet.db

Verify everything is running:

# Check daemon
curl -s http://127.0.0.1:10102/json_rpc \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"DERO.GetInfo"}' | python3 -m json.tool | grep topoheight
 
# Check wallet
curl -s http://127.0.0.1:10103/json_rpc \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"GetAddress"}' | python3 -m json.tool | grep address

Step 1: Write the Contract

Create a file called tipjar.bas with this content:

Function Initialize() Uint64
  10 IF EXISTS("owner") THEN GOTO 100
  20 STORE("owner", SIGNER())
  30 STORE("totalTips", 0)
  40 STORE("tipCount", 0)
  50 RETURN 0
  100 RETURN 1
End Function

Function Tip() Uint64
  10 IF DEROVALUE() == 0 THEN GOTO 100
  20 STORE("totalTips", LOAD("totalTips") + DEROVALUE())
  30 STORE("tipCount", LOAD("tipCount") + 1)
  40 STORE("lastTipper", SIGNER())
  50 RETURN 0
  100 RETURN 1
End Function

Function Withdraw(amount Uint64) Uint64
  10 IF SIGNER() != LOAD("owner") THEN GOTO 100
  20 SEND_DERO_TO_ADDRESS(SIGNER(), amount)
  30 RETURN 0
  100 RETURN 1
End Function

Function GetStats() Uint64
  10 RETURN LOAD("tipCount")
End Function

What each function does:

FunctionWho Can CallWhat Happens
InitializeDeployer (once)Sets the deployer as owner, initializes counters
TipAnyoneAccepts DERO, increments counters, records last tipper
WithdrawOwner onlySends DERO from the contract to the owner
GetStatsAnyoneReturns the tip count
⚠️

RETURN 0 = success, RETURN 1 = failure. If a function returns 1, all state changes revert and any DERO sent is returned to the caller.


Step 2: Deploy the Contract

Deployment uses the wallet's transfer RPC method with the contract source in the sc field. The wallet accepts the code as base64.

# Base64-encode the contract
SC_CODE=$(base64 < tipjar.bas)
 
# Deploy
curl -s http://127.0.0.1:10103/json_rpc \
  -H 'Content-Type: application/json' \
  -d "{
    \"jsonrpc\": \"2.0\",
    \"id\": \"1\",
    \"method\": \"transfer\",
    \"params\": {
      \"sc\": \"$SC_CODE\",
      \"ringsize\": 2
    }
  }"

The response contains the TXID, which is also the SCID (Smart Contract ID):

{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "txid": "a1b2c3d4e5f6..."
  }
}

Save this TXID — it's the permanent address of your contract on the blockchain.

SCID = TXID. On DERO, the smart contract's unique identifier is the hash of the deployment transaction. There is no separate "contract address" concept.

Deploy with Initialize Parameters

If your Initialize function accepts parameters, pass them via sc_rpc:

curl -s http://127.0.0.1:10103/json_rpc \
  -H 'Content-Type: application/json' \
  -d "{
    \"jsonrpc\": \"2.0\",
    \"id\": \"1\",
    \"method\": \"transfer\",
    \"params\": {
      \"sc\": \"$SC_CODE\",
      \"sc_rpc\": [
        {\"name\": \"someParam\", \"datatype\": \"S\", \"value\": \"hello\"},
        {\"name\": \"someNumber\", \"datatype\": \"U\", \"value\": 42}
      ],
      \"ringsize\": 2
    }
  }"

Data types: "S" = String, "U" = Uint64, "H" = Hash.


Step 3: Wait for Confirmation

DERO blocks are produced roughly every 18 seconds. Wait at least one block before interacting with the contract:

sleep 20
⚠️

One TX at a time. DERO's encrypted balance system only allows one pending (unconfirmed) transaction per wallet. Always wait for a transaction to confirm before sending the next one. Attempting concurrent transactions will cause the second one to be silently dropped.


Step 4: Verify Deployment

Query the contract's state using the daemon's getsc method:

SCID="your_txid_here"
 
curl -s http://127.0.0.1:10102/json_rpc \
  -H 'Content-Type: application/json' \
  -d "{
    \"jsonrpc\": \"2.0\",
    \"id\": \"1\",
    \"method\": \"DERO.GetSC\",
    \"params\": {
      \"scid\": \"$SCID\",
      \"code\": true,
      \"variables\": true
    }
  }" | python3 -m json.tool

A successful deployment returns the contract's stored variables:

{
  "result": {
    "stringkeys": {
      "C": "46756e6374696f6e20496e69...",
      "owner": "2f5388721e5efad8...",
      "totalTips": 0,
      "tipCount": 0
    },
    "balance": 0,
    "status": "OK"
  }
}
FieldMeaning
stringkeys.CThe contract source code (hex-encoded)
stringkeys.ownerThe raw address of the deployer
stringkeys.totalTipsCurrent total tips (0 after deploy)
balanceDERO held by the contract

If stringkeys is missing or empty, the deployment failed — likely a syntax error in the contract or an Initialize that returned 1.

Query Specific Variables

Instead of fetching all variables, you can request specific keys:

curl -s http://127.0.0.1:10102/json_rpc \
  -H 'Content-Type: application/json' \
  -d "{
    \"jsonrpc\": \"2.0\",
    \"id\": \"1\",
    \"method\": \"DERO.GetSC\",
    \"params\": {
      \"scid\": \"$SCID\",
      \"keysstring\": [\"totalTips\", \"tipCount\"]
    }
  }" | python3 -m json.tool

This returns valuesstring with the values in the same order as the requested keys.


Step 5: Call a Function (Send a Tip)

Use the scinvoke method to call contract functions. To send a tip, call Tip with some DERO attached:

curl -s http://127.0.0.1:10103/json_rpc \
  -H 'Content-Type: application/json' \
  -d "{
    \"jsonrpc\": \"2.0\",
    \"id\": \"1\",
    \"method\": \"scinvoke\",
    \"params\": {
      \"scid\": \"$SCID\",
      \"sc_rpc\": [
        {\"name\": \"entrypoint\", \"datatype\": \"S\", \"value\": \"Tip\"}
      ],
      \"sc_dero_deposit\": 10000,
      \"ringsize\": 2
    }
  }"
ParameterPurpose
scidThe contract to call
sc_rpc[0].valueThe function name ("Tip")
sc_dero_depositDERO to send (in atomic units: 10000 = 0.1 DERO)
ringsizeMust be 2 for functions that use SIGNER()

Atomic units: 1 DERO = 100,000 atomic units. So 10000 = 0.1 DERO, 100000 = 1 DERO.

Call a Function with Arguments

For functions that take parameters (like Withdraw), add them to sc_rpc:

curl -s http://127.0.0.1:10103/json_rpc \
  -H 'Content-Type: application/json' \
  -d "{
    \"jsonrpc\": \"2.0\",
    \"id\": \"1\",
    \"method\": \"scinvoke\",
    \"params\": {
      \"scid\": \"$SCID\",
      \"sc_rpc\": [
        {\"name\": \"entrypoint\", \"datatype\": \"S\", \"value\": \"Withdraw\"},
        {\"name\": \"amount\", \"datatype\": \"U\", \"value\": 5000}
      ],
      \"ringsize\": 2
    }
  }"

Step 6: Verify the State Changed

After waiting for confirmation (~20 seconds), query the state again:

sleep 20
 
curl -s http://127.0.0.1:10102/json_rpc \
  -H 'Content-Type: application/json' \
  -d "{
    \"jsonrpc\": \"2.0\",
    \"id\": \"1\",
    \"method\": \"DERO.GetSC\",
    \"params\": {
      \"scid\": \"$SCID\",
      \"variables\": true
    }
  }" | python3 -c "
import sys, json
data = json.load(sys.stdin)
keys = data['result'].get('stringkeys', {})
print('tipCount:', keys.get('tipCount', '?'))
print('totalTips:', keys.get('totalTips', '?'))
print('balance:', data['result'].get('balance', 0))
"

Expected output after one 0.1 DERO tip:

tipCount: 1
totalTips: 10000
balance: 10000

Complete Workflow Summary

Write the contract

Create a .bas file with Initialize and your business logic functions.

Deploy

Base64-encode the source and send it via the wallet's transfer method with the sc field.

Wait for confirmation

At least one block (~18 seconds). Always wait between transactions.

Verify deployment

Query with DERO.GetSC — check that stringkeys contains your stored variables.

Interact

Call functions with scinvoke. Attach DERO with sc_dero_deposit. Pass arguments in sc_rpc.

Query state

Use DERO.GetSC with variables: true or targeted keysstring queries.


Common Mistakes

MistakeSymptomFix
Putting SC_CODE in sc_rpc instead of the sc fieldContract deploys but has no state (empty stringkeys)Use the top-level "sc" parameter — the wallet auto-decodes base64 from this field only
Sending two transactions without waitingSecond TX silently dropped (TXID returned but never mined)Wait ≥20 seconds between transactions
Using ringsize > 2 with SIGNER()Function returns 1 (access check fails)Use ringsize: 2 for any function that checks SIGNER()
Forgetting DEROVALUE() checkContract accepts 0-value calls as depositsAlways guard with IF DEROVALUE() == 0 THEN GOTO [fail]
Not checking stringkeys after deployAssuming deploy succeeded when Initialize actually failedAlways verify with GetSC — if stringkeys is empty, the contract has no state

What's Next