Creating Transfers via API

This topic explains how to create transfers, handle different asset types, and monitor transfer status using the IBM Digital Asset Haven API and SDK.

Prerequisites

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

Creating a native token transfer

Send native tokens (such as ETH, MATIC, or SOL):
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
Parameter Required Description
kind Yes Transfer type: Native, Erc20, Erc721, etc.
to Yes Recipient address
amount Yes Amount in base units (wei for ETH)
externalId No 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)
  }
})
Note: 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 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
Retrieve 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
Status Description
Pending Transfer created; may be awaiting approval
Broadcasted Transaction sent to the network
Confirmed Transaction confirmed on the blockchain
Failed Transaction 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 requiring approval, it 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
}
For more information on programmatic approvals, see managing policies.

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')
})

For more information on setting up webhooks, see the Webhooks guide.

Polling for status

Polling is an alternative to webhooks.

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))
  }
}

For best practices on polling, see Transaction monitoring.

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)
  }
}