Interfaces
Interfaces enable your logic to access data and invoke endpoints on other (external) logics. You can read external state directly but must invoke endpoints to mutate it.
Defining an Interface
An interface has four optional sections:
interface TokenInterface:
state logic:
total_supply U256
owner Identifier
state actor:
balance U256
endpoint:
dynamic Transfer(to Identifier, amount U256)
static GetBalance(account Identifier) -> (balance U256)
asset:
asset Mint(amount U256) -> (new_supply U256)
| Section | Purpose |
|---|---|
state logic | Fields on external logic's state |
state actor | Fields on external actor's state |
endpoint | Regular endpoints to invoke |
asset | Asset endpoints to invoke |
Only define the fields and endpoints you need — you don't have to mirror the entire external logic.
Binding to a Logic
Bind an interface to a deployed logic using its identifier:
endpoint dynamic UseExternal(token_id Identifier):
memory token = TokenInterface(token_id)
// Read external state
observe supply <- token.Logic.total_supply
observe bal <- token.Sender.balance
// Call external endpoint
token.Transfer(to: recipient, amount: 100)
Specify Sender
By default, cross-logic calls are made as Sender. You can specify a different participant:
// Bind with specific sender
memory token = TokenInterface(token_id, participant_id)
token.Transfer(to: recipient, amount: 100) // Called as participant_id
This is useful for swaps where two participants exchange assets:
interface AssetInterface:
asset:
Transfer(to Identifier, amount U256)
endpoint dynamic Swap(
asset1 Identifier, user1 Identifier, amount1 U256,
asset2 Identifier, user2 Identifier, amount2 U256
):
// User1 sends to User2
memory a1 = AssetInterface(asset1, user1)
a1.Transfer(to: user2, amount: amount1)
// User2 sends to User1
memory a2 = AssetInterface(asset2, user2)
a2.Transfer(to: user1, amount: amount2)
Complete Example
External logic (answer.coco):
coco answer
state actor:
ActorAnswer U64
endpoint dynamic SetActorAnswer(actor_id Identifier, new_answer U64):
mutate new_answer -> answer.Actor(actor_id).ActorAnswer
Your logic (question.coco):
coco question
interface Answer:
state actor:
ActorAnswer U64
endpoint:
dynamic SetActorAnswer(actor_id Identifier, new_answer U64)
endpoint dynamic SetAnswer(answer_logic Identifier, value U64):
memory iface = Answer(answer_logic)
iface.SetActorAnswer(actor_id: Sender, new_answer: value)
endpoint static ReadAnswer(answer_logic Identifier) -> (value U64):
memory iface = Answer(answer_logic)
observe value <- iface.Sender.ActorAnswer
The same interface can be bound to different logics at runtime, as long as they implement the expected fields and endpoints.
Reading Complex External State
Use gather inside observe blocks to read complex objects from external state:
observe gg <- iface.Logic.guru:
memory fn []String
gather fn <- gg.friend_names
guru = Person{
name: gg.name,
age: gg.age,
friend_names: fn,
}
Capturing Endpoint Return Values
// Call endpoint with return capture
endpointResult = (balance) <- iface.GetBalance(account: Sender)
// Call endpoint without return (side effects only)
iface.Transfer(to: recipient, amount: 100)