Skip to content
Permalink
Browse files Browse the repository at this point in the history
finish transfer validation implementation
  • Loading branch information
jordansexton committed Jul 28, 2022
1 parent f41701d commit ac6ce0d
Showing 1 changed file with 60 additions and 18 deletions.
78 changes: 60 additions & 18 deletions core/src/validateTransfer.ts
@@ -1,15 +1,23 @@
import { getAssociatedTokenAddress } from '@solana/spl-token';
import {
decodeInstruction,
getAssociatedTokenAddress,
isTransferCheckedInstruction,
isTransferInstruction,
} from '@solana/spl-token';
import {
ConfirmedTransactionMeta,
Connection,
Finality,
LAMPORTS_PER_SOL,
Message,
SystemInstruction,
Transaction,
TransactionResponse,
TransactionSignature,
} from '@solana/web3.js';
import BigNumber from 'bignumber.js';
import { Amount, Memo, Recipient, References, SPLToken } from './types';
import { MEMO_PROGRAM_ID } from './constants';
import { Amount, Memo, Recipient, Reference, References, SPLToken } from './types';

/**
* Thrown when a transaction doesn't contain a valid Solana Pay transfer.
Expand Down Expand Up @@ -58,33 +66,49 @@ export async function validateTransfer(
if (!meta) throw new ValidateTransferError('missing meta');
if (meta.err) throw meta.err;

const [preAmount, postAmount] = splToken
? await validateSPLTokenTransfer(message, meta, recipient, splToken)
: await validateSystemTransfer(message, meta, recipient);
if (reference && !Array.isArray(reference)) {
reference = [reference];
}

const [preAmount, postAmount] = splToken
? await validateSPLTokenTransfer(message, meta, recipient, splToken, reference)
: await validateSystemTransfer(message, meta, recipient, reference);
if (postAmount.minus(preAmount).lt(amount)) throw new ValidateTransferError('amount not transferred');

if (reference) {
if (!Array.isArray(reference)) {
reference = [reference];
}

for (const pubkey of reference) {
if (!message.accountKeys.some((accountKey) => accountKey.equals(pubkey)))
throw new ValidateTransferError('reference not found');
}
if (memo) {
// Check that the second instruction is a memo instruction with the expected memo.
const transaction = Transaction.populate(message);
const instruction = transaction.instructions[1];
if (!instruction) throw new ValidateTransferError('missing memo instruction');
if (!instruction.programId.equals(MEMO_PROGRAM_ID)) throw new ValidateTransferError('invalid memo program');
if (!instruction.data.equals(Buffer.from(memo, 'utf8'))) throw new ValidateTransferError('invalid memo');
}

// FIXME: add memo check

return response;
}

async function validateSystemTransfer(
message: Message,
meta: ConfirmedTransactionMeta,
recipient: Recipient
recipient: Recipient,
references?: Reference[]
): Promise<[BigNumber, BigNumber]> {
if (references) {
// Check that the first instruction is a system transfer instruction.
const transaction = Transaction.populate(message);
const instruction = transaction.instructions[0];
SystemInstruction.decodeTransfer(instruction);

// Check that the expected reference keys exactly match the extra keys provided to the instruction.
const [_from, _to, ...extraKeys] = instruction.keys;
const length = extraKeys.length;
if (length !== references.length) throw new ValidateTransferError('invalid references');

for (let i = 0; i < length; i++) {
if (!extraKeys[i].pubkey.equals(references[i])) throw new ValidateTransferError(`invalid reference ${i}`);
}
}

const accountIndex = message.accountKeys.findIndex((pubkey) => pubkey.equals(recipient));
if (accountIndex === -1) throw new ValidateTransferError('recipient not found');

Expand All @@ -98,8 +122,26 @@ async function validateSPLTokenTransfer(
message: Message,
meta: ConfirmedTransactionMeta,
recipient: Recipient,
splToken: SPLToken
splToken: SPLToken,
references?: Reference[]
): Promise<[BigNumber, BigNumber]> {
if (references) {
// Check that the first instruction is an SPL token transfer instruction.
const transaction = Transaction.populate(message);
const instruction = decodeInstruction(transaction.instructions[0]);
if (!isTransferCheckedInstruction(instruction) && !isTransferInstruction(instruction))
throw new ValidateTransferError('invalid transfer');

// Check that the expected reference keys exactly match the extra keys provided to the instruction.
const extraKeys = instruction.keys.multiSigners;
const length = extraKeys.length;
if (length !== references.length) throw new ValidateTransferError('invalid references');

for (let i = 0; i < length; i++) {
if (!extraKeys[i].pubkey.equals(references[i])) throw new ValidateTransferError(`invalid reference ${i}`);
}
}

const recipientATA = await getAssociatedTokenAddress(splToken, recipient);
const accountIndex = message.accountKeys.findIndex((pubkey) => pubkey.equals(recipientATA));
if (accountIndex === -1) throw new ValidateTransferError('recipient not found');
Expand Down

0 comments on commit ac6ce0d

Please sign in to comment.