sendTransaction method
Builds, signs, and broadcasts a native SUI transfer. Returns its digest.
Implementation
@override
Future<String> sendTransaction(SuiTxRequest request, [SuiNetwork? network]) async {
Future<String> instrumentationOperation() async {
final operationScope = _state.flowScope.capture(replace: false);
await operationScope.ready();
operationScope.check();
final configured = _suiService.getNetworks((_state.projectSettings?.networks?.map((networkGroup) => (networkGroup.chainName == "sui" ? networkGroup.networks?.map((networkConfiguration) => NetworkData(chain: "SUI", networkId: networkConfiguration.networkId, name: networkConfiguration.name, displayName: (networkConfiguration.vanityName == "" ? networkConfiguration.name : (networkConfiguration.vanityName ?? networkConfiguration.name)), rpcUrls: NetworkRpcUrls(http: [...(networkConfiguration.privateCustomerRpcUrls ?? <String>[]), ...networkConfiguration.rpcUrls]), blockExplorerUrls: (networkConfiguration.blockExplorerUrls ?? <String>[]), iconUrl: firstOrNull(networkConfiguration.iconUrls), nativeCurrency: networkConfiguration.nativeCurrency, cluster: networkConfiguration.cluster, genesisHash: networkConfiguration.genesisHash, testnet: (networkConfiguration.testnet ?? false))).toList() ?? const [] : <NetworkData>[])).toList() ?? const []).expand((items) => items).toList(), (_suiState.suiNetworks ?? <SuiNetwork>[]));
operationScope.check();
final selected = firstWhereOrNull<SuiNetwork>(configured, (item) => item.name == (_state.activeSuiNetworkName ?? ""));
final requestedNetwork = (network ?? selected ?? firstOrNull(configured));
if (requestedNetwork == null) {
throw DynamicStateException("no Sui GraphQL networks configured; provide one with useSui");
}
operationScope.check();
final resolvedNetworks = _suiService.getNetworks((_state.projectSettings?.networks?.map((networkGroup) => (networkGroup.chainName == "sui" ? networkGroup.networks?.map((networkConfiguration) => NetworkData(chain: "SUI", networkId: networkConfiguration.networkId, name: networkConfiguration.name, displayName: (networkConfiguration.vanityName == "" ? networkConfiguration.name : (networkConfiguration.vanityName ?? networkConfiguration.name)), rpcUrls: NetworkRpcUrls(http: [...(networkConfiguration.privateCustomerRpcUrls ?? <String>[]), ...networkConfiguration.rpcUrls]), blockExplorerUrls: (networkConfiguration.blockExplorerUrls ?? <String>[]), iconUrl: firstOrNull(networkConfiguration.iconUrls), nativeCurrency: networkConfiguration.nativeCurrency, cluster: networkConfiguration.cluster, genesisHash: networkConfiguration.genesisHash, testnet: (networkConfiguration.testnet ?? false))).toList() ?? const [] : <NetworkData>[])).toList() ?? const []).expand((items) => items).toList(), <SuiNetwork>[requestedNetwork]);
operationScope.check();
final resolvedNetwork = firstOrNull(resolvedNetworks);
if (resolvedNetwork == null) {
throw DynamicStateException("no Sui GraphQL networks configured; provide one with useSui");
}
final chainId = resolvedNetwork.networkId;
if (chainId == null) {
throw DynamicStateException("Sui signing requires a networkId; set it for custom clusters");
}
if (!((chainId).length > 0)) {
throw DynamicStateException("Sui networkId must not be empty");
}
operationScope.check();
final gasPrice = await _suiService.getReferenceGasPrice(resolvedNetwork);
operationScope.check();
final gasBudget = request.gasBudget ?? "10000000";
final needed = dynParseLong(request.mist) + dynParseLong(gasBudget);
operationScope.check();
final candidates = await _suiService.getCoins(request.from, resolvedNetwork);
operationScope.check();
if (needed > candidates.fold<int>(0, (sum, item) => sum + item.balanceMist)) {
throw DynamicStateException("SUI coins do not cover the amount plus the gas budget");
}
final unsignedTransaction = SuiUnsignedTransaction(sender: request.from, recipient: request.to, mist: request.mist, gasCoins: candidates, gasPrice: gasPrice, gasBudget: gasBudget).transactionDataHex();
final signerCredential = firstOrNull(_state.user?.verifiedCredentials?.map((signerCandidate) => (((signerCandidate.format == "blockchain") && ((switch ((signerCandidate.chain ?? "")) { "eip155" => "EVM", "solana" => "SOL", "bip122" => "BTC", _ => (signerCandidate.chain ?? "").toUpperCase() }) == "SUI")) && ((("SUI" == "EVM" ? (signerCandidate.address ?? "").toLowerCase() : (signerCandidate.address ?? "")) == ("SUI" == "EVM" ? request.from.toLowerCase() : request.from)) || (firstOrNull(signerCandidate.walletAdditionalAddresses?.map((additionalAddress) => (("SUI" == "EVM" ? additionalAddress.address.toLowerCase() : additionalAddress.address) == ("SUI" == "EVM" ? request.from.toLowerCase() : request.from) ? additionalAddress : null)).where((v) => v != null).map((v) => v!).toList() ?? const []) != null)) ? signerCandidate : null)).where((v) => v != null).map((v) => v!).toList() ?? const []);
final embeddedSigner = _client.findCapability<EmbeddedWalletSigner>();
final externalSigner = _client.findCapability<ExternalWalletSigner>();
if (!((signerCredential != null) || !((embeddedSigner != null) && (externalSigner != null)))) {
throw DynamicStateException("wallet ownership is unknown; cannot choose between embedded and external signers");
}
final WalletSigner? selectedSigner = signerCredential != null ? (signerCredential?.walletProvider == "embeddedWallet" ? embeddedSigner : externalSigner) : _client.findCapability<WalletSigner>();
final signer = selectedSigner;
if (signer == null) {
throw DynamicStateException("no WalletSigner installed to sign with (e.g. useWaas) — add one first");
}
operationScope.check();
final signature = await signer.signTransaction("SUI", request.from, unsignedTransaction, chainId, null, null);
operationScope.check();
if ((signature).length == 128) {
throw DynamicStateException("Sui requires a serialized signature with its scheme flag and public key, not a bare 64-byte signature");
}
operationScope.check();
final digest = await _suiService.broadcastTransaction(dynBytesToBase64(dynHexToBytes(unsignedTransaction)), dynBytesToBase64(dynHexToBytes(signature)), resolvedNetwork);
operationScope.check();
return digest;
}
return await _api.instrumentation.runAsync("sui.sendTransaction", instrumentationOperation);
}