Skip to content

Commit 02c50d0

Browse files
authored
Merge pull request #287 from streamflow-finance/feat/p-fee-fetchers
feat: priority fee estimation features
2 parents ccdfc22 + e178d39 commit 02c50d0

40 files changed

+3463
-1640
lines changed

.github/workflows/gh-alpha-release.yml

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
outputs:
1717
alpha_version: ${{ steps.set_alpha_version.outputs.alpha_version }}
18+
pr_number: ${{ steps.set_alpha_version.outputs.pr_number }}
1819
steps:
1920
- name: Checkout code
2021
uses: actions/checkout@v4
@@ -26,6 +27,7 @@ jobs:
2627
BASE_VERSION=$(node -p 'require("./lerna.json").version')
2728
SHORT_SHA=$(git rev-parse --short HEAD)
2829
ALPHA_VERSION=""
30+
PR_NUMBER=""
2931
if [ "${{ github.event_name }}" = "pull_request" ]; then
3032
BRANCH="${{ github.head_ref }}"
3133
if [[ "$BRANCH" == alpha/* ]]; then
@@ -42,7 +44,9 @@ jobs:
4244
fi
4345
fi
4446
echo "alpha_version=$ALPHA_VERSION" >> $GITHUB_OUTPUT
47+
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
4548
echo "ALPHA_VERSION=$ALPHA_VERSION"
49+
echo "PR_NUMBER=$PR_NUMBER"
4650
4751
tests:
4852
if: needs.resolve-alpha.outputs.alpha_version != ''
@@ -99,15 +103,25 @@ jobs:
99103
env:
100104
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
101105
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
102-
- uses: actions/github-script@v4
106+
- name: Add comment to PR
107+
uses: actions/github-script@v4
103108
continue-on-error: true
109+
env:
110+
PR_NUMBER: ${{ needs.resolve-alpha.outputs.pr_number }}
111+
ALPHA_VERSION: ${{ needs.resolve-alpha.outputs.alpha_version }}
104112
with:
105113
github-token: ${{secrets.GITHUB_TOKEN}}
106114
script: |
115+
const prNumber = process.env.PR_NUMBER || (context.issue && context.issue.number);
116+
const alphaVersion = process.env.ALPHA_VERSION;
117+
if (!prNumber) {
118+
core.warning('No PR number available to comment on.');
119+
return;
120+
}
107121
const comments = await github.issues.listComments({
108122
owner: context.repo.owner,
109123
repo: context.repo.repo,
110-
issue_number: context.issue.number
124+
issue_number: prNumber
111125
});
112126
113127
const botComment = comments.data.find(comment =>
@@ -116,9 +130,9 @@ jobs:
116130
);
117131
118132
const commentBody = `Alpha versions of packages are published 📦\n\n` +
119-
`PR: #${context.issue.number}\n` +
120-
`Version: ${process.env.ALPHA_VERSION}\n` +
121-
`NPM: https://www.npmjs.com/package/@streamflow/common/v/${process.env.ALPHA_VERSION}`;
133+
`PR: #${prNumber}\n` +
134+
`Version: ${alphaVersion}\n` +
135+
`NPM: https://www.npmjs.com/package/@streamflow/common/v/${alphaVersion}`;
122136
123137
if (botComment) {
124138
await github.issues.updateComment({
@@ -131,7 +145,7 @@ jobs:
131145
await github.issues.createComment({
132146
owner: context.repo.owner,
133147
repo: context.repo.repo,
134-
issue_number: context.issue.number,
148+
issue_number: prNumber,
135149
body: commentBody
136150
});
137151
}

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"pattern": "./packages/*/"
99
}
1010
],
11-
"cSpell.words": ["permissionless", "Solana", "Streamflow", "Unstake"],
11+
"cSpell.words": ["Blockhash", "merkle", "permissionless", "Solana", "Streamflow", "Unstake"],
1212
"typescript.tsdk": "node_modules/typescript/lib",
1313
"javascript.preferences.importModuleSpecifierEnding": "js",
1414
"typescript.preferences.importModuleSpecifierEnding": "js",

examples/ts-esm-unbundled/src/start-node20.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { StreamflowDistributorSolana } from "@streamflow/distributor";
1414
import { SolanaStakingClient } from "@streamflow/staking";
1515
import { SolanaLaunchpadClient } from "@streamflow/launchpad";
1616
// Other entrypoints to test that the package.json is correct
17-
import { MerkleDistributor } from "@streamflow/distributor/solana";
17+
import { MerkleDistributor, ClaimStatus } from "@streamflow/distributor/solana";
1818
import {prepareTransaction} from "@streamflow/common/solana";
1919

2020
// Import IDL files to test import resolving

lerna.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"packages": [
33
"packages/*"
44
],
5-
"version": "8.0.1",
5+
"version": "8.0.2",
66
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
77
"command": {
88
"run": {

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
"examples:verify": "pnpm --filter \"./examples/*\" run verify",
4141
"examples:install": "pnpm --filter \"./examples/*\" install",
4242
"examples:build": "pnpm --filter \"./examples/*\" --parallel run build",
43-
"examples": "pnpm examples:install && pnpm examples:build && pnpm examples:verify"
43+
"examples": "pnpm examples:install && pnpm examples:build && pnpm examples:verify",
44+
"watch:docs": "typedoc --out dist/docs",
45+
"watch:docs:serve": "pnpx serve dist/docs -p 51921",
46+
"serve:docs": "pnpm run --stream \"/^watch:docs.*/\""
4447
},
4548
"devDependencies": {
4649
"@arethetypeswrong/cli": "0.17.4",

packages/common/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@streamflow/common",
3-
"version": "8.0.1",
3+
"version": "8.0.2",
44
"description": "Common utilities and types used by streamflow packages.",
55
"homepage": "https://github.com/streamflow-finance/js-sdk/",
66
"type": "module",
@@ -24,6 +24,14 @@
2424
"import": "./dist/esm/solana/index.js",
2525
"require": "./dist/cjs/solana/index.cjs"
2626
},
27+
"./solana/rpc": {
28+
"types": {
29+
"import": "./dist/esm/solana/rpc/index.d.ts",
30+
"require": "./dist/cjs/solana/rpc/index.d.cts"
31+
},
32+
"import": "./dist/esm/solana/rpc/index.js",
33+
"require": "./dist/cjs/solana/rpc/index.cjs"
34+
},
2735
"./package.json": "./package.json"
2836
},
2937
"scripts": {

packages/common/solana/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
export * from "./account-filters.js";
22
export * from "./instructions.js";
3-
export * from "./public-key.js";
43
export * from "./types.js";
54
export * from "./utils.js";
5+
export * from "./lib/deserialize-raw-transaction.js";
6+
export * from "./lib/estimate.js";
7+
export * from "./lib/public-key.js";
8+
export * from "./lib/unwrap-auto-simulate-ext.js";
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Transaction, VersionedTransaction, type PublicKey } from "@solana/web3.js";
2+
import { Buffer } from "buffer";
3+
4+
interface TransactionAccountsProvider {
5+
accountKeys: PublicKey[];
6+
}
7+
8+
export function deserializeRawTransaction(serializedTx: string) {
9+
const txBuffer = Buffer.from(serializedTx, "base64");
10+
try {
11+
const tx = Transaction.from(txBuffer);
12+
return resolveTransactionAccounts(tx);
13+
} catch (error) {
14+
try {
15+
const vtx = VersionedTransaction.deserialize(txBuffer);
16+
return resolveTransactionAccounts(vtx);
17+
} catch (vError) {
18+
throw new Error("Failed to deserialize transaction: " + (vError instanceof Error ? vError.message : vError), {
19+
cause: vError,
20+
});
21+
}
22+
}
23+
}
24+
25+
export const resolveTransactionAccounts = (tx: VersionedTransaction | Transaction) => {
26+
if (tx instanceof Transaction) {
27+
const message = tx.compileMessage();
28+
const accounts = message.accountKeys;
29+
const writableAccounts = accounts.filter((_, idx) => message.isAccountWritable(idx));
30+
return {
31+
type: "legacy",
32+
transaction: tx,
33+
accounts,
34+
writableAccounts,
35+
};
36+
}
37+
38+
const message = tx.message;
39+
const accounts =
40+
"staticAccountKeys" in message
41+
? message.staticAccountKeys
42+
: (message as unknown as TransactionAccountsProvider).accountKeys;
43+
44+
const writableAccounts = accounts.filter((_, idx) => message.isAccountWritable(idx));
45+
return {
46+
type: "versioned",
47+
transaction: tx,
48+
accounts,
49+
writableAccounts,
50+
};
51+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { TransactionInstruction } from "@solana/web3.js";
2+
3+
import { invariant } from "../../lib/assertions.js";
4+
import type { ComputePriceEstimate, IInteractSolanaExt, ITransactionSolanaExtResolved } from "../types.js";
5+
import { createVersionedTransaction } from "../utils.js";
6+
import { pk } from "./public-key.js";
7+
import { resolveTransactionAccounts } from "./deserialize-raw-transaction.js";
8+
9+
const createTestTransaction = (
10+
ixs: Parameters<typeof createVersionedTransaction>[0],
11+
payer: Parameters<typeof createVersionedTransaction>[1],
12+
recentBlockhash?: Parameters<typeof createVersionedTransaction>[2],
13+
partialSigners?: Parameters<typeof createVersionedTransaction>[3],
14+
) => {
15+
return createVersionedTransaction(ixs, payer, recentBlockhash ?? "11111111111111111111111111111111", partialSigners);
16+
};
17+
18+
export async function estimateComputeUnitPrice(
19+
estimate: ComputePriceEstimate,
20+
testTx: ReturnType<typeof createTestTransaction>,
21+
): Promise<number> {
22+
return estimate(resolveTransactionAccounts(testTx).writableAccounts);
23+
}
24+
25+
export async function createAndEstimateTransaction<
26+
ParamsT extends ITransactionSolanaExtResolved<IInteractSolanaExt>,
27+
CreateFn extends (extParams: ParamsT) => Promise<TransactionInstruction[]>,
28+
>(createFn: CreateFn, extParams: ParamsT): Promise<Awaited<ReturnType<CreateFn>>>;
29+
30+
export async function createAndEstimateTransaction<
31+
ParamsT extends ITransactionSolanaExtResolved<IInteractSolanaExt>,
32+
CreateFn extends (extParams: ParamsT) => Promise<any>,
33+
>(
34+
createFn: CreateFn,
35+
extParams: ParamsT,
36+
select: (result: Awaited<ReturnType<CreateFn>>) => TransactionInstruction[]
37+
): Promise<Awaited<ReturnType<CreateFn>>>;
38+
39+
export async function createAndEstimateTransaction<
40+
ParamsT extends ITransactionSolanaExtResolved<IInteractSolanaExt>,
41+
CreateFn extends (extParams: ParamsT) => Promise<any>,
42+
>(
43+
createFn: CreateFn,
44+
extParams: ParamsT,
45+
select?: (result: Awaited<ReturnType<CreateFn>>) => TransactionInstruction[],
46+
): Promise<Awaited<ReturnType<CreateFn>>> {
47+
select = select ?? ((value: Awaited<ReturnType<CreateFn>>) => value);
48+
const createResult = await createFn(extParams);
49+
const prepareIxs = select(createResult);
50+
51+
const { computePrice, computeLimit } = extParams;
52+
const invoker = extParams.invoker.publicKey;
53+
invariant(invoker, "Invoker's PublicKey is not available, check passed wallet adapter!");
54+
const testTx = createTestTransaction(prepareIxs, pk(invoker), undefined, undefined);
55+
const estimatedComputeLimit = typeof computeLimit === "function" ? await computeLimit(testTx) : computeLimit;
56+
57+
if (typeof computePrice !== "function") {
58+
if (typeof computeLimit !== "function") {
59+
return createResult;
60+
}
61+
62+
return createFn({
63+
...extParams,
64+
computeLimit: estimatedComputeLimit,
65+
});
66+
}
67+
68+
const estimatedComputeUnitPrice =
69+
typeof computePrice === "function"
70+
? await estimateComputeUnitPrice(computePrice, testTx)
71+
: // unachievable because we don't execute estimation for constant or undefined priority fee
72+
computePrice;
73+
74+
return createFn({
75+
...extParams,
76+
computePrice: estimatedComputeUnitPrice,
77+
computeLimit: estimatedComputeLimit,
78+
});
79+
}

0 commit comments

Comments
 (0)