qTeVerse — Developer Integration Guide
DEVELOPER RELEASE

Build on qTeVerse

Everything you need to create a new marketplace application on the qTe ecosystem. Use the shared library, plug into AT Protocol, and ship on four platforms from a single codebase.

⚡ Quick Start: Build a qTeVerse App in 5 Steps

1 Create MAUI App .NET 10 multi-TFM dotnet new maui 2 Reference qTe Add project reference to qTe.vbproj 3 Register Services DI: Auth, Repo, Crypto Messaging, Directory 4 Define Models FromRecord / ToRecord Custom lexicon NSIDs 5 Build Pages XAML + code-behind DI constructor inject

📁 Step 1: Project Setup

Create a .NET MAUI app targeting .NET 10 with multi-platform TFMs. Use hardcoded package versions for compatibility.

Required .csproj Configuration

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst;net10.0-windows10.0.19041.0</TargetFrameworks>
    <OutputType>Exe</OutputType>
    <UseMaui>true</UseMaui>
    <SingleProject>true</SingleProject>
    <MauiXamlInflator>SourceGen</MauiXamlInflator>  <!-- Required for MAUI 10 -->
  </PropertyGroup>

  <ItemGroup>
    <!-- Pin versions explicitly (do NOT use $(MauiVersion)) -->
    <PackageReference Include="Microsoft.Maui.Controls" Version="10.0.51" />
    <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="10.0.51" />
    <PackageReference Include="CommunityToolkit.Maui" Version="14.0.1" />
  </ItemGroup>

  <ItemGroup>
    <!-- Reference the shared qTe library -->
    <ProjectReference Include="..\qTe\qTe.vbproj" />
  </ItemGroup>
</Project>
⚠ Important: Always use MauiXamlInflator=SourceGen. This is required for MAUI 10's XAML source generator. Without it, XAML bindings may silently fail. Also, always use element syntax for StrokeShape in XAML (e.g., <Border.StrokeShape><RoundRectangle CornerRadius="12"/></Border.StrokeShape>).

🔧 Step 2: DI Service Registration

Register the qTe marketplace service stack in your MauiProgram.cs. Order matters — each service depends on the ones before it.

DEPENDENCY INJECTION CHAIN (registration order) HttpClient Singleton MartXrpcClient needs: HttpClient MartAuthService needs: XrpcClient MartRepoService needs: Xrpc, Auth MartCryptoService Singleton (no deps) MartMessagingService needs: Repo, Auth, Crypto MartDirectoryService needs: Repo YourAppService needs: Auth, Repo, Msg, etc. Dashed green box = YOUR custom service. Solid boxes = qTe shared library services.

MauiProgram.cs Template

using CommunityToolkit.Maui;

namespace YourApp;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit();

        // 1. Transport
        builder.Services.AddSingleton<HttpClient>();
        builder.Services.AddSingleton<MartXrpcClient>();

        // 2. Core services (order matters!)
        builder.Services.AddSingleton<MartAuthService>();
        builder.Services.AddSingleton<MartRepoService>();
        builder.Services.AddSingleton<MartCryptoService>();
        builder.Services.AddSingleton<MartMessagingService>();
        builder.Services.AddSingleton<MartDirectoryService>();

        // 3. Your app-specific services
        builder.Services.AddSingleton<YourRepoService>();
        builder.Services.AddSingleton<YourAppService>();

        // 4. Pages
        builder.Services.AddTransient<LoginPage>();
        builder.Services.AddTransient<ExplorePage>();
        // ... register all pages

        return builder.Build();
    }
}
🚨 Critical: MartMessagingService requires MartCryptoService in its constructor. If you forget to register MartCryptoService, the app will crash at runtime when resolving MartMessagingService. This is the #1 DI registration bug.

📐 Step 3: Model Pattern

Every model follows the FromRecord / ToRecord pattern for AT Protocol serialization.

Model Template (C#)

using System.Text.Json;

namespace YourApp.Models;

public sealed class Widget
{
    public string Title { get; set; } = "";
    public string OwnerDid { get; set; } = "";
    public string CreatedAt { get; set; } = "";
    public string Rkey { get; set; } = "";
    public string Uri { get; set; } = "";

    public static Widget FromRecord(JsonElement v)
    {
        var w = new Widget();
        if (v.TryGetProperty("title", out var p))
            w.Title = p.GetString() ?? "";
        if (v.TryGetProperty("ownerDid", out p))
            w.OwnerDid = p.GetString() ?? "";
        if (v.TryGetProperty("createdAt", out p))
            w.CreatedAt = p.GetString() ?? "";
        return w;
    }

    public Dictionary<string, object> ToRecord() =>
        new()
        {
            ["$type"] = YourLexicon.Widget,
            ["title"] = Title,
            ["ownerDid"] = OwnerDid,
            ["createdAt"] = CreatedAt
        };
}

Lexicon Constants

namespace YourApp.Models;

/// Define your custom NSIDs
public static class YourLexicon
{
    public const string Widget =
        "com.qte.yourapp.widget";
    public const string Order =
        "com.qte.yourapp.order";
    public const string Review =
        "com.qte.yourapp.review";
}

Naming Convention

ScopeNamespace
Shared marketplacecom.qte.marketplace.*
Your app (public)com.qte.yourapp.*
Private storageapp.qte.storage.*

💾 Step 4: Repository Service Pattern

Wrap MartRepoService CRUD operations with your app's models. Every qTeVerse app uses this exact pattern.

public class YourRepoService
{
    private readonly MartRepoService _repo;
    private readonly MartAuthService _auth;

    public YourRepoService(MartRepoService repo, MartAuthService auth)
    {
        _repo = repo;
        _auth = auth;
    }

    /// Create a new widget record in the user's AT Protocol repo
    public async Task<MartResult<MartRecord>> CreateWidgetAsync(Widget widget)
    {
        widget.CreatedAt = DateTime.UtcNow.ToString("O");
        return await _repo.CreateRecordAsync(YourLexicon.Widget, widget.ToRecord());
    }

    /// List all widgets with cursor-based pagination
    public async Task<(List<Widget> Items, string? Cursor)> ListWidgetsAsync(string? cursor = null)
    {
        var result = await _repo.ListRecordsAsync(YourLexicon.Widget, 50, cursor);
        if (!result.Success) return (new(), null);

        var items = new List<Widget>();
        string? nextCursor = null;

        if (result.Value.TryGetProperty("records", out var records))
        {
            foreach (var rec in records.EnumerateArray())
            {
                if (rec.TryGetProperty("value", out var val))
                {
                    var w = Widget.FromRecord(val);
                    if (rec.TryGetProperty("uri", out var u))
                    {
                        w.Uri = u.GetString() ?? "";
                        w.Rkey = w.Uri.Split('/').LastOrDefault() ?? "";
                    }
                    items.Add(w);
                }
            }
        }
        if (result.Value.TryGetProperty("cursor", out var c))
            nextCursor = c.GetString();

        return (items, nextCursor);
    }

    /// Delete a widget by its record key
    public async Task<MartResult<bool>> DeleteWidgetAsync(string rkey)
        => await _repo.DeleteRecordAsync(YourLexicon.Widget, rkey);
}

📱 Step 5: Page Pattern

Pages use code-behind with constructor injection. No ViewModels — keep it simple. Use DisplayAlertAsync (not the deprecated DisplayAlert).

XAML Template

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="YourApp.Pages.ExplorePage"
    Title="Explore"
    BackgroundColor="{StaticResource PageBg}">

  <CollectionView x:Name="WidgetList"
    SelectionMode="Single"
    SelectionChanged="OnWidgetSelected">
    <CollectionView.ItemTemplate>
      <DataTemplate>
        <Border Padding="12" Margin="8">
          <Border.StrokeShape>
            <RoundRectangle CornerRadius="12"/>
          </Border.StrokeShape>
          <Label Text="{Binding Title}"/>
        </Border>
      </DataTemplate>
    </CollectionView.ItemTemplate>
  </CollectionView>
</ContentPage>

Code-Behind Template

namespace YourApp.Pages;

public partial class ExplorePage : ContentPage
{
    private readonly YourAppService _svc;

    public ExplorePage(YourAppService svc)
    {
        InitializeComponent();
        _svc = svc;
    }

    protected override async void
        OnAppearing()
    {
        base.OnAppearing();
        await LoadDataAsync();
    }

    private async Task LoadDataAsync()
    {
        try
        {
            var (items, _) =
              await _svc.Repo
                .ListWidgetsAsync();
            WidgetList.ItemsSource = items;
        }
        catch (Exception ex)
        {
            await DisplayAlertAsync(
              "Error", ex.Message, "OK");
        }
    }
}

📋 Standard Page Set

Every qTeVerse app includes these standard pages. Follow the naming convention exactly.

LoginPage

Bluesky handle + app password authentication via MartAuthService.

ExplorePage

Main landing page. Browse and search your app's primary content.

ProfilePage

User profile with marketplace profile display and logout.

MessagesPage

Conversation list via MartMessagingService.

MessageThreadPage

Individual conversation thread with send capability.

BankPage

qTeCoin/qTeToken balance, transaction history, and ledger.

TermsPage

Terms of service and community guidelines.

AdminPage

(Optional) Moderation and record management tools.

RecordManagerPage

(Optional) Raw AT Protocol record browser for debugging.

✅ Pre-Ship Checklist

CheckRequirementWhy
MartCryptoService registered in DIPrevents runtime DI crash when resolving MartMessagingService
All pages registered with AddTransientShell navigation requires DI-resolvable pages
DisplayAlertAsync (not DisplayAlert)DisplayAlert is deprecated in MAUI 10
StrokeShape uses element syntaxMauiXamlInflator=SourceGen requires it
Border (not Frame) for containersFrame is deprecated in MAUI 10
DatePicker.Date handled as DateTime?MAUI 10 changed Date to nullable
LogoutAsync awaitedMust await the async logout call
Microsoft.Maui.Controls pinned to 10.0.51$(MauiVersion) resolves to 10.0.20 which is too old
CommunityToolkit.Maui 14.0.1Requires Controls >= 10.0.41
Build each TFM individuallyFull solution build may timeout

🚀 Publishing to qTeVerse

Submit your app through the qTeVerse app store. Register as a developer, create an app listing, and publish.

Developer Registration

  1. Open the qTeVerse app
  2. Navigate to Dev Registration
  3. Submit your Bluesky handle + portfolio
  4. Await community approval

App Submission

  1. Create App listing via Dev Dashboard
  2. Upload screenshots and description
  3. Set license type and pricing
  4. Submit for community review

qTeVerse Developer Integration Guide • Quantum Telemetric Encoding • Built on qTe Protocol