strongly typed high-level functional blockchain language with built-in oracles, state channels and human-readable names (AENS)
REPOSITORYLearn More

Why Choose Sophia?

Sophia offers a functional programming paradigm that brings precision and security to smart contracts, ensuring developers write reliable code. With static typing and built-in security features, Sophia is the ideal language for building scalable and secure blockchain applications.

FUNCTIONAL PROGRAMMING PARADIGM

Inspired by languages like OCaml and Haskell, Sophia ensures code elegance and type safety. This functional programming approach makes it easier to reason about contract behavior, enhancing security and reducing bugs.

Strong Static Typing and Security

Sophia’s static typing catches errors at compile-time, minimizing the risk of runtime failures. It comes with built-in mechanisms to guard against common vulnerabilities, making your contracts more secure by default.

oracles, names, state channels

Sophia natively supports oracles for interacting with real-world data, state channels for off-chain performance enhancements, and the æternity Naming System (AENS) for easy-to-use, human-readable names and addresses.

Getting started

To get started, install the æternity node and compiler tools to begin writing and deploying smart contracts on the æternity blockchain:

Install the VSCode extension for enhanced development experience with syntax highlighting:

Try Sophia Without Setup – In ÆStudio

If you want to try Sophia without installing any tools, head over to ÆStudio, the web-based IDE for writing and testing smart contracts in real time.

Tutorials

Explore tutorials designed to guide you through the core concepts and practical uses of Sophia.

AEX-9 Fungible Token

We start by defining the contract and its state. This creates a fungible token following Aeternity’s AEX-9 standard, which is similar to the known ERC-20 standard on Ethereum.
Defining the contract states and events
@compiler >= 5
include "Option.aes"
include "String.aes"

contract FungibleToken =
  // Define state of type record encapsulating the contract's mutable state
  record state =
    { owner        : address      // the smart contract's owner address
    , total_supply : int          // total token supply
    , balances     : balances     // balances for each account
    , meta_info    : meta_info }  // token meta info (name, symbol, decimals)

  // This is the meta-information record type
  record meta_info =
    { name     : string
    , symbol   : string
    , decimals : int }

  // This is a type alias for the balances map
  type balances = map(address, int)

  // Declaration and structure of datatype event
  // and events that will be emitted on changes
  datatype event = Transfer(address, address, int)
Next, we define the init() entrypoint and the main functions, including get_balance() to retrieve an account’s balance and transfer() to move funds between accounts.
Initializing the contract with entrypoints
  entrypoint init(name: string, decimals : int, symbol : string, initial_balance : option(int)) =
    require(String.length(name) >= 1, "STRING_TOO_SHORT_NAME")
    require(String.length(symbol) >= 1, "STRING_TOO_SHORT_SYMBOL")
    require_non_negative_value(decimals)

    // If negative initial owner balance is passed, abort the execution
    let initial_supply = Option.default(0, initial_balance)
    require_non_negative_value(initial_supply)

    let owner = Call.caller
    { owner        = owner,
      total_supply = initial_supply,
      balances     = Option.match({}, (balance) => {[owner] = balance}, initial_balance),
      meta_info    = { name = name, symbol = symbol, decimals = decimals } }

  // Get balance for owner's address, returns option(int) or None
  entrypoint get_balance(account: address) : option(int) =
    Map.lookup(account, state.balances)

  // Transfer `value` from `Call.caller` to `to_account` account
  stateful entrypoint transfer(to_account: address, value: int) =
    internal_transfer(Call.caller, to_account, value)

Summary

In this example we created AEX 9 Fungible Token contract in Sophia, which can be used to create and manage fungible tokens on the Aeternity blockchain.

The contract includes functions for transferring tokens between accounts, checking account balances, and retrieving metadata about the token. The contract also defines and enforces example rules to ensure that transfers are valid and that account balances are maintained correctly.

THIS CONTRACT IS NOT SECURITY AUDITED!
DO NEVER USE THIS WITHOUT SECURITY AUDIT FIRST!

What’s next?

Curious about having a digital pet hamster? Read our “Crypto Hamster” tutorial to learn how to manage and interact with a collection of digital hamsters:

Explore more examples and complete tutorials:

Last, we define some internal functions like require_owner(), require_non_negative_value(), and require_balance() enforce contract rules, while internal_transfer() handles balance transfers. Getters provide access to metadata, total supply, owner address, and balances.
Internal functions and getters
  function require_owner() =
    require(Call.caller == state.owner, "ONLY_OWNER_CALL_ALLOWED")

  function require_non_negative_value(value : int) =
    require(value >= 0, "NON_NEGATIVE_VALUE_REQUIRED")

  function require_balance(account : address, value : int) =
    switch( get_balance(account))
      Some(balance) =>
        require(balance >= value, "ACCOUNT_INSUFFICIENT_BALANCE")
      None => abort("BALANCE_ACCOUNT_NOT_EXISTENT")

  stateful function internal_transfer(from_account: address, to_account: address, value: int) =
    require_non_negative_value(value)
    require_balance(from_account, value)
    put(state{ balances[from_account] @ b = b - value })
    put(state{ balances[to_account = 0] @ b = b + value })
    Chain.event(Transfer(from_account, to_account, value))

  // Getters
  entrypoint get_meta_info() : meta_info =
    state.meta_info
  entrypoint get_total_supply() : int =
    state.total_supply
  entrypoint get_owner_address() : address =
    state.owner
  entrypoint get_balances() : balances =
    state.balances

CHANGELOG — v8.0.0

— Added
Bitwise operations for integers: band, bor, bxor, bnot, << and >>.
Int.mulmod – combined builtin operation for multiplication and modulus.
Crypto.poseidon – a ZK/SNARK-friendly hash function (over the BLS12-381 scalar field).
Address.to_bytes – convert an address to its binary representation (for hashing, etc.).
(…)
— Changed
Crypto.verify_sig is changed to have msg : bytes(). I.e. the
signed data can be of any length (used to be limited to bytes(32)/hash).
System aliases are handled explicitly when converting to a Sophia value, this is only
observable for signature where a value of type signature is now represented as a
(…)