Skip to content

Commit ac6ce0d

Browse files
committed
finish transfer validation implementation
1 parent f41701d commit ac6ce0d

File tree

1 file changed

+60
-18
lines changed

1 file changed

+60
-18
lines changed

Diff for: core/src/validateTransfer.ts

+60-18
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
import { getAssociatedTokenAddress } from '@solana/spl-token';
1+
import {
2+
decodeInstruction,
3+
getAssociatedTokenAddress,
4+
isTransferCheckedInstruction,
5+
isTransferInstruction,
6+
} from '@solana/spl-token';
27
import {
38
ConfirmedTransactionMeta,
49
Connection,
510
Finality,
611
LAMPORTS_PER_SOL,
712
Message,
13+
SystemInstruction,
14+
Transaction,
815
TransactionResponse,
916
TransactionSignature,
1017
} from '@solana/web3.js';
1118
import BigNumber from 'bignumber.js';
12-
import { Amount, Memo, Recipient, References, SPLToken } from './types';
19+
import { MEMO_PROGRAM_ID } from './constants';
20+
import { Amount, Memo, Recipient, Reference, References, SPLToken } from './types';
1321

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

61-
const [preAmount, postAmount] = splToken
62-
? await validateSPLTokenTransfer(message, meta, recipient, splToken)
63-
: await validateSystemTransfer(message, meta, recipient);
69+
if (reference && !Array.isArray(reference)) {
70+
reference = [reference];
71+
}
6472

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

67-
if (reference) {
68-
if (!Array.isArray(reference)) {
69-
reference = [reference];
70-
}
71-
72-
for (const pubkey of reference) {
73-
if (!message.accountKeys.some((accountKey) => accountKey.equals(pubkey)))
74-
throw new ValidateTransferError('reference not found');
75-
}
78+
if (memo) {
79+
// Check that the second instruction is a memo instruction with the expected memo.
80+
const transaction = Transaction.populate(message);
81+
const instruction = transaction.instructions[1];
82+
if (!instruction) throw new ValidateTransferError('missing memo instruction');
83+
if (!instruction.programId.equals(MEMO_PROGRAM_ID)) throw new ValidateTransferError('invalid memo program');
84+
if (!instruction.data.equals(Buffer.from(memo, 'utf8'))) throw new ValidateTransferError('invalid memo');
7685
}
7786

78-
// FIXME: add memo check
79-
8087
return response;
8188
}
8289

8390
async function validateSystemTransfer(
8491
message: Message,
8592
meta: ConfirmedTransactionMeta,
86-
recipient: Recipient
93+
recipient: Recipient,
94+
references?: Reference[]
8795
): Promise<[BigNumber, BigNumber]> {
96+
if (references) {
97+
// Check that the first instruction is a system transfer instruction.
98+
const transaction = Transaction.populate(message);
99+
const instruction = transaction.instructions[0];
100+
SystemInstruction.decodeTransfer(instruction);
101+
102+
// Check that the expected reference keys exactly match the extra keys provided to the instruction.
103+
const [_from, _to, ...extraKeys] = instruction.keys;
104+
const length = extraKeys.length;
105+
if (length !== references.length) throw new ValidateTransferError('invalid references');
106+
107+
for (let i = 0; i < length; i++) {
108+
if (!extraKeys[i].pubkey.equals(references[i])) throw new ValidateTransferError(`invalid reference ${i}`);
109+
}
110+
}
111+
88112
const accountIndex = message.accountKeys.findIndex((pubkey) => pubkey.equals(recipient));
89113
if (accountIndex === -1) throw new ValidateTransferError('recipient not found');
90114

@@ -98,8 +122,26 @@ async function validateSPLTokenTransfer(
98122
message: Message,
99123
meta: ConfirmedTransactionMeta,
100124
recipient: Recipient,
101-
splToken: SPLToken
125+
splToken: SPLToken,
126+
references?: Reference[]
102127
): Promise<[BigNumber, BigNumber]> {
128+
if (references) {
129+
// Check that the first instruction is an SPL token transfer instruction.
130+
const transaction = Transaction.populate(message);
131+
const instruction = decodeInstruction(transaction.instructions[0]);
132+
if (!isTransferCheckedInstruction(instruction) && !isTransferInstruction(instruction))
133+
throw new ValidateTransferError('invalid transfer');
134+
135+
// Check that the expected reference keys exactly match the extra keys provided to the instruction.
136+
const extraKeys = instruction.keys.multiSigners;
137+
const length = extraKeys.length;
138+
if (length !== references.length) throw new ValidateTransferError('invalid references');
139+
140+
for (let i = 0; i < length; i++) {
141+
if (!extraKeys[i].pubkey.equals(references[i])) throw new ValidateTransferError(`invalid reference ${i}`);
142+
}
143+
}
144+
103145
const recipientATA = await getAssociatedTokenAddress(splToken, recipient);
104146
const accountIndex = message.accountKeys.findIndex((pubkey) => pubkey.equals(recipientATA));
105147
if (accountIndex === -1) throw new ValidateTransferError('recipient not found');

0 commit comments

Comments
 (0)