-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
187 lines (173 loc) · 6.65 KB
/
index.ts
File metadata and controls
187 lines (173 loc) · 6.65 KB
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import {
AddressLookupTableAccount, ComputeBudgetProgram,
PublicKey,
TransactionInstruction,
TransactionMessage, VersionedTransaction,
} from "@solana/web3.js";
import * as multisig from "@sqds/multisig";
const BATCH_ADD_MAX_SIZE_CHECK = 1100;
const PRIORITY_FEES = 50000;
const SQUADS_PROGRAM_ID = multisig.PROGRAM_ID;
// this uses dummy keys, merely to see if the size goes above the limit
const calculateSerializedTxSize = (
addressLookupTableAccounts: AddressLookupTableAccount[],
transactionMessage: TransactionMessage
): number => {
try {
const batchAddTx = createBatchAddSingleTransaction({
blockhash: PublicKey.default.toBase58(),
proposer: PublicKey.default,
rentPayer: PublicKey.default,
squadAddress: PublicKey.default,
transactionIndex: 0,
batchIndex: BigInt(0),
vaultIndex: 0,
ephemeralSigners: 0,
transactionMessage: {
// create a new one since we will actually have a fee priority compute ix in each tx
message: new TransactionMessage({
payerKey: PublicKey.default,
recentBlockhash: PublicKey.default.toBase58(),
instructions: transactionMessage.instructions,
}),
addressLookupTableAccounts,
},
});
return batchAddTx.serialize().length;
} catch (e) {
console.error("Size exceeded in preparation");
return BATCH_ADD_MAX_SIZE_CHECK + 1; // Assume it exceeds max size to handle error
}
};
const tryAddBucket = (
message: TransactionMessage,
bucket: TransactionInstruction[],
addressLookupTableAccounts: AddressLookupTableAccount[]
): boolean => {
const originalInstructions = [...message.instructions];
message.instructions = [...message.instructions, ...bucket];
if (
calculateSerializedTxSize(addressLookupTableAccounts, message) >
BATCH_ADD_MAX_SIZE_CHECK
) {
message.instructions = originalInstructions; // Revert if exceeds limit
return false;
}
return true;
};
export interface PackagedBatchedInstructionsResult {
transactionMessages: TransactionMessage[];
failedBuckets: number[];
bucketTxMessageIndexes: Array<number>;
}
// function takes an array of arrays, with each have a collection of instructions (that may need to be executed together to represent a single tx)
// ie [[ix1_requirement, ix1_requirement, ix1], [ix2_requirement, ix3_requirement, ix3]...]
// if there are no atomic requirements, this can simply be a list [[ix1], [ix2], [ix3]]
export const packageInstructions = (
instructionsBuckets: TransactionInstruction[][],
addressLookupTableAccounts: AddressLookupTableAccount[]
): PackagedBatchedInstructionsResult => {
const transactionMessages: TransactionMessage[] = [];
const failedBuckets: number[] = [];
const bucketTxMessageIndexes: Array<number> = [];
// the initial message to add instructions to
let currentMessage = new TransactionMessage({
payerKey: PublicKey.default,
recentBlockhash: PublicKey.default.toBase58(),
instructions: [],
});
instructionsBuckets.forEach((bucket, index) => {
// if bucket does not have any instructions, skip it
if (bucket.length === 0) {
failedBuckets.push(index);
bucketTxMessageIndexes.push(-1);
return;
}
if (!tryAddBucket(currentMessage, bucket, addressLookupTableAccounts)) {
// Attempt to add the current bucket to the message failed due to size constraints
if (currentMessage.instructions.length > 0) {
transactionMessages.push(currentMessage); // Save the message if it has any instructions
}
// Create a new message for the next attempt
currentMessage = new TransactionMessage({
payerKey: PublicKey.default,
recentBlockhash: PublicKey.default.toBase58(),
instructions: [],
});
// Try adding the bucket to the new message to see if it fits
if (!tryAddBucket(currentMessage, bucket, addressLookupTableAccounts)) {
// If the bucket alone exceeds the max size, mark it as failed
failedBuckets.push(index);
bucketTxMessageIndexes.push(-1);
} else {
// If the bucket is successfully added, record its message index
bucketTxMessageIndexes.push(transactionMessages.length);
}
} else {
// The bucket was added successfully to the current message
bucketTxMessageIndexes.push(transactionMessages.length);
}
});
if (currentMessage.instructions.length > 0) {
transactionMessages.push(currentMessage); // Ensure the last message is added
}
return {transactionMessages, failedBuckets, bucketTxMessageIndexes};
};
// for prexisting batch drafts - this is a solo transaction to create the ix to attach
// a single tx to a batch
// IMPORTANT! batchIndex is the actual proposal/transaction index, and transactionIndex is the
// batch TX themselves!
export function createBatchAddSingleTransaction({
blockhash,
proposer,
rentPayer,
squadAddress,
transactionIndex,
vaultIndex,
batchIndex,
ephemeralSigners,
transactionMessage,
fees,
}: {
blockhash: string;
proposer: PublicKey;
rentPayer: PublicKey;
squadAddress: PublicKey;
transactionIndex: number;
batchIndex: bigint;
vaultIndex: number;
ephemeralSigners: number;
transactionMessage: {
message: TransactionMessage;
addressLookupTableAccounts: AddressLookupTableAccount[];
};
fees?: number;
}): VersionedTransaction {
const batchIx = multisig.instructions.batchAddTransaction({
vaultIndex,
multisigPda: new PublicKey(squadAddress),
member: proposer,
rentPayer,
batchIndex,
transactionIndex,
ephemeralSigners,
transactionMessage: transactionMessage.message,
addressLookupTableAccounts: transactionMessage.addressLookupTableAccounts,
programId: SQUADS_PROGRAM_ID,
});
return new VersionedTransaction(
new TransactionMessage({
payerKey: rentPayer ?? proposer,
recentBlockhash: blockhash,
instructions: [
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: fees || PRIORITY_FEES,
}),
ComputeBudgetProgram.setComputeUnitLimit({
units: 100_000,
}),
batchIx,
],
}).compileToV0Message(transactionMessage.addressLookupTableAccounts)
);
}