๐ 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:
| Variable | Type | Description |
|---|---|---|
HEIGHT | Int | Current blockchain height |
SELF | Box | The box being spent |
INPUTS | Coll[Box] | All input boxes |
OUTPUTS | Coll[Box] | All output boxes |
CONTEXT | Context | Full 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
! // NOTComparison โ
scala
== // Equal
!= // Not equal
> // Greater than
>= // Greater or equal
< // Less than
<= // Less or equalArithmetic โ
scala
+ // Addition
- // Subtraction
* // Multiplication
/ // Division
% // ModuloType 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 depositsBest 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 โ
| Error | Cause | Fix |
|---|---|---|
| "Type mismatch" | Wrong type used | Check variable types |
| "Undefined variable" | Typo or missing val | Declare with val |
| "sigmaProp expected" | Missing wrapper | Wrap with sigmaProp() |
| "Cannot access R4" | Register not set | Use .isDefined check |
Next Steps โ
- Smart Contracts Tutorial โ - Build contracts
- Contract Examples โ - See working code
- Multi-Sig Guide โ - Team wallets