diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/claimRewards.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/claimRewards.ts index a558d9da9..d8c7d5777 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/claimRewards.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/claimRewards.ts @@ -236,16 +236,37 @@ export const claimRewards = publicProcedure.hotspots.claimRewards.handler( const recipientKeys = assets.map( (asset) => recipientKey(lazyDistributor, asset)[0], ); - const recipientAccounts = - await ldProgram.account.recipientV0.fetchMultiple(recipientKeys); - const numRecipientsNeeded = recipientAccounts.filter((r) => !r).length; + const [recipientAccountInfos, ldAcc] = await Promise.all([ + connection.getMultipleAccountsInfo(recipientKeys), + ldProgram.account.lazyDistributorV0.fetch(lazyDistributor), + ]); + const numRecipientsNeeded = recipientAccountInfos.filter((r: unknown) => !r).length; const jitoTipCost = shouldUseJitoBundle(allVtxs.length, getCluster()) ? getJitoTipAmountLamports() : 0; - const estimatedResizeCost = assets.length * 500_000; + + // Compute resize cost for existing recipients. + // resize_to_fit sets new_size = serialized_size + 64 (padding). + // RecipientV0 serialized = 8 (discriminator) + 32 + 32 + 8 + 2 + // + 4 + 9*num_oracles + 1 + 8 + 32 = 127 + 9*num_oracles + // new_size = 127 + 9*num_oracles + 64 = 191 + 9*num_oracles + const numOracles = ldAcc.oracles.length; + const newRecipientSize = 191 + 9 * numOracles; + // Solana rent: 19.055441478 lamports per byte-year * 2 years + 128 bytes base + // rent.minimum_balance(size) = (size + 128) * 6960 (approx, but use exact formula) + const LAMPORTS_PER_BYTE_YEAR = 3480; + const rentExemptForNewSize = (newRecipientSize + 128) * LAMPORTS_PER_BYTE_YEAR * 2; + const resizeCost = recipientAccountInfos.reduce( + (sum: number, info: { lamports: number } | null) => { + if (!info) return sum; // New recipients handled by RECIPIENT_RENT_LAMPORTS + const deficit = rentExemptForNewSize - info.lamports; + return sum + Math.max(0, deficit); + }, + 0, + ); const rentCost = numRecipientsNeeded * RECIPIENT_RENT_LAMPORTS; const requiredLamports = calculateRequiredBalance( - txFees + jitoTipCost + estimatedResizeCost, + txFees + jitoTipCost + resizeCost, rentCost, ); const senderBalance = await connection.getBalance( diff --git a/packages/blockchain-api/src/server/api/routers/migration/procedures/migrate.ts b/packages/blockchain-api/src/server/api/routers/migration/procedures/migrate.ts index e1959a137..48217491c 100644 --- a/packages/blockchain-api/src/server/api/routers/migration/procedures/migrate.ts +++ b/packages/blockchain-api/src/server/api/routers/migration/procedures/migrate.ts @@ -210,7 +210,18 @@ export const migrate = publicProcedure.migration.migrate.handler( ), ); - const mintInfo = await getMint(connection, mintKey); + const [mintInfo, sourceAtaInfo] = await Promise.all([ + getMint(connection, mintKey), + getAccount(connection, sourceAta).catch(() => null), + ]); + + // Skip frozen token accounts (e.g. DC tokens are frozen by the data credits program) + if (sourceAtaInfo?.isFrozen) { + warnings.push( + `Skipping ${token.mint}: token account is frozen and cannot be transferred`, + ); + continue; + } tokenTransferInstructions.push( createTransferCheckedInstruction( @@ -224,9 +235,6 @@ export const migrate = publicProcedure.migration.migrate.handler( ); // Close source ATA if transferring full balance (rent → fee payer) - const sourceAtaInfo = await getAccount(connection, sourceAta).catch( - () => null, - ); if ( sourceAtaInfo && BigInt(sourceAtaInfo.amount.toString()) === rawAmount