validateSolanaPayTransaction method

Future<TransactionDetails> validateSolanaPayTransaction({
  1. required TransactionId signature,
  2. required Ed25519HDPublicKey recipient,
  3. required Decimal amount,
  4. Ed25519HDPublicKey? splToken,
  5. Iterable<Ed25519HDPublicKey>? reference,
  6. TokenProgramType tokenProgramType = TokenProgramType.tokenProgram,
  7. Commitment commitment = Commitment.finalized,
})

Validates that a given transaction signature corresponds with a transaction containing a valid Solana Pay transfer.

Implementation

Future<TransactionDetails> validateSolanaPayTransaction({
  required TransactionId signature,
  required Ed25519HDPublicKey recipient,
  required Decimal amount,
  Ed25519HDPublicKey? splToken,
  Iterable<Ed25519HDPublicKey>? reference,
  TokenProgramType tokenProgramType = TokenProgramType.tokenProgram,
  Commitment commitment = Commitment.finalized,
}) async {
  final response = await rpcClient.getTransaction(signature, commitment: commitment);

  if (response == null) {
    throw const ValidateTransactionException('Transaction not found.');
  }

  final meta = response.meta;
  if (meta == null) {
    throw const ValidateTransactionException('Missing meta.');
  }

  if (meta.err != null) {
    throw const ValidateTransactionException('Transaction error.');
  }

  final Decimal preAmount, postAmount;
  if (splToken == null) {
    // ignore: avoid-type-casts, controlled type
    final accountIndex = (response.transaction as ParsedTransaction).message.accountKeys
        .indexWhere((a) => a.pubkey == recipient.toBase58());
    if (accountIndex == -1) {
      throw const ValidateTransactionException('Recipient not found.');
    }

    preAmount = Decimal.fromInt(meta.preBalances[accountIndex]).shift(-solDecimalPlaces);
    postAmount = Decimal.fromInt(meta.postBalances[accountIndex]).shift(-solDecimalPlaces);
  } else {
    final recipientATA = await findAssociatedTokenAddress(
      owner: recipient,
      mint: splToken,
      tokenProgramType: tokenProgramType,
    );
    // ignore: avoid-type-casts, controlled type
    final accountIndex = (response.transaction as ParsedTransaction).message.accountKeys
        .indexWhere((a) => a.pubkey == recipientATA.toBase58());
    if (accountIndex == -1) {
      throw const ValidateTransactionException('Recipient not found.');
    }

    final preBalance = meta.preTokenBalances.firstWhereOrNull(
      (a) => a.accountIndex == accountIndex,
    );
    final postBalance = meta.postTokenBalances.firstWhereOrNull(
      (a) => a.accountIndex == accountIndex,
    );

    preAmount = Decimal.parse(preBalance?.uiTokenAmount.uiAmountString ?? '0');
    postAmount = Decimal.parse(postBalance?.uiTokenAmount.uiAmountString ?? '0');
  }

  if (postAmount - preAmount < amount) {
    throw const ValidateTransactionException('Amount not transferred.');
  }

  if (reference != null) {
    // ignore: avoid-type-casts, controlled type
    final keys = (response.transaction as ParsedTransaction).message.accountKeys.map(
      (e) => e.pubkey,
    );
    if (reference.any((e) => !keys.contains(e.toBase58()))) {
      throw const ValidateTransactionException('Reference not found.');
    }
  }

  return response;
}