Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mls.onchainden.com/llms.txt

Use this file to discover all available pages before exploring further.

What is a token transfer transaction?

A token transfer transaction moves ERC-20 tokens from one account to another. This guide uses an ERC-20 transfer (USDC) as a reference example. Replace the token, decimals, and amount for your asset.

1) Set up the client and signer

Create the SDK client and signer used for approvals.
import { DenClient } from "@onchainden/mls-sdk-ts";
import { privateKeyToAccount } from "viem/accounts";
import { encodeFunctionData } from "viem";

const signer = privateKeyToAccount("0xYOUR_PRIVATE_KEY");

const client = new DenClient({
  apiKey: process.env.DEN_API_KEY!,
  baseUrl: process.env.DEN_API_BASE_URL!,
});

2) Build the transfer data

Encode the ERC-20 transfer call data for the token transfer.
const erc20TransferAbi = [
  {
    name: "transfer",
    type: "function",
    stateMutability: "nonpayable",
    inputs: [
      { name: "to", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    outputs: [{ name: "success", type: "bool" }],
  },
];

const callData = encodeFunctionData({
  abi: erc20TransferAbi,
  functionName: "transfer",
  args: ["0xRecipient...", 250_000_000n],
});

3) Create the transaction

Create a transaction proposal using the encoded transfer data.
const { data: queued } = await client.createTransaction({
  accountId: "acc_123",
  initiatorWalletAddress: signer.address,
  networkId: 1,
  policyId: "pol_123",
  description: "USDC payout",
  to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  value: "0",
  data: callData,
});

4) Sign and execute

Sign as the initiator, then execute once approvals are ready.
const initiatorSig = await signer.signMessage({
  message: queued.signatureData.initiatorPayload,
});

const { data: afterInitiator } = await client.signTransaction(queued.id, {
  type: "initiator",
  signature: initiatorSig,
});

if (afterInitiator.signatureData.status === "approvalReady") {
  await client.executeTransaction(queued.id, { type: "approve" });
}

Full example

import { DenClient } from "@onchainden/mls-sdk-ts";
import { privateKeyToAccount } from "viem/accounts";
import { encodeFunctionData } from "viem";

// 1) Set up the client and signer.
const signer = privateKeyToAccount("0xYOUR_PRIVATE_KEY");

const client = new DenClient({
  apiKey: process.env.DEN_API_KEY!,
  baseUrl: process.env.DEN_API_BASE_URL!,
});

// 2) Build the transfer data.
const erc20TransferAbi = [
  {
    name: "transfer",
    type: "function",
    stateMutability: "nonpayable",
    inputs: [
      { name: "to", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    outputs: [{ name: "success", type: "bool" }],
  },
];

const callData = encodeFunctionData({
  abi: erc20TransferAbi,
  functionName: "transfer",
  args: ["0xRecipient...", 250_000_000n],
});

// 3) Create the transaction.
const { data: queued } = await client.createTransaction({
  accountId: "acc_123",
  initiatorWalletAddress: signer.address,
  networkId: 1,
  policyId: "pol_123",
  description: "USDC payout",
  to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  value: "0",
  data: callData,
});

// 4) Sign and execute.
const initiatorSig = await signer.signMessage({
  message: queued.signatureData.initiatorPayload,
});

const { data: afterInitiator } = await client.signTransaction(queued.id, {
  type: "initiator",
  signature: initiatorSig,
});

if (afterInitiator.signatureData.status === "approvalReady") {
  await client.executeTransaction(queued.id, { type: "approve" });
}