In the last few days I made significant progress in the Argent compiler, and I believe some of the features that evolved may be important to this spec discussion.
It boils down to a term I call open ICC.
ICC stands for Inter-Covenant Communication, or more specifically the idea of two or more covenants being co-spent in the same transaction in some interlinked way. Open ICC means that the interacting contracts do not have to fully know each other at compile time.
Starting from the simple case
In the context of assets, the most basic form of interaction is an atomic swap between two asset UTXOs:
Alice's asset A + Bob's asset B
->
asset A to Bob + asset B to Alice
Both users sign the complete transaction, so each signer authorizes the full transition after inspecting the transfer amounts on both assets.
This already gives atomicity, but the relation is mostly mediated by signatures. Neither asset covenant needs to understand the other transition.
A more interesting ICC pattern is asymmetric:
Asset A requires Controller C to be co-spent.
Controller C observes and validates A's transition.
A treats the presence of C as its authorization condition. C is compiled against A, understands its exact state layout and surrounding contract template, and validates the relevant A inputs and outputs.
I call this closed ICC, because C is compiled against a specific A implementation.
Open ICC
Open, or dynamic, ICC targets the case where A is authorized by C, but C was not compiled against the concrete A implementation.
It currently relies on two main features.
First, C can observe an actor following some fixed state layout, call it Capsule, without knowing the concrete contract at compile time.
state Capsule {
...
}
state ControllerState {
byte[32] agent_covid;
actor<Capsule> agent_type;
...
}
actor Controller owns ControllerState {
entry advance()
observes remote by self.agent_covid {
inputs { agent: self.agent_type; }
outputs { agent: self.agent_type; }
}
emits { controller: Controller; } {
Capsule next_state = ...;
require remote.outputs become {
agent <- self.agent_type(next_state);
};
...
}
}
The controller receives an actor<Capsule> representing the concrete contract template.
Although it was not compiled against that contract, it can:
-
decode the foreign input using the known
Capsulelayout -
verify that the non-state parts match the supplied contract template hash
-
inspect and constrain the state transition
-
require an output using the same concrete contract plus the updated state
This already gives dynamic onchain composition, but it limits every compatible implementation to exactly the same state layout. That is highly limiting and leads to the second feature.
Virtual state
A capsule can contain an opaque virtual field:
state Capsule {
...
virtual memory;
...
}
Each concrete implementation can expand it into a different typed object:
state AgentMemory {
int hunger;
...
}
state AgentState expands Capsule {
memory: AgentMemory;
}
actor Agent owns AgentState {
entry step(...) emits { agent: Agent; } {
AgentState next_state = {
...
memory: AgentMemory {
hunger: memory.hunger + 1,
...
},
...
};
become agent <- Agent(next_state);
}
}
The base Capsule contains only a digest for memory.
The open observer can inspect and constrain the shared Capsule fields while allowing the virtual field to change without understanding its contents. The concrete Agent contract receives the AgentMemory preimage, verifies it against the digest, exposes its typed fields, and repacks the updated object into the next digest.
So both contracts constrain the same output:
Controller:
understands and constrains the shared Capsule state
Agent:
understands and constrains its expanded Memory state
Neither has to understand the other’s internal logic.
A working example of this model is here:
The generated contracts are ordinary Silverscript. Argent also packages the contracts, state layouts, entrypoints, template commitments, routing information, hidden witness recipes and attached-app fingerprints into portable artifacts consumed by argent-runtime.
Back to KCC20
In the context of KCC20, this means we may now have tools for defining onchain composability standards, rather than only offchain wallet and indexer standards.
For example, the stable KCC20 state could contain the common asset fields plus a virtual extension:
state KCC20State {
byte[32] owner_identifier;
byte identifier_type;
int amount;
virtual extension;
}
The exact fields and types are of course part of the spec discussion.
A concrete asset could then expand the extension into any state it needs:
state AssetMemory {
covid controller_id;
bool is_minter;
...
}
state AssetState expands KCC20State {
extension: AssetMemory;
}
The common KCC20 fields remain directly visible onchain, while controller logic, minting state, borrower policy or any other implementation-specific state lives behind the extension digest.
Any asset implementing the standard is still usable by another covenant as:
actor<KCC20State>
The composing covenant understands the standard asset state, while the asset implementation retains arbitrary internal state and independently validates it.
A DEX as an example of dynamic composition
A DEX is a useful example because the composing covenant can be written once, while the concrete assets it interacts with may only be chosen much later.
A pool actor could open-observe two KCC20 transitions belonging to two different asset covenants in the same transaction. It would compile only against KCC20State, not against the concrete implementation of either asset.
For example:
trader asset A
pool reserve A
pool reserve B
pool A/B actor
|
v
updated reserve A
updated reserve B
asset B to trader
updated pool actor
The pool could verify onchain that:
reserve A increased by dx
reserve B decreased by dy
the configured ratio/invariant holds
both reserves remain owned by the pool
At the same time:
-
asset A independently validates its own authorization, conservation and extension-state transition
-
asset B independently validates its own authorization, conservation and extension-state transition
-
the pool does not need to understand either asset’s concrete controller or policy state
This is different from an offchain SDK merely knowing how to construct transactions for several asset implementations. A DEX compiled against KCC20State could directly inspect and constrain future compatible asset implementations onchain.
For actual reserve ownership, one possible design is a DEX root controlling a family of pair-specific pool covenants:
DEX root / registry
/ \
Pool A/B covid Pool A/C covid
/ \ / \
A reserve B reserve A reserve C reserve
The reserve UTXOs are owned by the pair-specific covenant id, giving narrowly scoped co-spend authorization. The root can register or control the family of pools without directly being the broad owner of every reserve.
There are probably other DEX designs involving another level of indirection or more elaborate authorization chaining. That part can remain a problem for the DEX designers. The important point for this discussion is that the KCC20 state itself can be made generically inspectable and composable onchain.
Argent is still experimental, but this point is relevant to the KCC20 state design now.
If the first standard adopts a closed state layout, adding implementation-owned extension state later may require an incompatible format after wallets, indexers and assets have already converged around the original one.
KCC20 does not need to depend on Argent. But reserving and defining this extension point early may be essential for future onchain composability.