glyphdocs
Protocol / Relay

Use Relay v2

Prepare, register, subscribe, and launch through the official Relay v2 session API.

Use Relay v2

Relay v2 is a short-lived result bridge for dApps that do not want to receive a direct wallet callback. The dApp creates a session, registers it with the official relay, subscribes with a read URL, and gives the wallet only the callback URL. This path does not require a callback page or public callback route in the dApp. For the larger mental model, see Understand Glyph Connect and Relay v2.

The lifecycle

1. Create the request

Build the request before preparing delivery. The nonce and expiry become part of the request binding.

const request = createSignMessageRequest({
  type: "sign_message",
  dapp: { origin: "https://example.app" },
  message: "Sign in to Example",
});

2. Prepare and register the session

import { prepareRelaySession } from "@glyph-oss/connect";

const session = await prepareRelaySession();

prepareRelaySession() generates distinct opaque session, callback, and read values, then sends the callback and read values to POST /v2/register/:session. A successful registration returns a prepared session with:

  • callbackUrl for the wallet's POST callback
  • streamUrl for the dApp's SSE subscription
  • resultUrl for dApp polling
  • the session values used to construct those URLs

Registration must happen before launching the wallet. The official relay URL is fixed to https://relay.glyphq.org.

3. Subscribe before launch

Create the envelope with session.callbackUrl, then subscribe with the prepared session:

import { k12, verify } from "@qubic.org/crypto";
import {
  createEnvelope,
  subscribeViaRelayV2,
} from "@glyph-oss/connect";

const envelope = createEnvelope(request, {
  callback: session.callbackUrl,
});

const resultPromise = subscribeViaRelayV2(request, session, {
  verification: {
    requireSigned: true,
    expectedRequestHash: envelope.request_hash,
    expectedNetwork: envelope.network,
    expectedDappOrigin: request.dapp.origin,
    expectedExp: request.exp ?? null,
    expectedCallbackUrl: session.callbackUrl,
    verifySignature: ({ algorithm, payload, signature, publicKey }) => {
      if (algorithm !== "qubic-schnorrq-sha256") return false;
      return verify(k12(payload, 32), signature, publicKey);
    },
  },
});

The subscription uses session.streamUrl, which is authorized for reading. The wallet never receives that URL.

@qubic.org/crypto verifies the signed callback in the dApp. Glyph Wallet signs the callback after approval, and the dApp must not become a signer by holding private key material in browser code.

4. Launch and await

import { launchGlyphRequest } from "@glyph-oss/connect";

launchGlyphRequest(envelope);
const result = await resultPromise;

The relay emits a result SSE event when the wallet posts a JSON callback. The SDK parses and verifies it before resolving. It rejects on an invalid HTTP response, missing body, stream timeout, timeout event, close event without a result, or callback verification error.

Keep authorities separate

  • Put callbackUrl only in the envelope sent to the wallet.
  • Keep streamUrl and resultUrl inside the dApp process.
  • Do not log or send session URLs to analytics or third parties.
  • Do not reuse a callback URL as a read URL.
  • Do not call subscribeViaRelayV2() until registration has succeeded.

The relay stores the first accepted result for up to ten minutes. The SSE wait is five minutes and a session supports at most five listeners. A second callback for the same session is rejected. Treat each session as short-lived and single-use for delivery, and create a fresh session for the next request.

Polling instead of SSE

The SDK exposes resultUrl for a read-only GET. Before a result arrives, Relay returns HTTP 404 with { "status": "pending" }. After delivery, it returns HTTP 200 with { "status": "ok", "result": ... }. Polling does not remove the need to verify a signed callback envelope.

On this page