Skip to content

Commit 09e122e

Browse files
authored
Merge branch 'main' into fix-issues-1182-1186-1190-1192
2 parents 3b2a01b + 30b203f commit 09e122e

52 files changed

Lines changed: 2921 additions & 267 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/auto-merge.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
runs-on: ubuntu-latest
4949
steps:
5050
- name: Evaluate and merge
51-
uses: actions/github-script@v7
51+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
5252
with:
5353
github-token: ${{ secrets.GITHUB_TOKEN }}
5454
script: |

.github/workflows/changelog.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616

1717
steps:
1818
- name: Checkout repository
19-
uses: actions/checkout@v4
19+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
2020
with:
2121
fetch-depth: 0
2222

@@ -46,7 +46,7 @@ jobs:
4646
run: git-cliff --config cliff.toml --output CHANGELOG.md
4747

4848
- name: Commit and push CHANGELOG.md
49-
uses: stefanzweifel/git-auto-commit-action@v5
49+
uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5
5050
with:
5151
file_pattern: CHANGELOG.md
5252
commit_message: "chore(changelog): update changelog [skip ci]"

.github/workflows/contract-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ jobs:
1919

2020
steps:
2121
- name: Checkout repository
22-
uses: actions/checkout@v4
22+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
2323

2424
- name: Install Rust stable toolchain
25-
uses: dtolnay/rust-toolchain@stable
25+
uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # stable
2626
with:
2727
components: clippy
2828
targets: wasm32-unknown-unknown
2929

3030
- name: Cache Cargo dependencies
31-
uses: Swatinem/rust-cache@v2
31+
uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2
3232
with:
3333
workspaces: Dechat/stellar-contracts -> target
3434

.github/workflows/contracts.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ jobs:
2525

2626
steps:
2727
- name: Checkout repository
28-
uses: actions/checkout@v4
28+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
2929

3030
- name: Set up Rust
31-
uses: dtolnay/rust-toolchain@stable
31+
uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # stable
3232
with:
3333
toolchain: stable
3434
components: clippy
3535
targets: wasm32-unknown-unknown
3636

3737
- name: Cache Cargo registry
38-
uses: actions/cache@v4
38+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
3939
with:
4040
path: |
4141
~/.cargo/registry

.github/workflows/deploy-futurenet.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ jobs:
2424

2525
steps:
2626
- name: Checkout repository
27-
uses: actions/checkout@v4
27+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
2828

2929
- name: Set up Rust
30-
uses: dtolnay/rust-toolchain@stable
30+
uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # stable
3131
with:
3232
toolchain: stable
3333
targets: wasm32-unknown-unknown
@@ -38,7 +38,7 @@ jobs:
3838
soroban --version
3939
4040
- name: Cache Cargo registry
41-
uses: actions/cache@v4
41+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
4242
with:
4343
path: |
4444
~/.cargo/registry
@@ -68,15 +68,15 @@ jobs:
6868
6969
- name: Upload contract ID artifact
7070
if: success()
71-
uses: actions/upload-artifact@v4
71+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
7272
with:
7373
name: contract-deployment-${{ github.sha }}
7474
path: Dechat/stellar-contracts/contract_id_futurenet.txt
7575
retention-days: 90
7676

7777
- name: Create deployment status
7878
if: always()
79-
uses: actions/github-script@v7
79+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
8080
with:
8181
script: |
8282
const status = '${{ job.status }}' === 'success' ? 'success' : 'failure';

.github/workflows/frontend-build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ jobs:
1818

1919
steps:
2020
- name: Checkout repository
21-
uses: actions/checkout@v4
21+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
2222

2323
- name: Install pnpm
24-
uses: pnpm/action-setup@v2
24+
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
2525
with:
2626
version: 8
2727

2828
- name: Set up Node.js
29-
uses: actions/setup-node@v4
29+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
3030
with:
3131
node-version: "20"
3232
cache: "pnpm"

.github/workflows/frontend.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ jobs:
2525

2626
steps:
2727
- name: Checkout repository
28-
uses: actions/checkout@v4
28+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
2929

3030
- name: Install pnpm
31-
uses: pnpm/action-setup@v2
31+
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
3232
with:
3333
version: 8
3434

3535
- name: Set up Node.js
36-
uses: actions/setup-node@v4
36+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
3737
with:
3838
node-version: "24"
3939
cache: "pnpm"
@@ -96,15 +96,15 @@ jobs:
9696

9797
steps:
9898
- name: Checkout repository
99-
uses: actions/checkout@v4
99+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
100100

101101
- name: Install pnpm
102-
uses: pnpm/action-setup@v2
102+
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
103103
with:
104104
version: 8
105105

106106
- name: Set up Node.js
107-
uses: actions/setup-node@v4
107+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
108108
with:
109109
node-version: "24"
110110
cache: "pnpm"
@@ -136,7 +136,7 @@ jobs:
136136
NEXT_PUBLIC_STELLAR_NETWORK: ${{ secrets.NEXT_PUBLIC_STELLAR_NETWORK || 'TESTNET' }}
137137

138138
- name: Upload Playwright report
139-
uses: actions/upload-artifact@v4
139+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
140140
if: always()
141141
with:
142142
name: playwright-report
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { NextRequest } from 'next/server';
3+
4+
vi.mock('@/lib/telemetry', () => ({
5+
telemetry: {
6+
extractTraceFromHeaders: () => ({ traceId: 'trace', spanId: 'parent' }),
7+
createSpan: () => ({ spanId: 'span' }),
8+
addLog: vi.fn(),
9+
finishSpan: vi.fn(),
10+
setTraceHeaders: vi.fn(),
11+
},
12+
}));
13+
14+
const { GET } = await import('./route');
15+
16+
function request(query = '', ip = '198.51.100.100') {
17+
return new NextRequest(`http://localhost/api/banks${query}`, {
18+
headers: { 'x-forwarded-for': ip },
19+
});
20+
}
21+
22+
describe('GET /api/banks', () => {
23+
beforeEach(() => {
24+
vi.clearAllMocks();
25+
});
26+
27+
it('returns a machine-readable 400 response for unsupported query parameters', async () => {
28+
const response = await GET(request('?country=ghana'));
29+
30+
expect(response.status).toBe(400);
31+
await expect(response.json()).resolves.toMatchObject({
32+
success: false,
33+
error: { code: 'INVALID_REQUEST' },
34+
});
35+
});
36+
37+
it('rejects requests after the shared rate-limit quota is exhausted', async () => {
38+
const ip = '198.51.100.101';
39+
40+
for (let attempt = 0; attempt < 30; attempt += 1) {
41+
expect((await GET(request('', ip))).status).toBe(200);
42+
}
43+
44+
const response = await GET(request('', ip));
45+
expect(response.status).toBe(429);
46+
await expect(response.json()).resolves.toMatchObject({ success: false });
47+
expect(response.headers.get('Retry-After')).toBe('60');
48+
});
49+
});

Dechat/dex_with_fiat_frontend/src/app/api/banks/route.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import { NextResponse } from 'next/server';
1+
import { NextRequest, NextResponse } from 'next/server';
22
import axios from 'axios';
33
import { telemetry } from '@/lib/telemetry';
44
import { env } from '@/lib/env';
5+
import { banksQuerySchema } from '@/lib/apiSchemas';
6+
import { applyRateLimit, getClientIp } from '@/lib/rateLimit';
57

68
const PAYSTACK_SECRET_KEY = env.PAYSTACK_SECRET_KEY;
9+
const RATE_LIMIT = { maxRequests: 30, windowMs: 60_000 };
10+
11+
export async function GET(request: NextRequest) {
12+
const limited = applyRateLimit(getClientIp(request), '/api/banks', RATE_LIMIT);
13+
if (limited) return limited;
714

8-
export async function GET(request: Request) {
915
const traceContext = telemetry.extractTraceFromHeaders(
1016
request.headers as Headers,
1117
);
@@ -20,6 +26,30 @@ export async function GET(request: Request) {
2026
endpoint: '/api/banks',
2127
});
2228

29+
const query = Object.fromEntries(request.nextUrl.searchParams.entries());
30+
const validationResult = banksQuerySchema.safeParse(query);
31+
if (!validationResult.success) {
32+
telemetry.addLog(span.spanId, 'warn', 'Banks query validation failed', {
33+
errors: validationResult.error.issues,
34+
});
35+
telemetry.finishSpan(span.spanId, { success: false, error: 'Invalid request' });
36+
37+
const response = NextResponse.json(
38+
{
39+
success: false,
40+
error: {
41+
code: 'INVALID_REQUEST',
42+
issues: validationResult.error.issues,
43+
},
44+
},
45+
{ status: 400 },
46+
);
47+
telemetry.setTraceHeaders(response.headers as Headers, traceContext);
48+
return response;
49+
}
50+
51+
const { country } = validationResult.data;
52+
2353
if (!PAYSTACK_SECRET_KEY) {
2454
telemetry.addLog(
2555
span.spanId,
@@ -104,11 +134,11 @@ export async function GET(request: Request) {
104134
// Call real Paystack API to get Nigerian banks
105135
telemetry.addLog(span.spanId, 'info', 'Calling Paystack API', {
106136
endpoint: 'https://api.paystack.co/bank',
107-
country: 'nigeria',
137+
country,
108138
});
109139

110140
const response = await axios.get(
111-
'https://api.paystack.co/bank?country=nigeria',
141+
`https://api.paystack.co/bank?country=${country}`,
112142
{
113143
headers: {
114144
Authorization: `Bearer ${PAYSTACK_SECRET_KEY}`,

Dechat/dex_with_fiat_frontend/src/components/ChatMessages.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ interface ChatMessagesProps {
2323
actionType: string,
2424
data?: Record<string, unknown>,
2525
) => void;
26+
/**
27+
* Resend a message that failed to send. Receives the failed message's id and
28+
* its original content, ready to be submitted again as-is.
29+
*/
30+
onRetry?: (messageId: string, content: string) => void | Promise<void>;
2631
isLoading?: boolean;
2732
searchQuery?: string;
2833
}
@@ -108,6 +113,7 @@ function HelpCard({
108113
export default function ChatMessages({
109114
messages: allMessages,
110115
onActionClick,
116+
onRetry,
111117
isLoading = false,
112118
searchQuery = '',
113119
}: ChatMessagesProps) {
@@ -460,6 +466,7 @@ export default function ChatMessages({
460466
key={message.id}
461467
message={message}
462468
onActionClick={onActionClick}
469+
onRetry={onRetry}
463470
shouldAnimate={isReadyToAnimate && !isLoadingMore && !seenMessageIds.current.has(message.id)}
464471
/>
465472
))}

0 commit comments

Comments
 (0)