First connection
Prepare Relay v2, launch a glyph://v2 request from user activation, and verify the signed result.
A Relay v2 session must be registered before it is included in a wallet request. Prepare it ahead of the connection action, then keep the launch itself synchronous inside the user's click or tap handler.
Prepare Relay before the action
The first registration can take a moment. Show a calm Preparing connection… state and provide a retry action. Do not make the user repeat a wallet approval just because Relay was not ready.
import {
prepareRelaySession,
type GlyphPreparedRelaySession,
} from "@glyph-oss/connect";
let preparedRelay: GlyphPreparedRelaySession | null = null;
let preparation: Promise<void> | null = null;
function prepareForConnection() {
if (preparation) return preparation;
showStatus("Preparing connection…");
preparation = prepareRelaySession()
.then((session) => {
preparedRelay = session;
showStatus("Ready");
})
.catch(() => {
showStatus("Connection setup is not ready yet. Try again.");
})
.finally(() => {
preparation = null;
});
return preparation;
}
// Run on page load, or from pointer/focus/touch intent handlers.
void prepareForConnection();If preparation fails, call prepareForConnection() again from the retry control. A click that arrives before readiness should start or repeat preparation, not wait for network I/O and then try to launch.
Launch from direct user activation
launchGlyphRequest() opens the glyph://v2/request URL through a synchronous link click. Do not await prepareRelaySession() in the launch handler. When a prepared session is available, create the envelope, start the Relay subscription, and launch without an earlier await:
import { k12, verify } from "@qubic.org/crypto";
import {
GLYPH_MAINNET,
createConnectRequest,
createEnvelope,
launchGlyphRequest,
subscribeViaRelayV2,
} from "@glyph-oss/connect";
const APP_ORIGIN = "https://app.example.org";
export async function onConnectClick() {
if (!preparedRelay) {
showStatus("Preparing connection…");
void prepareForConnection();
return;
}
const relay = preparedRelay;
preparedRelay = null; // use a prepared session once
const request = createConnectRequest({
type: "connect",
dapp: { name: "Example app", origin: APP_ORIGIN },
permissions: ["transfer", "sign_message"],
});
const envelope = createEnvelope(request, {
callback: relay.callbackUrl,
network: GLYPH_MAINNET, // explicit { id: "qubic:mainnet" }
});
const resultPromise = subscribeViaRelayV2(request, relay, {
verification: {
requireSigned: true,
expectedRequestHash: envelope.request_hash,
expectedNetwork: envelope.network,
expectedDappOrigin: request.dapp.origin,
expectedExp: request.exp ?? null,
expectedCallbackUrl: relay.callbackUrl,
verifySignature({ algorithm, payload, signature, publicKey }) {
if (algorithm !== "qubic-schnorrq-sha256") return false;
return verify(k12(payload, 32), signature, publicKey);
},
},
onStatus(status) {
if (status.state === "awaiting_approval") {
showStatus("Continue in Glyph Wallet");
}
},
});
// Keep this call in the direct user-activation path.
launchGlyphRequest(envelope);
try {
const result = await resultPromise;
showResult(result);
} catch {
showStatus("The connection did not complete. Try again.");
void prepareForConnection();
}
}The example uses the verified public API from @qubic.org/crypto@1.0.0: hash the canonical signed-payload bytes with k12(payload, 32), then pass that digest, the decoded signature, and the decoded public key to verify. This is dApp-side verification. Glyph Wallet performs signing after the user approves the request, so the dApp does not need a seed or private key.
If your app maintains an allowlist of wallet callback public keys, also pass it as trustedPublicKeys.
The SDK checks the signed glyph-connect-callback-envelope/2 shape, result hash, nonce, request type, request hash, network, dApp origin, expiry, callback binding, and canonical signed payload before calling your verifier. Keep requireSigned: true and all expected bindings for Relay results.
What the Relay session contains
prepareRelaySession() registers the session with the official Relay v2 service before launch. The wallet receives relay.callbackUrl, which uses the write-only callback capability. subscribeViaRelayV2() reads from the separate read capability held by the dApp. Pass the prepared session to the SDK rather than constructing Relay URLs yourself.
Next step
Test the flow locally and deploy it, or inspect the complete starter-dapp implementation.
