From 0391535e1f1ebcbd31f7130ab39db40a1b9f4e64 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 20 Mar 2024 16:32:42 +0300 Subject: [PATCH 1/4] chore(LLC): update @solana/spl-token@0.4.6, @solana/web3.js@1.91.6 --- libs/coin-modules/coin-solana/package.json | 4 +- libs/ledger-live-common/package.json | 2 +- pnpm-lock.yaml | 134 ++++++++++++++++++--- 3 files changed, 122 insertions(+), 18 deletions(-) diff --git a/libs/coin-modules/coin-solana/package.json b/libs/coin-modules/coin-solana/package.json index 398ad49de6bd..8dd57c0a47fe 100644 --- a/libs/coin-modules/coin-solana/package.json +++ b/libs/coin-modules/coin-solana/package.json @@ -53,8 +53,8 @@ "@ledgerhq/logs": "workspace:^", "@ledgerhq/types-cryptoassets": "workspace:^", "@ledgerhq/types-live": "workspace:^", - "@solana/spl-token": "^0.3.7", - "@solana/web3.js": "1.77.3", + "@solana/spl-token": "0.4.6", + "@solana/web3.js": "1.91.6", "bignumber.js": "^9.1.2", "bs58": "^4.0.1", "expect": "^27.4.6", diff --git a/libs/ledger-live-common/package.json b/libs/ledger-live-common/package.json index fb35cf7d9dbb..9eb0f102cdca 100644 --- a/libs/ledger-live-common/package.json +++ b/libs/ledger-live-common/package.json @@ -253,7 +253,7 @@ "@ledgerhq/types-cryptoassets": "workspace:^", "@ledgerhq/types-devices": "workspace:^", "@ledgerhq/types-live": "workspace:^", - "@solana/web3.js": "1.77.3", + "@solana/web3.js": "1.91.6", "@svgr/core": "^5.5.0", "@tanstack/react-query": "^5.28.9", "@testing-library/react": "^14.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b502993c8961..cc6618c3afcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2643,11 +2643,11 @@ importers: specifier: workspace:^ version: link:../../ledgerjs/packages/types-live '@solana/spl-token': - specifier: ^0.3.7 - version: 0.3.8(@solana/web3.js@1.77.3) + specifier: 0.4.6 + version: 0.4.6(@solana/web3.js@1.91.6) '@solana/web3.js': - specifier: 1.77.3 - version: 1.77.3 + specifier: 1.91.6 + version: 1.91.6 bignumber.js: specifier: ^9.1.2 version: 9.1.2 @@ -3793,8 +3793,8 @@ importers: specifier: workspace:^ version: link:../ledgerjs/packages/types-live '@solana/web3.js': - specifier: 1.77.3 - version: 1.77.3 + specifier: 1.91.6 + version: 1.91.6 '@svgr/core': specifier: ^5.5.0 version: 5.5.0 @@ -13112,14 +13112,54 @@ packages: resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} engines: {node: '>=5.10'} - '@solana/spl-token@0.3.8': - resolution: {integrity: sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg==} + '@solana/codecs-core@2.0.0-preview.2': + resolution: {integrity: sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg==} + + '@solana/codecs-data-structures@2.0.0-preview.2': + resolution: {integrity: sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg==} + + '@solana/codecs-numbers@2.0.0-preview.2': + resolution: {integrity: sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw==} + + '@solana/codecs-strings@2.0.0-preview.2': + resolution: {integrity: sha512-EgBwY+lIaHHgMJIqVOGHfIfpdmmUDNoNO/GAUGeFPf+q0dF+DtwhJPEMShhzh64X2MeCZcmSO6Kinx0Bvmmz2g==} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + + '@solana/codecs@2.0.0-preview.2': + resolution: {integrity: sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA==} + + '@solana/errors@2.0.0-preview.2': + resolution: {integrity: sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA==} + hasBin: true + + '@solana/options@2.0.0-preview.2': + resolution: {integrity: sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w==} + + '@solana/spl-token-group@0.0.4': + resolution: {integrity: sha512-7+80nrEMdUKlK37V6kOe024+T7J4nNss0F8LQ9OOPYdWCCfJmsGUzVx2W3oeizZR4IHM6N4yC9v1Xqwc3BTPWw==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.91.6 + + '@solana/spl-token-metadata@0.1.4': + resolution: {integrity: sha512-N3gZ8DlW6NWDV28+vCCDJoTqaCZiF/jDUnk3o8GRkAFzHObiR60Bs1gXHBa8zCPdvOwiG6Z3dg5pg7+RW6XNsQ==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.91.6 + + '@solana/spl-token@0.4.6': + resolution: {integrity: sha512-1nCnUqfHVtdguFciVWaY/RKcQz1IF4b31jnKgAmjU9QVN1q7dRUkTEWJZgTYIEtsULjVnC9jRqlhgGN39WbKKA==} engines: {node: '>=16'} peerDependencies: - '@solana/web3.js': ^1.47.4 + '@solana/web3.js': ^1.91.6 + + '@solana/spl-type-length-value@0.1.0': + resolution: {integrity: sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==} + engines: {node: '>=16'} - '@solana/web3.js@1.77.3': - resolution: {integrity: sha512-PHaO0BdoiQRPpieC1p31wJsBaxwIOWLh8j2ocXNKX8boCQVldt26Jqm2tZE4KlrvnCIV78owPLv1pEUgqhxZ3w==} + '@solana/web3.js@1.91.6': + resolution: {integrity: sha512-dm20nN6HQvXToo+kM51nxHdtaa2wMSRdeK37p+WIWESfeiVHqV8XbV4XnWupq6ngt5vIckhGFG7ZnTBxUgLzDA==} '@stablelib/binary@1.0.1': resolution: {integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==} @@ -40963,7 +41003,7 @@ snapshots: '@solana/buffer-layout-utils@0.2.0': dependencies: '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.77.3 + '@solana/web3.js': 1.91.6 bigint-buffer: 1.1.5 bignumber.js: 9.1.2 transitivePeerDependencies: @@ -40975,18 +41015,82 @@ snapshots: dependencies: buffer: 6.0.3(patch_hash=2xnca52oxhztvr7iaoovwclcze) - '@solana/spl-token@0.3.8(@solana/web3.js@1.77.3)': + '@solana/codecs-core@2.0.0-preview.2': + dependencies: + '@solana/errors': 2.0.0-preview.2 + + '@solana/codecs-data-structures@2.0.0-preview.2': + dependencies: + '@solana/codecs-core': 2.0.0-preview.2 + '@solana/codecs-numbers': 2.0.0-preview.2 + '@solana/errors': 2.0.0-preview.2 + + '@solana/codecs-numbers@2.0.0-preview.2': + dependencies: + '@solana/codecs-core': 2.0.0-preview.2 + '@solana/errors': 2.0.0-preview.2 + + '@solana/codecs-strings@2.0.0-preview.2': + dependencies: + '@solana/codecs-core': 2.0.0-preview.2 + '@solana/codecs-numbers': 2.0.0-preview.2 + '@solana/errors': 2.0.0-preview.2 + + '@solana/codecs@2.0.0-preview.2': + dependencies: + '@solana/codecs-core': 2.0.0-preview.2 + '@solana/codecs-data-structures': 2.0.0-preview.2 + '@solana/codecs-numbers': 2.0.0-preview.2 + '@solana/codecs-strings': 2.0.0-preview.2 + '@solana/options': 2.0.0-preview.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/errors@2.0.0-preview.2': + dependencies: + chalk: 5.3.0 + commander: 12.0.0 + + '@solana/options@2.0.0-preview.2': + dependencies: + '@solana/codecs-core': 2.0.0-preview.2 + '@solana/codecs-numbers': 2.0.0-preview.2 + + '@solana/spl-token-group@0.0.4(@solana/web3.js@1.91.6)': + dependencies: + '@solana/codecs': 2.0.0-preview.2 + '@solana/spl-type-length-value': 0.1.0 + '@solana/web3.js': 1.91.6 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/spl-token-metadata@0.1.4(@solana/web3.js@1.91.6)': + dependencies: + '@solana/codecs': 2.0.0-preview.2 + '@solana/spl-type-length-value': 0.1.0 + '@solana/web3.js': 1.91.6 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/spl-token@0.4.6(@solana/web3.js@1.91.6)': dependencies: '@solana/buffer-layout': 4.0.1 '@solana/buffer-layout-utils': 0.2.0 - '@solana/web3.js': 1.77.3 + '@solana/spl-token-group': 0.0.4(@solana/web3.js@1.91.6) + '@solana/spl-token-metadata': 0.1.4(@solana/web3.js@1.91.6) + '@solana/web3.js': 1.91.6 buffer: 6.0.3(patch_hash=2xnca52oxhztvr7iaoovwclcze) transitivePeerDependencies: - bufferutil - encoding + - fastestsmallesttextencoderdecoder - utf-8-validate - '@solana/web3.js@1.77.3': + '@solana/spl-type-length-value@0.1.0': + dependencies: + buffer: 6.0.3(patch_hash=2xnca52oxhztvr7iaoovwclcze) + + '@solana/web3.js@1.91.6': dependencies: '@babel/runtime': 7.24.1 '@noble/curves': 1.4.0 From b44ea0300394947b9331f27b1820c350a5958c88 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 21 Mar 2024 13:39:07 +0300 Subject: [PATCH 2/4] feat(LLC): calc and possibly append priority fee to solana instructions --- .../coin-solana/src/api/cached.ts | 6 + .../coin-solana/src/api/chain/index.ts | 11 +- .../coin-solana/src/api/chain/web3.ts | 168 ++++++++++-------- .../src/bridge.integration.test.ts | 12 ++ .../coin-solana/src/buildTransaction.ts | 28 +-- .../coin-solana/src/synchronization.ts | 4 +- libs/coin-modules/coin-solana/src/utils.ts | 15 ++ .../src/families/solana/bridge/mock-data.ts | 16 ++ 8 files changed, 175 insertions(+), 85 deletions(-) diff --git a/libs/coin-modules/coin-solana/src/api/cached.ts b/libs/coin-modules/coin-solana/src/api/cached.ts index bc82f12ecdc3..44f7e5b99f10 100644 --- a/libs/coin-modules/coin-solana/src/api/cached.ts +++ b/libs/coin-modules/coin-solana/src/api/cached.ts @@ -74,6 +74,12 @@ export function cached(api: ChainAPI): ChainAPI { getEpochInfo: makeLRUCache(api.getEpochInfo, cacheKeyEmpty, minutes(1)), + getRecentPrioritizationFees: makeLRUCache( + api.getRecentPrioritizationFees, + cacheKeyByArgs, + seconds(30), + ), + config: api.config, }; } diff --git a/libs/coin-modules/coin-solana/src/api/chain/index.ts b/libs/coin-modules/coin-solana/src/api/chain/index.ts index fe95cde5b0d1..796d9ec27df4 100644 --- a/libs/coin-modules/coin-solana/src/api/chain/index.ts +++ b/libs/coin-modules/coin-solana/src/api/chain/index.ts @@ -11,6 +11,7 @@ import { sendAndConfirmRawTransaction, SignaturesForAddressOptions, StakeProgram, + GetRecentPrioritizationFeesConfig, } from "@solana/web3.js"; import { makeLRUCache, minutes } from "@ledgerhq/live-network/cache"; import { getEnv } from "@ledgerhq/live-env"; @@ -71,6 +72,10 @@ export type ChainAPI = Readonly<{ getEpochInfo: () => ReturnType; + getRecentPrioritizationFees: ( + config?: GetRecentPrioritizationFeesConfig, + ) => ReturnType; + config: Config; }>; @@ -87,7 +92,7 @@ export function getChainAPI( logger === undefined ? undefined : (url, options, fetch) => { - logger(url, options); + logger(url.toString(), options); fetch(url, options); }; @@ -207,6 +212,10 @@ export function getChainAPI( getEpochInfo: () => connection().getEpochInfo().catch(remapErrors), + getRecentPrioritizationFees: (config?: GetRecentPrioritizationFeesConfig) => { + return connection().getRecentPrioritizationFees(config).catch(remapErrors); + }, + config, }; } diff --git a/libs/coin-modules/coin-solana/src/api/chain/web3.ts b/libs/coin-modules/coin-solana/src/api/chain/web3.ts index 1e6f193ef5ea..1d6cd4b35968 100644 --- a/libs/coin-modules/coin-solana/src/api/chain/web3.ts +++ b/libs/coin-modules/coin-solana/src/api/chain/web3.ts @@ -11,8 +11,10 @@ import { StakeProgram, SystemProgram, TransactionInstruction, + ComputeBudgetProgram, } from "@solana/web3.js"; import chunk from "lodash/chunk"; +import uniqBy from "lodash/uniqBy"; import { ChainAPI } from "."; import { Awaited } from "../../logic"; import { @@ -25,7 +27,7 @@ import { TokenTransferCommand, TransferCommand, } from "../../types"; -import { drainSeqAsyncGen } from "../../utils"; +import { drainSeqAsyncGen, median } from "../../utils"; import { parseTokenAccountInfo, tryParseAsTokenAccount, tryParseAsVoteAccount } from "./account"; import { parseStakeAccountInfo } from "./account/parser"; import { StakeAccountInfo } from "./account/stake"; @@ -127,12 +129,10 @@ export function getTransactions( return drainSeqAsyncGen(getTransactionsGen(address, untilTxSignature, api)); } -export const buildTransferInstructions = ({ - sender, - recipient, - amount, - memo, -}: TransferCommand): TransactionInstruction[] => { +export const buildTransferInstructions = async ( + api: ChainAPI, + { sender, recipient, amount, memo }: TransferCommand, +): Promise => { const fromPublicKey = new PublicKey(sender); const toPublicKey = new PublicKey(recipient); @@ -153,12 +153,13 @@ export const buildTransferInstructions = ({ instructions.push(memoIx); } - return instructions; + return appendMaybePriorityFeeInstruction(api, [fromPublicKey, toPublicKey], instructions); }; -export const buildTokenTransferInstructions = ( +export const buildTokenTransferInstructions = async ( + api: ChainAPI, command: TokenTransferCommand, -): TransactionInstruction[] => { +): Promise => { const { ownerAddress, ownerAssociatedTokenAccountAddress, @@ -271,6 +272,36 @@ export async function getStakeAccountAddressWithSeed({ return pubkey.toBase58(); } +export async function getPriorityFee(api: ChainAPI, accounts: PublicKey[]): Promise { + const uniqAccs = uniqBy(accounts, acc => acc.toBase58()); + const recentFees = await api.getRecentPrioritizationFees({ + lockedWritableAccounts: uniqAccs, + }); + + return median(recentFees.map(item => item.prioritizationFee)); +} + +export async function buildMaybePriorityFeeInstruction( + api: ChainAPI, + accounts: PublicKey[], +): Promise { + const priorityFee = await getPriorityFee(api, accounts); + if (priorityFee === 0) return null; + + return ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: priorityFee, + }); +} + +export async function appendMaybePriorityFeeInstruction( + api: ChainAPI, + accounts: PublicKey[], + ixs: TransactionInstruction[], +): Promise { + const priorityFeeIx = await buildMaybePriorityFeeInstruction(api, accounts); + return priorityFeeIx ? [priorityFeeIx, ...ixs] : ixs; +} + export function buildCreateAssociatedTokenAccountInstruction({ mint, owner, @@ -292,86 +323,82 @@ export function buildCreateAssociatedTokenAccountInstruction({ return instructions; } -export function buildStakeDelegateInstructions({ - authorizedAccAddr, - stakeAccAddr, - voteAccAddr, -}: StakeDelegateCommand): TransactionInstruction[] { +export async function buildStakeDelegateInstructions( + api: ChainAPI, + { authorizedAccAddr, stakeAccAddr, voteAccAddr }: StakeDelegateCommand, +): Promise { + const withdrawAuthority = new PublicKey(authorizedAccAddr); + const stakeAcc = new PublicKey(stakeAccAddr); + const voteAcc = new PublicKey(voteAccAddr); const tx = StakeProgram.delegate({ - authorizedPubkey: new PublicKey(authorizedAccAddr), - stakePubkey: new PublicKey(stakeAccAddr), - votePubkey: new PublicKey(voteAccAddr), + authorizedPubkey: withdrawAuthority, + stakePubkey: stakeAcc, + votePubkey: voteAcc, }); - return tx.instructions; + return appendMaybePriorityFeeInstruction(api, [withdrawAuthority, stakeAcc], tx.instructions); } -export function buildStakeUndelegateInstructions({ - authorizedAccAddr, - stakeAccAddr, -}: StakeUndelegateCommand): TransactionInstruction[] { +export async function buildStakeUndelegateInstructions( + api: ChainAPI, + { authorizedAccAddr, stakeAccAddr }: StakeUndelegateCommand, +): Promise { + const withdrawAuthority = new PublicKey(authorizedAccAddr); + const stakeAcc = new PublicKey(stakeAccAddr); const tx = StakeProgram.deactivate({ - authorizedPubkey: new PublicKey(authorizedAccAddr), - stakePubkey: new PublicKey(stakeAccAddr), + authorizedPubkey: withdrawAuthority, + stakePubkey: stakeAcc, }); - return tx.instructions; + return appendMaybePriorityFeeInstruction(api, [withdrawAuthority, stakeAcc], tx.instructions); } -export function buildStakeWithdrawInstructions({ - authorizedAccAddr, - stakeAccAddr, - amount, - toAccAddr, -}: StakeWithdrawCommand): TransactionInstruction[] { +export async function buildStakeWithdrawInstructions( + api: ChainAPI, + { authorizedAccAddr, stakeAccAddr, amount, toAccAddr }: StakeWithdrawCommand, +): Promise { + const withdrawAuthority = new PublicKey(authorizedAccAddr); + const stakeAcc = new PublicKey(stakeAccAddr); + const recipient = new PublicKey(toAccAddr); const tx = StakeProgram.withdraw({ - authorizedPubkey: new PublicKey(authorizedAccAddr), - stakePubkey: new PublicKey(stakeAccAddr), + authorizedPubkey: withdrawAuthority, + stakePubkey: stakeAcc, lamports: amount, - toPubkey: new PublicKey(toAccAddr), + toPubkey: recipient, }); - return tx.instructions; + return appendMaybePriorityFeeInstruction(api, [withdrawAuthority, stakeAcc], tx.instructions); } -export function buildStakeSplitInstructions({ - authorizedAccAddr, - stakeAccAddr, - seed, - amount, - splitStakeAccAddr, -}: StakeSplitCommand): TransactionInstruction[] { - // HACK: switch to split_with_seed when supported by @solana/web3.js - const splitIx = StakeProgram.split({ - authorizedPubkey: new PublicKey(authorizedAccAddr), +export async function buildStakeSplitInstructions( + api: ChainAPI, + { authorizedAccAddr, stakeAccAddr, seed, amount, splitStakeAccAddr }: StakeSplitCommand, +): Promise { + const basePk = new PublicKey(authorizedAccAddr); + const stakePk = new PublicKey(stakeAccAddr); + const splitStakePk = new PublicKey(splitStakeAccAddr); + const splitIx = StakeProgram.splitWithSeed({ + authorizedPubkey: basePk, lamports: amount, - stakePubkey: new PublicKey(stakeAccAddr), - splitStakePubkey: new PublicKey(splitStakeAccAddr), - }).instructions[1]; - - if (splitIx === undefined) { - throw new Error("expected split instruction"); - } - - const allocateIx = SystemProgram.allocate({ - accountPubkey: new PublicKey(splitStakeAccAddr), - basePubkey: new PublicKey(authorizedAccAddr), - programId: StakeProgram.programId, + stakePubkey: stakePk, + splitStakePubkey: splitStakePk, + basePubkey: basePk, seed, - space: StakeProgram.space, }); - - return [allocateIx, splitIx]; + return appendMaybePriorityFeeInstruction(api, [basePk, stakePk], splitIx.instructions); } -export function buildStakeCreateAccountInstructions({ - fromAccAddress, - stakeAccAddress, - seed, - amount, - stakeAccRentExemptAmount, - delegate, -}: StakeCreateAccountCommand): TransactionInstruction[] { +export async function buildStakeCreateAccountInstructions( + api: ChainAPI, + { + fromAccAddress, + stakeAccAddress, + seed, + amount, + stakeAccRentExemptAmount, + delegate, + }: StakeCreateAccountCommand, +): Promise { const fromPubkey = new PublicKey(fromAccAddress); const stakePubkey = new PublicKey(stakeAccAddress); @@ -394,6 +421,5 @@ export function buildStakeCreateAccountInstructions({ votePubkey: new PublicKey(delegate.voteAccAddress), }), ); - - return tx.instructions; + return appendMaybePriorityFeeInstruction(api, [fromPubkey, stakePubkey], tx.instructions); } diff --git a/libs/coin-modules/coin-solana/src/bridge.integration.test.ts b/libs/coin-modules/coin-solana/src/bridge.integration.test.ts index 014c7765a27a..fbda3455567f 100644 --- a/libs/coin-modules/coin-solana/src/bridge.integration.test.ts +++ b/libs/coin-modules/coin-solana/src/bridge.integration.test.ts @@ -952,6 +952,18 @@ const baseTx = { const baseAPI = { getLatestBlockhash: () => Promise.resolve(LATEST_BLOCKHASH_MOCK), getFeeForMessage: (_msg: unknown) => Promise.resolve(testOnChainData.fees.lamportsPerSignature), + getRecentPrioritizationFees: (_: string[]) => { + return Promise.resolve([ + { + slot: 122422797, + prioritizationFee: 0, + }, + { + slot: 122422797, + prioritizationFee: 0, + }, + ]); + }, } as ChainAPI; type StakeTestSpec = { diff --git a/libs/coin-modules/coin-solana/src/buildTransaction.ts b/libs/coin-modules/coin-solana/src/buildTransaction.ts index cb7f9b17bad8..146b594ca7fc 100644 --- a/libs/coin-modules/coin-solana/src/buildTransaction.ts +++ b/libs/coin-modules/coin-solana/src/buildTransaction.ts @@ -23,7 +23,7 @@ export const buildTransactionWithAPI = async ( transaction: Transaction, api: ChainAPI, ): Promise OnChainTransaction]> => { - const instructions = buildInstructions(transaction); + const instructions = await buildInstructions(api, transaction); const recentBlockhash = await api.getLatestBlockhash(); @@ -46,7 +46,10 @@ export const buildTransactionWithAPI = async ( ]; }; -function buildInstructions(tx: Transaction): TransactionInstruction[] { +async function buildInstructions( + api: ChainAPI, + tx: Transaction, +): Promise { const { commandDescriptor } = tx.model; if (commandDescriptor === undefined) { throw new Error("missing command descriptor"); @@ -54,27 +57,30 @@ function buildInstructions(tx: Transaction): TransactionInstruction[] { if (Object.keys(commandDescriptor.errors).length > 0) { throw new Error("can not build invalid command"); } - return buildInstructionsForCommand(commandDescriptor.command); + return buildInstructionsForCommand(api, commandDescriptor.command); } -function buildInstructionsForCommand(command: Command): TransactionInstruction[] { +async function buildInstructionsForCommand( + api: ChainAPI, + command: Command, +): Promise { switch (command.kind) { case "transfer": - return buildTransferInstructions(command); + return buildTransferInstructions(api, command); case "token.transfer": - return buildTokenTransferInstructions(command); + return buildTokenTransferInstructions(api, command); case "token.createATA": return buildCreateAssociatedTokenAccountInstruction(command); case "stake.createAccount": - return buildStakeCreateAccountInstructions(command); + return buildStakeCreateAccountInstructions(api, command); case "stake.delegate": - return buildStakeDelegateInstructions(command); + return buildStakeDelegateInstructions(api, command); case "stake.undelegate": - return buildStakeUndelegateInstructions(command); + return buildStakeUndelegateInstructions(api, command); case "stake.withdraw": - return buildStakeWithdrawInstructions(command); + return buildStakeWithdrawInstructions(api, command); case "stake.split": - return buildStakeSplitInstructions(command); + return buildStakeSplitInstructions(api, command); default: return assertUnreachable(command); } diff --git a/libs/coin-modules/coin-solana/src/synchronization.ts b/libs/coin-modules/coin-solana/src/synchronization.ts index b324c9e635bd..348956645c07 100644 --- a/libs/coin-modules/coin-solana/src/synchronization.ts +++ b/libs/coin-modules/coin-solana/src/synchronization.ts @@ -542,7 +542,7 @@ function getMainAccOperationTypeFromTx(tx: ParsedTransaction): OperationType | u const parsedIxs = instructions .map(ix => parseQuiet(ix)) - .filter(({ program }) => program !== "spl-memo"); + .filter(({ program }) => program !== "spl-memo" && program !== "unknown"); if (parsedIxs.length === 3) { const [first, second, third] = parsedIxs; @@ -636,7 +636,7 @@ function getTokenAccOperationType({ const { instructions } = tx.message; const [mainIx, ...otherIxs] = instructions .map(ix => parseQuiet(ix)) - .filter(({ program }) => program !== "spl-memo"); + .filter(({ program }) => program !== "spl-memo" && program !== "unknown"); if (mainIx !== undefined && otherIxs.length === 0) { switch (mainIx.program) { diff --git a/libs/coin-modules/coin-solana/src/utils.ts b/libs/coin-modules/coin-solana/src/utils.ts index 5e8fd41f437e..15b6f00492af 100644 --- a/libs/coin-modules/coin-solana/src/utils.ts +++ b/libs/coin-modules/coin-solana/src/utils.ts @@ -2,6 +2,7 @@ import { Cluster, clusterApiUrl } from "@solana/web3.js"; import { partition } from "lodash/fp"; import { getEnv } from "@ledgerhq/live-env"; import { ValidatorsAppValidator } from "./validator-app"; +import BigNumber from "bignumber.js"; // Hardcoding the Ledger validator info as backup, // because backend is flaky and sometimes doesn't return it anymore @@ -174,3 +175,17 @@ export const tupleOfUnion = export function sweetch(caze: T, cases: Record): R { return cases[caze]; } + +export function median(values: number[]): number { + const length = values.length; + if (!length) return 0; + + const sorted = values.sort((a, b) => a - b); + const middle = Math.floor(length / 2); + return length % 2 + ? BigNumber(sorted[middle]) + .plus(sorted[middle - 1]) + .div(2) + .toNumber() + : sorted[middle]; +} diff --git a/libs/ledger-live-common/src/families/solana/bridge/mock-data.ts b/libs/ledger-live-common/src/families/solana/bridge/mock-data.ts index b21218e84d8b..900af12183f0 100644 --- a/libs/ledger-live-common/src/families/solana/bridge/mock-data.ts +++ b/libs/ledger-live-common/src/families/solana/bridge/mock-data.ts @@ -889,4 +889,20 @@ export const getMockedMethods = (): { }, // manual { method: "getLatestBlockhash", params: [], answer: LATEST_BLOCKHASH_MOCK }, + { + method: "getRecentPrioritizationFees", + params: [ + ["AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh"] + ], + answer: [[ + { + slot: 122422797, + prioritizationFee: 0, + }, + { + slot: 122422797, + prioritizationFee: 0, + }, + ]], + }, ]; From d1676df702cba616443a52896aa7cdad6b0e8ec8 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 26 Apr 2024 11:29:58 +0300 Subject: [PATCH 3/4] feat(LLC): add solana compute units optimisations --- .../coin-solana/src/api/cached.ts | 18 ++++++ .../coin-solana/src/api/chain/index.ts | 44 ++++++++++++++- .../coin-solana/src/api/chain/web3.ts | 55 ++++++++++++------- .../src/bridge.integration.test.ts | 1 + libs/coin-modules/coin-solana/src/tx-fees.ts | 15 ++--- 5 files changed, 103 insertions(+), 30 deletions(-) diff --git a/libs/coin-modules/coin-solana/src/api/cached.ts b/libs/coin-modules/coin-solana/src/api/cached.ts index 44f7e5b99f10..828471f14d2d 100644 --- a/libs/coin-modules/coin-solana/src/api/cached.ts +++ b/libs/coin-modules/coin-solana/src/api/cached.ts @@ -1,4 +1,5 @@ import { makeLRUCache, minutes, seconds } from "@ledgerhq/live-network/cache"; +import { PublicKey, TransactionInstruction, TransactionMessage } from "@solana/web3.js"; import hash from "object-hash"; import { ChainAPI } from "./chain"; @@ -8,6 +9,17 @@ const cacheKeyAssocTokenAccAddress = (owner: string, mint: string) => `${owner}: const cacheKeyMinimumBalanceForRentExemption = (dataLengt: number) => dataLengt.toString(); const cacheKeyTransactions = (signatures: string[]) => hash([...signatures].sort()); +const cacheKeyInstructions = (ixs: TransactionInstruction[], payer: PublicKey) => { + return hash( + new TransactionMessage({ + instructions: ixs, + payerKey: payer, + recentBlockhash: payer.toString(), + }) + .compileToLegacyMessage() + .serialize(), + ); +}; const cacheKeyByArgs = (...args: any[]) => hash(args); @@ -80,6 +92,12 @@ export function cached(api: ChainAPI): ChainAPI { seconds(30), ), + getSimulationComputeUnits: makeLRUCache( + api.getSimulationComputeUnits, + cacheKeyInstructions, + seconds(30), + ), + config: api.config, }; } diff --git a/libs/coin-modules/coin-solana/src/api/chain/index.ts b/libs/coin-modules/coin-solana/src/api/chain/index.ts index 796d9ec27df4..7ad46f725e2b 100644 --- a/libs/coin-modules/coin-solana/src/api/chain/index.ts +++ b/libs/coin-modules/coin-solana/src/api/chain/index.ts @@ -12,6 +12,10 @@ import { SignaturesForAddressOptions, StakeProgram, GetRecentPrioritizationFeesConfig, + TransactionInstruction, + ComputeBudgetProgram, + VersionedTransaction, + TransactionMessage, } from "@solana/web3.js"; import { makeLRUCache, minutes } from "@ledgerhq/live-network/cache"; import { getEnv } from "@ledgerhq/live-env"; @@ -73,9 +77,14 @@ export type ChainAPI = Readonly<{ getEpochInfo: () => ReturnType; getRecentPrioritizationFees: ( - config?: GetRecentPrioritizationFeesConfig, + accounts: string[], ) => ReturnType; + getSimulationComputeUnits: ( + instructions: Array, + payer: PublicKey, + ) => Promise; + config: Config; }>; @@ -212,8 +221,37 @@ export function getChainAPI( getEpochInfo: () => connection().getEpochInfo().catch(remapErrors), - getRecentPrioritizationFees: (config?: GetRecentPrioritizationFeesConfig) => { - return connection().getRecentPrioritizationFees(config).catch(remapErrors); + getRecentPrioritizationFees: (accounts: string[]) => { + return connection() + .getRecentPrioritizationFees({ + lockedWritableAccounts: accounts.map(acc => new PublicKey(acc)), + }) + .catch(remapErrors); + }, + + getSimulationComputeUnits: async (instructions, payer) => { + // https://solana.com/developers/guides/advanced/how-to-request-optimal-compute + const testInstructions = [ + // Set an arbitrarily high number in simulation + // so we can be sure the transaction will succeed + // and get the real compute units used + ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }), + ...instructions, + ]; + const testTransaction = new VersionedTransaction( + new TransactionMessage({ + instructions: testInstructions, + payerKey: payer, + // RecentBlockhash can by any public key during simulation + // since 'replaceRecentBlockhash' is set to 'true' below + recentBlockhash: PublicKey.default.toString(), + }).compileToV0Message(), + ); + const rpcResponse = await connection().simulateTransaction(testTransaction, { + replaceRecentBlockhash: true, + sigVerify: false, + }); + return rpcResponse.value.err ? null : rpcResponse.value.unitsConsumed || null; }, config, diff --git a/libs/coin-modules/coin-solana/src/api/chain/web3.ts b/libs/coin-modules/coin-solana/src/api/chain/web3.ts index 1d6cd4b35968..fff6da9b0e03 100644 --- a/libs/coin-modules/coin-solana/src/api/chain/web3.ts +++ b/libs/coin-modules/coin-solana/src/api/chain/web3.ts @@ -14,7 +14,7 @@ import { ComputeBudgetProgram, } from "@solana/web3.js"; import chunk from "lodash/chunk"; -import uniqBy from "lodash/uniqBy"; +import uniq from "lodash/uniq"; import { ChainAPI } from "."; import { Awaited } from "../../logic"; import { @@ -153,7 +153,7 @@ export const buildTransferInstructions = async ( instructions.push(memoIx); } - return appendMaybePriorityFeeInstruction(api, [fromPublicKey, toPublicKey], instructions); + return appendMaybePriorityFeeInstructions(api, instructions, fromPublicKey); }; export const buildTokenTransferInstructions = async ( @@ -272,18 +272,31 @@ export async function getStakeAccountAddressWithSeed({ return pubkey.toBase58(); } -export async function getPriorityFee(api: ChainAPI, accounts: PublicKey[]): Promise { - const uniqAccs = uniqBy(accounts, acc => acc.toBase58()); - const recentFees = await api.getRecentPrioritizationFees({ - lockedWritableAccounts: uniqAccs, - }); - +export async function getPriorityFee(api: ChainAPI, accounts: string[]): Promise { + const recentFees = await api.getRecentPrioritizationFees(uniq(accounts)); return median(recentFees.map(item => item.prioritizationFee)); } +export async function appendMaybePriorityFeeInstructions( + api: ChainAPI, + ixs: TransactionInstruction[], + payer: PublicKey, +): Promise { + const instructions = [...ixs]; + const writableAccs = instructions + .map(ix => ix.keys.filter(acc => acc.isWritable).map(acc => acc.pubkey.toBase58())) + .flat(); + const priorityFeeIx = await buildMaybePriorityFeeInstruction(api, writableAccs); + if (priorityFeeIx) instructions.unshift(priorityFeeIx); + const computeUnitsIx = await buildComputeUnitInstruction(api, instructions, payer); + + if (computeUnitsIx) instructions.unshift(computeUnitsIx); + return instructions; +} + export async function buildMaybePriorityFeeInstruction( api: ChainAPI, - accounts: PublicKey[], + accounts: string[], ): Promise { const priorityFee = await getPriorityFee(api, accounts); if (priorityFee === 0) return null; @@ -292,14 +305,16 @@ export async function buildMaybePriorityFeeInstruction( microLamports: priorityFee, }); } - -export async function appendMaybePriorityFeeInstruction( +export async function buildComputeUnitInstruction( api: ChainAPI, - accounts: PublicKey[], ixs: TransactionInstruction[], -): Promise { - const priorityFeeIx = await buildMaybePriorityFeeInstruction(api, accounts); - return priorityFeeIx ? [priorityFeeIx, ...ixs] : ixs; + payer: PublicKey, +): Promise { + const computeUnits = await api.getSimulationComputeUnits(ixs, payer); + // adding 10% more CPU to make sure it will work + return computeUnits + ? ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits * 0.1 + computeUnits }) + : null; } export function buildCreateAssociatedTokenAccountInstruction({ @@ -336,7 +351,7 @@ export async function buildStakeDelegateInstructions( votePubkey: voteAcc, }); - return appendMaybePriorityFeeInstruction(api, [withdrawAuthority, stakeAcc], tx.instructions); + return appendMaybePriorityFeeInstructions(api, tx.instructions, withdrawAuthority); } export async function buildStakeUndelegateInstructions( @@ -350,7 +365,7 @@ export async function buildStakeUndelegateInstructions( stakePubkey: stakeAcc, }); - return appendMaybePriorityFeeInstruction(api, [withdrawAuthority, stakeAcc], tx.instructions); + return appendMaybePriorityFeeInstructions(api, tx.instructions, withdrawAuthority); } export async function buildStakeWithdrawInstructions( @@ -367,7 +382,7 @@ export async function buildStakeWithdrawInstructions( toPubkey: recipient, }); - return appendMaybePriorityFeeInstruction(api, [withdrawAuthority, stakeAcc], tx.instructions); + return appendMaybePriorityFeeInstructions(api, tx.instructions, withdrawAuthority); } export async function buildStakeSplitInstructions( @@ -385,7 +400,7 @@ export async function buildStakeSplitInstructions( basePubkey: basePk, seed, }); - return appendMaybePriorityFeeInstruction(api, [basePk, stakePk], splitIx.instructions); + return appendMaybePriorityFeeInstructions(api, splitIx.instructions, basePk); } export async function buildStakeCreateAccountInstructions( @@ -421,5 +436,5 @@ export async function buildStakeCreateAccountInstructions( votePubkey: new PublicKey(delegate.voteAccAddress), }), ); - return appendMaybePriorityFeeInstruction(api, [fromPubkey, stakePubkey], tx.instructions); + return appendMaybePriorityFeeInstructions(api, tx.instructions, fromPubkey); } diff --git a/libs/coin-modules/coin-solana/src/bridge.integration.test.ts b/libs/coin-modules/coin-solana/src/bridge.integration.test.ts index fbda3455567f..124ae2f7bdfb 100644 --- a/libs/coin-modules/coin-solana/src/bridge.integration.test.ts +++ b/libs/coin-modules/coin-solana/src/bridge.integration.test.ts @@ -964,6 +964,7 @@ const baseAPI = { }, ]); }, + getSimulationComputeUnits: (_ixs: any[], _payer: any) => Promise.resolve(1000), } as ChainAPI; type StakeTestSpec = { diff --git a/libs/coin-modules/coin-solana/src/tx-fees.ts b/libs/coin-modules/coin-solana/src/tx-fees.ts index 4a05372c9f44..b25bdbfb363f 100644 --- a/libs/coin-modules/coin-solana/src/tx-fees.ts +++ b/libs/coin-modules/coin-solana/src/tx-fees.ts @@ -2,9 +2,10 @@ import { ChainAPI } from "./api"; import { buildTransactionWithAPI } from "./buildTransaction"; import createTransaction from "./createTransaction"; import { Transaction, TransactionModel } from "./types"; -import { assertUnreachable } from "./utils"; +import { LEDGER_VALIDATOR, assertUnreachable } from "./utils"; import { VersionedTransaction as OnChainTransaction } from "@solana/web3.js"; import { log } from "@ledgerhq/logs"; +import { getStakeAccountAddressWithSeed } from "./api/chain/web3"; const DEFAULT_TX_FEE = 5000; @@ -13,7 +14,7 @@ export async function estimateTxFee( address: string, kind: TransactionModel["kind"], ) { - const tx = createDummyTx(address, kind); + const tx = await createDummyTx(address, kind); const [onChainTx] = await buildTransactionWithAPI(address, tx, api); let fee = await api.getFeeForMessage(onChainTx.message); @@ -76,7 +77,7 @@ const createDummyTransferTx = (address: string): Transaction => { }; }; -const createDummyStakeCreateAccountTx = (address: string): Transaction => { +const createDummyStakeCreateAccountTx = async (address: string): Promise => { return { ...createTransaction({} as any), model: { @@ -87,12 +88,12 @@ const createDummyStakeCreateAccountTx = (address: string): Transaction => { kind: "stake.createAccount", amount: 0, delegate: { - voteAccAddress: randomAddresses[0], + voteAccAddress: LEDGER_VALIDATOR.voteAccount, }, fromAccAddress: address, seed: "", - stakeAccAddress: randomAddresses[1], - stakeAccRentExemptAmount: 0, + stakeAccAddress: await getStakeAccountAddressWithSeed({ fromAddress: address, seed: "" }), + stakeAccRentExemptAmount: 2282880, }, ...commandDescriptorCommons, }, @@ -149,7 +150,7 @@ const createDummyStakeWithdrawTx = (address: string): Transaction => { amount: 0, authorizedAccAddr: address, stakeAccAddr: randomAddresses[0], - toAccAddr: randomAddresses[1], + toAccAddr: address, }, ...commandDescriptorCommons, }, From 1dce47f40dc669bf441d15c2ea20d148ea31f330 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 30 Sep 2024 13:06:33 +0300 Subject: [PATCH 4/4] chore: downgrade @solana/spl-token@0.3.9 --- libs/coin-modules/coin-solana/package.json | 4 +- pnpm-lock.yaml | 221 +++------------------ 2 files changed, 34 insertions(+), 191 deletions(-) diff --git a/libs/coin-modules/coin-solana/package.json b/libs/coin-modules/coin-solana/package.json index 8dd57c0a47fe..ccbac599475d 100644 --- a/libs/coin-modules/coin-solana/package.json +++ b/libs/coin-modules/coin-solana/package.json @@ -53,7 +53,7 @@ "@ledgerhq/logs": "workspace:^", "@ledgerhq/types-cryptoassets": "workspace:^", "@ledgerhq/types-live": "workspace:^", - "@solana/spl-token": "0.4.6", + "@solana/spl-token": "0.3.9", "@solana/web3.js": "1.91.6", "bignumber.js": "^9.1.2", "bs58": "^4.0.1", @@ -65,12 +65,12 @@ "superstruct": "0.14.2" }, "devDependencies": { + "@faker-js/faker": "^8.4.1", "@types/bs58": "^4.0.1", "@types/invariant": "^2.2.2", "@types/jest": "^29.5.10", "@types/lodash": "^4.14.191", "@types/object-hash": "^2.1.0", - "@faker-js/faker": "^8.4.1", "jest": "^29.7.0", "ts-jest": "^29.1.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc6618c3afcc..b556279293e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2643,8 +2643,8 @@ importers: specifier: workspace:^ version: link:../../ledgerjs/packages/types-live '@solana/spl-token': - specifier: 0.4.6 - version: 0.4.6(@solana/web3.js@1.91.6) + specifier: 0.3.9 + version: 0.3.9(@solana/web3.js@1.91.6) '@solana/web3.js': specifier: 1.91.6 version: 1.91.6 @@ -7736,7 +7736,7 @@ packages: '@azure/core-http@3.0.4': resolution: {integrity: sha512-Fok9VVhMdxAFOtqiiAtg74fL0UJkt0z3D+ouUUxcRLzZNBioPRAMJFVxiWoJljYpXsRi4GDQHzQHDc9AiYaIUQ==} engines: {node: '>=14.0.0'} - deprecated: deprecating as we migrated to core v2 + deprecated: This package is no longer supported. Please migrate to use @azure/core-rest-pipeline '@azure/core-lro@2.7.1': resolution: {integrity: sha512-kXSlrNHOCTVZMxpXNRqzgh9/j4cnNXU5Hf2YjMyjddRhCXFiFRzmNaqwN+XO9rGTsCOIaaG7M67zZdyliXZG9g==} @@ -10744,7 +10744,7 @@ packages: resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} peerDependencies: '@types/react': '*' - react: '>=16' + react: '>=18.2.0' peerDependenciesMeta: '@types/react': optional: true @@ -11408,6 +11408,7 @@ packages: '@playwright/test@1.40.1': resolution: {integrity: sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==} engines: {node: '>=16'} + deprecated: Please update to the latest version of Playwright to test up-to-date browsers. hasBin: true '@playwright/test@1.45.0': @@ -11757,7 +11758,7 @@ packages: resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 + react: '>=18.2.0' peerDependenciesMeta: '@types/react': optional: true @@ -11793,8 +11794,8 @@ packages: peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: '>=18.2.0' + react-dom: '>=18.2.0' peerDependenciesMeta: '@types/react': optional: true @@ -13112,51 +13113,11 @@ packages: resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} engines: {node: '>=5.10'} - '@solana/codecs-core@2.0.0-preview.2': - resolution: {integrity: sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg==} - - '@solana/codecs-data-structures@2.0.0-preview.2': - resolution: {integrity: sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg==} - - '@solana/codecs-numbers@2.0.0-preview.2': - resolution: {integrity: sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw==} - - '@solana/codecs-strings@2.0.0-preview.2': - resolution: {integrity: sha512-EgBwY+lIaHHgMJIqVOGHfIfpdmmUDNoNO/GAUGeFPf+q0dF+DtwhJPEMShhzh64X2MeCZcmSO6Kinx0Bvmmz2g==} - peerDependencies: - fastestsmallesttextencoderdecoder: ^1.0.22 - - '@solana/codecs@2.0.0-preview.2': - resolution: {integrity: sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA==} - - '@solana/errors@2.0.0-preview.2': - resolution: {integrity: sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA==} - hasBin: true - - '@solana/options@2.0.0-preview.2': - resolution: {integrity: sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w==} - - '@solana/spl-token-group@0.0.4': - resolution: {integrity: sha512-7+80nrEMdUKlK37V6kOe024+T7J4nNss0F8LQ9OOPYdWCCfJmsGUzVx2W3oeizZR4IHM6N4yC9v1Xqwc3BTPWw==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.91.6 - - '@solana/spl-token-metadata@0.1.4': - resolution: {integrity: sha512-N3gZ8DlW6NWDV28+vCCDJoTqaCZiF/jDUnk3o8GRkAFzHObiR60Bs1gXHBa8zCPdvOwiG6Z3dg5pg7+RW6XNsQ==} + '@solana/spl-token@0.3.9': + resolution: {integrity: sha512-1EXHxKICMnab35MvvY/5DBc/K/uQAOJCYnDZXw83McCAYUAfi+rwq6qfd6MmITmSTEhcfBcl/zYxmW/OSN0RmA==} engines: {node: '>=16'} peerDependencies: - '@solana/web3.js': ^1.91.6 - - '@solana/spl-token@0.4.6': - resolution: {integrity: sha512-1nCnUqfHVtdguFciVWaY/RKcQz1IF4b31jnKgAmjU9QVN1q7dRUkTEWJZgTYIEtsULjVnC9jRqlhgGN39WbKKA==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.91.6 - - '@solana/spl-type-length-value@0.1.0': - resolution: {integrity: sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==} - engines: {node: '>=16'} + '@solana/web3.js': ^1.47.4 '@solana/web3.js@1.91.6': resolution: {integrity: sha512-dm20nN6HQvXToo+kM51nxHdtaa2wMSRdeK37p+WIWESfeiVHqV8XbV4XnWupq6ngt5vIckhGFG7ZnTBxUgLzDA==} @@ -13839,7 +13800,7 @@ packages: metro-react-native-babel-preset: '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - webpack: '*' + webpack: '5' peerDependenciesMeta: react: optional: true @@ -16151,7 +16112,7 @@ packages: resolution: {integrity: sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==} engines: {node: '>=6'} peerDependencies: - rxjs: '*' + rxjs: ^5.5.10 zenObservable: '*' peerDependenciesMeta: rxjs: @@ -19356,7 +19317,7 @@ packages: peerDependencies: '@typescript-eslint/eslint-plugin': ^4.0.0 || ^5.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - jest: '*' + jest: ^27.0.0 peerDependenciesMeta: '@typescript-eslint/eslint-plugin': optional: true @@ -20116,8 +20077,8 @@ packages: hasBin: true peerDependencies: expo-modules-core: '*' - react: '*' - react-native: '*' + react: 18.2.0 + react-native: 0.72.10 peerDependenciesMeta: expo-modules-core: optional: true @@ -20132,8 +20093,8 @@ packages: peerDependencies: expo-modules-autolinking: '*' expo-modules-core: '*' - react: '*' - react-native: '*' + react: 18.2.0 + react-native: 0.73.6 peerDependenciesMeta: expo-modules-autolinking: optional: true @@ -25919,6 +25880,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode-terminal@0.11.0: @@ -26157,7 +26119,7 @@ packages: resolution: {integrity: sha512-kBGxI+MIZGBf4wZhNCWwHkMcVP+kbpmrLWH/SkO0qCKc7D7eSPcxQbfpsmsCo8v2KCBYjuGSou+xTqK44D/jMg==} engines: {npm: ^3.0.0} peerDependencies: - prop-types: '*' + prop-types: ^15.6.1 react: '>=15.0.0' peerDependenciesMeta: prop-types: @@ -26184,8 +26146,8 @@ packages: react-native-animatable@1.3.3: resolution: {integrity: sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==} peerDependencies: - react: '*' - react-native: '*' + react: 16.9.0 + react-native: 0.61.2 peerDependenciesMeta: react: optional: true @@ -26195,8 +26157,8 @@ packages: react-native-animatable@1.4.0: resolution: {integrity: sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==} peerDependencies: - react: '*' - react-native: '*' + react: 18.2.0 + react-native: 0.72.6 peerDependenciesMeta: react: optional: true @@ -34745,7 +34707,7 @@ snapshots: '@ethersproject/transactions': 5.7.0 '@ethersproject/web': 5.7.1 bech32: 1.1.4 - ws: 7.4.6 + ws: 8.18.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -41015,81 +40977,17 @@ snapshots: dependencies: buffer: 6.0.3(patch_hash=2xnca52oxhztvr7iaoovwclcze) - '@solana/codecs-core@2.0.0-preview.2': - dependencies: - '@solana/errors': 2.0.0-preview.2 - - '@solana/codecs-data-structures@2.0.0-preview.2': - dependencies: - '@solana/codecs-core': 2.0.0-preview.2 - '@solana/codecs-numbers': 2.0.0-preview.2 - '@solana/errors': 2.0.0-preview.2 - - '@solana/codecs-numbers@2.0.0-preview.2': - dependencies: - '@solana/codecs-core': 2.0.0-preview.2 - '@solana/errors': 2.0.0-preview.2 - - '@solana/codecs-strings@2.0.0-preview.2': - dependencies: - '@solana/codecs-core': 2.0.0-preview.2 - '@solana/codecs-numbers': 2.0.0-preview.2 - '@solana/errors': 2.0.0-preview.2 - - '@solana/codecs@2.0.0-preview.2': - dependencies: - '@solana/codecs-core': 2.0.0-preview.2 - '@solana/codecs-data-structures': 2.0.0-preview.2 - '@solana/codecs-numbers': 2.0.0-preview.2 - '@solana/codecs-strings': 2.0.0-preview.2 - '@solana/options': 2.0.0-preview.2 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - '@solana/errors@2.0.0-preview.2': - dependencies: - chalk: 5.3.0 - commander: 12.0.0 - - '@solana/options@2.0.0-preview.2': - dependencies: - '@solana/codecs-core': 2.0.0-preview.2 - '@solana/codecs-numbers': 2.0.0-preview.2 - - '@solana/spl-token-group@0.0.4(@solana/web3.js@1.91.6)': - dependencies: - '@solana/codecs': 2.0.0-preview.2 - '@solana/spl-type-length-value': 0.1.0 - '@solana/web3.js': 1.91.6 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - '@solana/spl-token-metadata@0.1.4(@solana/web3.js@1.91.6)': - dependencies: - '@solana/codecs': 2.0.0-preview.2 - '@solana/spl-type-length-value': 0.1.0 - '@solana/web3.js': 1.91.6 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - '@solana/spl-token@0.4.6(@solana/web3.js@1.91.6)': + '@solana/spl-token@0.3.9(@solana/web3.js@1.91.6)': dependencies: '@solana/buffer-layout': 4.0.1 '@solana/buffer-layout-utils': 0.2.0 - '@solana/spl-token-group': 0.0.4(@solana/web3.js@1.91.6) - '@solana/spl-token-metadata': 0.1.4(@solana/web3.js@1.91.6) '@solana/web3.js': 1.91.6 buffer: 6.0.3(patch_hash=2xnca52oxhztvr7iaoovwclcze) transitivePeerDependencies: - bufferutil - encoding - - fastestsmallesttextencoderdecoder - utf-8-validate - '@solana/spl-type-length-value@0.1.0': - dependencies: - buffer: 6.0.3(patch_hash=2xnca52oxhztvr7iaoovwclcze) - '@solana/web3.js@1.91.6': dependencies: '@babel/runtime': 7.24.1 @@ -47266,7 +47164,7 @@ snapshots: bs58check@2.1.2: dependencies: - bs58: 4.0.1 + bs58: 5.0.0 create-hash: 1.2.0 safe-buffer: 5.2.1 @@ -50146,8 +50044,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -50204,23 +50102,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): - dependencies: - debug: 4.3.4 - enhanced-resolve: 5.16.0 - eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - fast-glob: 3.3.2 - get-tsconfig: 4.7.5 - is-core-module: 2.13.1 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.4 @@ -50265,17 +50146,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -50365,33 +50235,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.13.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 @@ -51208,12 +51051,12 @@ snapshots: react: 18.2.0 react-native: 0.73.6(@babel/core@7.24.3)(react@18.2.0) - expo-font@11.4.0(67hrxnryf4ycl5nhvln5laaykq): + expo-font@11.4.0(hxmq25pkdf6fvmayb5kzqynb3q): dependencies: expo: 49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) fontfaceobserver: 2.3.0 optionalDependencies: - expo-asset: 8.10.1(tw2cl75ufjodqrrf2cewclqsqi) + expo-asset: 8.10.1(expo-constants@14.5.1(expo-modules-core@1.5.11)(expo@49.0.23)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-modules-core: 1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) react: 18.2.0 @@ -51338,7 +51181,7 @@ snapshots: expo-asset: 8.10.1(tw2cl75ufjodqrrf2cewclqsqi) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-file-system: 15.4.5(tw2cl75ufjodqrrf2cewclqsqi) - expo-font: 11.4.0(67hrxnryf4ycl5nhvln5laaykq) + expo-font: 11.4.0(hxmq25pkdf6fvmayb5kzqynb3q) expo-keep-awake: 12.3.0(tw2cl75ufjodqrrf2cewclqsqi) expo-modules-autolinking: 1.5.1(55fu4l7dolnnxrys6pt2pnhfne) fbemitter: 3.0.0