Skip to content

Commit 9294247

Browse files
solana: add priority fee to redeem transactions (#1795)
* solana: add priority fee to redeem transactions * make compute budget ixs first * Revert "make compute budget ixs first" This reverts commit d376a79. * filter out 0s, take median * move compute budget logic into util * log errors * await promise * add compute budget for last redeem tx separately it depends on the first txs and simulation will fail otherwise --------- Co-authored-by: Artur Sapek <art@wormholelabs.xyz>
1 parent bcc9183 commit 9294247

File tree

4 files changed

+168
-100
lines changed

4 files changed

+168
-100
lines changed

sdk/src/contexts/solana/context.ts

+57-94
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
import {
2424
clusterApiUrl,
2525
Commitment,
26-
ComputeBudgetProgram,
2726
Connection,
2827
Keypair,
2928
PublicKey,
@@ -66,6 +65,7 @@ import {
6665
getClaim,
6766
getPostedMessage,
6867
} from './utils/wormhole';
68+
import { addComputeBudget } from './utils/computeBudget';
6969
import { ForeignAssetCache } from '../../utils';
7070
import { RelayerAbstract } from '../abstracts/relayer';
7171
import {
@@ -78,9 +78,6 @@ import {
7878
const SOLANA_SEQ_LOG = 'Program log: Sequence: ';
7979
const SOLANA_CHAIN_NAME = MAINNET_CONFIG.chains.solana!.key;
8080

81-
// Add priority fee according to 75th percentile of recent fees paid
82-
const SOLANA_FEE_PERCENTILE = 0.75;
83-
8481
const SOLANA_MAINNET_EMMITER_ID =
8582
'ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5';
8683
const SOLANA_TESTNET_EMITTER_ID =
@@ -277,18 +274,15 @@ export class SolanaContext<
277274
payerPublicKey,
278275
tokenPublicKey,
279276
);
280-
const transaction = new Transaction();
281-
transaction.add(
282-
...(await this.determineComputeBudget([
283-
tokenPublicKey,
284-
associatedPublicKey,
285-
])),
277+
const transaction = new Transaction(
278+
await this.connection?.getLatestBlockhash(commitment),
286279
);
287280
transaction.add(createAccountInst);
288-
289-
const { blockhash } = await this.connection.getLatestBlockhash(commitment);
290-
transaction.recentBlockhash = blockhash;
291281
transaction.feePayer = payerPublicKey;
282+
await addComputeBudget(this.connection!, transaction, [
283+
tokenPublicKey,
284+
associatedPublicKey,
285+
]);
292286
return transaction;
293287
}
294288

@@ -397,12 +391,9 @@ export class SolanaContext<
397391
payerPublicKey, //authority
398392
);
399393

400-
const { blockhash } = await this.connection.getLatestBlockhash(commitment);
401-
const transaction = new Transaction();
402-
transaction.add(...(await this.determineComputeBudget([NATIVE_MINT])));
403-
404-
transaction.recentBlockhash = blockhash;
405-
transaction.feePayer = payerPublicKey;
394+
const transaction = new Transaction(
395+
await this.connection?.getLatestBlockhash(commitment),
396+
);
406397
transaction.add(
407398
createAncillaryAccountIx,
408399
initialBalanceTransferIx,
@@ -411,6 +402,9 @@ export class SolanaContext<
411402
tokenBridgeTransferIx,
412403
closeAccountIx,
413404
);
405+
406+
transaction.feePayer = payerPublicKey;
407+
await addComputeBudget(this.connection!, transaction, [NATIVE_MINT]);
414408
transaction.partialSign(message, ancillaryKeypair);
415409
return transaction;
416410
}
@@ -531,17 +525,16 @@ export class SolanaContext<
531525
recipientAddress,
532526
recipientChainId,
533527
);
534-
const transaction = new Transaction();
535-
transaction.add(
536-
...(await this.determineComputeBudget([
537-
new PublicKey(fromAddress),
538-
new PublicKey(mintAddress),
539-
])),
528+
529+
const transaction = new Transaction(
530+
await this.connection?.getLatestBlockhash(commitment),
540531
);
541532
transaction.add(approvalIx, tokenBridgeTransferIx);
542-
const { blockhash } = await this.connection.getLatestBlockhash(commitment);
543-
transaction.recentBlockhash = blockhash;
544533
transaction.feePayer = new PublicKey(senderAddress);
534+
await addComputeBudget(this.connection!, transaction, [
535+
new PublicKey(fromAddress),
536+
new PublicKey(mintAddress),
537+
]);
545538
transaction.partialSign(message);
546539
return transaction;
547540
}
@@ -901,26 +894,30 @@ export class SolanaContext<
901894
}
902895

903896
const parsed = parseTokenTransferVaa(signedVAA);
897+
const tokenKey = new PublicKey(parsed.tokenAddress);
904898
const isNativeSol =
905899
parsed.tokenChain === MAINNET_CHAINS.solana &&
906-
new PublicKey(parsed.tokenAddress).equals(NATIVE_MINT);
907-
if (isNativeSol) {
908-
return await redeemAndUnwrapOnSolana(
909-
this.connection,
910-
contracts.core,
911-
contracts.token_bridge,
912-
payerAddr,
913-
signedVAA,
914-
);
915-
} else {
916-
return await redeemOnSolana(
917-
this.connection,
918-
contracts.core,
919-
contracts.token_bridge,
920-
payerAddr,
921-
signedVAA,
922-
);
923-
}
900+
tokenKey.equals(NATIVE_MINT);
901+
902+
const transaction = isNativeSol
903+
? await redeemAndUnwrapOnSolana(
904+
this.connection,
905+
contracts.core,
906+
contracts.token_bridge,
907+
payerAddr,
908+
signedVAA,
909+
)
910+
: await redeemOnSolana(
911+
this.connection,
912+
contracts.core,
913+
contracts.token_bridge,
914+
payerAddr,
915+
signedVAA,
916+
);
917+
918+
await addComputeBudget(this.connection!, transaction, [tokenKey]);
919+
920+
return transaction;
924921
}
925922

926923
async redeemRelay(
@@ -953,8 +950,10 @@ export class SolanaContext<
953950
parsed.tokenChain,
954951
parsed.tokenAddress,
955952
);
956-
const transaction = new Transaction();
957-
transaction.add(...(await this.determineComputeBudget([mint])));
953+
954+
const transaction = new Transaction(
955+
await this.connection?.getLatestBlockhash('finalized'),
956+
);
958957
const recipientTokenAccount = getAssociatedTokenAddressSync(
959958
mint,
960959
recipient,
@@ -996,9 +995,8 @@ export class SolanaContext<
996995
);
997996
}
998997
transaction.add(redeemIx);
999-
const { blockhash } = await this.connection.getLatestBlockhash('finalized');
1000-
transaction.recentBlockhash = blockhash;
1001998
transaction.feePayer = new PublicKey(recipient);
999+
await addComputeBudget(this.connection!, transaction, [mint]);
10021000
return transaction;
10031001
}
10041002

@@ -1065,7 +1063,10 @@ export class SolanaContext<
10651063
);
10661064
const recipientChainId = this.context.toChainId(recipientChain);
10671065
const nonce = createNonce().readUint32LE();
1068-
const transaction = new Transaction();
1066+
const transaction = new Transaction(
1067+
await this.connection?.getLatestBlockhash('finalized'),
1068+
);
1069+
transaction.feePayer = new PublicKey(senderAddress);
10691070

10701071
if (token === NATIVE || token.chain === SOLANA_CHAIN_NAME) {
10711072
const mint = token === NATIVE ? NATIVE_MINT : token.address;
@@ -1099,9 +1100,6 @@ export class SolanaContext<
10991100
}
11001101
}
11011102

1102-
transaction.add(
1103-
...(await this.determineComputeBudget(writableAddresses)),
1104-
);
11051103
transaction.add(
11061104
await createTransferNativeTokensWithRelayInstruction(
11071105
this.connection,
@@ -1118,12 +1116,11 @@ export class SolanaContext<
11181116
wrapToken,
11191117
),
11201118
);
1119+
1120+
await addComputeBudget(this.connection!, transaction, writableAddresses);
11211121
} else {
11221122
const mint = await this.mustGetForeignAsset(token, sendingChain);
11231123

1124-
transaction.add(
1125-
...(await this.determineComputeBudget([new PublicKey(mint)])),
1126-
);
11271124
transaction.add(
11281125
await createTransferWrappedTokensWithRelayInstruction(
11291126
this.connection,
@@ -1139,11 +1136,11 @@ export class SolanaContext<
11391136
nonce,
11401137
),
11411138
);
1142-
}
11431139

1144-
const { blockhash } = await this.connection.getLatestBlockhash('finalized');
1145-
transaction.recentBlockhash = blockhash;
1146-
transaction.feePayer = new PublicKey(senderAddress);
1140+
await addComputeBudget(this.connection!, transaction, [
1141+
new PublicKey(mint),
1142+
]);
1143+
}
11471144
return transaction;
11481145
}
11491146

@@ -1213,38 +1210,4 @@ export class SolanaContext<
12131210
chain: 'solana',
12141211
};
12151212
}
1216-
1217-
async determineComputeBudget(
1218-
lockedWritableAccounts: PublicKey[] = [],
1219-
): Promise<TransactionInstruction[]> {
1220-
let fee = 100_000; // Set fee to 100,000 microlamport by default
1221-
1222-
try {
1223-
const recentFeesResponse =
1224-
await this.connection?.getRecentPrioritizationFees({
1225-
lockedWritableAccounts,
1226-
});
1227-
1228-
if (recentFeesResponse) {
1229-
// Get 75th percentile fee paid in recent slots
1230-
const recentFees = recentFeesResponse
1231-
.map((dp) => dp.prioritizationFee)
1232-
.sort((a, b) => a - b);
1233-
fee = recentFees[Math.floor(recentFees.length * SOLANA_FEE_PERCENTILE)];
1234-
}
1235-
} catch (e) {
1236-
console.error('Error fetching Solana recent fees', e);
1237-
}
1238-
1239-
console.info(`Setting Solana compute unit price to ${fee} microLamports`);
1240-
1241-
return [
1242-
ComputeBudgetProgram.setComputeUnitLimit({
1243-
units: 250_000,
1244-
}),
1245-
ComputeBudgetProgram.setComputeUnitPrice({
1246-
microLamports: fee,
1247-
}),
1248-
];
1249-
}
12501213
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
Connection,
3+
Transaction,
4+
TransactionInstruction,
5+
PublicKey,
6+
ComputeBudgetProgram,
7+
} from '@solana/web3.js';
8+
9+
// Add priority fee according to 50th percentile of recent fees paid
10+
const DEFAULT_FEE_PERCENTILE = 0.5;
11+
12+
export async function addComputeBudget(
13+
connection: Connection,
14+
transaction: Transaction,
15+
lockedWritableAccounts: PublicKey[] = [],
16+
): Promise<void> {
17+
const ixs = await determineComputeBudget(
18+
connection,
19+
transaction,
20+
lockedWritableAccounts,
21+
);
22+
transaction.add(...ixs);
23+
}
24+
25+
export async function determineComputeBudget(
26+
connection: Connection,
27+
transaction: Transaction,
28+
lockedWritableAccounts: PublicKey[] = [],
29+
feePercentile: number = DEFAULT_FEE_PERCENTILE,
30+
): Promise<TransactionInstruction[]> {
31+
let computeBudget = 250_000;
32+
let priorityFee = 1;
33+
34+
try {
35+
const simulateResponse = await connection.simulateTransaction(transaction);
36+
37+
if (simulateResponse.value.err) {
38+
console.error(
39+
`Error simulating Solana transaction: ${simulateResponse.value.err}`,
40+
);
41+
}
42+
43+
if (simulateResponse?.value?.unitsConsumed) {
44+
// Set compute budget to 120% of the units used in the simulated transaction
45+
computeBudget = Math.round(simulateResponse.value.unitsConsumed * 1.2);
46+
}
47+
48+
priorityFee = await determinePriorityFee(
49+
connection,
50+
lockedWritableAccounts,
51+
feePercentile,
52+
);
53+
} catch (e) {
54+
console.error(`Failed to get compute budget for Solana transaction: ${e}`);
55+
}
56+
57+
console.info(`Setting Solana compute unit budget to ${computeBudget} units`);
58+
console.info(
59+
`Setting Solana compute unit price to ${priorityFee} microLamports`,
60+
);
61+
62+
return [
63+
ComputeBudgetProgram.setComputeUnitLimit({
64+
units: computeBudget,
65+
}),
66+
ComputeBudgetProgram.setComputeUnitPrice({
67+
microLamports: priorityFee,
68+
}),
69+
];
70+
}
71+
72+
async function determinePriorityFee(
73+
connection: Connection,
74+
lockedWritableAccounts: PublicKey[] = [],
75+
percentile: number,
76+
): Promise<number> {
77+
// https://twitter.com/0xMert_/status/1768669928825962706
78+
79+
let fee = 1; // Set fee to 1 microlamport by default
80+
81+
try {
82+
const recentFeesResponse = await connection.getRecentPrioritizationFees({
83+
lockedWritableAccounts,
84+
});
85+
86+
if (recentFeesResponse) {
87+
// Get 75th percentile fee paid in recent slots
88+
const recentFees = recentFeesResponse
89+
.map((dp) => dp.prioritizationFee)
90+
.filter((dp) => dp > 0)
91+
.sort((a, b) => a - b);
92+
93+
if (recentFees.length > 0) {
94+
const medianFee =
95+
recentFees[Math.floor(recentFees.length * percentile)];
96+
fee = Math.max(fee, medianFee);
97+
}
98+
}
99+
} catch (e) {
100+
console.error('Error fetching Solana recent fees', e);
101+
}
102+
103+
return fee;
104+
}

0 commit comments

Comments
 (0)