何を元に計算しているのか
デスクトップウォレットの手数料計算は、以下RESTから取得したaverageFeeMultiplier
値で決定しているようです。
https://testnet1.symbol-mikun.net:3001/network/fees/transaction
この値はRESTのデフォルト設定では、直近300ブロックの手数料乗数の平均となっています。トランザクションなし or 手数料0で承認した場合では、以下の様になります(平均が100なのは、乗数0の場合100として扱われるため)。このときのウォレットの手数料は、早いで0.0176XYMです。
{
"averageFeeMultiplier": 100,
"medianFeeMultiplier": 100,
"highestFeeMultiplier": 0,
"lowestFeeMultiplier": 0,
"minFeeMultiplier": 10
}
上記にトランザクションを手数料乗数100で発行すると、highestFeeMultiplier
が100になります。
{
"averageFeeMultiplier": 100,
"medianFeeMultiplier": 100,
"highestFeeMultiplier": 100,
"lowestFeeMultiplier": 0,
"minFeeMultiplier": 10
}
さらに、トランザクションを手数料乗数1000で発行すると、highestFeeMultiplier
が1000になり、averageFeeMultiplier
も上昇します。ウォレットの手数料も変化して0.018128XYMになります。
{
"averageFeeMultiplier": 103,
"medianFeeMultiplier": 100,
"highestFeeMultiplier": 1000,
"lowestFeeMultiplier": 0,
"minFeeMultiplier": 10
}
どいう計算なのか
結論から言うと以下です。
早い: averageFeeMultiplier * TxSize
平均: (minFeeMultiplier + averageFeeMultiplier * 0.65) * TxSize
遅い: (minFeeMultiplier + averageFeeMultiplier * 0.35) * TxSize
最遅: minFeeMultiplier * TxSize
TxSize…トランザクションサイズは、メッセージのない転送の場合は176バイトです。
なので、以下の/network/fees/transaction
の場合
{
"averageFeeMultiplier": 103,
"medianFeeMultiplier": 100,
"highestFeeMultiplier": 1000,
"lowestFeeMultiplier": 0,
"minFeeMultiplier": 10
}
早い: 103 * 176 = 18128
平均: (10 + 103 * 0.65) * 176 = 13543.2
遅い: (10 + 103 * 0.35) * 176 = 8104.8
最遅: 10 * 176 = 1760
XYMの可分性が6なので、10^-6
を掛けた値が手数料になります。
手数料は何を設定すればいいのか
minFeeMultiplier
は各ノードで変更できる値でデフォルト値は100です。なので、minFeeMultiplier
を低く設定してあるノードを選択すれば、平均や遅いの手数料も減ります。このminFeeMultiplier
の条件を満たさない限り、ノードはトランザクションを受け付けず、未承認にすらなりません。
ただ、メインネットでは75や50に設定してあるノードがそこそこあるので、手数料なし以外なら何でもいいかなと思います。トランザクションほぼ飛んでませんし空いてますからね!
おまけ
トランザクションサイズ調べたときのコード。SDK v3.2.2
import { PrivateKey, PublicKey, utils } from 'symbol-sdk'
import {
Address,
Network,
SymbolFacade,
SymbolTransactionFactory,
descriptors,
generateNamespacePath,
models,
} from 'symbol-sdk/symbol'
import { SymConst } from '../SymConst.js'
const facade = new SymbolFacade(Network.TESTNET.name)
// アカウント
const aliceAccount = facade.createAccount(
new PrivateKey(SymConst.ALICE_PRIVATE_KEY)
)
const bobPublicKey = new PublicKey(utils.hexToUint8(SymConst.BOB_PUBLIC_KEY))
const bobAddress = new Address(facade.network.publicKeyToAddress(bobPublicKey))
// 転送トランザクション
const namespaceIds = generateNamespacePath('symbol.xym')
const namespaceId = namespaceIds[namespaceIds.length - 1]
const transferTxDescriptor = new descriptors.TransferTransactionV1Descriptor(
bobAddress,
[
new descriptors.UnresolvedMosaicDescriptor(
new models.UnresolvedMosaicId(namespaceId),
new models.Amount(1_000000n)
),
]
)
const transferTx = facade.createTransactionFromTypedDescriptor(
transferTxDescriptor,
aliceAccount.publicKey,
100, // feeMultiplier
60 * 60 * 2
)
// アリス署名
const sig = aliceAccount.signTransaction(transferTx)
const payloadJson = SymbolTransactionFactory.attachSignature(transferTx, sig)
console.log(`txHash: ${facade.hashTransaction(transferTx)}`)
console.log(`txSize: ${transferTx.size}`)
// // RESTにアナウンス
// const transactionsResponse = await fetch(
// new URL('/transactions', SymConst.REST_GATEWAY_URL),
// {
// method: 'PUT',
// headers: { 'Content-Type': 'application/json' },
// body: payloadJson,
// }
// )
// console.log((await transactionsResponse.json()).message)