デスクトップウォレットのトランザクション手数料

## 何を元に計算しているのか

デスクトップウォレットの手数料計算は、以下RESTから取得した`averageFeeMultiplier`値で決定しているようです。

<https://testnet1.symbol-mikun.net:3001/network/fees/transaction>

この値はRESTのデフォルト設定では、直近300ブロックの手数料乗数の平均となっています。トランザクションなし or 手数料0で承認した場合では、以下の様になります(平均が100なのは、乗数0の場合100として扱われるため)。このときのウォレットの手数料は、早いで0.0176XYMです。

```json
{
  "averageFeeMultiplier": 100,
  "medianFeeMultiplier": 100,
  "highestFeeMultiplier": 0,
  "lowestFeeMultiplier": 0,
  "minFeeMultiplier": 10
}
```

![スクリーンショット 2024-10-26 055953.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622558/96338e3d-41f7-1a6c-ad66-f1fda523d8b8.png)

上記にトランザクションを手数料乗数100で発行すると、`highestFeeMultiplier`が100になります。

```json
{
  "averageFeeMultiplier": 100,
  "medianFeeMultiplier": 100,
  "highestFeeMultiplier": 100,
  "lowestFeeMultiplier": 0,
  "minFeeMultiplier": 10
}
```

さらに、トランザクションを手数料乗数1000で発行すると、`highestFeeMultiplier`が1000になり、`averageFeeMultiplier`も上昇します。ウォレットの手数料も変化して0.018128XYMになります。

```json
{
  "averageFeeMultiplier": 103,
  "medianFeeMultiplier": 100,
  "highestFeeMultiplier": 1000,
  "lowestFeeMultiplier": 0,
  "minFeeMultiplier": 10
}
```

![スクリーンショット 2024-10-26 071837.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622558/9bcfb922-1fc7-6666-d1a2-a1f15b521213.png)

## どいう計算なのか

結論から言うと以下です。

早い: `averageFeeMultiplier * TxSize`  
平均: `(minFeeMultiplier + averageFeeMultiplier * 0.65) * TxSize`  
遅い: `(minFeeMultiplier + averageFeeMultiplier * 0.35) * TxSize`  
最遅: `minFeeMultiplier * TxSize`

TxSize…トランザクションサイズは、メッセージのない転送の場合は176バイトです。
なので、以下の`/network/fees/transaction`の場合

```json
{
  "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

```typescript
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)
```