Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/host/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
id="auth-button"
class="topbar-btn"
title="Login with Polkadot Mobile"
disabled
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
Expand Down
8 changes: 8 additions & 0 deletions apps/host/tests/e2e/helpers/extract-qr-payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ export async function extractQrPayload(
): Promise<string> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const embedded = await page
.locator(canvasSelector)
.getAttribute("data-qr-payload", { timeout: 250 })
.catch(() => null);
if (embedded?.startsWith("polkadotapp://")) {
return embedded;
}

const px = await page.evaluate((sel) => {
const canvas = document.querySelector(sel) as HTMLCanvasElement | null;
if (!canvas || canvas.width === 0) return null;
Expand Down
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 3 additions & 7 deletions packages/content/src/preimage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// lookup (convert hash to CID for P2P/IPFS retrieval).

import { blake2b } from "@noble/hashes/blake2.js";
import { fromHex, toHex } from "@dotli/shared/hex";
import { CID } from "multiformats/cid";
import { create } from "multiformats/hashes/digest";

Expand All @@ -19,18 +20,13 @@ const RAW_CID_CODEC = 0x55;
*/
export function computePreimageKey(data: Uint8Array): `0x${string}` {
const hash = blake2b(data, { dkLen: 32 });
return `0x${Array.from(hash, (b: number) => b.toString(16).padStart(2, "0")).join("")}`;
return toHex(hash);
}

/**
* Convert a 0x-prefixed Blake2b-256 hash hex to a CID v1 (raw codec, 0xb220 multihash).
*/
export function hashToCid(hashHex: string): CID {
const hex = hashHex.startsWith("0x") ? hashHex.slice(2) : hashHex;
const hashBytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hashBytes.length; i++) {
hashBytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
const digest = create(BLAKE2B_256_MULTIHASH_CODE, hashBytes);
const digest = create(BLAKE2B_256_MULTIHASH_CODE, fromHex(hashHex));
return CID.createV1(RAW_CID_CODEC, digest);
}
3 changes: 2 additions & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"vitest": "^4.1.8"
},
"dependencies": {
"@dotli/config": "workspace:*"
"@dotli/config": "workspace:*",
"@noble/hashes": "^2.2.0"
}
}
6 changes: 3 additions & 3 deletions packages/shared/src/archive-digest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// storage corruption and passive tampering, not an active same-origin
// adversary.

import { toHex } from "./hex";

/**
* Deterministic SHA-256 digest over a file map. For each file (in
* sorted-path order) the manifest holds SHA-256(path) ++ SHA-256(bytes):
Expand Down Expand Up @@ -38,7 +40,5 @@ export async function computeArchiveDigest(
manifest.set(fileHash, i * 64 + 32);
});
const digest = await crypto.subtle.digest("SHA-256", manifest);
return Array.from(new Uint8Array(digest), (b) =>
b.toString(16).padStart(2, "0"),
).join("");
return toHex(new Uint8Array(digest)).slice(2);
}
15 changes: 15 additions & 0 deletions packages/shared/src/hex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2026 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: AGPL-3.0-only

import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";

export type HexString = `0x${string}`;

export function toHex(bytes: Uint8Array): HexString {
return `0x${bytesToHex(bytes)}`;
}

export function fromHex(hex: string): Uint8Array {
const stripped = hex.startsWith("0x") ? hex.slice(2) : hex;
return hexToBytes(stripped);
}
7 changes: 3 additions & 4 deletions packages/truapi-debug/src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
// suitable for the debug panel. Handles Uint8Array (hex, truncated) and
// cycles. No DOM or SDK imports here, kept pure for easy testing.

import { toHex } from "@dotli/shared/hex";

const MAX_UINT8_PREVIEW_BYTES = 32;
const MAX_STRING_PREVIEW_CHARS = 200;

function hexOf(bytes: Uint8Array, max: number): string {
const preview = bytes.subarray(0, max);
const hex = Array.from(preview, (b) => b.toString(16).padStart(2, "0")).join(
"",
);
return `0x${hex}${bytes.length > max ? "…" : ""}`;
return `${toHex(preview)}${bytes.length > max ? "…" : ""}`;
}

function isUint8ArrayLike(v: unknown): v is Uint8Array {
Expand Down
30 changes: 14 additions & 16 deletions packages/truapi-debug/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,6 @@
color: #2dd4bf;
border-color: #0f766e;
}
.td-layer-attestation {
background: #1a121f;
color: #a78bfa;
border-color: #5b21b6;
}
.td-layer-session {
background: #040e20;
color: #7dd3fc;
border-color: #075985;
}

/* System row distinguisher — subtle left-border on rows */
.td-row.system {
Expand All @@ -90,7 +80,6 @@
--sw-accent: #2dd4bf;
}


.td-tooltip {
position: absolute;
z-index: 1000;
Expand All @@ -100,8 +89,8 @@
border: 1px solid #3a3a3a;
border-radius: 3px;
padding: 4px 8px;
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo,
Consolas, monospace;
font-family:
ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace;
font-size: 11px;
white-space: nowrap;
opacity: 0;
Expand All @@ -121,8 +110,8 @@
color: #d4d4d4;
border-top: 1px solid #1f1f1f;
z-index: 950;
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo,
Consolas, monospace;
font-family:
ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace;
font-size: 12px;
display: grid;
grid-template-rows: 5px 32px auto 1fr;
Expand Down Expand Up @@ -423,7 +412,7 @@
}
.td-row {
display: grid;
grid-template-columns: 72px 16px 90px 58px 1fr;
grid-template-columns: 72px 16px 90px 58px minmax(0, 1fr);
align-items: baseline;
gap: 8px;
padding: 3px 10px;
Expand All @@ -433,6 +422,12 @@
overflow: hidden;
text-overflow: ellipsis;
}
.td-row > * {
min-width: 0;
}
.td-row.system {
grid-template-columns: 72px max-content 90px minmax(0, 1fr);
}
.td-rid {
font-size: 10.5px;
font-weight: 600;
Expand Down Expand Up @@ -483,6 +478,9 @@
overflow: hidden;
text-overflow: ellipsis;
}
.td-row.system .td-tag-and-summary {
grid-column: auto;
}
.td-tag {
color: #e0e0e0;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/ui/src/styles/topbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,15 @@
border-color: rgba(255, 255, 255, 0.08);
color: #fff;
}
.topbar-btn:disabled {
cursor: default;
opacity: 0.45;
}
.topbar-btn:disabled:hover {
background: transparent;
border-color: transparent;
color: rgba(255, 255, 255, 0.65);
}
.topbar-btn:active {
background: rgba(255, 255, 255, 0.11);
}
Expand Down
7 changes: 4 additions & 3 deletions packages/ui/src/topbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { Identity } from "@novasamatech/host-papp";
import { log } from "@dotli/shared/log";
import { escapeHtml } from "@dotli/shared/html";
import { isMobileDevice } from "@dotli/shared/device";
import { toHex } from "@dotli/shared/hex";
import {
formatAppVersion,
getActiveAppManifest,
Expand Down Expand Up @@ -172,6 +173,7 @@ export function initTopBar(): void {

// Auth button: opens modal (logged out) or popover (logged in)
authButton.addEventListener("click", handleAuthButtonClick);
authButton.removeAttribute("disabled");

// Modal close button
modalClose.addEventListener("click", closeModal);
Expand Down Expand Up @@ -362,9 +364,7 @@ function renderLoggedIn(state: AuthState & { status: "authenticated" }): void {
username = fullName ?? liteName ?? "";
} else {
// Fallback to truncated account address
const id = Array.from(state.session.remoteAccount.accountId)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
const id = toHex(state.session.remoteAccount.accountId).slice(2);
username = `0x${id.slice(0, 6)}...${id.slice(-4)}`;
}
userPopoverUsername.textContent = username;
Expand All @@ -381,6 +381,7 @@ function renderPairing(payload: string): void {

// Render QR code (lazy-load qrcode lib, guard against stale appends)
const canvas = document.createElement("canvas");
canvas.dataset.qrPayload = payload;
const capturedPayload = payload;
void import("qrcode")
.then((QRCode) =>
Expand Down
Loading