Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
107 changes: 107 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: CI

on:
push:
branches: [develop, main]
pull_request:
branches: [develop, main]

permissions:
contents: read

jobs:
verify:
name: Lint · Typecheck · Test · Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Typecheck
run: npm run typecheck

- name: Test
run: npm test

- name: Build
run: npm run build

- name: Upload dist artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 7

publish-github-packages:
needs: verify
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
registry-url: 'https://npm.pkg.github.com'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Set prerelease version (develop)
if: github.ref == 'refs/heads/develop'
run: npm version "$(node -p "require('./package.json').version")-dev.${{ github.run_number }}" --no-git-tag-version

- name: Publish to GitHub Packages
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

publish-npm-public:
needs: verify
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
registry-url: 'https://registry.npmjs.org'

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Publish to npm (public)
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
continue-on-error: true
77 changes: 77 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Security

on:
push:
branches: [develop, main]
pull_request:
branches: [develop, main]
schedule:
- cron: '17 6 * * 1'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true

jobs:
npm-audit:
name: npm audit (fail on high)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci --no-audit --no-fund
- name: Audit production dependencies
run: npm run audit:ci

dependency-review:
name: Dependency review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
comment-summary-in-pr: on-failure

codeql:
name: CodeQL
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
strategy:
fail-fast: false
matrix:
language: ['javascript-typescript']
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"

secrets-scan:
name: TruffleHog secret scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@

.DS_Store
node_modules/
dist/
coverage/
*.log
.env
.env.local
.eslintcache
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,34 @@ All notable changes to `@web3settle/merchant-sdk` will be documented in this fil
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - 2026-04-16

### Security

- Force-pin vulnerable transitive deps via `overrides`: axios ≥1.15.0 (CVE-2026-40175 / GHSA-3p68-rc4w-qgx5), hono ≥4.12.14 (cookie/ipRestriction/serveStatic/toSSG advisories), lodash ≥4.18.1 (CVE-2026-4800 / CVE-2026-2950), follow-redirects ≥1.16.0 (GHSA-r4q5-vmmm-2653), esbuild ≥0.25.0 (GHSA-67mh-4wv8-2f99)
- Bump `vitest` to 3.x to drop the vulnerable bundled vite/esbuild
- Add `overrides` block to `package.json` to cover future regressions of the same advisories
- New `Security` CI workflow: npm audit gate (fail on high), GitHub Dependency Review on PRs, CodeQL analysis (security-and-quality queries), TruffleHog verified-only secret scan
- `Web3SettleApiClient` now validates `storefrontId` and `sessionId` as UUIDs and uses the `URL` constructor for base URL validation instead of string concat
- ERC-20 approvals remain exact-amount (unchanged, reaffirmed)

### Changed

- **[BREAKING]** Moved `wagmi`, `viem`, `@wagmi/core`, `@tanstack/react-query` from `dependencies` to `peerDependencies` to match their externalized-at-build status and avoid duplicate copies in consumer bundles
- Rework Vite build externals via a regex matching root packages and their subpaths — drops ~1MB of walletconnect/coinbase/reown chunks that were previously bundled; published artifact is now exactly `index.js`, `index.cjs`, and `styles.css`
- `vite-plugin-dts` now rolls all declarations into a single `index.d.ts` file
- Switch to ESLint 9 flat config with `typescript-eslint` recommended-type-checked + stylistic-type-checked, `eslint-plugin-react`, `react-hooks`, and `jsx-a11y`
- Tighten `tsconfig.json` with `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch`, `noImplicitReturns`, `noImplicitOverride`, `noUncheckedIndexedAccess`
- `TopUpModal` now ships real dialog semantics: `role="dialog"`, `aria-modal`, `aria-labelledby`, focus restoration on close, global Escape-key close, and labeled form controls. `autoFocus` removed in favor of ref-based focus
- Removed stray `React` default imports (project uses react-jsx runtime)
- Introduced `NATIVE_TOKEN_SENTINEL` and `TokenSelection` type to replace the redundant `string | 'native'` union

### Added

- New tests: `api-client-validation.test.ts` (UUID enforcement), `contract.test.ts` (parseTokenAmount behavior)
- `audit:ci` npm script and `prepublishOnly` guard running typecheck, lint, tests, and build
- `globals`, `typescript-eslint`, `@testing-library/user-event`, and `@vitest/coverage-v8` dev dependencies

## [0.1.0] - 2026-04-02

### Added
Expand Down
84 changes: 84 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import globals from 'globals';

export default tseslint.config(
{
ignores: ['dist/**', 'node_modules/**', 'coverage/**', '*.config.js', '*.config.ts'],
},
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
{
files: ['src/**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
globals: { ...globals.browser, ...globals.es2022 },
parserOptions: {
project: './tsconfig.eslint.json',
tsconfigRootDir: import.meta.dirname,
ecmaFeatures: { jsx: true },
},
},
plugins: {
react: reactPlugin,
'react-hooks': reactHooks,
'jsx-a11y': jsxA11y,
},
settings: {
react: { version: 'detect' },
},
rules: {
...reactPlugin.configs.recommended.rules,
...reactPlugin.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,

'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
'@typescript-eslint/restrict-template-expressions': [
'error',
{ allowNumber: true, allowBoolean: true },
],
'@typescript-eslint/switch-exhaustiveness-check': 'error',
'@typescript-eslint/no-non-null-assertion': 'error',

'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'react-hooks/exhaustive-deps': 'error',

'no-console': ['warn', { allow: ['warn', 'error'] }],
'eqeqeq': ['error', 'always'],
'no-implicit-coercion': 'error',
'no-return-await': 'error',
'prefer-const': 'error',
},
},
{
files: ['src/__tests__/**/*.{ts,tsx}'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'no-console': 'off',
},
},
);
Loading
Loading