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