Skip to content

๐Ÿ“œ ErgoScript Basics โ€‹

Writing spending conditions for Ergo smart contracts

What is ErgoScript? โ€‹

ErgoScript is Ergo's smart contract language. It's:

  • Declarative - Describes conditions, not procedures
  • Functional - No side effects, pure expressions
  • Sigma Protocol based - Cryptographic proof system

The Key Insight โ€‹

ErgoScript answers one question:

"Can this box be spent in this transaction?"

It returns true (can spend) or false (cannot spend).

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                                                         โ”‚
โ”‚   ErgoScript evaluates to sigmaProp (boolean-like)     โ”‚
โ”‚                                                         โ”‚
โ”‚   sigmaProp(true)  โ†’ โœ… Box can be spent               โ”‚
โ”‚   sigmaProp(false) โ†’ โŒ Box cannot be spent            โ”‚
โ”‚                                                         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Basic Syntax โ€‹

Hello World Contract โ€‹

scala
{
  sigmaProp(true)
}

This box can be spent by anyone (always returns true).

Owner Only โ€‹

scala
{
  val ownerPk = PK("9f4QF8AD1nQ3nJahQVkMj8hFSVVzVom77b52JU7EW71Zexg6N8v")
  sigmaProp(ownerPk)
}

Only the owner with the matching private key can spend.

Time Lock โ€‹

scala
{
  val unlockHeight = 1500000L
  sigmaProp(HEIGHT >= unlockHeight)
}

Anyone can spend, but only after block 1,500,000.

Context Variables โ€‹

ErgoScript has access to transaction context:

VariableTypeDescription
HEIGHTIntCurrent blockchain height
SELFBoxThe box being spent
INPUTSColl[Box]All input boxes
OUTPUTSColl[Box]All output boxes
CONTEXTContextFull transaction context

Examples โ€‹

scala
// Check current height
sigmaProp(HEIGHT > 1000000)

// Access the box being spent
val myValue = SELF.value

// Check first output
val firstOutput = OUTPUTS(0)
sigmaProp(firstOutput.value >= 1000000L)

// Count inputs
sigmaProp(INPUTS.size == 2)

Box Properties in Scripts โ€‹

scala
{
  // Value (ERG amount in nanoERG)
  val boxValue = SELF.value
  
  // Tokens in this box
  val tokens = SELF.tokens
  val firstToken = SELF.tokens(0)
  val tokenId = firstToken._1
  val tokenAmount = firstToken._2
  
  // Registers
  val r4Data = SELF.R4[Coll[Byte]].get
  val r5Number = SELF.R5[Long].get
  
  // Script hash
  val scriptBytes = SELF.propositionBytes
  
  sigmaProp(boxValue > 1000000L)
}

Common Patterns โ€‹

Multi-Signature (2-of-3) โ€‹

scala
{
  val alice = PK("9fAAA...")
  val bob = PK("9fBBB...")
  val charlie = PK("9fCCC...")
  
  sigmaProp(atLeast(2, Coll(alice, bob, charlie)))
}

Password Protected (Hash Lock) โ€‹

scala
{
  val secretHash = fromBase16("e3b0c44298fc1c...")
  val preimage = SELF.R4[Coll[Byte]].get
  
  sigmaProp(sha256(preimage) == secretHash)
}

Threshold Payment โ€‹

scala
{
  val minPayment = 10000000000L  // 10 ERG
  val recipient = PK("9fRecipient...")
  
  val correctRecipient = OUTPUTS(0).propositionBytes == recipient.propBytes
  val sufficientAmount = OUTPUTS(0).value >= minPayment
  
  sigmaProp(correctRecipient && sufficientAmount)
}

Token Gating โ€‹

scala
{
  val requiredTokenId = fromBase16("03faf2cb...")
  val requiredAmount = 100L
  
  // Check if spender has required tokens
  val hasToken = INPUTS.exists { (box: Box) =>
    box.tokens.exists { (token: (Coll[Byte], Long)) =>
      token._1 == requiredTokenId && token._2 >= requiredAmount
    }
  }
  
  sigmaProp(hasToken)
}

Operators โ€‹

Boolean โ€‹

scala
&&  // AND
||  // OR
!   // NOT

Comparison โ€‹

scala
==  // Equal
!=  // Not equal
>   // Greater than
>=  // Greater or equal
<   // Less than
<=  // Less or equal

Arithmetic โ€‹

scala
+   // Addition
-   // Subtraction
*   // Multiplication
/   // Division
%   // Modulo

Type Annotations โ€‹

scala
{
  // Explicit types help clarity
  val amount: Long = 1000000L
  val data: Coll[Byte] = SELF.R4[Coll[Byte]].get
  val pk: SigmaProp = PK("9f...")
  
  sigmaProp(pk)
}

Using with Fleet SDK โ€‹

typescript
import { compile } from "@fleet-sdk/compiler";

const script = `
{
  val ownerPk = PK("9f4QF8AD1nQ3nJahQVkMj8hFSVVzVom77b52JU7EW71Zexg6N8v")
  sigmaProp(HEIGHT > 1500000L && ownerPk)
}
`;

// Compile to ErgoTree
const compiled = compile(script);

console.log(compiled.ergoTree);  // Binary representation
console.log(compiled.address);    // P2S address for deposits

Best Practices โ€‹

1. Keep It Simple โ€‹

scala
// โœ… Good: Clear and simple
sigmaProp(HEIGHT > 1000000L)

// โŒ Avoid: Overly complex in single expression
sigmaProp(HEIGHT > 1000000L && INPUTS.size > 0 && OUTPUTS.size < 5 && ...)

2. Use Named Variables โ€‹

scala
// โœ… Good: Self-documenting
{
  val unlockHeight = 1500000L
  val owner = PK("9f...")
  sigmaProp(HEIGHT >= unlockHeight && owner)
}

// โŒ Avoid: Magic numbers
sigmaProp(HEIGHT >= 1500000L && PK("9f..."))

3. Handle Edge Cases โ€‹

scala
{
  // Check if register exists before accessing
  val hasR4 = SELF.R4[Long].isDefined
  val r4Value = if (hasR4) SELF.R4[Long].get else 0L
  
  sigmaProp(r4Value > 100L)
}

Common Errors โ€‹

ErrorCauseFix
"Type mismatch"Wrong type usedCheck variable types
"Undefined variable"Typo or missing valDeclare with val
"sigmaProp expected"Missing wrapperWrap with sigmaProp()
"Cannot access R4"Register not setUse .isDefined check

Next Steps โ€‹

Released under the MIT License.