Skip to main content

Get started by creating a transaction

Creating a transaction means assembling a transfer or contract call, submitting it for policy checks, and collecting the required signatures. The SDK helps you propose the transaction, tracks approvals, and executes once thresholds are met. This quickstart walks through getting accounts, selecting a policy, and creating a transaction proposal using the SDK.
We firmly recommend using the SDK instead of calling the API directly. The SDK performs local policy validation, which adds an additional layer of security to every account transaction.

1) Set up the client and signer

Initialize the SDK client and the signer that will submit approvals. You’ll need an API key created by an admin in the Den dashboard, and your signer wallet must be added to the organization as a member to propose, sign, and execute transactions. See Setting up an API member for the full setup process.
import { DenClient } from "@den/sdk";
import { privateKeyToAccount } from "viem/accounts";

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) List accounts

Fetch available accounts so you can choose a transaction source.
const accounts = await client.getAccounts({ limit: 20 });
const accountId = accounts.data[0].id;

3) List policies

Load policies and select one that matches the transaction you want to create.
const policies = await client.getPolicies();
const policyId = policies.data[0].id;

4) Create a transaction proposal

Create a proposal with the selected account, policy, and transaction details.
const queued = await client.createTransaction(accountId, {
  networkId: 1,
  policyId,
  description: "Payment to vendor",
  to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  value: "0",
  data: "0xa9059cbb...",
});
The initial response includes a signatureData object with the initiator signing payload. After the initiator signs, transaction responses use the same signatureData object to expose the approve and reject payloads.

5) Sign as the initiator

Sign the initiator payload to authorize the transaction.
const initiatorSig = await signer.signMessage({
  message: queued.signatureData.initiatorPayload,
});

const updatedTransaction = await client.signTransaction(accountId, queued.id, {
  type: "initiator",
  signature: initiatorSig,
});
For MANUAL_APPROVAL policies, approvers then submit their approve or reject signatures.

6) Execute once threshold is met

Execute the transaction after policy approvals are satisfied.
if (updatedTransaction.signatureData.status === "approvalReady") {
  const executed = await client.executeTransaction(accountId, queued.id, {
    type: "approve",
  });
  console.log(executed.result?.transactionHash);
}
If the approval threshold is met, the transaction is submitted on-chain and the response includes a transactionHash.

Production signing setups

The privateKeyToAccount example in this guide is for local development. Here are some example patterns you can use in production See Signing keys to learn more.
Cloud KMS
import { toAccount } from "viem/accounts";
import { KMSClient, SignCommand } from "@aws-sdk/client-kms";

const kms = new KMSClient({ region: "us-east-1" });

const signer = toAccount({
  address: "0xYOUR_WALLET_ADDRESS",
  signMessage: async ({ message }) => {
    const res = await kms.send(new SignCommand({ ... }));
    return res.Signature;
  },
});
HSM
import { toAccount } from "viem/accounts";

const signer = toAccount({
  address: "0xYOUR_WALLET_ADDRESS",
  signMessage: async ({ message }) => {
    return await hsmClient.sign(message);
  },
});
Environment variable
import { privateKeyToAccount } from "viem/accounts";

const signer = privateKeyToAccount(process.env.SIGNER_PRIVATE_KEY as `0x${string}`);

Full example

import { DenClient } from "@den/sdk";
import { privateKeyToAccount } from "viem/accounts";

// 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) List accounts.
const accounts = await client.getAccounts({ limit: 20 });
const accountId = accounts.data[0].id;

// 3) List policies.
const policies = await client.getPolicies();
const policyId = policies.data[0].id;

// 4) Create a transaction proposal.
const queued = await client.createTransaction(accountId, {
  networkId: 1,
  policyId,
  description: "Payment to vendor",
  to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  value: "0",
  data: "0xa9059cbb...",
});

// 5) Sign as the initiator.
const initiatorSig = await signer.signMessage({
  message: queued.signatureData.initiatorPayload,
});

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

// 6) Execute once threshold is met.
if (updatedTransaction.signatureData.status === "approvalReady") {
  const executed = await client.executeTransaction(accountId, queued.id, {
    type: "approve",
  });
  console.log(executed.result?.transactionHash);
}