unionlabs/union · error · Error

No return value from channel_balance

Error message

No return value from channel_balance

What it means

channel_balance reads a Move function through a dev-inspect wrapper: readContract builds a moveCall on package 0x835e…779 and executes client.devInspectTransactionBlock, then the caller destructures result[0].returnValues[0] as BCS u256 bytes. If the simulation produces no return values — typically because the Move call aborted (e.g. no channel exists for the passed channel id) or the result shape is unexpected — this guard throws. Note the throw happens after readContract's own retry/timeout pipeline, inside Effect.gen, so it surfaces as a defect rather than SuiReadContractError.

Source

Thrown at ts-sdk/src/sui/channel-balance.ts:45

      tx.pure.u32(config.channelId),
      tx.pure.u256(path),
      tx.pure("vector<u8>", hexToBytes(token)),
    ]

    yield* Effect.log("Getting channel_balance for token:", token)

    const result = yield* readContract(
      client,
      "0x835e6a7d0e415c0f1791ae61241f59e1dd9d669d59369cd056f02b3275f68779",
      contract_address,
      module_id,
      function_name,
      [],
      function_arguments,
      tx,
    )
    if (!result || result.length === 0 || !result[0].returnValues || !result[0].returnValues[0]) {
      throw new Error("No return value from channel_balance")
    }
    const [bytesArray] = result[0].returnValues[0] as [number[], string]
    const data = new Uint8Array(bytesArray)
    const decoded = bcs.U256.parse(data)

    return decoded
  })

View on GitHub (pinned to 031785bb6d)

Solutions

  1. Verify the channel exists on the queried network and that channel_id/arguments are correct
  2. Inspect the dev-inspect result before destructuring: aborted calls usually carry a failure status/error you can surface instead of the generic message
  3. Catch the defect at the Effect boundary (Effect.catchDefect) and fall back to event data or fail with context

Example fix

// before
if (!result || result.length === 0 || !result[0].returnValues || !result[0].returnValues[0]) {
  throw new Error("No return value from channel_balance")
}

// after
const r = result?.[0]
const status = (r as { status?: { status?: string; error?: string } })?.status
if (status?.status === "failure") {
  throw new Error(`channel_balance aborted: ${status.error ?? "unknown Move abort"}`)
}
if (!r?.returnValues?.[0]) {
  throw new Error("No return value from channel_balance")
}
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm the channel object exists before asking for its balance
const exists = await client.getObject({ id: channelId })
if (!exists.data) {
  throw new Error(`Channel ${channelId} does not exist on this network`)
}

Type guard

const hasReturnValue = (
  r: { returnValues?: unknown[][] } | undefined,
): r is { returnValues: [unknown[], ...unknown[][]] } =>
  Array.isArray(r?.returnValues) && r.returnValues.length > 0 && Array.isArray(r.returnValues[0])

Try / catch

import { Effect, Exit } from "effect"

// the throw happens inside Effect.gen → defect, not SuiReadContractError
const exit = await Effect.runPromiseExit(
  program.pipe(Effect.catchDefect(e => Effect.fail(e))),
)
if (Exit.isFailure(exit)) {
  // inspect dev-inspect status/args; verify channel_id and encoding
}

Prevention

When it happens

Trigger: Passing a channel_id for which no channel object exists (channel not opened yet); wrong contract_address/module_id for the network; function_arguments mis-encoded so the Move entry point aborts during simulation.

Common situations: Querying the balance of a channel that has not been created yet; network mismatch between the SuiClient endpoint and the hardcoded package address; refactoring that changed argument order of the Move function.

Related errors


AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16). Data as JSON: /api/errors/e8c0e6307fd8fe98. Report an issue: GitHub.