Use DID login for payments (payout address)
Offline-testable todayAfter a verified login you can resolve the DID on your server and read the payout address from the canonical record. You can write and self-test that logic offline today; reading a real address in production is blocked with the rest of production login. pay:address is a declaration the user sees and approves, not a permission.
Journey statusOffline-testable today
Steps
1Where the payout address actually comes from
Works todayA login response carries a did, a canonical message and a signature — no account address. To get the address you resolve the DID and read the canonical record on the server.
The record field owner is the 44-character MSG account address (msg + 40 hex + one checksum digit). The document's controller field is different: it lists controller DIDs, not addresses, so never send MSG to a did:msg:... string. Check deactivated before you use the address, and do not cache it longer than your resolve TTL (5 to 15 seconds); a name-to-address mapping can change between DID updates.
js // After a verified login you have `did` (result.did) — never an address. import { createAgentApiResolver, createCachedResolver } from '@aidid/login'; const resolve = createCachedResolver({ resolver: createAgentApiResolver({ agentApiEndpoint: process.env.AGENT_API }), }); const record = await resolve.resolve(result.did); // canonical record, 10 s TTL by default if (record.deactivated) throw new Error('did_deactivated'); // refuse a retired identity first const payoutAddress = record.owner; // 'msg' + 40 hex + 1 checksum = 44 chars, an ACCOUNT address // record.document.controller is a list of controller DIDs — NOT a payment address.2resources: ['pay:address'] is a declaration, not a grant
Works todayAdding pay:address to resources puts a statement into the canonical message that the user signs, and the wallet shows it on the approval page: "this site will obtain your payout address". It is a declaration, and the protocol says so explicitly.
It is not a permission, it grants no on-chain right, and it does not mean the user authorised a payment. The response still contains no address, so you must resolve the DID yourself. If you do not request pay:address, do not derive, display or use the address for payment at all.
js import { createSignInRequest } from '@aidid/login'; const request = createSignInRequest({ domain: 'shop.example', chainId: 'msg-chain-1', uri: 'https://shop.example/aidid/callback', statement: 'Sign in to shop.example.', // A declaration the user sees and approves. It is NOT a permission and the // response carries no address, so you still resolve the DID yourself. resources: ['did:auth', 'pay:address'], }); // result of the callback: { ok: true, did: 'did:msg:...' } — no address field, by design.3Binding the address to the DID: keys, devices and sub-DIDs
Offline-testable todayThe address belongs to the DID, so the questions that matter are about the DID and where it lives.
New device or restored backup: a restore brings back the same DID document, so the same owner address still applies. A newly created DID gets a new address, so never hard-code an address against a user account.
Key rotation or a DID update: re-resolve before every payout; the record carries a monotonic version and an updated block time, and the owner can change across updates.
Sub-DIDs: a sub-DID is its own did:msg with its own resolve record. Pay the sub-DID's owner only when that sub-DID is the identity you accepted, and remember that a sub-DID grant has no on-chain revocation channel today — a revoked grant may still resolve until the document is updated.
Deactivated DIDs: refuse before reading an address (did_deactivated), and keep your own deny list, because the chain has no blacklist.
4Today: offline self-test yes, production address no
Not available todayEverything above can be written and self-tested offline today, with a fixture resolver in place of canonical resolve: the request, the approval declaration, the callback, the resolve step and the address read.
What you cannot do today is read a real address in production. Resolve is not authorised on the public network, so the address step fails closed with endpoint_unavailable for every DID. Do not ship a checkout or payout flow that depends on it; the honest status is fail-closed until the chain side authorises the endpoint and ordinary users can actually hold a DID.
Why this is blocked today
didLoginProductionThe chain-side DID resolve endpoint (`GET {agent_api}/agent/v1/aidid/resolve`) is not authorized yet, so an RP cannot resolve any DID in production.
Reason taken from status.json#capabilities — not written by hand for this page.