This example demonstrates a common and powerful use case for DID Connect: requesting a user to sign and submit an on-chain transaction for payment. This pattern is ideal for e-commerce checkouts, service subscriptions, content purchases, or any scenario that requires a transfer of tokens or assets.
We will use the signature claim to ask the user to sign a pre-constructed token transfer transaction.
How It Works
The process involves the application creating a transaction and then asking the user's wallet to sign it. This ensures that the user maintains full control over their assets, and the application never handles the user's private key.
Here is the typical flow:
Transaction Creation
The application's backend determines the details of the payment (e.g., amount, token type, recipient address) and uses a library like
@ocap/clientto construct a raw transaction.Claim Request
The application initiates a DID Connect session and requests a
signatureclaim from the user. The raw transaction data is embedded within this claim request.User Approval
The user's DID Wallet receives the request, decodes the transaction, and displays a human-readable summary (e.g., "Pay 100 ABC Tokens to 'My App'"). The user can then choose to approve and sign the transaction with their private key.
Response Handling
Upon approval, the wallet sends the signed transaction back to the application. The application can then verify the signature and broadcast the transaction to the blockchain to complete the payment.
This entire process is secure and non-custodial, providing a seamless Web3 payment experience.
Code Example
Let's build an example where our application requests a payment of 100 native tokens from the user.
1. Configure WalletAuthenticator
First, set up the WalletAuthenticator with your application's details and the chain information.
Transaction Payment Authenticator Setup
import { WalletAuthenticator } from '@did-connect/lib';
import { fromRandom } from '@ocap/wallet';
// Your application's wallet
const appWallet = fromRandom();
const auth = new WalletAuthenticator({
wallet: appWallet,
baseUrl: 'https://yourapp.com',
appInfo: {
name: 'My Awesome App',
description: 'An app that requires payment.',
icon: 'https://yourapp.com/logo.png',
link: 'https://yourapp.com',
},
chainInfo: {
host: 'https://beta.abtnetwork.io/api',
id: 'beta',
},
});2. Define the Signature Claim
Next, define the claims to be requested. We'll use a single signature claim. Inside the claim function, we'll construct the transaction.
This requires @ocap/client to encode the transaction and @ocap/util to convert the resulting buffer to a Base58 string, which is the format the wallet expects.
Create Transaction Signature Claim
import Client from '@ocap/client';
import { fromPublicKey } from '@ocap/wallet';
import { toBase58 } from '@ocap/util';
const client = new Client('https://beta.abtnetwork.io/api');
const NATIVE_TOKEN_ADDRESS = 'z35nB2SA6xxBuoaiuUXUw6Gah2SNU3UzqdgEt'; // Native token on ArcBlock chains
// Define the claims for an Express.js handler
const claims = {
signature: async ({ userDid, userPk }) => {
// 1. Construct the transaction object
const txPayload = {
tx: {
from: userDid,
pk: userPk,
itx: {
to: appWallet.address, // Send payment to the app's address
tokens: [
{
address: NATIVE_TOKEN_ADDRESS,
value: '100000000000000000000', // 100 tokens (with 18 decimals)
},
],
},
},
wallet: fromPublicKey(userPk),
};
// 2. Encode the transaction into a buffer
const encoded = await client.encodeTransferV2Tx(txPayload);
// 3. Return the claim object for the wallet
return {
type: 'fg:t:transaction', // Specifies a native framework transaction
data: toBase58(encoded.buffer), // The transaction data must be Base58 encoded
description: 'Sign this transaction to complete your payment of 100 tokens.',
// The requirement object is used by the wallet to display a clear summary to the user
requirement: {
tokens: [
{
address: NATIVE_TOKEN_ADDRESS,
value: '100000000000000000000',
},
],
},
};
},
};
// In your route handler, you would then generate the request URL
// const signed = await auth.sign({ context: { userDid, userPk }, claims });In this example:
userDidanduserPkare provided by the DID Connect session context after the user has connected their wallet.- We use
client.encodeTransferV2Txto create a standard token transfer transaction. - The
toaddress is our application's wallet address, where the funds will be sent. - The
requirementobject is crucial for a good user experience. It allows the wallet to parse the transaction and show the user exactly what they are being asked to pay, preventing them from signing a malicious or incorrect transaction.
3. Handling the Wallet's Response
After the user signs the transaction in their wallet, your application's callback endpoint will receive the signed transaction data. You can then use a library like @ocap/client again to broadcast it to the blockchain.
For a complete guide on setting up handlers to process wallet responses, see the Handling Wallet Responses guide.
This example covers a fundamental Web3 interaction. By leveraging the signature claim, you can securely request payments and other on-chain actions without ever taking custody of user keys.
To see another practical application of claims, check out the NFT Gated Access example.