simulationResolvePlugin function
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();
};
}