Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
95614da
feat(truapi): show product-referenced profiles in host UI
replghost Sep 25, 2026
0091b6f
feat(truapi): disclose a profile to chat contacts, present a contact'…
corey-hathaway Sep 26, 2026
298cf42
feat(chat): relay disclosed profile references host to host
corey-hathaway Sep 26, 2026
e89fd09
docs(rfc): profile disclosure to chat contacts
corey-hathaway Sep 26, 2026
dcd38a0
refactor(truapi): move the Profile service to wire trait 22
replghost Sep 26, 2026
77bf245
docs(truapi): use a hex peer identity in the presentContact example
replghost Sep 27, 2026
09e9a88
Merge feat/chat-v2-product-authority (#709): forward #540 CI fixes
replghost Sep 27, 2026
5b6f7ce
Merge branch 'refresh/709-021' into refresh/1001-021
replghost Sep 27, 2026
430ee98
Merge branch 'refresh/709-021' into refresh/1001-021
replghost Sep 27, 2026
05fef87
Merge feat/chat-v2-product-authority (#709): release the wallet lease…
replghost Sep 27, 2026
5be162d
Merge remote-tracking branch 'origin/feat/chat-v2-product-authority' …
replghost Sep 28, 2026
571f348
fix(profile): order, scope and bound the Chat profile relay
replghost Sep 28, 2026
911feab
feat(profile): draw placed contact avatars in host UI
replghost Sep 28, 2026
ed967c4
fix(chat): open Chat state saved with the previous profile watermark …
replghost Sep 28, 2026
98b84ab
feat(truapi)!: move the Profile service to prototype wire trait 69
replghost Sep 28, 2026
2d94812
feat(profile): relay shares when chats become ready or the share changes
replghost Sep 28, 2026
3e1dc69
feat(profile): re-disclosing resends to contacts; placed avatars carr…
replghost Sep 28, 2026
fa7217c
Merge updated Chat base
replghost Sep 28, 2026
073ea6e
feat(profile): name the contact who shared a presented profile
replghost Sep 28, 2026
9409b3e
feat(profile): pass the contact's username to the presented profile
replghost Sep 29, 2026
d3df641
Merge light-client CLI base into feat/chat-seity-profile
replghost Sep 29, 2026
56eac0e
Merge refreshed Chat host stack into Seity profile integration
replghost Sep 29, 2026
6f96fe2
Merge commit '979dda2fd' into HEAD
replghost Sep 29, 2026
41b28c7
Merge commit '1772ce966' into HEAD
replghost Sep 29, 2026
50beeb4
Merge commit '382075199' into HEAD
replghost Sep 29, 2026
131f5a3
Preserve renewal target visibility in browser tests
replghost Sep 29, 2026
2b5556c
Merge commit 'ba194768e09df03f246bd0d82f2d6030fa8248b3' into HEAD
replghost Sep 29, 2026
c213487
Migrate Seity native bindings and regenerate unified API catalogs
replghost Sep 29, 2026
b1ca53e
Merge commit 'd40c6930d' into HEAD
replghost Sep 29, 2026
5f777e6
Merge commit '46c7c0128a46947e89120bf72b635779cb47376f' into HEAD
replghost Sep 29, 2026
1b0b38b
test(profile): retain behavior coverage instead of capability echoes
replghost Sep 29, 2026
4efb884
Merge Chat contract qualification into Seity
replghost Sep 29, 2026
c57f71d
fix(truapi): restore ChatReaction payload export
replghost Sep 29, 2026
d4767cb
chore(stack): integrate durable Chat schema migration
replghost Sep 29, 2026
6778214
feat(profile): own profile status, presentation and avatar slot
replghost Sep 29, 2026
a802b52
test(profile): convert unit responses before version downgrading
replghost Sep 29, 2026
f194f2e
Merge commit 'c1d982ccc' into HEAD
replghost Sep 29, 2026
4bedd1d
Merge remote-tracking branch 'origin/feat/chat-v2-product-authority' …
replghost Sep 29, 2026
cbb13e8
fix(ci): retain iOS test failure diagnostics
replghost Sep 29, 2026
0d34d46
feat(profile): share independently with apps and selected contacts
replghost Sep 30, 2026
2a6fd0a
docs: clarify Profile audience consent scope
replghost Sep 30, 2026
8b144b6
Merge Chat contacts directory into Seity profile layer
replghost Sep 30, 2026
b6a8060
Merge Chat contacts adapter fallback into Seity profile layer
replghost Sep 30, 2026
d9c7150
Merge remote-tracking branch 'origin/feat/chat-v2-product-authority' …
replghost Sep 30, 2026
f031658
Merge commit '437a46c5a' into HEAD
replghost Sep 30, 2026
510b84f
Merge commit '89d9f34650a9c5819f3a1c103dc538719a67ce67' into HEAD
replghost Sep 30, 2026
3d057d5
Merge commit '4b241e8cfa7b6cdbc6ce90e56866087fc31331f6' into HEAD
replghost Sep 30, 2026
b61218f
Merge refreshed Chat runtime into Seity with profile-scoped services
replghost Oct 1, 2026
a1f6a2a
Merge commit 'daf0ff5675bc6816969e44d457bffe8a0e3f582a' into HEAD
replghost Oct 1, 2026
734e5ad
Merge commit '21c870cf12f04c4fdd9b0be36d2a7aa61795d4a6' into HEAD
replghost Oct 1, 2026
ed93487
Merge commit 'e6c6ed300c58fa8f9040f87eea0a39819c32aef1' into HEAD
replghost Oct 1, 2026
978b7d3
Merge commit '08bddf36912d61fcfa76e4fb733fd585c6b22bca' into HEAD
replghost Oct 1, 2026
176454a
Merge commit 'ef02f0c02dbb15b701dd8408bb6e2a98e4e04fe5' into HEAD
replghost Oct 1, 2026
aa72ef6
Merge commit 'c1e38d306f012b9a12f62b55d735873161e91d1e' into HEAD
replghost Oct 1, 2026
94f597b
Merge commit '6d97c7932f26cf190dd6c506ed5f4da110bada74' into HEAD
replghost Oct 1, 2026
3a364f1
Merge commit '726ef76bcce42b91d664d753ac4225df0a093472' into HEAD
replghost Oct 2, 2026
b1d8b30
Merge feat/chat-v2-product-authority into feat/chat-seity-profile
replghost Oct 2, 2026
3c4cd4b
Merge Chat test-host synchronization into feat/chat-seity-profile
replghost Oct 2, 2026
8826fca
Merge Chat runtime synchronization into feat/chat-seity-profile
replghost Oct 2, 2026
0261351
Merge Chat allowance cache regression fix into feat/chat-seity-profile
replghost Oct 2, 2026
7b130f6
Merge Chat snapshot fixture correction into feat/chat-seity-profile
replghost Oct 2, 2026
1cbc95d
feat(contacts): support opaque audience selection and host labels
replghost Oct 2, 2026
bda45fd
docs(contacts): describe selection and private labels
replghost Oct 2, 2026
84e163a
fix(contacts): preserve domain errors on host interruption
replghost Oct 2, 2026
b4fa7eb
style(contacts): format native feature changes
replghost Oct 2, 2026
085489b
style(contacts): preserve surrounding documentation layout
replghost Oct 2, 2026
75c12f8
Merge frozen Chat main update into feat/chat-seity-profile
replghost Oct 2, 2026
c19b8a9
Merge incoming Contacts audience selection into candidate Seity
replghost Oct 2, 2026
9a5c696
fix(profile): present host-owned feedback for absent contact profiles
replghost Oct 2, 2026
77429cd
Merge empty contact profile feedback into Seity
replghost Oct 2, 2026
9656643
Merge host locale conversion from Chat into Seity
replghost Oct 2, 2026
f1edc91
Merge explicit API version binding fix into Seity
replghost Oct 2, 2026
ee0d338
chore(codegen): regenerate combined locale and Profile client
replghost Oct 2, 2026
8310356
Merge regenerated Chat locale catalog into Seity
replghost Oct 2, 2026
2a8e909
Merge generator fixture compile fix into Seity
replghost Oct 2, 2026
7277fd3
merge: forward pinned-nightly Locale formatting into native 1001
replghost Oct 2, 2026
bd55ec2
Merge qualified runtime updates from #709 into #1001
replghost Oct 3, 2026
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
70 changes: 70 additions & 0 deletions .changeset/profile-disclose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
"@parity/truapi": minor
"@parity/truapi-host": minor
---

Add `profile.disclose`, `profile.retract` and `profile.presentContact`. A product discloses one opaque reference to the
user's chat contacts and may withdraw it; a product names a contact by peer identity and the host presents the reference
that contact disclosed, so no product holds a contact's reference. The first `disclose` from a product asks the user
once through `userConfirmation.confirmPermission` with a new `ProfileDisclosure` review, remembered as the
`ProfileDisclosure` permission; a refusal is `PermissionDenied`. Hosts must render that review.

This change includes the Chat relay. In legacy `ChatApps` mode the host sends the disclosure to every ready Chat v2
contact as a host-private app-scoped message and keeps, per contact, the newest frame their host sent back, withdrawals
included, whatever order the chat product opens them in. Both live in wallet- and network-scoped core storage
(`ProfileDisclosure`, `ProfileReferencesReceived`). The host queues the disclosure when the chat product initializes or reconciles, in the
response to a Chat request in which a contact became ready, and, without delaying the call, as soon as `disclose` or
`retract` changes it while a Chat of the same wallet is open; the chat product still has to run to submit it. Delivery
is best effort: relayed references never take outbox room from other Chat traffic, and one that lapses unacknowledged
after a statement lifetime is signed again for a ready contact, at most three frames per contact and disclosure. Every
`disclose` call is a new disclosure, even with the reference already held: a profile whose record changed behind the
same reference is sent to the selected ready recipients again, with a fresh attempt count.

Add `profile.placeContactAvatars`. A chat App tells the host where it draws contacts' avatars (surface size and, per
avatar, a slot id, peer identity, square rect and clip), and the host draws the photo and mood ring of each contact who
shared a profile with it on its own layer. The core filters the placement to contacts with a current reference, hands
them with their references and `sharedAt` (Unix ms of the contact's share) to the new
`ProfilePlatform.placeContactAvatars(product, placed)` callback, and redraws the remembered placement when a reference
arrives, is re-shared or is withdrawn; a larger `sharedAt` for the same reference tells the host its cached profile is
stale; it clears it when the connection goes away.
The product is answered `Ok` whoever shared; only a malformed placement (more than 64 slots, a surface side outside 1 to
16384, a non-square avatar or one outside 1 to 1024 a side, a repeated slot) is refused, and a host that cannot draw
answers `Unsupported`. A JS host that supplies a `profile` group must implement the callback; the Rust trait's default
draws nothing.

Add `profile.ownStatus` and `profile.presentOwn`, and an optional `own` slot in version 2 of
`profile.placeContactAvatars`. A chat product can report whether its signed-in user has configured a profile and ask
the host to present it without receiving the bearer reference. The core fills the own slot from the user's disclosure
and hands it to the existing callback in the same replacement set as the contact avatars, redraws it when the user
discloses or retracts, and still reveals nothing per slot. Version 1 placements keep working unchanged.
The avatar regression suite also exercises version-1 response downgrading alongside the version-2 own-profile slot.

Version 2 of `profile.disclose` adds explicit `ChatApps`, `App { productId }`, and
`Contacts { handles }` audiences. App-scoped and selected-contact personal grants coexist: personal grants are
host-renderable across products, never returned to them. All handles are verified against the host Contacts lookup
before committing the replacement; empty audiences configure only the user's own profile. Existing V1 calls retain
their app-scoped all-Chat behavior. Groups remain product-owned sets of opaque handles, not a new host group API.

Personal relay uses distinct Chat content 22 (scope 1) and wallet/network-scoped
`ProfilePersonalReferencesReceived` storage. App content 21 is unchanged. Durable revisions, separate scoped
watermarks and withdrawal tombstones prevent an older personal share delivered through another app from reviving a
withdrawn grant. Removing one audience does not revoke an overlapping grant in another scope. Delivery still requires
a ready authenticated Chat channel and a running transport product; Contacts membership alone creates neither.

Version 2 of `profile.presentContact` accepts either a peer identity or a Contacts handle and hides profile
availability, including host rendering failures. V1 retains its app-only lookup and errors, so it cannot probe new
cross-app personal grants. Version 3 of `profile.placeContactAvatars` accepts the same selectors alongside the own slot;
V1/V2 placement bytes and replies remain compatible. Contacts-change notifications invalidate cached handle lookups
and refresh remembered avatars. App-specific references take precedence over personal ones; personal updates redraw
all affected wallet placements.
Personal revisions also advance the host-rendered freshness timestamp when a newer share arrives through an actor
whose clock is older, preventing a same-reference update from leaving stale cached profile contents.

Add `contacts.pickMany` with preselected opaque handles and explicit picked, dismissed, and no-contacts outcomes.
Add `contacts.placeLabels` so Apps can reserve host-rendered contact names without receiving those names or profile
availability. The core validates bounded placements and wallet-scoped handles, refreshes labels after Contacts changes,
and releases them when the connection closes. Hosts without a label surface return `Unsupported`; Worker products
cannot place labels. Clearing a session serializes removal of its remembered contact labels with pending refreshes.
Host-side interruption returns a Contacts domain error, reserving wire `Cancelled` for a peer's explicit cancellation.
Failed directory lookups preserve the prior label surface and report a retryable error instead of clearing it as if
the contacts were missing.
14 changes: 14 additions & 0 deletions .changeset/profile-present-contact-attribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@parity/truapi-host": minor
---

Name the contact in host-owned profile presentation, including a friendly empty state when no live reference has arrived.
`ProfilePlatform.presentContactProfile(product, presented)` receives `peerIdentity`, optional verified `username`,
and optional `shared: { reference, sharedAt }`. Absence of `shared` requests empty-profile feedback without a fetch.
The product-facing V2 reply does not reveal whether any information was available or displayed.
The username is the one the product's Chat roster verified for that contact, else the contact's verified dotNS name,
looked up for at most 2 seconds; it never comes from the product. It names who sent the reference, not whose profile it
is: the record is not signed by its owner, and a contact can forward someone else's reference. The default adapter
can present a shared reference through `presentProfile`; empty-profile feedback requires `presentContactProfile`.
Storage errors, invalid references and invalid handles are not misrepresented as absent sharing. The product-facing
Profile wire is unchanged.
11 changes: 11 additions & 0 deletions .changeset/profile-present.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@parity/truapi": minor
"@parity/truapi-host": minor
---

Add the `profile` service. `profile.present({ reference })` asks the host to show a referenced profile in host-owned
UI; the host resolves, decrypts and renders it, and nothing but acceptance returns to the product. Hosts opt in with the
optional `profile` callbacks (`ProfilePlatform`); a host that supplies none answers `Unsupported`.

Keep the optional profile bindings available under the consolidated native
`runtime` feature, and use the shared host clock for disclosure revisions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ TypeScript client, and hosts and products implement against the same shared type
- [TrUAPI reference](https://docs.polkadot.com/reference/apps/protocol/truapi/)
- [Rust API reference](https://paritytech.github.io/trinity-user-agents/)
- [Draft: Host-owned native Chat and main-purse payments](docs/rfcs/native-chat-main-purse.md)
- [Draft: Profile disclosure audiences and host-rendered contacts](docs/rfcs/profile-disclosure.md)

<!-- TODO: Add hero screenshot of the playground showing methods + a live call/response. Capture with a screenshot tool, save to `assets/screenshots/playground.png`, then place it here. -->

Expand Down Expand Up @@ -130,6 +131,19 @@ authenticated, ready peers from authorized native Chat products, scopes them to
and never exposes a product or SSO directory method. Actors opened on this version are durably indexed; historical
unindexed Chat products must be opened once before their peers can appear. Pairing hosts do not supply this directory.

Contacts trait 20 retains the single picker at method 0, adds `pickMany({ selected })` at method 1, and
`placeLabels({ surfaceWidth, surfaceHeight, slots })` at method 2. Multi-select confirmation returns only
wallet-scoped handles, including a confirmed empty selection; dismissal never edits the audience. Host-owned
labels show directory usernames or account fallbacks independently of Profile photos, without returning names,
accounts or per-slot availability. Selections and placements are bounded to 256 entries; unresolved initial
selections fail closed. Hosts implement `pickContacts(product, ContactSelection)` and
`placeContactLabels(product, PlacedContactLabels)` through the canonical native/WASM/worker callbacks.

Profile V2 presentation opens host-owned feedback even when no live contact reference has arrived.
`PresentedContactProfile.shared` holds the reference and freshness timestamp when present; `None` requests an
empty-profile view. The host receives the verified contact name, while the product receives the same success reply
for shared and absent information. Storage failures and invalid handles are not presented as an empty profile.

The [native Chat/main-purse RFC](docs/rfcs/native-chat-main-purse.md) specifies the method 12 request/response and
compatibility contract, device eligibility, custody-before-ACK rule, and delivery versus clearing semantics. It is a
draft for review in #709, not an approved standard or a release claim. It builds on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ import uniffi.truapi.ProductExecutionConfig
import uniffi.truapi.HostContactLookup
import uniffi.truapi.HostContactMatches
import uniffi.truapi.HostContactPick
import uniffi.truapi.ContactSelection
import uniffi.truapi.HostContactsPick
import uniffi.truapi.PlacedContactLabels
import uniffi.truapi.HostContactsPlaceLabelsException
import uniffi.truapi.NativeContactsCallbacks
import uniffi.truapi.SsoRequestOutcome

Expand Down Expand Up @@ -798,6 +802,17 @@ interface ContactsHostBridge {
*/
@Throws(HostRejection::class)
suspend fun pickContact(productId: String): HostContactPick

/** Edit the complete audience; cancelling does not confirm an empty one. */
@Throws(HostRejection::class)
suspend fun pickContacts(productId: String, selection: ContactSelection): HostContactsPick =
HostContactsPick.Unsupported

/** Replace names on the host surface without exposing them to products. */
@Throws(HostContactsPlaceLabelsException::class)
suspend fun placeContactLabels(productId: String, placed: PlacedContactLabels) {
throw HostContactsPlaceLabelsException.Unsupported()
}
}

private class ContactsCallbackAdapter(private val bridge: ContactsHostBridge) : NativeContactsCallbacks {
Expand All @@ -812,6 +827,19 @@ private class ContactsCallbackAdapter(private val bridge: ContactsHostBridge) :
} catch (error: Throwable) {
throw HostRejection.Rejected(hostRejectionReason(error))
}

override suspend fun pickContacts(productId: String, selection: ContactSelection): HostContactsPick =
withHostRejection { bridge.pickContacts(productId, selection) }

override suspend fun placeContactLabels(productId: String, placed: PlacedContactLabels) {
try {
bridge.placeContactLabels(productId, placed)
} catch (error: HostContactsPlaceLabelsException) {
throw error
} catch (error: Throwable) {
throw HostContactsPlaceLabelsException.Unknown("contact label callback failed")
}
}
}

private class PocketCallbackAdapter(private val bridge: PocketHostBridge) : NativePocketCallbacks {
Expand Down
47 changes: 34 additions & 13 deletions docs/rfcs/contacts-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ status: draft
_How the implemented pieces fit together is in
[Contacts Pick, End to End](../design/contacts-pick-end-to-end.md)._

A product asks the Host to let the user pick a contact. The Host renders an overlay from its Chat
workers' chat lists, the user selects one person, and the product receives one opaque handle — never
the list, a name, or an account. The handle is not an address: the core resolves it when building a
transaction.
A product asks the Host to let the user pick one or more contacts. The Host renders the
picker from its contact directory and returns opaque handles, never the list, names or
accounts. The handle is not an address: the core resolves it when building a transaction.
Host-owned name labels let users recognize selected handles without sharing the names
or requiring a Profile photo.

## Motivation

Expand Down Expand Up @@ -49,12 +50,34 @@ names it as the recipient and the core substitutes the account when it builds th
product-scoped address is not derivable at all, which is why the handle is resolvable rather than
directly usable.

### Multi-select audiences

Trait 20 method 0 remains `pick`. Method 1, `pickMany({ selected })`, edits a complete
selection of at most 256 handles. The core deduplicates and resolves the initial
selection before opening the picker; any unresolved handle rejects the whole request.
The host callback `pickContacts(product, ContactSelection { selected })` receives
accounts only inside the trusted host boundary. Confirming an empty selection returns
`Picked { handles: [] }`; closing the picker returns `Dismissed`. Session or directory
invalidation during resolution or confirmation cancels the change.

### Host-owned contact labels

Method 2, `placeLabels({ surfaceWidth, surfaceHeight, slots })`, replaces at most 256
name rectangles. Each slot supplies `{ slot, handle, rect, clip }`, reusing `AvatarRect`.
The host resolves handles and draws directory usernames, or account fallbacks, on its
own layer. Names do not depend on Profile disclosure. Missing contacts leave no label
and produce the same success response; products never receive names or availability.
Surfaces and rectangle sides are bounded to 16384 units, clip sides may be zero, and
slot ids must be unique. Empty slots, connection teardown and session changes clear
the layer. On same-wallet directory invalidation, the host clears stale names and
refreshes the latest live placement without another product request.


## Trade-offs

- A host that serves no picker answers `Unsupported`, which a product cannot retry its way out of.
- `NoContacts` reveals whether the user has any contacts — zero-or-not, never a count.
- No product-rendered contact UI, every selection is a user interaction, one contact per call,
read-only.
- No product-rendered contact directory: every selection is a host-owned user interaction.
- Dropped: returning the list scoped per product (`display_name` was a correlator no scoping fixed,
and it needed a permission over the whole social graph); per-product handles (forfeit a durable
shared id, break under contact sync); returning the chat account (transactable, but a global
Expand All @@ -67,11 +90,9 @@ A product declares the handles its call names, on the transaction payload, and t

Substitution happens before the confirmation, so the signing overlay is drawn from a call that names an account the Host can put a name to. That is what closes the display gap for the flow that matters: a product renders a neutral chip, and the user sees who they are paying in trusted UI at the moment of consent.

## Open questions
## Recognition outside signing

How a product shows the user which contact they picked outside a signature. A product holds 32 bytes and no name, so it
renders a neutral chip. Two parts close that, and neither is specified here: the Host redraws the name
in its own signing confirmation, which knows the account and is where consent is given, so a product
never needs the name for the flow to be safe; and a product labels the handle itself, letting the user
name those 32 bytes once. A user-supplied label keeps the Host from handing back the correlator that
ruled out `display_name`.
A product holds only handles and reserves rectangles for `placeLabels`. The host
draws names in those rectangles without returning a global correlator. Profile avatar
slots remain separate and photo-only, so users can recognize a contact even when that
contact has never shared a profile.
Loading
Loading