simulationResolvePlugin function

TransactionPlugin simulationResolvePlugin(
  1. SuiCoreClient client, {
  2. ResolveSimulate? simulate,
})

The node fills in object versions, gas price, budget and payment, selecting gas when budget or payment is unset. simulate defaults to simulateTransaction.

Implementation

TransactionPlugin simulationResolvePlugin(
  SuiCoreClient client, {
  ResolveSimulate? simulate,
}) {
  simulate ??=
      (request, {required doGasSelection, required checksEnabled}) async {
        final result = await client.simulateTransaction(
          request,
          doGasSelection: doGasSelection,
          checksEnabled: checksEnabled,
          include: const TransactionIncludeOptions(effects: true, bcs: true),
        );
        return (bcs: result.bcs, epoch: result.epoch, status: result.status);
      };

  return (data, options, next) async {
    final original = data.snapshot();
    final onlyKind = options.onlyTransactionKind;
    final doGasSelection =
        !onlyKind &&
        (original.gasData.budget == null || original.gasData.payment == null);

    final request = Transaction()
      .._blockData = TransactionBlockDataBuilder(data.snapshot());
    request._blockData.sender ??= normalizeSuiAddress('0x0');

    final ResolvedSimulation result;
    try {
      result = await simulate!(
        request,
        doGasSelection: doGasSelection,
        checksEnabled: !onlyKind,
      );
    } on Exception catch (error) {
      throw SimulationError(
        error is GrpcError ? (error.message ?? '$error') : '$error',
        cause: error,
      );
    }

    if (!onlyKind && !result.status.success) {
      final executionError = result.status.error;
      throw SimulationError(
        'Transaction resolution failed: '
        '${executionError?.message ?? 'Transaction failed'}',
        executionError: executionError,
      );
    }
    final bytes = result.bcs;
    if (bytes == null) {
      throw StateError('simulateTransaction returned no resolved transaction');
    }

    final resolved = TransactionBlockDataBuilder.fromBytes(bytes).snapshot();
    if (onlyKind) {
      resolved
        ..sender = null
        ..expiration = null
        ..gasData = GasConfig();
    }
    data.applyResolvedData(resolved);

    // an explicit empty payment has no coin version to stop a replay
    final current = data.expiration;
    final hasWindow =
        current != null &&
        (current.epoch != null ||
            current.validDuring != null ||
            current.validity != null);
    if (!onlyKind &&
        !doGasSelection &&
        original.expiration == null &&
        original.gasData.payment?.isEmpty == true &&
        !hasWindow) {
      final chain = await client.getChainIdentifier();
      final epoch = BigInt.parse(
        result.epoch ?? (await client.getCurrentSystemState()).epoch,
      );
      data.expiration = TransactionExpiration(
        validDuring: {
          'minEpoch': epoch.toString(),
          'maxEpoch': (epoch + BigInt.one).toString(),
          'minTimestamp': null,
          'maxTimestamp': null,
          'chain': chain,
          'nonce': Random().nextInt(0x100000000),
        },
      );
    }

    return next();
  };
}