partiallySignTransaction function

Future<Transaction> partiallySignTransaction(
  1. List<KeyPair> keyPairs,
  2. Transaction transaction
)

Given a list of KeyPair objects which are key pairs pertaining to addresses that are required to sign a transaction, this method will return a new signed transaction.

Though the resulting transaction might have every signature it needs to land on the network, this function will not assert that it does.

Implementation

Future<Transaction> partiallySignTransaction(
  List<KeyPair> keyPairs,
  Transaction transaction,
) async {
  Map<Address, SignatureBytes>? newSignatures;
  Set<Address>? unexpectedSigners;

  for (final keyPair in keyPairs) {
    final addr = getAddressFromPublicKey(keyPair.publicKey);
    final existingSignature = transaction.signatures[addr];

    // Check if the address is expected to sign the transaction.
    if (!transaction.signatures.containsKey(addr)) {
      unexpectedSigners ??= <Address>{};
      unexpectedSigners.add(addr);
      continue;
    }

    // Skip if there are already unexpected signers since we won't be using
    // the signatures.
    if (unexpectedSigners != null) {
      continue;
    }

    final newSignature = signBytes(
      keyPair.privateKey,
      transaction.messageBytes,
    );

    if (existingSignature != null &&
        _bytesEqual(newSignature.value, existingSignature.value)) {
      // Already have the same signature.
      continue;
    }

    newSignatures ??= <Address, SignatureBytes>{};
    newSignatures[addr] = newSignature;
  }

  if (unexpectedSigners != null && unexpectedSigners.isNotEmpty) {
    final expectedSigners = transaction.signatures.keys.toList();
    throw SolanaError(
      SolanaErrorCode.transactionAddressesCannotSignTransaction,
      {
        'expectedAddresses': expectedSigners.map((a) => a.value).toList(),
        'unexpectedAddresses': unexpectedSigners.map((a) => a.value).toList(),
      },
    );
  }

  if (newSignatures == null) {
    return transaction;
  }

  final mergedSignatures = <Address, SignatureBytes?>{
    ...transaction.signatures,
    ...newSignatures,
  };

  if (transaction is TransactionWithLifetime) {
    return TransactionWithLifetime(
      messageBytes: transaction.messageBytes,
      signatures: mergedSignatures,
      lifetimeConstraint: transaction.lifetimeConstraint,
    );
  }

  return Transaction(
    messageBytes: transaction.messageBytes,
    signatures: mergedSignatures,
  );
}