diff --git a/packages/blockchain-api/src/lib/constants/tokens.ts b/packages/blockchain-api/src/lib/constants/tokens.ts index b95b2e825..1e6ab9310 100644 --- a/packages/blockchain-api/src/lib/constants/tokens.ts +++ b/packages/blockchain-api/src/lib/constants/tokens.ts @@ -1,3 +1,6 @@ +import { Connection, PublicKey } from "@solana/web3.js"; +import { getMint } from "@solana/spl-token"; + /** * Common token mint addresses on Solana mainnet */ @@ -33,3 +36,20 @@ export const TOKEN_DECIMALS: Record = { [TOKEN_MINTS.IOT]: 6, [TOKEN_MINTS.DC]: 0, }; + +const decimalsCache = new Map(Object.entries(TOKEN_DECIMALS)); + +/** + * Get decimals for a token mint. Returns from static map for known tokens, + * otherwise fetches from RPC and caches the result. + */ +export async function getTokenDecimals(mint: string): Promise { + const cached = decimalsCache.get(mint); + if (cached !== undefined) return cached; + + const { env } = await import("@/lib/env"); + const connection = new Connection(env.SOLANA_RPC_URL); + const mintInfo = await getMint(connection, new PublicKey(mint)); + decimalsCache.set(mint, mintInfo.decimals); + return mintInfo.decimals; +} diff --git a/packages/blockchain-api/src/lib/utils/token-math.ts b/packages/blockchain-api/src/lib/utils/token-math.ts index 97b5e7222..7b650ab32 100644 --- a/packages/blockchain-api/src/lib/utils/token-math.ts +++ b/packages/blockchain-api/src/lib/utils/token-math.ts @@ -5,7 +5,11 @@ import type { TokenAmountInput, TokenAmountOutput, } from "@helium/blockchain-api/schemas/common"; -import { TOKEN_DECIMALS, TOKEN_MINTS } from "@/lib/constants/tokens"; +import { + TOKEN_DECIMALS, + TOKEN_MINTS, + getTokenDecimals, +} from "@/lib/constants/tokens"; export const HNT_DECIMALS = TOKEN_DECIMALS[TOKEN_MINTS.HNT]; const HNT_DIVISOR = new BN(10).pow(new BN(HNT_DECIMALS)); @@ -27,19 +31,16 @@ export function solToLamportsBN(sol: number): BN { return new BN(Math.round(sol * 10 ** SOL_DECIMALS)); } -export function resolveTokenAmountInput( +export async function resolveTokenAmountInput( input: TokenAmountInput, expectedMint?: string, -): BN { +): Promise { if (expectedMint && input.mint !== expectedMint) { throw new Error( `Mint mismatch: expected ${expectedMint}, got ${input.mint}`, ); } - const decimals = TOKEN_DECIMALS[input.mint]; - if (decimals === undefined) { - throw new Error(`Unknown mint: ${input.mint}`); - } + await getTokenDecimals(input.mint); return new BN(input.amount); } @@ -54,14 +55,11 @@ function formatTokenAmount(raw: string, decimals: number): string { return isNegative ? `-${result}` : result; } -export function toTokenAmountOutput( +export async function toTokenAmountOutput( rawAmount: BN, mint: string, -): TokenAmountOutput { - const decimals = TOKEN_DECIMALS[mint]; - if (decimals === undefined) { - throw new Error(`Unknown mint: ${mint}`); - } +): Promise { + const decimals = await getTokenDecimals(mint); const amount = rawAmount.toString(); diff --git a/packages/blockchain-api/src/server/api/routers/fiat/router.ts b/packages/blockchain-api/src/server/api/routers/fiat/router.ts index 46b76aeec..1845bc22b 100644 --- a/packages/blockchain-api/src/server/api/routers/fiat/router.ts +++ b/packages/blockchain-api/src/server/api/routers/fiat/router.ts @@ -632,7 +632,7 @@ const sendFunds = publicProcedure.fiat.sendFunds.handler( tag, actionMetadata: { type: "bank_send", usdAmount: (parseFloat(quoteResponse.outAmount) / 1e6).toFixed(2), bankAccountId: id }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(getTransactionFee(tx) + rentCost), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/claim-rewards.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/claim-rewards.ts index e7de5ce17..30461e3c6 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/claim-rewards.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/claim-rewards.ts @@ -94,7 +94,7 @@ export const claimRewards = return { transactionData: { transactions: [], parallel: false, tag }, hasMore: false, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(0), NATIVE_MINT.toBase58(), ), @@ -154,7 +154,7 @@ export const claimRewards = actionMetadata: { type: "delegation_claim_rewards", positionCount: positionMints.length }, }, hasMore: claimResult.hasMore || batchHasMore, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/delegate.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/delegate.ts index 4803598ef..b0690f1d6 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/delegate.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/delegate.ts @@ -288,7 +288,7 @@ export const delegate = publicProcedure.governance.delegatePositions.handler( tag, }, hasMore: true, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(claimTxFee), NATIVE_MINT.toBase58(), ), @@ -599,7 +599,7 @@ export const delegate = publicProcedure.governance.delegatePositions.handler( return { transactionData: { transactions: [], parallel: false, tag }, hasMore: false, - estimatedSolFee: toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), + estimatedSolFee: await toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), }; } @@ -644,7 +644,7 @@ export const delegate = publicProcedure.governance.delegatePositions.handler( tag, actionMetadata: { type: "delegation_delegate", subDaoMint, positionCount: positionMints.length }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/extend.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/extend.ts index 8e182708c..918c84abe 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/extend.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/extend.ts @@ -118,7 +118,7 @@ export const extend = publicProcedure.governance.extendDelegation.handler( if (delegatedPositionAcc.expirationTs.gte(newExpirationTs)) { return { transactionData: { transactions: [], parallel: false, tag }, - estimatedSolFee: toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), + estimatedSolFee: await toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), }; } @@ -182,7 +182,7 @@ export const extend = publicProcedure.governance.extendDelegation.handler( tag, actionMetadata: { type: "delegation_extend", positionMint }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/undelegate.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/undelegate.ts index 9a841a48d..30255210a 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/undelegate.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/delegation/undelegate.ts @@ -128,7 +128,7 @@ export const undelegate = publicProcedure.governance.undelegatePosition.handler( tag, }, hasMore: true, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), @@ -231,7 +231,7 @@ export const undelegate = publicProcedure.governance.undelegatePosition.handler( actionMetadata: { type: "delegation_undelegate", positionMint }, }, hasMore: batchHasMore, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/helpers/build-claim-instructions.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/helpers/build-claim-instructions.ts index 19fc48b89..de497d6ac 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/helpers/build-claim-instructions.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/helpers/build-claim-instructions.ts @@ -143,7 +143,7 @@ export async function buildClaimInstructions( const bitmapWindowEnd = lastClaimedEpoch.add(new BN(129)).toNumber(); const rawEndEpoch = isDecayed ? decayedEpoch.add(new BN(1)).toNumber() - : currentEpoch.sub(new BN(1)).toNumber(); + : currentEpoch.toNumber(); const endEpoch = Math.min(rawEndEpoch, bitmapWindowEnd); if (rawEndEpoch > bitmapWindowEnd) { diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/close.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/close.ts index 432b7da60..a831449e0 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/close.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/close.ts @@ -147,7 +147,7 @@ export const close = publicProcedure.governance.closePosition.handler( tag, actionMetadata: { type: "position_close", positionMint }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/create.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/create.ts index 9a87ab856..412ab32a6 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/create.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/create.ts @@ -68,7 +68,7 @@ export const create = publicProcedure.governance.createPosition.handler( const { connection, provider } = createSolanaConnection(walletAddress); const walletPubkey = new PublicKey(walletAddress); const mintPubkey = new PublicKey(tokenAmount.mint); - const amount = resolveTokenAmountInput(tokenAmount); + const amount = await resolveTokenAmountInput(tokenAmount); const hsdProgram = await initHsd(provider); const vsrProgram = await initVsr(provider); @@ -355,7 +355,7 @@ export const create = publicProcedure.governance.createPosition.handler( tag, actionMetadata: { type: "position_create", - tokenAmount: toTokenAmountOutput( + tokenAmount: await toTokenAmountOutput( new BN(tokenAmount.amount), tokenAmount.mint, ), @@ -364,7 +364,7 @@ export const create = publicProcedure.governance.createPosition.handler( lockupPeriodDays: lockupPeriodsInDays, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/extend.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/extend.ts index ca8f79d6e..77626728d 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/extend.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/extend.ts @@ -107,7 +107,7 @@ export const extend = publicProcedure.governance.extendPosition.handler( tag, actionMetadata: { type: "position_extend", positionMint, lockupPeriodDays: lockupPeriodsInDays }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/flip-lockup-kind.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/flip-lockup-kind.ts index 426b0a565..5a476f235 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/flip-lockup-kind.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/flip-lockup-kind.ts @@ -134,7 +134,7 @@ export const flipLockupKind = publicProcedure.governance.flipLockupKind.handler( tag, actionMetadata: { type: "position_flip_lockup", positionMint }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/reset-lockup.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/reset-lockup.ts index 7781baaf0..a987c8fe0 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/reset-lockup.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/reset-lockup.ts @@ -109,7 +109,7 @@ export const resetLockup = publicProcedure.governance.resetLockup.handler( tag, actionMetadata: { type: "position_reset_lockup", positionMint, lockupKind, lockupPeriodDays: lockupPeriodsInDays }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/split.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/split.ts index 5c62902d2..942269f42 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/split.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/split.ts @@ -195,11 +195,11 @@ export const split = publicProcedure.governance.splitPosition.handler( actionMetadata: { type: "position_split", positionMint, - tokenAmount: toTokenAmountOutput(amountBN, depositMintStr), + tokenAmount: await toTokenAmountOutput(amountBN, depositMintStr), tokenName: TOKEN_NAMES[depositMintStr], }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer-ownership.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer-ownership.ts index aabbd85c0..5ae677cc7 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer-ownership.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer-ownership.ts @@ -96,7 +96,7 @@ export const transferOwnership = tag, actionMetadata: { type: "position_transfer_ownership", positionMint, from, to }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer.ts index c358b0539..eaa01bb11 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/positions/transfer.ts @@ -163,11 +163,11 @@ export const transfer = publicProcedure.governance.transferPosition.handler( type: "position_transfer", positionMint, targetPositionMint, - tokenAmount: toTokenAmountOutput(amountBN, depositMintStr), + tokenAmount: await toTokenAmountOutput(amountBN, depositMintStr), tokenName: TOKEN_NAMES[depositMintStr], }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/assign.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/assign.ts index 42c16476d..aa1406622 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/assign.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/assign.ts @@ -353,7 +353,7 @@ export const assign = publicProcedure.governance.assignProxies.handler( actionMetadata: { type: "proxy_assign", proxyKey, positionCount: positionMints.length }, }, hasMore, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(totalFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/unassign.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/unassign.ts index 206396494..9c08b8e97 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/unassign.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/proxy/unassign.ts @@ -187,7 +187,7 @@ export const unassign = publicProcedure.governance.unassignProxies.handler( actionMetadata: { type: "proxy_unassign", proxyKey, positionCount: positionMints.length }, }, hasMore, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(totalFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-position-votes.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-position-votes.ts index 094af63c9..63acac287 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-position-votes.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-position-votes.ts @@ -161,7 +161,7 @@ export const relinquishPositionVotes = actionMetadata: { type: "voting_relinquish_position", positionMint, organization }, }, hasMore, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(totalFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-vote.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-vote.ts index 0ddced797..412c36f69 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-vote.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/relinquish-vote.ts @@ -251,7 +251,7 @@ export const relinquishVote = publicProcedure.governance.relinquishVote.handler( actionMetadata: { type: "voting_relinquish", proposalKey, choice, positionCount: positionMints.length }, }, hasMore, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(totalFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/vote.ts b/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/vote.ts index ded09e51f..dde1c1a56 100644 --- a/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/vote.ts +++ b/packages/blockchain-api/src/server/api/routers/governance/procedures/voting/vote.ts @@ -398,7 +398,7 @@ export const vote = publicProcedure.governance.vote.handler( actionMetadata: { type: "voting_vote", proposalKey, choice, positionCount: positionMints.length }, }, hasMore, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(totalFee), NATIVE_MINT.toBase58(), ), 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 d8c7d5777..26aa9f0bc 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 @@ -214,7 +214,7 @@ export const claimRewards = publicProcedure.hotspots.claimRewards.handler( estimatedPendingRewards, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(0), NATIVE_MINT.toBase58(), ), @@ -303,7 +303,7 @@ export const claimRewards = publicProcedure.hotspots.claimRewards.handler( estimatedPendingRewards, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFees + rentCost), NATIVE_MINT.toBase58(), ), @@ -449,7 +449,7 @@ export const claimRewards = publicProcedure.hotspots.claimRewards.handler( estimatedPendingRewards, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFees + rentCost), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/closeAutomation.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/closeAutomation.ts index 59d0db514..fcc948b5e 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/closeAutomation.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/closeAutomation.ts @@ -148,7 +148,7 @@ export const closeAutomation = publicProcedure.hotspots.closeAutomation.handler( tag: `close_automation:${walletAddress}`, actionMetadata: { type: "close_automation" }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFees), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createAutomation.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createAutomation.ts index d5414b775..d1a0fe59c 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createAutomation.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createAutomation.ts @@ -316,7 +316,7 @@ export const createAutomation = tag: `setup_automation:${walletAddress}`, actionMetadata: { type: "setup_automation", schedule, duration }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createSplit.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createSplit.ts index 604dd8305..19599ab6e 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createSplit.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/createSplit.ts @@ -127,20 +127,20 @@ export const createSplit = publicProcedure.hotspots.createSplit.handler( const { instruction: initIx, pubkeys } = await miniFanoutProgram.methods .initializeMiniFanoutV0({ seed: new PublicKey(assetId).toBuffer(), - shares: rewardsSplit.map((split) => ({ + shares: await Promise.all(rewardsSplit.map(async (split) => ({ wallet: new PublicKey(split.address), share: split.type === "fixed" ? { fixed: { - amount: resolveTokenAmountInput( + amount: await resolveTokenAmountInput( split.tokenAmount, HNT_MINT.toBase58() ), }, } : { share: { amount: split.amount } }, - })), + }))), schedule: rewardsSchedule, preTask: { remoteV0: { @@ -281,7 +281,7 @@ export const createSplit = publicProcedure.hotspots.createSplit.handler( schedule, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58() ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/deleteSplit.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/deleteSplit.ts index e54e78d8f..54cbc7cd3 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/deleteSplit.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/deleteSplit.ts @@ -158,7 +158,7 @@ export const deleteSplit = publicProcedure.hotspots.deleteSplit.handler( hotspotName, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58() ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/fundAutomation.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/fundAutomation.ts index db21a821c..1259a9afb 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/fundAutomation.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/fundAutomation.ts @@ -176,7 +176,7 @@ export const fundAutomation = publicProcedure.hotspots.fundAutomation.handler( tag: `fund_automation:${walletAddress}`, actionMetadata: { type: "fund_automation", additionalDuration }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getPendingRewards.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getPendingRewards.ts index 3da58cd97..d871bb166 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getPendingRewards.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getPendingRewards.ts @@ -28,12 +28,12 @@ interface MiniFanoutShare { }; } -function zeroPending(mintAddress: string): { +async function zeroPending(mintAddress: string): Promise<{ total: TokenAmountOutput; claimable: TokenAmountOutput; automated: TokenAmountOutput; -} { - const zero = toTokenAmountOutput(new BN(0), mintAddress); +}> { + const zero = await toTokenAmountOutput(new BN(0), mintAddress); return { total: zero, claimable: zero, automated: zero }; } @@ -44,7 +44,7 @@ export const getPendingRewards = const mintAddress = mint.toBase58(); const lazyDistributor = getLazyDistributorForNetwork(network); - const tokenOutput = (bn: BN): TokenAmountOutput => + const tokenOutput = (bn: BN): Promise => toTokenAmountOutput(bn, mintAddress); const hotspotsData = await getHotspotsByOwner({ @@ -53,7 +53,7 @@ export const getPendingRewards = }); if (hotspotsData.hotspots.length === 0) { - return { pending: zeroPending(mintAddress), byHotspot: [] }; + return { pending: await zeroPending(mintAddress), byHotspot: [] }; } // Deduplicate hotspots by asset to prevent double-counting @@ -225,13 +225,13 @@ export const getPendingRewards = let claimableBn = new BN(0); let automatedBn = pendingInMiniFanoutATAs; // ATA balance is always automated - const byHotspot = uniqueHotspots - .map((hotspot, idx) => { + const byHotspot = (await Promise.all(uniqueHotspots + .map(async (hotspot, idx) => { const oraclePendingBn = netPendingPerHotspot[idx]!; const ataPendingBn = ataBalancePerHotspot[idx]!; const totalPendingBn = oraclePendingBn.add(ataPendingBn); const miniFanout = miniFanouts[idx]; - const zeroOut = tokenOutput(new BN(0)); + const zeroOut = await tokenOutput(new BN(0)); const isAutomated = hasAutomation || !!miniFanout; if (isAutomated) { @@ -242,7 +242,7 @@ export const getPendingRewards = if (totalPendingBn.isZero()) return null; - const totalOut = tokenOutput(totalPendingBn); + const totalOut = await tokenOutput(totalPendingBn); return { hotspotPubKey: hotspot.entityKey, pending: { @@ -251,18 +251,18 @@ export const getPendingRewards = automated: isAutomated ? totalOut : zeroOut, }, }; - }) - .filter(truthy); + }), + )).filter(truthy); closeSingleton(connection); - const totalOut = tokenOutput(totalPendingBn); + const totalOut = await tokenOutput(totalPendingBn); return { pending: { total: totalOut, - claimable: tokenOutput(claimableBn), - automated: tokenOutput(automatedBn), + claimable: await tokenOutput(claimableBn), + automated: await tokenOutput(automatedBn), }, byHotspot, }; diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getSplit.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getSplit.ts index 9544eb57d..f3cef5086 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getSplit.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/getSplit.ts @@ -67,17 +67,17 @@ export const getSplit = publicProcedure.hotspots.getSplit.handler( walletAddress, hotspotPubkey, splitAddress: assetOwner.recipient.split.address, - shares: shares.map((share: MiniFanoutShare) => ({ + shares: await Promise.all(shares.map(async (share: MiniFanoutShare) => ({ wallet: share.wallet, delegate: share.delegate, - fixed: toTokenAmountOutput( + fixed: await toTokenAmountOutput( new BN(String(share.share?.fixed?.amount ?? 0)), HNT_MINT.toBase58(), ), shares: share.share?.share?.amount ? share.share.share.amount / totalShares : 0, - })), + }))), }; }, ); diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/transferHotspot.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/transferHotspot.ts index de242df6e..35e851865 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/transferHotspot.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/transferHotspot.ts @@ -206,7 +206,7 @@ export const transferHotspot = publicProcedure.hotspots.transferHotspot.handler( tag, actionMetadata: { type: TRANSACTION_TYPES.HOTSPOT_TRANSFER, hotspotKey: assetId, hotspotName, recipient }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateHotspotInfo.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateHotspotInfo.ts index 173c0fcc1..76db9c08e 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateHotspotInfo.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateHotspotInfo.ts @@ -312,7 +312,7 @@ export const updateHotspotInfo = }), }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(totalFee), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateRewardsDestination.ts b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateRewardsDestination.ts index f3e05ff2c..65656bf82 100644 --- a/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateRewardsDestination.ts +++ b/packages/blockchain-api/src/server/api/routers/hotspots/procedures/updateRewardsDestination.ts @@ -216,7 +216,7 @@ export const updateRewardsDestination = lazyDistributors, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), 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 48217491c..e7742646a 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 @@ -820,7 +820,7 @@ export const migrate = publicProcedure.migration.migrate.handler( tag, actionMetadata: { type: "migration", sourceWallet, destinationWallet, hotspotCount: hotspots?.length ?? 0 }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFees), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/claim.ts b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/claim.ts index aa1b13fbc..04fb2b984 100644 --- a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/claim.ts +++ b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/claim.ts @@ -244,6 +244,6 @@ async function buildClaimTransaction({ parallel: false, tag, }, - estimatedSolFee: toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), + estimatedSolFee: await toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), }; } diff --git a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/create.ts b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/create.ts index 238312303..6c257d678 100644 --- a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/create.ts +++ b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/create.ts @@ -138,10 +138,10 @@ export const create = publicProcedure.rewardContract.create.handler( rentCost += RENT_COSTS.WELCOME_PACK + RENT_COSTS.USER_WELCOME_PACKS; const claimableRecipient = recipients.find((r) => r.type === "CLAIMABLE"); if (claimableRecipient?.type === "CLAIMABLE") { - rentCost += resolveTokenAmountInput( + rentCost += (await resolveTokenAmountInput( claimableRecipient.giftedCurrency, NATIVE_MINT.toBase58(), - ).toNumber(); + )).toNumber(); } } else { // Mini-fanout path - add funding amount @@ -181,13 +181,13 @@ export const create = publicProcedure.rewardContract.create.handler( const claimableRecipient = recipients.find((r) => r.type === "CLAIMABLE"); const solAmount = claimableRecipient?.type === "CLAIMABLE" - ? resolveTokenAmountInput( + ? await resolveTokenAmountInput( claimableRecipient.giftedCurrency, NATIVE_MINT.toBase58(), ) : new BN(0); - const rewardsSplit = recipients.map((r) => { + const rewardsSplit = await Promise.all(recipients.map(async (r) => { const wallet = r.type === "PRESET" ? new PublicKey(r.walletAddress) @@ -197,7 +197,7 @@ export const create = publicProcedure.rewardContract.create.handler( return { share: { fixed: { - amount: resolveTokenAmountInput( + amount: await resolveTokenAmountInput( r.receives.tokenAmount, HNT_MINT.toBase58(), ), @@ -210,7 +210,7 @@ export const create = publicProcedure.rewardContract.create.handler( share: { share: { amount: r.receives.shares } }, wallet, }; - }); + })); const { instruction: ix } = await ( await initializeWelcomePack({ @@ -250,7 +250,7 @@ export const create = publicProcedure.rewardContract.create.handler( const oracleSigner = new PublicKey(env.ORACLE_SIGNER); const oracleUrl = env.ORACLE_URL; - const shares = recipients.map((r) => { + const shares = await Promise.all(recipients.map(async (r) => { const wallet = r.type === "PRESET" ? new PublicKey(r.walletAddress) @@ -261,7 +261,7 @@ export const create = publicProcedure.rewardContract.create.handler( wallet, share: { fixed: { - amount: resolveTokenAmountInput( + amount: await resolveTokenAmountInput( r.receives.tokenAmount, HNT_MINT.toBase58(), ), @@ -273,7 +273,7 @@ export const create = publicProcedure.rewardContract.create.handler( wallet, share: { share: { amount: r.receives.shares } }, }; - }); + })); const { instruction: initIx, pubkeys } = await miniFanoutProgram.methods .initializeMiniFanoutV0({ @@ -377,7 +377,7 @@ export const create = publicProcedure.rewardContract.create.handler( parallel: false, tag, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/delete.ts b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/delete.ts index c8ffd7132..eaeb3ccd6 100644 --- a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/delete.ts +++ b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/delete.ts @@ -259,6 +259,6 @@ async function buildDeleteTransaction( parallel: false, tag, }, - estimatedSolFee: toTokenAmountOutput(new BN(txFee), NATIVE_MINT.toBase58()), + estimatedSolFee: await toTokenAmountOutput(new BN(txFee), NATIVE_MINT.toBase58()), }; } diff --git a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/estimateCreationCost.ts b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/estimateCreationCost.ts index 0b4d0c8cd..f0bdc500a 100644 --- a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/estimateCreationCost.ts +++ b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/estimateCreationCost.ts @@ -58,7 +58,7 @@ export const estimateCreationCost = (r) => r.type === "CLAIMABLE", ); if (claimableRecipient?.type === "CLAIMABLE") { - recipientGift = resolveTokenAmountInput( + recipientGift = await resolveTokenAmountInput( claimableRecipient.giftedCurrency, NATIVE_MINT.toBase58(), ); @@ -76,11 +76,11 @@ export const estimateCreationCost = const solMint = NATIVE_MINT.toBase58(); return { - total: toTokenAmountOutput(total, solMint), + total: await toTokenAmountOutput(total, solMint), lineItems: { - transactionFees: toTokenAmountOutput(transactionFees, solMint), - rentFee: toTokenAmountOutput(rentFee, solMint), - recipientGift: toTokenAmountOutput(recipientGift, solMint), + transactionFees: await toTokenAmountOutput(transactionFees, solMint), + rentFee: await toTokenAmountOutput(rentFee, solMint), + recipientGift: await toTokenAmountOutput(recipientGift, solMint), }, }; }, diff --git a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/find.ts b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/find.ts index 5304efc37..230bdcae0 100644 --- a/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/find.ts +++ b/packages/blockchain-api/src/server/api/routers/reward-contract/procedures/find.ts @@ -60,12 +60,12 @@ export const find = publicProcedure.rewardContract.find.handler( status: "PENDING", contract: { delegateWalletAddress: wpRecord.owner, - recipients: wpRecord.rewardsSplit.map((split) => { + recipients: await Promise.all(wpRecord.rewardsSplit.map(async (split) => { const receives = split.type === "fixed" ? { type: "FIXED" as const, - tokenAmount: toTokenAmountOutput( + tokenAmount: await toTokenAmountOutput( new BN(split.tokenAmount.amount), hntMint, ), @@ -77,7 +77,7 @@ export const find = publicProcedure.rewardContract.find.handler( if (split.address === PublicKey.default.toBase58()) { return { type: "CLAIMABLE" as const, - giftedCurrency: toTokenAmountOutput( + giftedCurrency: await toTokenAmountOutput( new BN(wpRecord.solAmount), solMint, ), @@ -89,7 +89,7 @@ export const find = publicProcedure.rewardContract.find.handler( walletAddress: split.address, receives, }; - }), + })), rewardSchedule: toFiveColumnCron(wpRecord.rewardsSchedule), }, }; @@ -110,7 +110,7 @@ export const find = publicProcedure.rewardContract.find.handler( const directWelcomePack = await wpProgram.account.welcomePackV0.fetchNullable(assetOwner); if (directWelcomePack && directWelcomePack.asset.equals(assetPubkey)) { - return buildPendingContractResponse(directWelcomePack); + return await buildPendingContractResponse(directWelcomePack); } // Iteration fallback - assetOwner is a wallet with UserWelcomePacks @@ -127,7 +127,7 @@ export const find = publicProcedure.rewardContract.find.handler( await wpProgram.account.welcomePackV0.fetchNullable(welcomePackK); if (welcomePack && welcomePack.asset.equals(assetPubkey)) { - return buildPendingContractResponse(welcomePack); + return await buildPendingContractResponse(welcomePack); } } } @@ -150,13 +150,13 @@ export const find = publicProcedure.rewardContract.find.handler( contract: { delegateWalletAddress: mfRecord.owner, entityOwnerAddress: mfRecord.namespace, - recipients: mfRecord.shares.map((share) => ({ + recipients: await Promise.all(mfRecord.shares.map(async (share) => ({ walletAddress: share.wallet, receives: share.share.fixed && share.share.fixed.amount !== "0" ? { type: "FIXED" as const, - tokenAmount: toTokenAmountOutput( + tokenAmount: await toTokenAmountOutput( new BN(share.share.fixed.amount), hntMint, ), @@ -165,7 +165,7 @@ export const find = publicProcedure.rewardContract.find.handler( type: "SHARES" as const, shares: share.share.share?.amount || 0, }, - })), + }))), rewardSchedule: toFiveColumnCron(mfRecord.schedule), }, }; @@ -202,8 +202,8 @@ export const find = publicProcedure.rewardContract.find.handler( contract: { delegateWalletAddress: miniFanoutAccount.owner.toBase58(), entityOwnerAddress: miniFanoutAccount.namespace.toBase58(), - recipients: miniFanoutAccount.shares.map( - (share: { + recipients: await Promise.all(miniFanoutAccount.shares.map( + async (share: { wallet: PublicKey; share: { fixed?: { amount: BN }; share?: { amount: number } }; }) => ({ @@ -212,7 +212,7 @@ export const find = publicProcedure.rewardContract.find.handler( share.share.fixed && !share.share.fixed.amount.isZero() ? { type: "FIXED" as const, - tokenAmount: toTokenAmountOutput( + tokenAmount: await toTokenAmountOutput( share.share.fixed.amount, hntMint, ), @@ -222,7 +222,7 @@ export const find = publicProcedure.rewardContract.find.handler( shares: share.share.share?.amount || 0, }, }), - ), + )), rewardSchedule: toFiveColumnCron(miniFanoutAccount.schedule), }, }; @@ -238,21 +238,21 @@ export const find = publicProcedure.rewardContract.find.handler( }, ); -function buildPendingContractResponse( +async function buildPendingContractResponse( welcomePack: any, -): FindRewardContractResponse { +): Promise { const hntMint = HNT_MINT.toBase58(); const solMint = NATIVE_MINT.toBase58(); return { status: "PENDING", contract: { delegateWalletAddress: welcomePack.owner.toBase58(), - recipients: welcomePack.rewardsSplit.map((split: any) => { + recipients: await Promise.all(welcomePack.rewardsSplit.map(async (split: any) => { const receives = split.share.fixed && !split.share.fixed.amount.isZero() ? { type: "FIXED" as const, - tokenAmount: toTokenAmountOutput( + tokenAmount: await toTokenAmountOutput( split.share.fixed.amount, hntMint, ), @@ -264,7 +264,7 @@ function buildPendingContractResponse( if (split.wallet.equals(PublicKey.default)) { return { type: "CLAIMABLE" as const, - giftedCurrency: toTokenAmountOutput(welcomePack.solAmount, solMint), + giftedCurrency: await toTokenAmountOutput(welcomePack.solAmount, solMint), receives, }; } @@ -273,7 +273,7 @@ function buildPendingContractResponse( walletAddress: split.wallet.toBase58(), receives, }; - }), + })), rewardSchedule: toFiveColumnCron(welcomePack.rewardsSchedule), }, }; diff --git a/packages/blockchain-api/src/server/api/routers/swap/procedures/getInstructions.ts b/packages/blockchain-api/src/server/api/routers/swap/procedures/getInstructions.ts index 1ebb7c995..ebbe2bb98 100644 --- a/packages/blockchain-api/src/server/api/routers/swap/procedures/getInstructions.ts +++ b/packages/blockchain-api/src/server/api/routers/swap/procedures/getInstructions.ts @@ -171,11 +171,11 @@ export const getInstructions = publicProcedure.swap.getInstructions.handler( tag, actionMetadata: { type: "swap", - inputTokenAmount: toTokenAmountOutput( + inputTokenAmount: await toTokenAmountOutput( new BN(quoteResponse.inAmount), quoteResponse.inputMint, ), - outputTokenAmount: toTokenAmountOutput( + outputTokenAmount: await toTokenAmountOutput( new BN(quoteResponse.outAmount), quoteResponse.outputMint, ), diff --git a/packages/blockchain-api/src/server/api/routers/swap/procedures/getQuote.ts b/packages/blockchain-api/src/server/api/routers/swap/procedures/getQuote.ts index 6b10565a1..6472b0edd 100644 --- a/packages/blockchain-api/src/server/api/routers/swap/procedures/getQuote.ts +++ b/packages/blockchain-api/src/server/api/routers/swap/procedures/getQuote.ts @@ -26,6 +26,15 @@ export const getQuote = publicProcedure.swap.getQuote.handler( if (!quoteResponse.ok) { const errorText = await quoteResponse.text(); console.error("Jupiter API error:", errorText); + + // TOKEN_NOT_TRADABLE is a client issue (e.g. wallet requesting a non-tradable token), + // not a server error — return 400 so it doesn't get sent to Sentry. + if (errorText.includes("TOKEN_NOT_TRADABLE")) { + throw errors.BAD_REQUEST({ + message: `Token is not tradable`, + }); + } + throw errors.JUPITER_ERROR({ message: `Failed to get quote from Jupiter: HTTP ${quoteResponse.status}: ${errorText.slice(0, 500)}`, }); diff --git a/packages/blockchain-api/src/server/api/routers/tokens/procedures/createHntAccount.ts b/packages/blockchain-api/src/server/api/routers/tokens/procedures/createHntAccount.ts index 6a3858b4e..ed8d9e295 100644 --- a/packages/blockchain-api/src/server/api/routers/tokens/procedures/createHntAccount.ts +++ b/packages/blockchain-api/src/server/api/routers/tokens/procedures/createHntAccount.ts @@ -87,7 +87,7 @@ export const createHntAccount = publicProcedure.tokens.createHntAccount.handler( ], parallel: false, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/tokens/procedures/transfer.ts b/packages/blockchain-api/src/server/api/routers/tokens/procedures/transfer.ts index 3d5383ba6..49614cfcf 100644 --- a/packages/blockchain-api/src/server/api/routers/tokens/procedures/transfer.ts +++ b/packages/blockchain-api/src/server/api/routers/tokens/procedures/transfer.ts @@ -126,7 +126,7 @@ export const transfer = publicProcedure.tokens.transfer.handler( }); } - const transferTokenAmount = toTokenAmountOutput( + const transferTokenAmount = await toTokenAmountOutput( new BN(tokenAmount.amount), tokenAmount.mint, ); @@ -155,7 +155,7 @@ export const transfer = publicProcedure.tokens.transfer.handler( recipient: destination, }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/transactions/procedures/estimate.ts b/packages/blockchain-api/src/server/api/routers/transactions/procedures/estimate.ts index f2cc03337..22921d0b0 100644 --- a/packages/blockchain-api/src/server/api/routers/transactions/procedures/estimate.ts +++ b/packages/blockchain-api/src/server/api/routers/transactions/procedures/estimate.ts @@ -9,11 +9,11 @@ import type { TokenAmountOutput } from "@helium/blockchain-api/schemas/common"; const SOL_MINT = NATIVE_MINT.toBase58(); -function solOutput(lamports: BN): TokenAmountOutput { +async function solOutput(lamports: BN): Promise { return toTokenAmountOutput(lamports, SOL_MINT); } -function zeroSol(): TokenAmountOutput { +async function zeroSol(): Promise { return solOutput(new BN(0)); } @@ -62,8 +62,8 @@ export const estimate = publicProcedure.transactions.estimate.handler( success: false, error: `Failed to deserialize transaction: ${err instanceof Error ? err.message : "Unknown error"}`, costs: { - transactionFees: zeroSol(), - rent: zeroSol(), + transactionFees: await zeroSol(), + rent: await zeroSol(), tokenTransfers: [], }, }; @@ -96,8 +96,8 @@ export const estimate = publicProcedure.transactions.estimate.handler( error: `Simulation failed: ${errorMessage}`, logs: simulation.value.logs ?? undefined, costs: { - transactionFees: zeroSol(), - rent: zeroSol(), + transactionFees: await zeroSol(), + rent: await zeroSol(), tokenTransfers: [], }, }; @@ -145,8 +145,8 @@ export const estimate = publicProcedure.transactions.estimate.handler( computeUnits: simulation.value.unitsConsumed ?? 0, success: true, costs: { - transactionFees: solOutput(txFee), - rent: solOutput(rentCost), + transactionFees: await solOutput(txFee), + rent: await solOutput(rentCost), tokenTransfers, }, }; @@ -157,7 +157,7 @@ export const estimate = publicProcedure.transactions.estimate.handler( const aggregateTokenTransfers: TokenAmountOutput[] = []; for (const [mint, amount] of tokenTransfersByMint) { if (!amount.isZero()) { - aggregateTokenTransfers.push(toTokenAmountOutput(amount, mint)); + aggregateTokenTransfers.push(await toTokenAmountOutput(amount, mint)); } } @@ -165,10 +165,10 @@ export const estimate = publicProcedure.transactions.estimate.handler( const solTotal = totalTransactionFees.add(totalRent); return { - totalSol: solOutput(solTotal), + totalSol: await solOutput(solTotal), breakdown: { - transactionFees: solOutput(totalTransactionFees), - rent: solOutput(totalRent), + transactionFees: await solOutput(totalTransactionFees), + rent: await solOutput(totalRent), tokenTransfers: aggregateTokenTransfers, }, transactions: transactionEstimates, diff --git a/packages/blockchain-api/src/server/api/routers/transactions/procedures/submit.ts b/packages/blockchain-api/src/server/api/routers/transactions/procedures/submit.ts index 39a9d44bd..8732aa591 100644 --- a/packages/blockchain-api/src/server/api/routers/transactions/procedures/submit.ts +++ b/packages/blockchain-api/src/server/api/routers/transactions/procedures/submit.ts @@ -7,7 +7,7 @@ import { getCluster } from "@/lib/solana"; import { BundleSimulationError } from "@/lib/utils/jito"; import { submitTransactionBatch } from "@/lib/utils/transaction-submission"; import * as Sentry from "@sentry/nextjs"; -import { Connection, VersionedTransaction } from "@solana/web3.js"; +import { Connection, PublicKey, VersionedTransaction } from "@solana/web3.js"; import { classifySimulationLogs } from "@/lib/utils/simulation-classifier"; import { publicProcedure } from "../../../procedures"; @@ -146,6 +146,19 @@ export const submit = publicProcedure.transactions.submit.handler( }, ); + if (category === "account_not_found") { + const balance = await connection.getBalance(new PublicKey(payer)); + if (balance === 0) { + throw errors.SIMULATION_FAILED({ + message: `Transaction payer ${payer} has 0 SOL`, + data: { + logs: ff?.logs ?? undefined, + link: ff?.link ?? undefined, + }, + }); + } + } + throw errors.SIMULATION_FAILED({ message: ff.error ?? "Transaction simulation failed", data: { @@ -189,6 +202,18 @@ export const submit = publicProcedure.transactions.submit.handler( }); } catch (error) { if (error instanceof BundleSimulationError) { + if (error.message.includes("AccountNotFound")) { + const connection = new Connection(env.SOLANA_RPC_URL); + const balance = await connection.getBalance(new PublicKey(payer)); + if (balance === 0) { + throw errors.SIMULATION_FAILED({ + message: `Transaction payer ${payer} has 0 SOL`, + data: { + logs: error.logs, + }, + }); + } + } throw errors.SIMULATION_FAILED({ message: error.message, data: { diff --git a/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/claim.ts b/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/claim.ts index e4416e535..d37779184 100644 --- a/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/claim.ts +++ b/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/claim.ts @@ -131,7 +131,7 @@ export const claim = publicProcedure.welcomePacks.claim.handler( rewardsMint: rewardsMint?.toBase58(), }, }, - estimatedSolFee: toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), + estimatedSolFee: await toTokenAmountOutput(new BN(0), NATIVE_MINT.toBase58()), }; }, ); diff --git a/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/create.ts b/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/create.ts index 1619e4560..2103736cf 100644 --- a/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/create.ts +++ b/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/create.ts @@ -121,10 +121,10 @@ export const create = publicProcedure.welcomePacks.create.handler( rentCost += RENT_COSTS.RECIPIENT; } // Add gifted SOL amount - rentCost += resolveTokenAmountInput( + rentCost += (await resolveTokenAmountInput( solAmount, NATIVE_MINT.toBase58(), - ).toNumber(); + )).toNumber(); const required = calculateRequiredBalance(BASE_TX_FEE_LAMPORTS, rentCost); if (walletBalance < required) { @@ -155,10 +155,10 @@ export const create = publicProcedure.welcomePacks.create.handler( program, assetId: new PublicKey(assetId), owner: new PublicKey(walletAddress), - solAmount: resolveTokenAmountInput(solAmount, NATIVE_MINT.toBase58()), + solAmount: await resolveTokenAmountInput(solAmount, NATIVE_MINT.toBase58()), rentRefund: new PublicKey(rentRefund), assetReturnAddress: new PublicKey(assetReturnAddress), - rewardsSplit: rewardsSplit.map((split) => + rewardsSplit: await Promise.all(rewardsSplit.map(async (split) => split.type === "percentage" ? { share: { share: { amount: split.amount } }, @@ -167,7 +167,7 @@ export const create = publicProcedure.welcomePacks.create.handler( : { share: { fixed: { - amount: resolveTokenAmountInput( + amount: await resolveTokenAmountInput( split.tokenAmount, HNT_MINT.toBase58(), ), @@ -175,7 +175,7 @@ export const create = publicProcedure.welcomePacks.create.handler( }, wallet: new PublicKey(split.address), }, - ), + )), rewardsSchedule, getAssetFn: (_, assetId) => getAsset( @@ -260,7 +260,7 @@ export const create = publicProcedure.welcomePacks.create.handler( actionMetadata: { type: "welcome_pack_create", assetId, - solAmount: toTokenAmountOutput( + solAmount: await toTokenAmountOutput( new BN(input.solAmount.amount), input.solAmount.mint, ), @@ -268,7 +268,7 @@ export const create = publicProcedure.welcomePacks.create.handler( recipients: input.rewardsSplit.map((s) => s.address), }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(estimatedSolFeeLamports), NATIVE_MINT.toBase58(), ), diff --git a/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/deletePack.ts b/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/deletePack.ts index 34bfcab1d..fafda8a98 100644 --- a/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/deletePack.ts +++ b/packages/blockchain-api/src/server/api/routers/welcomePacks/procedures/deletePack.ts @@ -108,7 +108,7 @@ export const deletePack = publicProcedure.welcomePacks.delete.handler( tag, actionMetadata: { type: "welcome_pack_delete", packId }, }, - estimatedSolFee: toTokenAmountOutput( + estimatedSolFee: await toTokenAmountOutput( new BN(txFee), NATIVE_MINT.toBase58(), ),