-
Notifications
You must be signed in to change notification settings - Fork 57
/
Copy pathcompress-transaction-message.ts
133 lines (120 loc) · 5.37 KB
/
compress-transaction-message.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { Address } from '@solana/addresses';
import { AccountRole, IAccountLookupMeta, IAccountMeta, IInstruction, isSignerRole } from '@solana/instructions';
import { AddressesByLookupTableAddress } from './addresses-by-lookup-table-address';
import { BaseTransactionMessage, TransactionMessage } from './transaction-message';
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// Look up the address in lookup tables, return a lookup meta if it is found in any of them
function findAddressInLookupTables(
address: Address,
role: AccountRole.READONLY | AccountRole.WRITABLE,
addressesByLookupTableAddress: AddressesByLookupTableAddress,
): IAccountLookupMeta | undefined {
for (const [lookupTableAddress, addresses] of Object.entries(addressesByLookupTableAddress)) {
for (let i = 0; i < addresses.length; i++) {
if (address === addresses[i]) {
return {
address,
addressIndex: i,
lookupTableAddress: lookupTableAddress as Address,
role,
};
}
}
}
}
type TransactionMessageNotLegacy = Exclude<TransactionMessage, { version: 'legacy' }>;
// Each account can be IAccountLookupMeta | IAccountMeta
type WidenInstructionAccounts<TInstruction extends IInstruction> =
TInstruction extends IInstruction<infer TProgramAddress, infer TAccounts>
? IInstruction<
TProgramAddress,
{
[K in keyof TAccounts]: TAccounts[K] extends IAccountMeta<infer TAddress>
? IAccountLookupMeta<TAddress> | IAccountMeta<TAddress>
: TAccounts[K];
}
>
: TInstruction;
type ExtractAdditionalProps<T, U> = Omit<T, keyof U>;
type WidenTransactionMessageInstructions<TTransactionMessage extends TransactionMessage> =
TTransactionMessage extends BaseTransactionMessage<infer TVersion, infer TInstruction>
? BaseTransactionMessage<TVersion, WidenInstructionAccounts<TInstruction>> &
ExtractAdditionalProps<
TTransactionMessage,
BaseTransactionMessage<TVersion, WidenInstructionAccounts<TInstruction>>
>
: TTransactionMessage;
/**
* Given a transaction message and a mapping of lookup tables to the addresses stored in them, this
* function will return a new transaction message with the same instructions but with all non-signer
* accounts that are found in the given lookup tables represented by an {@link IAccountLookupMeta}
* instead of an {@link IAccountMeta}.
*
* This means that these accounts will take up less space in the compiled transaction message. This
* size reduction is most significant when the transaction includes many accounts from the same
* lookup table.
*
* @example
* ```ts
* import { address } from '@solana/addresses';
* import { compressTransactionMessageUsingAddressLookupTables } from '@solana/transaction-messages';
*
* const lookupTableAddress = address('4QwSwNriKPrz8DLW4ju5uxC2TN5cksJx6tPUPj7DGLAW');
* const accountAddress = address('5n2ADjHPsqB4EVUNEX48xRqtnmuLu5XSHDwkJRR98qpM');
* const lookupTableAddresses: AddressesByLookupTableAddress = {
* [lookupTableAddress]: [accountAddress],
* };
*
* const compressedTransactionMessage = compressTransactionMessageUsingAddressLookupTables(
* transactionMessage,
* lookupTableAddresses,
* );
* ```
*
*/
export function compressTransactionMessageUsingAddressLookupTables<
TTransactionMessage extends TransactionMessageNotLegacy = TransactionMessageNotLegacy,
>(
transactionMessage: TTransactionMessage,
addressesByLookupTableAddress: AddressesByLookupTableAddress,
): TTransactionMessage | WidenTransactionMessageInstructions<TTransactionMessage> {
const lookupTableAddresses = new Set(Object.values(addressesByLookupTableAddress).flatMap(a => a));
const newInstructions: IInstruction[] = [];
let updatedAnyInstructions = false;
for (const instruction of transactionMessage.instructions) {
if (!instruction.accounts) {
newInstructions.push(instruction);
continue;
}
const newAccounts: Mutable<NonNullable<IInstruction['accounts']>> = [];
let updatedAnyAccounts = false;
for (const account of instruction.accounts) {
// If the address is already a lookup, is not in any lookup tables, or is a signer role, return as-is
if (
'lookupTableAddress' in account ||
!lookupTableAddresses.has(account.address) ||
isSignerRole(account.role)
) {
newAccounts.push(account);
continue;
}
// We already checked it's in one of the lookup tables
const lookupMetaAccount = findAddressInLookupTables(
account.address,
account.role,
addressesByLookupTableAddress,
)!;
newAccounts.push(Object.freeze(lookupMetaAccount));
updatedAnyAccounts = true;
updatedAnyInstructions = true;
}
newInstructions.push(
Object.freeze(updatedAnyAccounts ? { ...instruction, accounts: newAccounts } : instruction),
);
}
return Object.freeze(
updatedAnyInstructions ? { ...transactionMessage, instructions: newInstructions } : transactionMessage,
);
}