Used to check for browser translation.
用于检测浏览器翻译。
ブラウザの翻訳を検出する
Examples

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

type

TransferV2Tx specifies that the user is being asked to sign a transfer transaction.

data.itx.to

The recipient's address. In this case, it's the application's own wallet address.

data.itx.tokens

An array of token objects to be transferred.

tokens[].address

The contract address of the token being transferred.

tokens[].value

The amount of the token to transfer, specified in its smallest unit (e.g., wei). The @ocap/util helper fromTokenToUnit is used for this conversion.

description

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:

  1. Finding the signature claim from the wallet's response.
  2. Decoding the transaction data.
  3. Creating a wallet instance for the user from their DID.
  4. Calling client.sendTransferV2Tx with the transaction, user's wallet, and signature to broadcast it.
  5. 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.