self_refs::Holder, contract refs) make things ergonomic,
but the generic path through GvmComponentId is more flexible for advanced
use cases — anywhere you can’t statically pin the recipient’s type at the
caller, or where you want to interact with components defined outside your
own crate.
Understanding the issue
Taketransfer() as it stands today — recipient is the typed
self_refs::Holder:
The change — bare GvmComponentId
Replace the typed parameter with a GvmComponentId and reach the holder via
the contract client:
increment() body in my-token) no longer wraps the
id in Holder::new(...):
Why this matters
- Cross-contract composition —
GvmComponentIdis the universal address the network speaks. Any component on any contract can be a target. - Reduced coupling — the caller doesn’t have to import the recipient’s type at compile time. Useful for plugin-style architectures.
- Symmetric with the runtime — what you’d construct from a CLI/SDK call
is exactly the same
GvmComponentId, so call shapes match across the client/contract boundary.
Exercise
Change the code inmy-token so that it doesn’t reference Holder at all,
and the same test as in
Borrowed Code · Make It Run still
passes — incrementer balance drops by 1, receiver’s grows by 1, counter is 1.
Checkpoint — the test passes without any
Holder::new(...) calls or
Holder-typed parameters anywhere in my_token_contracts.rs or its tests.What’s next
Composable Errors
Conditioning a follow-up call on the success of a prior one —
.await
vs .spawn().
