Handling Wallet Responses
Once a DID Connect session is initiated, it progresses through a series of states, from the moment a user scans a QR code to the final submission of their response. The @arcblock/did-connect-js
library allows you to hook into this lifecycle using a set of callback functions. These callbacks are your primary tool for defining the business logic of your application, whether it's checking permissions, processing user data, or handling errors.
Each callback is passed as an argument to the handlers.attach
method, giving you precise control over the session flow. For a high-level view of the entire process, you may want to review The DID Connect Workflow first.
The Session Lifecycle#
The interaction between your application, the SDK, and the user's wallet can be visualized as a sequence of events. Each key event in the DID Connect protocol triggers a corresponding callback in your code, allowing you to react accordingly.
Lifecycle Callbacks#
You can provide any of the following callbacks to handlers.attach
to manage the session. The onAuth
callback is the only one that is required.
Callback | Description |
---|---|
| Called when a new session starts and a challenge token is generated. Useful for logging or initial setup. |
| Called after the user scans the QR code and connects their wallet, but before claims are sent. Ideal for permission checks or defining dynamic claims. |
| Required. Called after the user approves the request in their wallet and submits the requested claims. This is where your primary business logic should reside. |
| Called if the user explicitly rejects the request in their wallet. |
| Called when the session has concluded, either through successful authentication or decline. Good for cleanup tasks. |
| Called if the session token expires before the user completes the action. |
| Called when an unexpected error occurs during the session. Defaults to |
onConnect
: Pre-Approval Logic and Dynamic Claims#
The onConnect
callback is your first opportunity to interact with the user's DID. It's triggered as soon as the wallet establishes a connection. You can use this step to perform checks based on the user's DID or to dynamically determine which claims to request.
For example, you could check if userDid
is in an allowlist. If the check fails, throwing an error from this callback will halt the session. You can also return a claims object to dynamically request information.
handlers.attach({
action: 'dynamic-claims',
// This function can be async
onConnect: ({ userDid }) => {
// Here you can check userDid against a database
console.log(`User connected: ${userDid}`);
// You can also return a claims object to dynamically request information
return {
profile: () => ({
fields: ['fullName', 'email'],
description: 'Please provide your name and email to continue',
}),
};
},
onAuth: async ({ claims, userDid }) => {
// `claims` will contain the result for the dynamically requested profile
const profile = claims.find((x) => x.type === 'profile');
console.log('Auth success', { userDid, profile });
},
});
onAuth
: Handling Submitted Data#
The onAuth
callback is the core of your DID Connect logic. It executes only after the user has approved the request and submitted the required claims. The claims
parameter is an array containing the data you requested.
handlers.attach({
action: 'profile-and-asset',
claims: {
profile: () => ({
fields: ['fullName', 'email'],
description: 'Please provide your name and email to continue',
}),
asset: () => ({
description: 'Please provide a valid NFT',
trustedIssuers: ['z8iZhf5aT4C9g4aP35m2J5Yoe1g4i5J2E4o2'],
}),
},
onAuth: async ({ userDid, claims }) => {
// `claims` contains what the user has submitted
try {
const profile = claims.find((x) => x.type === 'profile');
const asset = claims.find((x) => x.type === 'asset');
console.info('Login success:', { userDid, profile, asset });
// TODO: Add your business logic here, e.g., create a user session, grant access, etc.
} catch (err) {
console.error('Login error:', err);
}
},
});
onDecline
: Handling Rejection#
If a user chooses to decline the request in their wallet, the onDecline
callback is triggered. This allows you to gracefully handle the rejection, for instance, by logging the event or displaying an appropriate message on the frontend.
handlers.attach({
action: 'profile-login',
claims: {
profile: () => ({
fields: ['fullName'],
description: 'Please provide your name to continue',
}),
},
onAuth: async ({ userDid, claims }) => {
// ... handle success
},
onDecline: ({ userDid }) => {
console.log(`User ${userDid} declined the connection request.`);
// You might update the UI to show a 'request declined' message
},
});
updateSession
: Persisting Data for the Frontend#
Within callbacks like onAuth
or onConnect
, you may need to persist data that the frontend can later retrieve, such as a login token or a transaction status. The updateSession
function, available as a parameter to most callbacks, allows you to save data to the session store.
handlers.attach({
action: 'complex-flow',
onAuth: async ({ userDid, updateSession }) => {
const loginToken = 'user-login-token-generated-on-backend';
// Persist non-sensitive info
await updateSession({ status: 'completed' });
// To persist sensitive info, set the second argument to true to encrypt it
await updateSession({ authToken: loginToken }, true);
},
});
The frontend can then poll the session status endpoint to retrieve this data once the wallet interaction is complete.
Next Steps#
Now that you know how to manage wallet responses, you can build more sophisticated user journeys. Consider learning how to link multiple DID Connect requests together or diving deeper into the API.