Now that you know what’s being passed (resources, handles), this page covers the dotpath grammar that wires them together.A ref is a dotpath string wrapped in
{ "$ref": "…" }. Refs let a Call bind one of its input ports to a value produced elsewhere in the Flow: a declared input, a previous call’s output, or a runtime context value.
The grammar recognises three scopes and reserves a handful of prefixes to avoid ambiguity.
The three ref scopes
1. input.<name>
References a flow-level input declared in flow.inputs.
{ scope: "input", port: "amountIn" }.
2. context.<key>
References a runtime context value. Only two keys are currently recognised:
| Key | Meaning |
|---|---|
sender | The transaction signer’s address. |
executionAddress | The predicted execution proxy address where the compiled flow runs. |
{ scope: "context", key: "sender" }. Any other key under context.* is rejected.
3. <nodeId>.<port>
Any ref whose prefix is not input or context is treated as a reference to another call’s output port. The <nodeId> is the user-defined id you passed when you added the node (e.g. builder.lifi.swap('swap', …) produces refs of the form swap.<port>).
{ scope: "output", node: "swap", port: "amountOut" }.
Reserved prefixes
Three prefixes are reserved and cannot be used as node ids:input, context, and literal. Flow validation rejects a node whose id matches any of them.
literal is not a ref scope; it is reserved to keep the grammar unambiguous. Literal values are expressed through a separate mechanism, a LiteralBinding, which lives alongside refs in a node’s bind record:
How SDK handles become refs
Partner code usually does not construct refs directly. The SDK gives you handles (typed JavaScript values returned by the builder), and converts them to refs when it serialises calls. Handles come in three flavours:InputHandle. Returned bybuilder.inputs.<name>. Serialises toinput.<name>.ResourceInputHandle. A subtype ofInputHandlefor resource inputs, carrying the resource declaration.OutputHandle. Returned by an op call.builder.lifi.swap('swap', …).amountOutserialises toswap.amountOut(whereswapis the user-defined node id you passed as the first argument).
bind and the SDK produces the right $ref string. Context refs are exposed through builder.context:
builder.context.sender→{ $ref: "context.sender" }builder.context.executionAddress→{ $ref: "context.executionAddress" }
Raw refs (escape hatch)
When you need to reference an output of a call you created throughbuilder.untypedOp(...), or a path the typed API does not cover, use raw.ref<T>(path) to get a typed TypedRef accepted by a Bindable<T> slot:
raw.ref does no runtime validation; the caller is responsible for choosing the right type parameter.
Summary
- Three scopes exist:
input.<name>,context.<sender|executionAddress>,<nodeId>.<port>. <name>and<nodeId>are user-defined: you pick them when you author the flow.literalis a reserved prefix, not a ref scope. Literal values useLiteralBinding, not a ref.- SDK handles convert to refs automatically. You almost never write ref strings by hand.
- Use
raw.ref<T>(path)only when the typed API cannot express the ref you need.
Once a Flow is wired, you POST it to /compose. The next page, Execution Model, covers the pipeline.

