← Book map

Chapter 04 · Tabs

When the service signs, not the user

The only tab with a server-side lifecycle: a generated request id, an automatic callback, a status endpoint, and a service identity holding the key.

Service signer — the moving parts A service identity signs an update for a target identity, with a generated request id, an automatic callback redirect, and a status endpoint the browser polls. generateRequestId() ensureCallbackRedirect() getidentity (target) merge name + parent + contentmultimap sign with service signer callback → txid status polling QR of the signed request The wallet signs nothing here: it only authorises. The service holds the key.

FIGURE 4.1 · Who does what in the service-signer flow.

The inversion

Every other tab signs with a user identity that lives in the daemon's wallet. The service-signer tab signs with a service identity configured on the server (SERVICE_SIGNER_IADDRESS / SERVICE_SIGNER_WIF), targets someone else's identity, and uses the QR only to get the user's authorisation.

That is the model used by provisioning and conversion services: the user authorises by scanning, the service performs the update. The request is still a normal IdentityUpdateRequestOrdinalVDXFObject — what differs is who signed it and how the answer comes back.

Assembly, step by step

1. requestId = generateRequestId()                       // new i-address-ish id per request
2. redirects = ensureCallbackRedirect(redirects, uri)    // adds type "1" -> /api/service-signer/callback
3. { name, parent } = getidentity(targetIdentity)        // RPC, must resolve
4. merged = { ...identityChanges, name, parent, contentmultimap }
5. details = IdentityUpdateRequestDetails.fromCLIJson(merged, { requestid })
6. request = buildGenericRequestFromDetails({ details, signed: true,
              signingId: SERVICE_SIGNER_IADDRESS, redirects })
7. signRequest(...)  with the service signing id
8. QR / deeplink

Note step 3: the tab refuses to build a request if the target identity's name and parent cannot be resolved. A partial identity that names an identity by something the chain cannot resolve is a request the wallet would reject anyway.

The callback loop

EndpointPurpose
GET /api/service-signer/callback?requestId=…Wallet redirect target; renders a page that then polls status
POST /api/service-signer/callback?requestId=…Programmatic callback carrying the wallet's response
GET /api/service-signer/status?requestId=…Poll request state (pending / complete / error)

parseIdentityUpdateResponse extracts the txid from the wallet's response and stores it against the request id. The UI polls status until it sees one. This is the only tab with a server-side lifecycle — everything else is request/response in a single HTTP call.

If the service-signer QR works but the page never updates, the callback redirect is missing — and because buildResponseUris drops unknown types silently, the failure looks like a wallet problem rather than a form problem.

keydefiner.ts

A 22-line CLI helper: given a VDXF key URI, it constructs a DefinedKey, prints its i-address, and emits the identity-update JSON fragment that registers it under DATA_TYPE_DEFINEDKEY. It is how you create the key you will then write into a content multimap in the update tab.

Reading the code

src/routes/requests/serviceSigner.ts
├── generateServiceSignerQr     # assembly + QR
├── serviceSignerCallback       # POST from wallet
├── serviceSignerCallbackRedirect # GET redirect + page
├── serviceSignerStatus         # polling
└── fetchTargetIdentityInfo     # getidentity, name + parent
src/keydefiner.ts               # VDXF key → i-address + update JSON