This guide covers Hedera-specific concepts when using Dfns, including address formats, transaction types, and smart contract interactions.
Hedera uses two address formats:
| Format | Example | When to use |
|---|
| Native | 0.0.1234567 | Hedera-native operations, account references |
| EVM | 0x00000000000000000000000000000012d687 | Smart contract interactions, EVM compatibility |
Native to EVM:
function nativeToEvm(accountId: string): string {
const [shard, realm, num] = accountId.split('.').map(Number)
// Hedera EVM addresses encode shard.realm.num in the last 20 bytes
const evmAddress =
'0x' +
shard.toString(16).padStart(8, '0') +
realm.toString(16).padStart(8, '0') +
num.toString(16).padStart(8, '0')
return evmAddress
}
// Example: "0.0.1234567" -> "0x000000000000000000000000000012d687"
EVM to Native:
function evmToNative(evmAddress: string): string {
const hex = evmAddress.slice(2) // remove 0x
const shard = parseInt(hex.slice(0, 8), 16)
const realm = parseInt(hex.slice(8, 16), 16)
const num = parseInt(hex.slice(16, 24), 16)
return `${shard}.${realm}.${num}`
}
// Example: "0x000000000000000000000000000012d687" -> "0.0.1234567"
Dfns wallets on Hedera return the native format (e.g., 0.0.1234567) in the address field. Convert to EVM format when needed for smart contract calls.
Transaction types
Native Hedera transactions
Use the Hiero/Hashgraph SDK to build native Hedera transactions:
import { Client, TransferTransaction, Hbar } from '@hashgraph/sdk'
const walletId = 'wa-xxxxx-xxxxx-xxxxxxxxxxxxxxxx'
const wallet = await dfnsClient.wallets.getWallet({ walletId })
// Create a Hedera client (for transaction building only)
const client = Client.forMainnet()
// Build the transaction
const transaction = await new TransferTransaction()
.addHbarTransfer(recipient, Hbar.fromTinybars(1000000)) // 0.01 HBAR
.addHbarTransfer(wallet.address!, Hbar.fromTinybars(-1000000))
.freezeWith(client)
// Broadcast via Dfns
const res = await dfnsClient.wallets.broadcastTransaction({
walletId,
body: {
kind: 'Transaction',
transaction: `0x${Buffer.from(transaction.toBytes()).toString('hex')}`,
},
})
Always call freezeWith(client) before serializing. This freezes the transaction with network parameters (node IDs, valid duration) required for signing.
Smart contract interactions
For smart contract calls, use ContractExecuteTransaction:
import {
Client,
ContractExecuteTransaction,
ContractFunctionParameters,
Hbar
} from '@hashgraph/sdk'
const contractId = '0.0.123456' // Your contract's native ID
const client = Client.forMainnet()
// Build contract call
const transaction = await new ContractExecuteTransaction()
.setContractId(contractId)
.setGas(100000)
.setFunction(
'transfer',
new ContractFunctionParameters()
.addAddress(recipientEvmAddress)
.addUint256(amount)
)
.freezeWith(client)
// Broadcast via Dfns
const res = await dfnsClient.wallets.broadcastTransaction({
walletId,
body: {
kind: 'Transaction',
transaction: `0x${Buffer.from(transaction.toBytes()).toString('hex')}`,
},
})
Common errors
”Transaction ID not found” or similar
This usually means the transaction wasn’t properly frozen before serialization.
Solution: Ensure you call freezeWith(client) with a properly configured client:
// Correct
const client = Client.forMainnet() // or Client.forTestnet()
const tx = await new TransferTransaction()
// ... add transfers
.freezeWith(client) // Required!
// Then serialize
const bytes = tx.toBytes()
If you’re getting address format errors, ensure you’re using the right format:
- Native Hedera APIs expect
0.0.xxxxx
- EVM/Solidity contracts expect
0x...
Use the conversion functions above to switch between formats.
Gas estimation failures
For contract calls, start with a higher gas limit and adjust:
.setGas(200000) // Start high, reduce based on actual usage
Check actual gas used in the transaction receipt to optimize.