Skip to main content

Transaction Payment

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:

  1. 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/client to construct a raw transaction.

  2. Claim Request

    The application initiates a DID Connect session and requests a signature claim from the user. The raw transaction data is embedded within this claim request.

  3. 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.

  4. 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

javascript
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

javascript
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:

  • userDid and userPk are provided by the DID Connect session context after the user has connected their wallet.
  • We use client.encodeTransferV2Tx to create a standard token transfer transaction.
  • The to address is our application's wallet address, where the funds will be sent.
  • The requirement object 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.