Transaction Payment
This example demonstrates how an application can request a user to sign and submit a token transfer transaction. This is a common requirement for scenarios like paying for a service, purchasing a digital good, or covering operational fees.
DID Connect facilitates this by allowing the application to prepare a transaction and request the user's signature via their DID Wallet. The wallet ensures the user reviews and consents to the transaction before signing and returning it to the application for broadcasting.
This process leverages the signature
claim type, which you can learn more about in the Signature Claim reference guide.
Workflow Overview#
The transaction payment flow involves the application backend, the user's DID Wallet, and the blockchain. The backend prepares an unsigned transaction, the wallet signs it, and the backend then broadcasts it.
Example 1: Simple Token Transfer#
Let's walk through a standard scenario where an application requests a specific amount of a token from the user.
Step 1: Define the Signature Claim#
The application backend defines a signature
claim that constructs a TransferV2Tx
. This claim specifies the recipient, the token, and the amount.
const { fromTokenToUnit } = require('@ocap/util');
const { wallet } = require('../../libs/auth'); // Application's wallet
const { getTokenInfo } = require('../../libs/util');
const env = require('../../libs/env');
// ...
{
action: 'send_token',
claims: {
signature: async ({ userDid, userPk, extraParams: { locale, chain, amount } }) => {
const token = await getTokenInfo();
// For demonstration, randomize if no amount is given
if (amount === 'random') {
amount = (Math.random() * 10).toFixed(6);
}
const description = {
en: `Please pay ${amount} ${token[chain].symbol} to application`,
zh: `请支付 ${amount} ${token[chain].symbol}`,
};
return {
type: 'TransferV2Tx',
data: {
from: userDid,
pk: userPk,
itx: {
to: wallet.address, // The application's receiving address
tokens: [
{
address: chain === 'local' ? env.localTokenId : env.foreignTokenId,
value: fromTokenToUnit(amount, token[chain].decimal).toString(),
},
],
},
},
description: description[locale] || description.en,
};
},
},
// ... onAuth handler follows
}
Key Parameters in the Claim:
Parameter | Description |
---|---|
|
|
| The recipient's address. In this case, it's the application's own wallet address. |
| An array of token objects to be transferred. |
| The contract address of the token being transferred. |
| The amount of the token to transfer, specified in its smallest unit (e.g., wei). The |
| A human-readable string that the DID Wallet will display to the user to explain the request. |
Step 2: Handle the Wallet's Response#
After the user approves and signs the transaction in their wallet, the signed data is sent to the onAuth
callback. The backend's role is to take this signed transaction and broadcast it to the network.
const { fromAddress } = require('@ocap/wallet');
const { client } = require('../../libs/auth');
// ...
{
// ... claims definition
onAuth: async ({ req, claims, userDid }) => {
try {
const claim = claims.find(x => x.type === 'signature');
const tx = client.decodeTx(claim.origin);
const user = fromAddress(userDid);
// Send the transaction signed by the user's wallet
const hash = await client.sendTransferV2Tx({
tx,
wallet: user,
signature: claim.sig,
});
logger.info('send_token.onAuth', { userDid, hash });
return { hash, tx: claim.origin }; // Return the hash as confirmation
} catch (err) {
logger.error('send_token.onAuth.error', err);
throw new Error('Send token failed!');
}
},
}
This onAuth
function completes the process by:
- Finding the
signature
claim from the wallet's response. - Decoding the transaction data.
- Creating a wallet instance for the user from their DID.
- Calling
client.sendTransferV2Tx
with the transaction, user's wallet, and signature to broadcast it. - Returning the resulting transaction hash to the frontend, confirming the payment was successfully submitted.
Example 2: Combined Asset and Token Transfer#
DID Connect's TransferV2Tx
is flexible and can handle more than just tokens. You can request the transfer of on-chain assets (like NFTs) and tokens within a single transaction. This is useful for marketplace scenarios where a user transfers an asset and pays a transaction fee simultaneously.
{
action: 'transfer_token_asset_out',
claims: {
signature: async ({ userDid }) => {
// Find a transferable asset owned by the user
const { assets } = await client.listAssets({ ownerAddress: userDid });
const asset = assets.find(x => x.transferrable);
if (!asset) {
throw new Error('You do not have any asset that can be transferred');
}
return {
type: 'TransferV2Tx',
data: {
itx: {
to: wallet.address, // Application's receiving address
assets: [asset.address], // Add the asset's address
tokens: [{ address: env.localTokenId, value: (await client.fromTokenToUnit(1)).toString() }] // Add a token fee
},
},
description: `Please send me the certificate ${asset.address} and 1 TKN`,
};
},
},
// The onAuth handler is nearly identical to the simple token transfer example
}
Notice the itx
object now contains both an assets
array and a tokens
array. The onAuth
handler for this case is functionally the same, as sendTransferV2Tx
is designed to handle these multi-component transactions.
With this pattern, you can build a wide range of payment and transfer functionalities into your application. To see how to chain multiple requests together, such as asking for a profile and then a payment, proceed to the Multi-Step Session example.