Skip to main content

Endpoints and Functions

Endpoints are externally callable entry points of a Coco logic. Functions are local helpers called from endpoints.

Endpoint Syntax​

endpoint [qualifier] Name(inputs) -> (outputs):
body

Example:

endpoint dynamic PurchaseTicket(quantity U64) -> (receipt_id String):
// implementation

Qualifiers​

Lifecycle Qualifiers​

QualifierPurpose
deployRuns once when logic is deployed (logic constructor)
(none)Standard endpoint, callable anytime

enlist is still a reserved word, but since v0.9.2 endpoint enlist doesn't compile on any PISA target. See Migrating enlist endpoints.

State Qualifiers​

QualifierAccessRequired when
pureNo state accessDefault — no state operations
staticRead-onlyUsing observe
dynamicRead & writeUsing mutate

The qualifier must match what the body actually does — exactly. Declaring too little or too much is a compile error:

function 'GetBalance' is declared as 'pure', but it requires state qualifier 'static'
function 'Transfer' is declared as 'static', but it requires state qualifier 'dynamic'

The requirement is the maximum over everything the body does: its own observe/mutate, the functions it calls, the asset methods it calls, and any cross-logic interface calls. deploy endpoints take no state qualifier — they are dynamic by definition.

endpoint pure Calculate(x U64) -> (result U64):
result = x * 2

endpoint static GetBalance() -> (balance U64):
observe balance <- Token.Sender.balance

endpoint dynamic Transfer(to Identifier, amount U64):
mutate bal <- Token.Sender.balance:
bal -= amount

Inputs and Outputs​

All parameters must be named for clarity:

// Named inputs and outputs
endpoint Transfer(
recipient Identifier,
amount U64
) -> (
success Bool,
new_balance U64
):
// ...
Why Named Parameters?

Looking at Transfer(recipient, amount) is clearer than Transfer(id, u64). You know what each value represents.


Functions​

Functions are local helpers that can only be called from within the same logic.

function Double(num U64) -> (result U64):
result = num * 2
Featureendpointfunction
Called fromBlockchain (external)Endpoints (internal)
PurposePublic interfaceReusable helper
Can use deployYesNo
Can call other endpointsNoN/A

Calling Functions​

endpoint Calculate():
// Full syntax: capture return value
memory x = (result) <- Double(num: 10)

// Shortcut: same variable name as parameter
memory num = 20
memory result = Double(num)

// Multiple return values
memory a, b = (x, y) <- GetPair()

function Double(num U64) -> (result U64):
result = num * 2

function GetPair() -> (x U64, y U64):
x, y = 3, 4

Since v0.9.2 the same capture syntax works on Environment.*, Invocation.*, Builtins.* and asset.* calls, as in ts = (timestamp) <- Environment.Timestamp(). See Named outputs.

Parameter Rules
  • Arguments are read-only — cannot modify input parameters
  • Return values are write-only — cannot read before assigning
function Invalid(a U64) -> (out U64):
a += 1 // ERROR: arguments are read-only
out = a
out += 1 // ERROR: return values are write-only

External Package Calls​

Use :: to call functions from other packages:

memory result = math::Sqrt(val: 16)

Deploy Endpoints​

deploy — Logic Initialization​

Runs once when the logic is first deployed. Required if you have state logic.

state logic:
total_supply U64

endpoint deploy Init(supply U64):
mutate supply -> Token.Logic.total_supply

Actor State Needs No Initialization​

Actor state has no constructor. An actor that has never been written reads every field as its zero value (0, "", false, or an empty collection), so the first mutate of an actor's field can start from that value directly.

Complete Example​

coco Token

state logic:
supply U64

state actor:
balance U64

// Called once at deployment
endpoint deploy SeedSupply():
mutate 1000 -> Token.Logic.supply

// Called anytime. A new actor's balance starts at 0
endpoint dynamic Claim():
mutate sup <- Token.Logic.supply:
sup -= 1
mutate bal <- Token.Sender.balance:
bal += 1
note
  • deploy can only be called once (at deployment)
  • If you have both state logic and state actor, only deploy is mandatory

Migrating enlist Endpoints​

MOI has no enlist interaction, so since v0.9.2 the compiler rejects endpoint enlist on every PISA target, legacy targets included:

invalid function: enlist endpoints are not supported

To port one:

  1. Usually, delete it. Actor state needs no initialization, so an enlist endpoint that only set a starting balance of zero did nothing.

  2. If it did real setup, make it dynamic. It can now be called any number of times, so guard it:

    state actor:
    registered Bool
    nickname String

    endpoint dynamic Register(nickname String):
    mutate done <- Token.Sender.registered:
    if done:
    throw "already registered"
    done = true
    mutate nickname -> Token.Sender.nickname
  3. Change the callers. In Cocolab and lab scripts, enlist Token.Register(nickname: "al") as alice becomes invoke Token.Register(nickname: "al") as alice. In inline tests, // < enlist TEST.Register(...) becomes // < invoke TEST.Register(...). invoke persists state just as enlist did.