Skip to content

Commit

Permalink
feat: Include priority fee instruction with SVM warp transfers (#4568)
Browse files Browse the repository at this point in the history
### Description

Update adapters to include priority fee instruction with sealevel
collateral warp transfers

### Drive-by changes

Hoist const to top of sealevel adapter file

### Related issues

Fixes
hyperlane-xyz/hyperlane-warp-ui-template#264

### Backward compatibility

Yes

### Testing

Tested in Warp UI
  • Loading branch information
jmrossy authored Sep 25, 2024
1 parent a513e1b commit fd536a7
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/smooth-clocks-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': patch
---

Include priority fee instruction with SVM warp transfers
83 changes: 72 additions & 11 deletions typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
Domain,
addressToBytes,
eqAddress,
median,
} from '@hyperlane-xyz/utils';

import { BaseSealevelAdapter } from '../../app/MultiProtocolApp.js';
Expand Down Expand Up @@ -51,6 +52,29 @@ import {

const NON_EXISTENT_ACCOUNT_ERROR = 'could not find account';

/**
* The compute limit to set for the transfer remote instruction.
* This is typically around ~160k, but can be higher depending on
* the index in the merkle tree, which can result in more moderately
* more expensive merkle tree insertion.
* Because a higher compute limit doesn't increase the fee for a transaction,
* we generously request 1M units.
*/
const TRANSFER_REMOTE_COMPUTE_LIMIT = 1_000_000;

/**
* The factor by which to multiply the median prioritization fee
* instruction added to transfer transactions.
*/
const PRIORITY_FEE_PADDING_FACTOR = 2;

/**
* The minimum priority fee to use if the median fee is
* unavailable or too low, set in micro-lamports.
* 100,000 * 1e-6 * 1,000,000 (compute unit limit) / 1e9 == 0.0001 SOL
*/
const MINIMUM_PRIORITY_FEE = 100_000;

// Interacts with native currencies
export class SealevelNativeTokenAdapter
extends BaseSealevelAdapter
Expand Down Expand Up @@ -173,14 +197,6 @@ interface HypTokenAddresses {
mailbox: Address;
}

// The compute limit to set for the transfer remote instruction.
// This is typically around ~160k, but can be higher depending on
// the index in the merkle tree, which can result in more moderately
// more expensive merkle tree insertion.
// Because a higher compute limit doesn't increase the fee for a transaction,
// we generously request 1M units.
const TRANSFER_REMOTE_COMPUTE_LIMIT = 1_000_000;

export abstract class SealevelHypTokenAdapter
extends SealevelTokenAdapter
implements IHypTokenAdapter<Transaction>
Expand Down Expand Up @@ -297,11 +313,17 @@ export abstract class SealevelHypTokenAdapter
});

const setComputeLimitInstruction = ComputeBudgetProgram.setComputeUnitLimit(
{
units: TRANSFER_REMOTE_COMPUTE_LIMIT,
},
{ units: TRANSFER_REMOTE_COMPUTE_LIMIT },
);

// For more info about priority fees, see:
// https://solanacookbook.com/references/basic-transactions.html#how-to-change-compute-budget-fee-priority-for-a-transaction
// https://docs.phantom.app/developer-powertools/solana-priority-fees
// https://www.helius.dev/blog/priority-fees-understanding-solanas-transaction-fee-mechanics
const setPriorityFeeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: (await this.getMedianPriorityFee()) || 0,
});

const recentBlockhash = (
await this.getProvider().getLatestBlockhash('finalized')
).blockhash;
Expand All @@ -313,6 +335,7 @@ export abstract class SealevelHypTokenAdapter
recentBlockhash,
})
.add(setComputeLimitInstruction)
.add(setPriorityFeeInstruction)
.add(transferRemoteInstruction);
tx.partialSign(randomWallet);
return tx;
Expand Down Expand Up @@ -484,6 +507,38 @@ export abstract class SealevelHypTokenAdapter
this.warpProgramPubKey,
);
}

/**
* Fetches the median prioritization fee for transfers of the collateralAddress token.
* @returns The median prioritization fee in micro-lamports
*/
async getMedianPriorityFee(): Promise<number | undefined> {
this.logger.debug('Fetching priority fee history for token transfer');

const collateralAddress = this.addresses.token;
const fees = await this.getProvider().getRecentPrioritizationFees({
lockedWritableAccounts: [new PublicKey(collateralAddress)],
});

const nonZeroFees = fees
.filter((fee) => fee.prioritizationFee > 0)
.map((fee) => fee.prioritizationFee);

if (nonZeroFees.length < 3) {
this.logger.warn(
'Insufficient historical prioritization fee data for padding, skipping',
);
return MINIMUM_PRIORITY_FEE;
}

const medianFee = Math.max(
Math.floor(median(nonZeroFees) * PRIORITY_FEE_PADDING_FACTOR),
MINIMUM_PRIORITY_FEE,
);

this.logger.debug(`Median priority fee: ${medianFee}`);
return medianFee;
}
}

// Interacts with Hyp Native token programs
Expand Down Expand Up @@ -529,6 +584,12 @@ export class SealevelHypNativeAdapter extends SealevelHypTokenAdapter {
return this.wrappedNative.getMetadata();
}

override async getMedianPriorityFee(): Promise<number | undefined> {
// Native tokens don't have a collateral address, so we don't fetch
// prioritization fee history
return undefined;
}

getTransferInstructionKeyList(params: KeyListParams): Array<AccountMeta> {
return [
...super.getTransferInstructionKeyList(params),
Expand Down

0 comments on commit fd536a7

Please sign in to comment.