resolveCoinBalanceIntents function
Future<void>
resolveCoinBalanceIntents(
- TransactionBlockDataBuilder data,
- TransactionBuilderClient? client
Implementation
Future<void> resolveCoinBalanceIntents(
TransactionBlockDataBuilder data, TransactionBuilderClient? client) async {
if (!data.commands.any(_isCwbIntent)) return;
if (data.sender == null) {
throw ArgumentError('Sender must be set to resolve CoinWithBalance');
}
if (client == null) {
throw ArgumentError('Client must be provided to build transactions '
'with CoinWithBalance intents');
}
final totalByType = <String, BigInt>{};
final intentsByType = <String, List<BigInt>>{};
// First pass: sum required balances per coin type and resolve any
// zero-balance intents in place (they need no coins).
for (var i = 0; i < data.commands.length; i++) {
final cmd = data.commands[i];
if (!_isCwbIntent(cmd)) continue;
final d = cmd[r'$Intent']['data'] as Map;
final type = d['type'] as String;
final balance = BigInt.parse(d['balance'].toString());
final outputKind = (d['outputKind'] ?? 'coin') as String;
if (outputKind != 'coin') {
throw UnsupportedError(
'CoinWithBalance outputKind "$outputKind" (address-balance) is not '
'supported');
}
if (balance == BigInt.zero) {
final coinType = type == 'gas' ? _suiType : type;
_replaceCommand(data, i, [_moveCall('$_coinPackage::coin::zero', [
coinType
], const [])]);
continue;
}
totalByType[type] = (totalByType[type] ?? BigInt.zero) + balance;
(intentsByType[type] ??= <BigInt>[]).add(balance);
}
if (totalByType.containsKey('gas') && totalByType.containsKey(_suiType)) {
throw ArgumentError('Cannot mix SUI CoinWithBalance intents that use the '
'gas coin with ones that do not (useGasCoin: false).');
}
// Object ids already used by other inputs must not be re-selected as coins.
final usedIds = <String>{};
for (final input in data.inputs) {
final owned = input['Object']?['ImmOrOwnedObject']?['objectId'];
if (owned != null) usedIds.add(normalizeSuiAddress(owned));
final unresolved = input['UnresolvedObject']?['objectId'];
if (unresolved != null) usedIds.add(normalizeSuiAddress(unresolved));
}
final coinsByType = <String, List<CoinStruct>>{};
for (final entry in totalByType.entries) {
if (entry.key == 'gas') continue;
coinsByType[entry.key] = await _selectCoins(
client, data.sender!, entry.key, entry.value, usedIds);
}
// Second pass: replace each intent with the real commands. All intents of a
// type share one merge+split, produced when the first is encountered.
final typeResults = <String, List<dynamic>>{};
final typeNext = <String, int>{};
var index = 0;
while (index < data.commands.length) {
final cmd = data.commands[index];
if (!_isCwbIntent(cmd)) {
index++;
continue;
}
final type = cmd[r'$Intent']['data']['type'] as String;
final commands = <Map<String, dynamic>>[];
if (!typeResults.containsKey(type)) {
final intents = intentsByType[type]!;
final sources = <dynamic>[];
if (type == 'gas') {
sources.add({r'$kind': 'GasCoin', 'GasCoin': true});
} else {
for (final coin in coinsByType[type]!) {
sources.add(data.addInput(
'object',
Inputs.objectRef(
SuiObjectRef(coin.digest, coin.coinObjectId, coin.version))));
}
}
final baseCoin = sources.first;
final rest = sources.sublist(1);
for (var i = 0; i < rest.length; i += 500) {
commands.add(_mergeCoins(baseCoin, rest.sublist(i, min(i + 500, rest.length))));
}
final splitCmdIndex = index + commands.length;
commands.add(_splitCoins(
baseCoin,
intents
.map((b) =>
data.addInput('pure', Inputs.pure(SuiBcs.U64.serialize(b))))
.toList()));
typeResults[type] = [
for (var i = 0; i < intents.length; i++)
{r'$kind': 'NestedResult', 'NestedResult': [splitCmdIndex, i]}
];
typeNext[type] = 0;
}
final intentResult = typeResults[type]![typeNext[type]!];
typeNext[type] = typeNext[type]! + 1;
_replaceCommand(data, index, commands, intentResult);
index += commands.length;
}
}