Skip to main content
This guide covers how to create transfers, handle different asset types, and monitor transfer status using the Dfns API and SDK.

Prerequisites

  • Service account or authenticated user with Wallets:Transfers:Create permission
  • Dfns SDK installed and configured
  • Wallet with sufficient balance for the transfer and gas fees

Creating a native token transfer

Send native tokens (ETH, MATIC, SOL, etc.):
import { DfnsApiClient } from '@dfns/sdk'

const dfns = new DfnsApiClient({
  baseUrl: 'https://api.dfns.io',
  // Your signer configuration
})

const transfer = await dfns.wallets.createTransfer({
  walletId: 'wa-xxx-xxx',
  body: {
    kind: 'Native',
    to: '0x1234...recipient',
    amount: '1000000000000000000' // 1 ETH in wei
  }
})

console.log('Transfer ID:', transfer.id)
console.log('Status:', transfer.status)

Request parameters

ParameterRequiredDescription
kindYesTransfer type: Native, Erc20, Erc721, etc.
toYesRecipient address
amountYesAmount in base units (wei for ETH)
externalIdNoYour reference ID for tracking

Creating an ERC-20 token transfer

Send ERC-20 tokens:
const transfer = await dfns.wallets.createTransfer({
  walletId: 'wa-xxx-xxx',
  body: {
    kind: 'Erc20',
    contract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
    to: '0x1234...recipient',
    amount: '1000000' // 1 USDC (6 decimals)
  }
})
Token amounts are in base units. USDC has 6 decimals, so 1 USDC = 1000000. ETH has 18 decimals, so 1 ETH = 1000000000000000000.

Creating an NFT transfer

Transfer ERC-721 NFTs:
const transfer = await dfns.wallets.createTransfer({
  walletId: 'wa-xxx-xxx',
  body: {
    kind: 'Erc721',
    contract: '0x...nft-contract',
    to: '0x1234...recipient',
    tokenId: '123'
  }
})

Transfer with external ID

Use external IDs for your own tracking:
const transfer = await dfns.wallets.createTransfer({
  walletId: 'wa-xxx-xxx',
  body: {
    kind: 'Native',
    to: '0x1234...recipient',
    amount: '1000000000000000000',
    externalId: 'payout-2024-001' // Your reference
  }
})

Checking transfer status

Get the status of a transfer:
const transfer = await dfns.wallets.getTransfer({
  walletId: 'wa-xxx-xxx',
  transferId: 'tr-xxx-xxx'
})

console.log('Status:', transfer.status)
// 'Pending', 'Broadcasted', 'Confirmed', 'Failed'

Transfer statuses

StatusDescription
PendingTransfer created, may be awaiting approval
BroadcastedTransaction sent to the network
ConfirmedTransaction confirmed on the blockchain
FailedTransaction failed

Listing transfers

List transfers for a wallet:
const transfers = await dfns.wallets.listTransfers({
  walletId: 'wa-xxx-xxx'
})

for (const transfer of transfers.items) {
  console.log(`${transfer.id}: ${transfer.status}`)
}

Handling policy approvals

If a transfer triggers a policy that requires approval, the transfer enters a Pending state:
const transfer = await dfns.wallets.createTransfer({
  walletId: 'wa-xxx-xxx',
  body: {
    kind: 'Native',
    to: '0x1234...recipient',
    amount: '100000000000000000000' // Large amount
  }
})

if (transfer.status === 'Pending') {
  console.log('Transfer requires approval')
  // The transfer will execute automatically once approved
}
See managing policies for working with approvals programmatically.

Monitoring with webhooks

Subscribe to transfer events for real-time updates:
// Subscribe to events
await dfns.webhooks.createWebhook({
  body: {
    url: 'https://your-app.com/webhooks/dfns',
    events: [
      'wallet.transfer.broadcasted',
      'wallet.transfer.confirmed',
      'wallet.transfer.failed'
    ],
    status: 'Enabled'
  }
})
Handle webhook events:
app.post('/webhooks/dfns', async (req, res) => {
  const event = req.body

  switch (event.kind) {
    case 'wallet.transfer.confirmed':
      await handleTransferConfirmed(event.data)
      break
    case 'wallet.transfer.failed':
      await handleTransferFailed(event.data)
      break
  }

  res.status(200).send('OK')
})
See webhooks guide for complete webhook setup.

Polling for status

Alternative to webhooks - poll for transfer status:
async function waitForConfirmation(walletId: string, transferId: string) {
  while (true) {
    const transfer = await dfns.wallets.getTransfer({
      walletId,
      transferId
    })

    if (transfer.status === 'Confirmed') {
      return transfer
    }

    if (transfer.status === 'Failed') {
      throw new Error(`Transfer failed: ${transfer.error}`)
    }

    // Wait before checking again
    await new Promise(resolve => setTimeout(resolve, 5000))
  }
}
See transaction monitoring for polling best practices.

Error handling

Handle common errors:
try {
  const transfer = await dfns.wallets.createTransfer({
    walletId: 'wa-xxx-xxx',
    body: {
      kind: 'Native',
      to: '0x1234...recipient',
      amount: '1000000000000000000'
    }
  })
} catch (error) {
  if (error.status === 403) {
    console.error('Permission denied')
  } else if (error.status === 400) {
    console.error('Invalid request:', error.message)
  } else if (error.message.includes('insufficient balance')) {
    console.error('Insufficient balance for transfer')
  } else {
    console.error('Error creating transfer:', error)
  }
}