# How to Resolve a Short Tailcat Connection Token to a Self-Contained Token

> Learn to resolve a short Tailcat connection token to a self-contained one using ConnBlob.Resolve. Enable offline connections by embedding relay details directly.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: how-to-guide
- Published: 2026-08-30

---

**Call `ConnBlob.Resolve` from the `tailscale/tailcat` client library to expand a short token containing only a DERP region ID into a self-contained token that embeds full relay details, enabling offline connections without network lookups.**

The `tailscale/tailcat` repository provides peer-to-peer networking tools that use compact **ConnBlob** tokens to establish connections. When you need to **resolve a short Tailcat connection token to a self-contained one**, the process involves fetching DERP map details and embedding them directly into the token string, eliminating the need for subsequent network round-trips.

## Understanding ConnBlob Token Types

Tailcat utilizes two distinct token formats encoded as **ConnBlob** strings. A **short token** contains only a reference to a DERP region ID, requiring the client to fetch the DERP map from the network to discover relay IP addresses. In contrast, a **self-contained token** embeds the complete DERP relay details—including node IP addresses and ports—directly within the CBOR-encoded payload. This self-contained format allows clients to establish connections offline without additional lookups, significantly improving connection latency.

## The Resolution Process in tailcat.go

The `Resolve` method in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 79–102) handles the conversion from short to self-contained tokens. According to the source code implementation, the method executes the following steps:

1. **Parse the token** – `ParseConnBlob` decodes the base64-encoded CBOR payload into a `ConnInfo` structure.
2. **Check for existing relay info** – If the `Region` slice is already populated, the token is already self-contained and is returned unchanged.
3. **Expand the region** – `ci.Expand` retrieves the DERP map via the DERP client and populates `ci.Region` with the full list of relay nodes for the referenced region.
4. **Trim the relay list** – To maintain brevity, only the first two relay nodes are retained (`r.Nodes = r.Nodes[:min(2, len(r.Nodes))]`).
5. **Re-encode** – The updated `ConnInfo` is serialized back into a new `ConnBlob` using `ci.ConnBlob()`, which prepends the `"tc"` prefix and base64-encodes the CBOR data.

The resulting token string is longer than the original but contains all necessary connection information baked directly into the string.

## Key Source Files and Implementation Details

The token resolution functionality spans several files in the `tailscale/tailcat` repository:

- **[`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go)** – Contains the core implementation, defining the `ConnBlob` type, `ConnInfo` structure, and the `Resolve` method that orchestrates the expansion process.
- **[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)** – Handles CBOR encoding and decoding of the wire format used by `ConnBlob` tokens, managing the binary serialization of relay metadata.
- **[`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go)** – Provides discovery utilities used when expanding short tokens to fetch DERP details from the network.
- **[`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go)** – CLI entry point that supports printing full-address tokens via the `-full-address` flag and can invoke the resolution logic directly.

## Practical Code Examples

The following example demonstrates resolving a short token on the client side:

```go
import (
    "context"
    "fmt"
    "tailscale.com/tailcat"
)

func main() {
    shortToken := tailcat.ConnBlob("tcAQAB...") // a short token (region ID only)

    // Resolve it to a self-contained token.
    resolved, err := shortToken.Resolve(context.Background())
    if err != nil {
        panic(err)
    }

    fmt.Println("Resolved token:", resolved)
    // Output: Resolved token: tcAQAB... (longer string with embedded DERP info)
}

```

Once resolved, use the self-contained token to initialize a client without requiring further network lookups:

```go
func startClient(resolvedToken tailcat.ConnBlob) error {
    // Parse the resolved token back into ConnInfo.
    ci, err := tailcat.ParseConnBlob(resolvedToken)
    if err != nil {
        return err
    }

    // Create a client that connects directly using the embedded relay data.
    client, err := tailcat.NewClient(ci)
    if err != nil {
        return err
    }
    return client.Connect()
}

```

## Summary

- Use `ConnBlob.Resolve` in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) to convert short tokens containing only region IDs into self-contained tokens with full relay details.
- Short tokens require active DERP map lookups, while self-contained tokens enable offline connection establishment.
- The resolution process automatically trims relay lists to the first two nodes to balance completeness with token size.
- The conversion leverages CBOR encoding via [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) and discovery utilities from [`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go).
- Self-contained tokens are fully backwards compatible and can be parsed by any standard Tailcat client using `ParseConnBlob`.

## Frequently Asked Questions

### What is the difference between a short and self-contained Tailcat token?

A **short token** contains only a DERP region identifier, requiring the client to fetch relay IP addresses from the network. A **self-contained token** embeds the complete DERP relay node details—including IP addresses and ports—directly within the base64-encoded CBOR payload, allowing immediate connection without additional lookups.

### How does the Resolve method handle tokens that are already self-contained?

According to the implementation in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) lines 79–102, the method first checks if the `Region` slice is already populated. If relay information is present, the method returns the original token unchanged, avoiding unnecessary network requests and re-encoding overhead.

### Why does the resolved token only include two relay nodes?

The resolution process intentionally trims the relay list to the first two nodes (`r.Nodes = r.Nodes[:min(2, len(r.Nodes))]`) to maintain a compact token size while preserving redundancy. This optimization ensures the self-contained token remains relatively short while still providing fallback connectivity options.

### Can resolved self-contained tokens be used with any Tailcat client?

Yes, once resolved, these tokens are fully compatible with standard Tailcat clients. Parsing the token with `ParseConnBlob` yields a `ConnInfo` structure containing all embedded relay data, allowing the client to establish connections via `NewClient` and `Connect` without requiring DERP resolution capabilities or network access for map fetching.