Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/src/controllers/stream.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ export const getStreamClaimableAmount = async (req: Request, res: Response) => {
where: { streamId: parsedStreamId },
select: {
streamId: true,
tokenAddress: true,
ratePerSecond: true,
depositedAmount: true,
withdrawnAmount: true,
Expand Down Expand Up @@ -595,6 +596,7 @@ export const getUserStreamSummary = async (
where: { sender: address },
select: {
streamId: true,
tokenAddress: true,
ratePerSecond: true,
depositedAmount: true,
withdrawnAmount: true,
Expand All @@ -611,6 +613,7 @@ export const getUserStreamSummary = async (
where: { recipient: address },
select: {
streamId: true,
tokenAddress: true,
ratePerSecond: true,
depositedAmount: true,
withdrawnAmount: true,
Expand Down
1 change: 1 addition & 0 deletions backend/src/routes/v1/streams/withdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const withdrawHandler = async (req: AuthenticatedRequest, res: Response)
streamId: true,
sender: true,
recipient: true,
tokenAddress: true,
ratePerSecond: true,
depositedAmount: true,
withdrawnAmount: true,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/services/claimable.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class ClaimableAmountService {
? Math.floor(requestedAt)
: Math.floor(this.nowMs() / 1000);

const cacheKey = `claimable:${stream.streamId}:${getStateFingerprint(stream)}:${calculatedAt}`;
const cacheKey = `claimable:${stream.tokenAddress}:${stream.streamId}:${getStateFingerprint(stream)}:${calculatedAt}`;
const cachedEntry = cache.get<Omit<ClaimableAmountResult, 'cached'>>(cacheKey);

if (cachedEntry) {
Expand Down
45 changes: 45 additions & 0 deletions backend/tests/claimable.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ClaimableAmountService } from '../src/services/claimable.service.js';
function makeStreamState(overrides: Partial<Parameters<ClaimableAmountService['getClaimableAmount']>[0]> = {}) {
return {
streamId: 1,
tokenAddress: 'TEST_TOKEN_ADDRESS',
ratePerSecond: '10',
depositedAmount: '100',
withdrawnAmount: '0',
Expand Down Expand Up @@ -247,4 +248,48 @@ describe('ClaimableAmountService', () => {
expect(cancelRefund + withdrawn + claimable <= deposited, `iteration ${iteration}: cancel settlement exceeded deposit`).toBe(true);
}
});

it('maintains independent cache entries for same streamId across different tokens', () => {
vi.setSystemTime(5_000);
const service = new ClaimableAmountService({
cacheTtlMs: 10_000,
});

// Two streams with the same numeric ID but different token contracts
const streamTokenA = makeStreamState({
streamId: 42,
tokenAddress: 'TOKEN_A',
ratePerSecond: '10',
depositedAmount: '1000',
withdrawnAmount: '0',
});

const streamTokenB = makeStreamState({
streamId: 42,
tokenAddress: 'TOKEN_B',
ratePerSecond: '5',
depositedAmount: '2000',
withdrawnAmount: '500',
});

const resultA = service.getClaimableAmount(streamTokenA, 100);
const resultB = service.getClaimableAmount(streamTokenB, 100);

// Both should be cache misses (different token = different cache key)
expect(resultA.cached).toBe(false);
expect(resultB.cached).toBe(false);

// The values should differ because the streams have different parameters
expect(resultA.claimableAmount).not.toBe(resultB.claimableAmount);

// Second call for Token A should be a cache hit
const resultA2 = service.getClaimableAmount(streamTokenA, 100);
expect(resultA2.cached).toBe(true);
expect(resultA2.claimableAmount).toBe(resultA.claimableAmount);

// Second call for Token B should also be a cache hit (independent of A)
const resultB2 = service.getClaimableAmount(streamTokenB, 100);
expect(resultB2.cached).toBe(true);
expect(resultB2.claimableAmount).toBe(resultB.claimableAmount);
});
});
Loading