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
| Qualifier | Purpose |
|---|---|
deploy | Runs 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
| Qualifier | Access | Required when |
|---|---|---|
pure | No state access | Default — no state operations |
static | Read-only | Using observe |
dynamic | Read & write | Using 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
):
// ...
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
| Feature | endpoint | function |
|---|---|---|
| Called from | Blockchain (external) | Endpoints (internal) |
| Purpose | Public interface | Reusable helper |
Can use deploy | Yes | No |
| Can call other endpoints | No | N/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.
- 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
deploycan only be called once (at deployment)- If you have both
state logicandstate actor, onlydeployis 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:
-
Usually, delete it. Actor state needs no initialization, so an
enlistendpoint that only set a starting balance of zero did nothing. -
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 -
Change the callers. In Cocolab and lab scripts,
enlist Token.Register(nickname: "al") as alicebecomesinvoke Token.Register(nickname: "al") as alice. In inline tests,// < enlist TEST.Register(...)becomes// < invoke TEST.Register(...).invokepersists state just asenlistdid.