Skip to main content

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, NetworkId } from "@den/sdk";
import { privateKeyToAccount } from "viem/accounts";
import { encodeFunctionData } from "viem";

const signer = privateKeyToAccount("0xYOUR_PRIVATE_KEY");

const client = new DenClient({
  apiKey: "ck_live_...",
  rpcProviders: {
    1: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
  },
});

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 data = encodeFunctionData({
  abi: erc20TransferAbi,
  functionName: "transfer",
  args: ["0xRecipient...", 250_000_000n],
});

3) Create the transaction

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

4) Sign and execute

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

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

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

Full example

import { DenClient, NetworkId } from "@den/sdk";
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: "ck_live_...",
  rpcProviders: {
    1: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
  },
});

// 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 data = encodeFunctionData({
  abi: erc20TransferAbi,
  functionName: "transfer",
  args: ["0xRecipient...", 250_000_000n],
});

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

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

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

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