├── .changeset
└── config.json
├── .github
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── ci.yml
│ ├── release.yml
│ └── relyance-sci.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .vscode
└── extensions.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── README.md
├── cspell.json
├── demo
├── next-15-template
│ ├── .env.sample
│ ├── .gitignore
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── middleware.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public
│ │ ├── file.svg
│ │ ├── globe.svg
│ │ ├── next.svg
│ │ ├── vercel.svg
│ │ └── window.svg
│ ├── src
│ │ ├── abi
│ │ │ └── TestContract.json
│ │ ├── app
│ │ │ ├── (protected)
│ │ │ │ ├── home
│ │ │ │ │ └── page.tsx
│ │ │ │ └── layout.tsx
│ │ │ ├── api
│ │ │ │ ├── auth
│ │ │ │ │ └── [...nextauth]
│ │ │ │ │ │ └── route.ts
│ │ │ │ ├── initiate-payment
│ │ │ │ │ └── route.ts
│ │ │ │ └── verify-proof
│ │ │ │ │ └── route.ts
│ │ │ ├── favicon.ico
│ │ │ ├── globals.css
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── auth
│ │ │ ├── index.ts
│ │ │ └── wallet
│ │ │ │ ├── client-helpers.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── server-helpers.ts
│ │ ├── components
│ │ │ ├── AuthButton
│ │ │ │ └── index.tsx
│ │ │ ├── Navigation
│ │ │ │ └── index.tsx
│ │ │ ├── PageLayout
│ │ │ │ └── index.tsx
│ │ │ ├── Pay
│ │ │ │ └── index.tsx
│ │ │ ├── Transaction
│ │ │ │ └── index.tsx
│ │ │ ├── UserInfo
│ │ │ │ └── index.tsx
│ │ │ ├── Verify
│ │ │ │ └── index.tsx
│ │ │ └── ViewPermissions
│ │ │ │ └── index.tsx
│ │ └── providers
│ │ │ ├── Eruda
│ │ │ ├── eruda-provider.tsx
│ │ │ └── index.tsx
│ │ │ └── index.tsx
│ └── tsconfig.json
└── with-next
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── abi
│ ├── Andy.json
│ ├── DEX.json
│ ├── Forward.json
│ ├── MinikitStaging.json
│ └── orb.json
│ ├── app
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ │ └── route.ts
│ │ ├── nonce
│ │ │ └── route.ts
│ │ ├── notifications
│ │ │ └── route.ts
│ │ └── verify-siwe
│ │ │ └── route.ts
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ └── test
│ │ └── page.tsx
│ ├── auth.ts
│ ├── components
│ ├── ClientContent
│ │ ├── Camera.tsx
│ │ ├── CheckRequests.tsx
│ │ ├── Eruda
│ │ │ ├── eruda-provider.tsx
│ │ │ └── index.tsx
│ │ ├── ExternalLinks.tsx
│ │ ├── GetPermissions.tsx
│ │ ├── Nav.tsx
│ │ ├── Pay.tsx
│ │ ├── RequestPermissions.tsx
│ │ ├── SearchParams.tsx
│ │ ├── SendHaptic.tsx
│ │ ├── Share.tsx
│ │ ├── ShareContacts.tsx
│ │ ├── SignMessage.tsx
│ │ ├── SignTypedMessage.tsx
│ │ ├── Transaction.tsx
│ │ ├── User.tsx
│ │ ├── VerifyAction
│ │ │ ├── index.tsx
│ │ │ ├── verify-cloud-proof.ts
│ │ │ └── verify-onchain.tsx
│ │ ├── Versions.tsx
│ │ ├── WalletAuth.tsx
│ │ ├── helpers
│ │ │ └── validate-schema.ts
│ │ └── index.tsx
│ ├── ClientProviders.tsx
│ ├── SessionProvider.tsx
│ └── config.ts
│ ├── contracts
│ ├── .gitignore
│ ├── foundry.toml
│ └── src
│ │ ├── ByteHasher.sol
│ │ ├── IWorldID.sol
│ │ └── TestVerify.sol
│ ├── global.d.ts
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public
│ ├── 800.jpeg
│ ├── dummy.pdf
│ ├── marble.png
│ ├── money.mp3
│ ├── next.svg
│ ├── test.png
│ └── vercel.svg
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── package.json
├── packages
├── core
│ ├── .eslintrc.cjs
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── global.d.ts
│ ├── helpers
│ │ ├── address-book
│ │ │ └── index.ts
│ │ ├── microphone
│ │ │ └── index.ts
│ │ ├── payment
│ │ │ └── client.ts
│ │ ├── proof
│ │ │ └── index.ts
│ │ ├── send-webview-event.ts
│ │ ├── share
│ │ │ └── index.ts
│ │ ├── siwe
│ │ │ ├── siwe.ts
│ │ │ └── validate-wallet-auth-command-input.ts
│ │ ├── transaction
│ │ │ └── validate-payload.ts
│ │ └── usernames
│ │ │ └── index.ts
│ ├── index.ts
│ ├── jest.config.ts
│ ├── minikit-provider.tsx
│ ├── minikit.ts
│ ├── package.json
│ ├── tests
│ │ ├── siwe.test.ts
│ │ └── validate-payload.test.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ └── types
│ │ ├── commands.ts
│ │ ├── errors.ts
│ │ ├── init.ts
│ │ ├── payment.ts
│ │ ├── responses.ts
│ │ ├── transactions.ts
│ │ └── wallet-auth.ts
├── create-mini-app
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── dist
│ │ └── index.js
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsup.config.ts
└── react
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── address-book
│ │ ├── index.ts
│ │ └── is-verified.tsx
│ ├── components
│ │ ├── index.ts
│ │ └── username-search.tsx
│ ├── index.ts
│ ├── transaction
│ │ ├── hooks.ts
│ │ └── index.ts
│ └── types
│ │ ├── client.ts
│ │ └── errors.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── tsconfig.json
└── turbo.json
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3 | "access": "public",
4 | "baseBranch": "main",
5 | "changelog": "@changesets/cli/changelog",
6 | "commit": false,
7 | "fixed": [],
8 | "ignore": ["minikit-monorepo-demo-with-next", "@worldcoin/next-15-template"],
9 | "linked": [],
10 | "updateInternalDependencies": "patch"
11 | }
12 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: '/'
5 | schedule:
6 | interval: monthly
7 | open-pull-requests-limit: 10
8 | reviewers:
9 | - 'andy-t-wang'
10 | - 'michalstruck'
11 | assignees:
12 | - 'andy-t-wang'
13 | - 'michalstruck'
14 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## PR Type
2 |
3 | - [ ] Regular Task
4 | - [ ] Bug Fix
5 | - [ ] QA Tests
6 |
7 | ## Description
8 |
9 |
16 |
17 | ## Checklist
18 |
19 |
20 |
21 | - [ ] I have self-reviewed this PR.
22 | - [ ] I have left comments in the code for clarity.
23 | - [ ] I have added necessary unit tests.
24 | - [ ] I have updated the documentation as needed.
25 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | - pull_request
4 |
5 | jobs:
6 | lint:
7 | name: Lint
8 | runs-on: ubuntu-24.04
9 |
10 | steps:
11 | - uses: actions/checkout@v4
12 | - uses: pnpm/action-setup@v4
13 | with:
14 | version: 9.9.0
15 |
16 | - name: Set up Node 20
17 | uses: actions/setup-node@v4
18 | with:
19 | node-version: 20
20 | cache: 'pnpm'
21 | cache-dependency-path: pnpm-lock.yaml
22 |
23 | - name: Install dependencies
24 | run: pnpm install --frozen-lockfile
25 |
26 | - name: Run lint
27 | run: pnpm lint
28 |
29 | typecheck:
30 | name: Typecheck
31 | runs-on: ubuntu-24.04
32 | steps:
33 | - uses: actions/checkout@v4
34 | - uses: pnpm/action-setup@v4
35 | with:
36 | version: 9.9.0
37 |
38 | - name: Set up Node 20
39 | uses: actions/setup-node@v4
40 | with:
41 | node-version: 20
42 | cache: 'pnpm'
43 | cache-dependency-path: pnpm-lock.yaml
44 |
45 | - name: Install dependencies
46 | run: pnpm install --frozen-lockfile
47 |
48 | - name: Build
49 | run: pnpm turbo run build
50 |
51 | - name: Run typecheck
52 | run: pnpm type-check
53 |
54 | run-tests:
55 | name: Run Tests
56 | runs-on: ubuntu-24.04
57 | defaults:
58 | run:
59 | working-directory: packages/core
60 | steps:
61 | - uses: actions/checkout@v4
62 | - uses: pnpm/action-setup@v4
63 | with:
64 | version: 9.9.0
65 |
66 | - name: Set up Node 20
67 | uses: actions/setup-node@v4
68 | with:
69 | node-version: 20
70 | cache: 'pnpm'
71 | cache-dependency-path: pnpm-lock.yaml
72 |
73 | - name: Install dependencies
74 | working-directory: ./
75 | run: pnpm install --frozen-lockfile
76 |
77 | - name: Run tests
78 | run: pnpm test
79 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | concurrency: ${{ github.workflow }}-${{ github.ref }}
9 |
10 | jobs:
11 | release:
12 | name: Release
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout Repo
16 | uses: actions/checkout@v4
17 |
18 | - name: Setup Node.js 22.x
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: 22.x
22 |
23 | - name: Setup PNPM
24 | uses: pnpm/action-setup@v2
25 | with:
26 | version: 9.12.1
27 |
28 | - name: Install Dependencies
29 | run: pnpm install
30 |
31 | - name: Create Release Pull Request or Publish to npm
32 | id: changesets
33 | uses: changesets/action@v1
34 | with:
35 | # This expects you to have a script called release which does a build for your packages and calls changeset publish
36 | publish: pnpm release
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
40 |
41 |
--------------------------------------------------------------------------------
/.github/workflows/relyance-sci.yml:
--------------------------------------------------------------------------------
1 | name: Relyance SCI Scan
2 |
3 | on:
4 | schedule:
5 | - cron: "32 0 * * *"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | execute-relyance-sci:
10 | name: Relyance SCI Job
11 | runs-on: ubuntu-latest
12 | permissions:
13 | contents: read
14 |
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 |
19 | - name: Pull and run SCI binary
20 | run: |-
21 | docker pull gcr.io/relyance-ext/compliance_inspector:release && \
22 | docker run --rm -v `pwd`:/repo --env API_KEY='${{ secrets.DPP_SCI_KEY }}' gcr.io/relyance-ext/compliance_inspector:release
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | .turbo
3 | .DS_Store
4 | .rollup.cache
5 | node_modules
6 |
7 | # editors
8 | /.vscode/*
9 | !/.vscode/extensions.json
10 | /.idea
11 | /.history
12 |
13 | # npmrc
14 | .npmrc
15 | .env
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Config files
2 | .DS_Store
3 | .env*
4 | *.log
5 | *.tgz
6 |
7 | # Tooling config
8 | *.config.js
9 | tsconfig.json
10 | .eslintrc.js
11 | .prettierrc.js
12 |
13 | # Node modules
14 | node_modules/
15 |
16 | # TODO: Improve
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build/**
2 | .turbo/**
3 | CHANGELOG.md
4 | pnpm-lock.yaml
5 | node_modules/**
6 | .vscode/**
7 | **/.next/**
8 | **/.turbo/**
9 | packages/core/package.json
10 | .github/**
11 | packages/create-mini-app/dist/index.js
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "tabWidth": 2,
5 | "plugins": ["prettier-plugin-organize-imports"]
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "streetsidesoftware.code-spell-checker",
4 | "dbaeumer.vscode-eslint",
5 | "esbenp.prettier-vscode",
6 | "bradlc.vscode-tailwindcss",
7 | "csstools.postcss"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.4.0](https://github.com/worldcoin/minikit-js/compare/minikit-js-v1.3.0...minikit-js-v1.4.0) (2024-12-03)
4 |
5 |
6 | ### Features
7 |
8 | * isUserVerified helper & react bindings ([#118](https://github.com/worldcoin/minikit-js/issues/118)) ([ab8ba8d](https://github.com/worldcoin/minikit-js/commit/ab8ba8da23709a7e5ee4fad7620d91f011735c49))
9 |
10 | ## [1.3.0](https://github.com/worldcoin/minikit-js/compare/minikit-js-v1.2.0...minikit-js-v1.3.0) (2024-11-26)
11 |
12 |
13 | ### Features
14 |
15 | * add static appId var ([#83](https://github.com/worldcoin/minikit-js/issues/83)) ([a83c9fb](https://github.com/worldcoin/minikit-js/commit/a83c9fb6cf731efdde5e3a2b7eafe6c0915cbb50))
16 | * Add username support ([#93](https://github.com/worldcoin/minikit-js/issues/93)) ([f28841e](https://github.com/worldcoin/minikit-js/commit/f28841e598fc181698d33819b0e56dcc73aa42a7))
17 | * share contacts command ([#104](https://github.com/worldcoin/minikit-js/issues/104)) ([bae6d76](https://github.com/worldcoin/minikit-js/commit/bae6d76735be04cd19637f38f3f833ae164c452f))
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * Add Readmes to prepare for open source ([#84](https://github.com/worldcoin/minikit-js/issues/84)) ([4d2f5a0](https://github.com/worldcoin/minikit-js/commit/4d2f5a01a392d8ab7743747ce3ca5ba481999db5))
23 | * app id text ([#10](https://github.com/worldcoin/minikit-js/issues/10)) ([8a1aff5](https://github.com/worldcoin/minikit-js/commit/8a1aff5b93b078a1582a06e655f27bd20da096ef))
24 | * CJS node 16 compatiblity ([#73](https://github.com/worldcoin/minikit-js/issues/73)) ([ccf3b6c](https://github.com/worldcoin/minikit-js/commit/ccf3b6c1283dbb97f3033e20fcc05bb59cc6ef8b))
25 | * pnpm version ([#112](https://github.com/worldcoin/minikit-js/issues/112)) ([5c38fd7](https://github.com/worldcoin/minikit-js/commit/5c38fd7d979b9626d86db3b1b6fb0ceb8a73e233))
26 | * Switch to viem ([#64](https://github.com/worldcoin/minikit-js/issues/64)) ([6dc47cc](https://github.com/worldcoin/minikit-js/commit/6dc47cc741abfea6a185c6e42ebc405d4fc28468))
27 | * test release please fix ([#94](https://github.com/worldcoin/minikit-js/issues/94)) ([bf6b007](https://github.com/worldcoin/minikit-js/commit/bf6b007e02c867ec09c853e07b9edb1ecba0d129))
28 | * ui ([#29](https://github.com/worldcoin/minikit-js/issues/29)) ([5d90fbd](https://github.com/worldcoin/minikit-js/commit/5d90fbdd75237e3d849eee52cc2aab97e38b2353))
29 | * Update not before ([#30](https://github.com/worldcoin/minikit-js/issues/30)) ([0a5cbd3](https://github.com/worldcoin/minikit-js/commit/0a5cbd39e4e3a8ef0f75c8a24d6dff790e0316fe))
30 | * use default `GITHUB_TOKEN` for release ([#109](https://github.com/worldcoin/minikit-js/issues/109)) ([c13b828](https://github.com/worldcoin/minikit-js/commit/c13b828420d2949ca6b7d384ff2dca1ca8871214))
31 | * username query parsing ([#108](https://github.com/worldcoin/minikit-js/issues/108)) ([b71e5ee](https://github.com/worldcoin/minikit-js/commit/b71e5eeed1e72c587fe76816d10309ae9a13e101))
32 | * wallet auth paylod typing ([#58](https://github.com/worldcoin/minikit-js/issues/58)) ([73f93ab](https://github.com/worldcoin/minikit-js/commit/73f93abcc14ff2f112d7c1ab12973ab34f711fb8))
33 |
34 | ## [1.2.0](https://github.com/worldcoin/minikit-js/compare/minikit-js-v1.1.1...minikit-js-v1.2.0) (2024-11-01)
35 |
36 |
37 | ### Features
38 |
39 | * add static appId var ([#83](https://github.com/worldcoin/minikit-js/issues/83)) ([a83c9fb](https://github.com/worldcoin/minikit-js/commit/a83c9fb6cf731efdde5e3a2b7eafe6c0915cbb50))
40 | * Add username support ([#93](https://github.com/worldcoin/minikit-js/issues/93)) ([f28841e](https://github.com/worldcoin/minikit-js/commit/f28841e598fc181698d33819b0e56dcc73aa42a7))
41 |
42 |
43 | ### Bug Fixes
44 |
45 | * Add Readmes to prepare for open source ([#84](https://github.com/worldcoin/minikit-js/issues/84)) ([4d2f5a0](https://github.com/worldcoin/minikit-js/commit/4d2f5a01a392d8ab7743747ce3ca5ba481999db5))
46 | * app id text ([#10](https://github.com/worldcoin/minikit-js/issues/10)) ([8a1aff5](https://github.com/worldcoin/minikit-js/commit/8a1aff5b93b078a1582a06e655f27bd20da096ef))
47 | * CJS node 16 compatiblity ([#73](https://github.com/worldcoin/minikit-js/issues/73)) ([ccf3b6c](https://github.com/worldcoin/minikit-js/commit/ccf3b6c1283dbb97f3033e20fcc05bb59cc6ef8b))
48 | * Switch to viem ([#64](https://github.com/worldcoin/minikit-js/issues/64)) ([6dc47cc](https://github.com/worldcoin/minikit-js/commit/6dc47cc741abfea6a185c6e42ebc405d4fc28468))
49 | * test release please fix ([#94](https://github.com/worldcoin/minikit-js/issues/94)) ([bf6b007](https://github.com/worldcoin/minikit-js/commit/bf6b007e02c867ec09c853e07b9edb1ecba0d129))
50 | * ui ([#29](https://github.com/worldcoin/minikit-js/issues/29)) ([5d90fbd](https://github.com/worldcoin/minikit-js/commit/5d90fbdd75237e3d849eee52cc2aab97e38b2353))
51 | * Update not before ([#30](https://github.com/worldcoin/minikit-js/issues/30)) ([0a5cbd3](https://github.com/worldcoin/minikit-js/commit/0a5cbd39e4e3a8ef0f75c8a24d6dff790e0316fe))
52 | * use default `GITHUB_TOKEN` for release ([#109](https://github.com/worldcoin/minikit-js/issues/109)) ([c13b828](https://github.com/worldcoin/minikit-js/commit/c13b828420d2949ca6b7d384ff2dca1ca8871214))
53 | * username query parsing ([#108](https://github.com/worldcoin/minikit-js/issues/108)) ([b71e5ee](https://github.com/worldcoin/minikit-js/commit/b71e5eeed1e72c587fe76816d10309ae9a13e101))
54 | * wallet auth paylod typing ([#58](https://github.com/worldcoin/minikit-js/issues/58)) ([73f93ab](https://github.com/worldcoin/minikit-js/commit/73f93abcc14ff2f112d7c1ab12973ab34f711fb8))
55 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributions to this repository are welcomed! If you're looking to contribute, simply open a PR or issue in this repository.
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # minikit-js
2 |
3 | ## 🚀 Getting Started
4 |
5 | MiniKit is currently available on npm. To install, run:
6 | `pnpm i @worldcoin/minikit-js`
7 |
8 | or use the CDN:
9 | `https://cdn.jsdelivr.net/npm/@worldcoin/minikit-js@[version]/+esm`
10 |
11 | For comprehensive setup instructions and usage examples, visit our [developer documentation](https://docs.world.org/mini-apps).
12 |
13 | ## 🛠 ️Developing Locally
14 |
15 | To run the example mini app locally:
16 |
17 | ```
18 | pnpm i
19 | cd demo/with-next
20 | pnpm dev
21 | ```
22 |
23 | This will launch a demo mini app with all essential commands implemented, allowing you to explore and test the features.
24 |
25 | ## 📦 Releasing
26 |
27 | To bump the version of the package, run:
28 |
29 | ```
30 | pnpm changeset
31 | ```
32 |
--------------------------------------------------------------------------------
/cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2",
3 | "enabled": true,
4 | "language": "en-US",
5 | "allowCompoundWords": true,
6 | "dictionaries": ["typescript", "node", "npm", "html"],
7 | "enabledLanguageIds": [
8 | "typescript",
9 | "typescriptreact",
10 | "javascript",
11 | "markdown",
12 | "yaml",
13 | "json"
14 | ],
15 | "words": ["idkit", "merkle", "OIDC", "SIWE", "USDC", "viem"]
16 | }
17 |
--------------------------------------------------------------------------------
/demo/next-15-template/.env.sample:
--------------------------------------------------------------------------------
1 | AUTH_SECRET="" # Added by `npx auth secret`. Read more: https://cli.authjs.dev
2 | HMAC_SECRET_KEY='some-secret' # create this by running `openssl rand -base64 32`
3 | AUTH_URL='' # For testing this should be your NGROK URL and for production this should be your production URL
4 | NEXT_PUBLIC_APP_ID='app_1234567890' # APP ID from the developer portal
--------------------------------------------------------------------------------
/demo/next-15-template/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 | !.env.sample
43 |
--------------------------------------------------------------------------------
/demo/next-15-template/README.md:
--------------------------------------------------------------------------------
1 | ## Create a Mini App
2 |
3 | [Mini apps](https://docs.worldcoin.org/mini-apps) enable third-party developers to create native-like applications within World App.
4 |
5 | This template is a way for you to quickly get started with authentication and examples of some of the trickier commands.
6 |
7 | ## Getting Started
8 |
9 | 1. cp .env.example .env.local
10 | 2. Follow the instructions in the .env.local file
11 | 3. Run `npm run dev`
12 | 4. Run `ngrok http 3000`
13 | 5. Run `npx auth secret` to update the `AUTH_SECRET` in the .env.local file
14 | 6. Add your domain to the `allowedDevOrigins` in the next.config.ts file.
15 | 7. [For Testing] If you're using a proxy like ngrok, you need to update the `AUTH_URL` in the .env.local file to your ngrok url.
16 | 8. Continue to developer.worldcoin.org and make sure your app is connected to the right ngrok url
17 | 9. [Optional] For Verify and Send Transaction to work you need to do some more setup in the dev portal. The steps are outlined in the respective component files.
18 |
19 | ## Authentication
20 |
21 | This starter kit uses [Minikit's](https://github.com/worldcoin/minikit-js) wallet auth to authenticate users, and [next-auth](https://authjs.dev/getting-started) to manage sessions.
22 |
23 | ## UI Library
24 |
25 | This starter kit uses [Mini Apps UI Kit](https://github.com/worldcoin/mini-apps-ui-kit) to style the app. We recommend using the UI kit to make sure you are compliant with [World App's design system](https://docs.world.org/mini-apps/design/app-guidelines).
26 |
27 | ## Eruda
28 |
29 | [Eruda](https://github.com/liriliri/eruda) is a tool that allows you to inspect the console while building as a mini app. You should disable this in production.
30 |
31 | ## Contributing
32 |
33 | This template was made with help from the amazing [supercorp-ai](https://github.com/supercorp-ai) team.
34 |
--------------------------------------------------------------------------------
/demo/next-15-template/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { FlatCompat } from '@eslint/eslintrc';
2 | import react from 'eslint-plugin-react';
3 | import { dirname } from 'path';
4 | import { fileURLToPath } from 'url';
5 |
6 | const __filename = fileURLToPath(import.meta.url);
7 | const __dirname = dirname(__filename);
8 |
9 | const compat = new FlatCompat({
10 | baseDirectory: __dirname,
11 | });
12 |
13 | const eslintConfig = [
14 | ...compat.extends('next/core-web-vitals', 'next/typescript'),
15 | {
16 | settings: { react: { version: 'detect' } },
17 | plugins: { react },
18 | rules: {
19 | ...react.configs.recommended.rules,
20 | ...react.configs['jsx-runtime'].rules,
21 | },
22 | },
23 | ];
24 |
25 | export default eslintConfig;
26 |
--------------------------------------------------------------------------------
/demo/next-15-template/middleware.ts:
--------------------------------------------------------------------------------
1 | export { auth as middleware } from '@/auth';
2 |
--------------------------------------------------------------------------------
/demo/next-15-template/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next';
2 |
3 | const nextConfig: NextConfig = {
4 | images: {
5 | domains: ['static.usernames.app-backend.toolsforhumanity.com'],
6 | },
7 | allowedDevOrigins: ['*'], // Add your dev origin here
8 | reactStrictMode: false,
9 | };
10 |
11 | export default nextConfig;
12 |
--------------------------------------------------------------------------------
/demo/next-15-template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@worldcoin/next-15-template",
3 | "version": "0.1.0",
4 | "homepage": "https://docs.worldcoin.org/mini-apps",
5 | "description": "Create a Mini App",
6 | "license": "MIT",
7 | "private": true,
8 | "type": "module",
9 | "scripts": {
10 | "dev": "next dev",
11 | "build": "next build",
12 | "start": "next start",
13 | "lint": "next lint"
14 | },
15 | "dependencies": {
16 | "@worldcoin/mini-apps-ui-kit-react": "^1.0.2",
17 | "@worldcoin/minikit-js": "latest",
18 | "@worldcoin/minikit-react": "latest",
19 | "clsx": "^2.1.1",
20 | "iconoir-react": "^7.11.0",
21 | "next": "15.2.3",
22 | "next-auth": "^5.0.0-beta.25",
23 | "react": "^19.0.0",
24 | "react-dom": "^19.0.0",
25 | "tailwind-merge": "^3.2.0",
26 | "viem": "2.23.5"
27 | },
28 | "devDependencies": {
29 | "@eslint/eslintrc": "^3",
30 | "@tailwindcss/postcss": "^4",
31 | "@types/node": "^20",
32 | "@types/react": "^19",
33 | "@types/react-dom": "^19",
34 | "eruda": "^3.4.1",
35 | "eslint": "^9",
36 | "eslint-config-next": "15.2.3",
37 | "tailwindcss": "^4",
38 | "typescript": "^5"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/demo/next-15-template/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: ['@tailwindcss/postcss'],
3 | };
4 |
5 | export default config;
6 |
--------------------------------------------------------------------------------
/demo/next-15-template/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/next-15-template/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/next-15-template/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/next-15-template/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/next-15-template/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/(protected)/home/page.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from '@/auth';
2 | import { Page } from '@/components/PageLayout';
3 | import { Pay } from '@/components/Pay';
4 | import { Transaction } from '@/components/Transaction';
5 | import { UserInfo } from '@/components/UserInfo';
6 | import { Verify } from '@/components/Verify';
7 | import { ViewPermissions } from '@/components/ViewPermissions';
8 | import { Marble, TopBar } from '@worldcoin/mini-apps-ui-kit-react';
9 |
10 | export default async function Home() {
11 | const session = await auth();
12 |
13 | return (
14 | <>
15 |
16 |
20 |
21 | {session?.user.username}
22 |
23 |
24 |
25 | }
26 | />
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | >
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/(protected)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from '@/auth';
2 | import { Navigation } from '@/components/Navigation';
3 | import { Page } from '@/components/PageLayout';
4 |
5 | export default async function TabsLayout({
6 | children,
7 | }: {
8 | children: React.ReactNode;
9 | }) {
10 | const session = await auth();
11 |
12 | // If the user is not authenticated, redirect to the login page
13 | if (!session) {
14 | console.log('Not authenticated');
15 | // redirect('/');
16 | }
17 |
18 | return (
19 |
20 | {children}
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from '@/auth';
2 | export const { GET, POST } = handlers;
3 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/api/initiate-payment/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | const uuid = crypto.randomUUID().replace(/-/g, '');
5 |
6 | // TODO: Store the ID field in your database so you can verify the payment later
7 |
8 | return NextResponse.json({ id: uuid });
9 | }
10 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/api/verify-proof/route.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ISuccessResult,
3 | IVerifyResponse,
4 | verifyCloudProof,
5 | } from '@worldcoin/minikit-js';
6 | import { NextRequest, NextResponse } from 'next/server';
7 |
8 | interface IRequestPayload {
9 | payload: ISuccessResult;
10 | action: string;
11 | signal: string | undefined;
12 | }
13 |
14 | /**
15 | * This route is used to verify the proof of the user
16 | * It is critical proofs are verified from the server side
17 | * Read More: https://docs.world.org/mini-apps/commands/verify#verifying-the-proof
18 | */
19 | export async function POST(req: NextRequest) {
20 | const { payload, action, signal } = (await req.json()) as IRequestPayload;
21 | const app_id = process.env.NEXT_PUBLIC_APP_ID as `app_${string}`;
22 |
23 | const verifyRes = (await verifyCloudProof(
24 | payload,
25 | app_id,
26 | action,
27 | signal,
28 | )) as IVerifyResponse; // Wrapper on this
29 |
30 | if (verifyRes.success) {
31 | // This is where you should perform backend actions if the verification succeeds
32 | // Such as, setting a user as "verified" in a database
33 | return NextResponse.json({ verifyRes, status: 200 });
34 | } else {
35 | // This is where you should handle errors from the World ID /verify endpoint.
36 | // Usually these errors are due to a user having already verified.
37 | return NextResponse.json({ verifyRes, status: 400 });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/demo/next-15-template/src/app/favicon.ico
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 |
3 | :root {
4 | --background: #ffffff;
5 | --foreground: #171717;
6 | }
7 |
8 | @theme inline {
9 | --color-background: var(--background);
10 | --color-foreground: var(--foreground);
11 | --font-sans: var(--font-geist-sans);
12 | --font-mono: var(--font-geist-mono);
13 | }
14 |
15 | @media (prefers-color-scheme: dark) {
16 | :root {
17 | --background: #0a0a0a;
18 | --foreground: #ededed;
19 | }
20 | }
21 |
22 | body {
23 | background: var(--background);
24 | color: var(--foreground);
25 | font-family: Arial, Helvetica, sans-serif;
26 | }
27 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from '@/auth';
2 | import ClientProviders from '@/providers';
3 | import '@worldcoin/mini-apps-ui-kit-react/styles.css';
4 | import type { Metadata } from 'next';
5 | import { Geist, Geist_Mono } from 'next/font/google';
6 | import './globals.css';
7 |
8 | const geistSans = Geist({
9 | variable: '--font-geist-sans',
10 | subsets: ['latin'],
11 | });
12 |
13 | const geistMono = Geist_Mono({
14 | variable: '--font-geist-mono',
15 | subsets: ['latin'],
16 | });
17 |
18 | export const metadata: Metadata = {
19 | title: 'Template Mini App',
20 | description: 'Generated by create next app',
21 | };
22 |
23 | export default async function RootLayout({
24 | children,
25 | }: Readonly<{
26 | children: React.ReactNode;
27 | }>) {
28 | const session = await auth();
29 | return (
30 |
31 |
32 | {children}
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { Page } from '@/components/PageLayout';
2 | import { AuthButton } from '../components/AuthButton';
3 |
4 | export default function Home() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/auth/index.ts:
--------------------------------------------------------------------------------
1 | import { hashNonce } from '@/auth/wallet/client-helpers';
2 | import {
3 | MiniAppWalletAuthSuccessPayload,
4 | MiniKit,
5 | verifySiweMessage,
6 | } from '@worldcoin/minikit-js';
7 | import NextAuth, { type DefaultSession } from 'next-auth';
8 | import Credentials from 'next-auth/providers/credentials';
9 |
10 | declare module 'next-auth' {
11 | interface User {
12 | walletAddress: string;
13 | username: string;
14 | profilePictureUrl: string;
15 | }
16 |
17 | interface Session {
18 | user: {
19 | walletAddress: string;
20 | username: string;
21 | profilePictureUrl: string;
22 | } & DefaultSession['user'];
23 | }
24 | }
25 |
26 | // Auth configuration for Wallet Auth based sessions
27 | // For more information on each option (and a full list of options) go to
28 | // https://authjs.dev/getting-started/authentication/credentials
29 | export const { handlers, signIn, signOut, auth } = NextAuth({
30 | secret: process.env.NEXTAUTH_SECRET,
31 | session: { strategy: 'jwt' },
32 | providers: [
33 | Credentials({
34 | name: 'World App Wallet',
35 | credentials: {
36 | nonce: { label: 'Nonce', type: 'text' },
37 | signedNonce: { label: 'Signed Nonce', type: 'text' },
38 | finalPayloadJson: { label: 'Final Payload', type: 'text' },
39 | },
40 | // @ts-expect-error TODO
41 | authorize: async ({
42 | nonce,
43 | signedNonce,
44 | finalPayloadJson,
45 | }: {
46 | nonce: string;
47 | signedNonce: string;
48 | finalPayloadJson: string;
49 | }) => {
50 | const expectedSignedNonce = hashNonce({ nonce });
51 |
52 | if (signedNonce !== expectedSignedNonce) {
53 | console.log('Invalid signed nonce');
54 | return null;
55 | }
56 |
57 | const finalPayload: MiniAppWalletAuthSuccessPayload =
58 | JSON.parse(finalPayloadJson);
59 | const result = await verifySiweMessage(finalPayload, nonce);
60 |
61 | if (!result.isValid || !result.siweMessageData.address) {
62 | console.log('Invalid final payload');
63 | return null;
64 | }
65 | // Optionally, fetch the user info from your own database
66 | const userInfo = await MiniKit.getUserInfo(finalPayload.address);
67 |
68 | return {
69 | id: finalPayload.address,
70 | ...userInfo,
71 | };
72 | },
73 | }),
74 | ],
75 | callbacks: {
76 | async jwt({ token, user }) {
77 | if (user) {
78 | token.userId = user.id;
79 | token.walletAddress = user.walletAddress;
80 | token.username = user.username;
81 | token.profilePictureUrl = user.profilePictureUrl;
82 | }
83 |
84 | return token;
85 | },
86 | session: async ({ session, token }) => {
87 | if (token.userId) {
88 | session.user.id = token.userId as string;
89 | session.user.walletAddress = token.address as string;
90 | session.user.username = token.username as string;
91 | session.user.profilePictureUrl = token.profilePictureUrl as string;
92 | }
93 |
94 | return session;
95 | },
96 | },
97 | });
98 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/auth/wallet/client-helpers.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 | /**
3 | * Generates an HMAC-SHA256 hash of the provided nonce using a secret key from the environment.
4 | * @param {Object} params - The parameters object.
5 | * @param {string} params.nonce - The nonce to be hashed.
6 | * @returns {string} The resulting HMAC hash in hexadecimal format.
7 | */
8 | export const hashNonce = ({ nonce }: { nonce: string }) => {
9 | const hmac = crypto.createHmac('sha256', process.env.HMAC_SECRET_KEY!);
10 | hmac.update(nonce);
11 | return hmac.digest('hex');
12 | };
13 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/auth/wallet/index.ts:
--------------------------------------------------------------------------------
1 | import { MiniKit } from '@worldcoin/minikit-js';
2 | import { signIn } from 'next-auth/react';
3 | import { getNewNonces } from './server-helpers';
4 |
5 | /**
6 | * Authenticates a user via their wallet using a nonce-based challenge-response mechanism.
7 | *
8 | * This function generates a unique `nonce` and requests the user to sign it with their wallet,
9 | * producing a `signedNonce`. The `signedNonce` ensures the response we receive from wallet auth
10 | * is authentic and matches our session creation.
11 | *
12 | * @returns {Promise} The result of the sign-in attempt.
13 | * @throws {Error} If wallet authentication fails at any step.
14 | */
15 | export const walletAuth = async () => {
16 | const { nonce, signedNonce } = await getNewNonces();
17 |
18 | const result = await MiniKit.commandsAsync.walletAuth({
19 | nonce,
20 | expirationTime: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
21 | notBefore: new Date(Date.now() - 24 * 60 * 60 * 1000),
22 | statement: `Authenticate (${crypto.randomUUID().replace(/-/g, '')}).`,
23 | });
24 | console.log('Result', result);
25 | if (!result) {
26 | throw new Error('No response from wallet auth');
27 | }
28 |
29 | if (result.finalPayload.status !== 'success') {
30 | console.error(
31 | 'Wallet authentication failed',
32 | result.finalPayload.error_code,
33 | );
34 | return;
35 | } else {
36 | console.log(result.finalPayload);
37 | }
38 |
39 | await signIn('credentials', {
40 | redirectTo: '/home',
41 | nonce,
42 | signedNonce,
43 | finalPayloadJson: JSON.stringify(result.finalPayload),
44 | });
45 | };
46 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/auth/wallet/server-helpers.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 | import crypto from 'crypto';
3 | import { hashNonce } from './client-helpers';
4 | /**
5 | * Generates a new random nonce and its corresponding HMAC signature.
6 | * @async
7 | * @returns {Promise<{ nonce: string, signedNonce: string }>} An object containing the nonce and its signed (hashed) value.
8 | */
9 | export const getNewNonces = async () => {
10 | const nonce = crypto.randomUUID().replace(/-/g, '');
11 | const signedNonce = hashNonce({ nonce });
12 | return {
13 | nonce,
14 | signedNonce,
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/components/AuthButton/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { walletAuth } from '@/auth/wallet';
3 | import { Button, LiveFeedback } from '@worldcoin/mini-apps-ui-kit-react';
4 | import { useMiniKit } from '@worldcoin/minikit-js/minikit-provider';
5 | import { useCallback, useEffect, useState } from 'react';
6 |
7 | /**
8 | * This component is an example of how to authenticate a user
9 | * We will use Next Auth for this example, but you can use any auth provider
10 | * Read More: https://docs.world.org/mini-apps/commands/wallet-auth
11 | */
12 | export const AuthButton = () => {
13 | const [isPending, setIsPending] = useState(false);
14 | const { isInstalled } = useMiniKit();
15 |
16 | const onClick = useCallback(async () => {
17 | if (!isInstalled || isPending) {
18 | return;
19 | }
20 | setIsPending(true);
21 | try {
22 | await walletAuth();
23 | } catch (error) {
24 | console.error('Wallet authentication button error', error);
25 | setIsPending(false);
26 | return;
27 | }
28 |
29 | setIsPending(false);
30 | }, [isInstalled, isPending]);
31 |
32 | useEffect(() => {
33 | const authenticate = async () => {
34 | if (isInstalled && !isPending) {
35 | setIsPending(true);
36 | try {
37 | await walletAuth();
38 | } catch (error) {
39 | console.error('Auto wallet authentication error', error);
40 | } finally {
41 | setIsPending(false);
42 | }
43 | }
44 | };
45 |
46 | authenticate();
47 | }, [isInstalled, isPending]);
48 |
49 | return (
50 |
58 |
64 | Login with Wallet
65 |
66 |
67 | );
68 | };
69 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/components/Navigation/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { TabItem, Tabs } from '@worldcoin/mini-apps-ui-kit-react';
4 | import { Bank, Home, User } from 'iconoir-react';
5 | import { useState } from 'react';
6 |
7 | /**
8 | * This component uses the UI Kit to navigate between pages
9 | * Bottom navigation is the most common navigation pattern in Mini Apps
10 | * We require mobile first design patterns for mini apps
11 | * Read More: https://docs.world.org/mini-apps/design/app-guidelines#mobile-first
12 | */
13 |
14 | export const Navigation = () => {
15 | const [value, setValue] = useState('home');
16 |
17 | return (
18 |
19 | } label="Home" />
20 | {/* // TODO: These currently don't link anywhere */}
21 | } label="Wallet" />
22 | } label="Profile" />
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/components/PageLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import { ReactNode } from 'react';
3 | import { twMerge } from 'tailwind-merge';
4 |
5 | /**
6 | * This component is a simple page layout component to help with design consistency
7 | * Feel free to modify this component to fit your needs
8 | */
9 | export const Page = (props: { children: ReactNode; className?: string }) => {
10 | return (
11 |
12 | {props.children}
13 |
14 | );
15 | };
16 |
17 | const Header = (props: { children: ReactNode; className?: string }) => {
18 | return (
19 |
25 | {props.children}
26 |
27 | );
28 | };
29 |
30 | const Main = (props: { children: ReactNode; className?: string }) => {
31 | return (
32 |
37 | {props.children}
38 |
39 | );
40 | };
41 |
42 | const Footer = (props: { children: ReactNode; className?: string }) => {
43 | return (
44 |
45 | {props.children}
46 |
47 | );
48 | };
49 |
50 | Page.Header = Header;
51 | Page.Main = Main;
52 | Page.Footer = Footer;
53 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/components/Pay/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Button, LiveFeedback } from '@worldcoin/mini-apps-ui-kit-react';
3 | import { MiniKit, Tokens, tokenToDecimals } from '@worldcoin/minikit-js';
4 | import { useState } from 'react';
5 |
6 | /**
7 | * This component is used to pay a user
8 | * The payment command simply does an ERC20 transfer
9 | * But, it also includes a reference field that you can search for on-chain
10 | */
11 | export const Pay = () => {
12 | const [buttonState, setButtonState] = useState<
13 | 'pending' | 'success' | 'failed' | undefined
14 | >(undefined);
15 |
16 | const onClickPay = async () => {
17 | // Lets use Alex's username to pay!
18 | const address = (await MiniKit.getUserByUsername('alex')).walletAddress;
19 | setButtonState('pending');
20 |
21 | const res = await fetch('/api/initiate-payment', {
22 | method: 'POST',
23 | });
24 | const { id } = await res.json();
25 |
26 | const result = await MiniKit.commandsAsync.pay({
27 | reference: id,
28 | to: address ?? '0x0000000000000000000000000000000000000000',
29 | tokens: [
30 | {
31 | symbol: Tokens.WLD,
32 | token_amount: tokenToDecimals(0.5, Tokens.WLD).toString(),
33 | },
34 | {
35 | symbol: Tokens.USDCE,
36 | token_amount: tokenToDecimals(0.1, Tokens.USDCE).toString(),
37 | },
38 | ],
39 | description: 'Test example payment for minikit',
40 | });
41 |
42 | console.log(result.finalPayload);
43 | if (result.finalPayload.status === 'success') {
44 | setButtonState('success');
45 | // It's important to actually check the transaction result on-chain
46 | // You should confirm the reference id matches for security
47 | // Read more here: https://docs.world.org/mini-apps/commands/pay#verifying-the-payment
48 | } else {
49 | setButtonState('failed');
50 | setTimeout(() => {
51 | setButtonState(undefined);
52 | }, 3000);
53 | }
54 | };
55 |
56 | return (
57 |
58 |
Pay
59 |
68 |
75 | Pay
76 |
77 |
78 |
79 | );
80 | };
81 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/components/UserInfo/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { CircularIcon, Marble } from '@worldcoin/mini-apps-ui-kit-react';
3 | import { CheckCircleSolid } from 'iconoir-react';
4 | import { useSession } from 'next-auth/react';
5 |
6 | /**
7 | * Minikit is only available on client side. Thus user info needs to be rendered on client side.
8 | * UserInfo component displays user information including profile picture, username, and verification status.
9 | * It uses the Marble component from the mini-apps-ui-kit-react library to display the profile picture.
10 | * The component is client-side rendered.
11 | */
12 | export const UserInfo = () => {
13 | // Fetching the user state client side
14 | const session = useSession();
15 |
16 | return (
17 |
18 |
19 |
20 |
21 | {session?.data?.user?.username}
22 |
23 | {session?.data?.user?.profilePictureUrl && (
24 |
25 |
26 |
27 | )}
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/components/Verify/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Button, LiveFeedback } from '@worldcoin/mini-apps-ui-kit-react';
3 | import { MiniKit, VerificationLevel } from '@worldcoin/minikit-js';
4 | import { useState } from 'react';
5 |
6 | /**
7 | * This component is an example of how to use World ID in Mini Apps
8 | * Minikit commands must be used on client components
9 | * It's critical you verify the proof on the server side
10 | * Read More: https://docs.world.org/mini-apps/commands/verify#verifying-the-proof
11 | */
12 | export const Verify = () => {
13 | const [buttonState, setButtonState] = useState<
14 | 'pending' | 'success' | 'failed' | undefined
15 | >(undefined);
16 |
17 | const [whichVerification, setWhichVerification] = useState(
18 | VerificationLevel.Device,
19 | );
20 |
21 | const onClickVerify = async (verificationLevel: VerificationLevel) => {
22 | setButtonState('pending');
23 | setWhichVerification(verificationLevel);
24 | const result = await MiniKit.commandsAsync.verify({
25 | action: 'test-action', // Make sure to create this in the developer portal -> incognito actions
26 | verification_level: verificationLevel,
27 | });
28 | console.log(result.finalPayload);
29 | // Verify the proof
30 | const response = await fetch('/api/verify-proof', {
31 | method: 'POST',
32 | body: JSON.stringify({
33 | payload: result.finalPayload,
34 | action: 'test-action',
35 | }),
36 | });
37 |
38 | const data = await response.json();
39 | if (data.verifyRes.success) {
40 | setButtonState('success');
41 | // Normally you'd do something here since the user is verified
42 | // Here we'll just do nothing
43 | } else {
44 | setButtonState('failed');
45 |
46 | // Reset the button state after 3 seconds
47 | setTimeout(() => {
48 | setButtonState(undefined);
49 | }, 2000);
50 | }
51 | };
52 |
53 | return (
54 |
55 |
Verify
56 |
69 | onClickVerify(VerificationLevel.Device)}
71 | disabled={buttonState === 'pending'}
72 | size="lg"
73 | variant="tertiary"
74 | className="w-full"
75 | >
76 | Verify (Device)
77 |
78 |
79 |
90 | onClickVerify(VerificationLevel.Orb)}
92 | disabled={buttonState === 'pending'}
93 | size="lg"
94 | variant="primary"
95 | className="w-full"
96 | >
97 | Verify (Orb)
98 |
99 |
100 |
101 | );
102 | };
103 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/components/ViewPermissions/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { ListItem } from '@worldcoin/mini-apps-ui-kit-react';
4 | import { MiniKit } from '@worldcoin/minikit-js';
5 | import { useMiniKit } from '@worldcoin/minikit-js/minikit-provider';
6 | import { useEffect, useState } from 'react';
7 | /**
8 | * This component is an example of how to view the permissions of a user
9 | * It's critical you use Minikit commands on client components
10 | * Read More: https://docs.world.org/mini-apps/commands/permissions
11 | */
12 |
13 | export const ViewPermissions = () => {
14 | const [permissions, setPermissions] = useState>({});
15 | const { isInstalled } = useMiniKit();
16 |
17 | useEffect(() => {
18 | const fetchPermissions = async () => {
19 | if (isInstalled) {
20 | try {
21 | // You can also fetch this by grabbing from user
22 | // MiniKit.user.permissions
23 | const permissions = await MiniKit.commandsAsync.getPermissions();
24 | if (permissions?.finalPayload.status === 'success') {
25 | setPermissions(permissions?.finalPayload.permissions || {});
26 | console.log('permissions', permissions);
27 | }
28 | } catch (error) {
29 | console.error('Failed to fetch permissions:', error);
30 | }
31 | } else {
32 | console.log('MiniKit is not installed');
33 | }
34 | };
35 | fetchPermissions();
36 | }, [isInstalled]);
37 |
38 | return (
39 |
40 |
Permissions
41 | {permissions &&
42 | Object.entries(permissions).map(([permission, value]) => (
43 |
48 | ))}
49 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/providers/Eruda/eruda-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import eruda from 'eruda';
4 | import { ReactNode, useEffect } from 'react';
5 |
6 | export const Eruda = (props: { children: ReactNode }) => {
7 | useEffect(() => {
8 | if (typeof window !== 'undefined') {
9 | try {
10 | eruda.init();
11 | } catch (error) {
12 | console.log('Eruda failed to initialize', error);
13 | }
14 | }
15 | }, []);
16 |
17 | return <>{props.children}>;
18 | };
19 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/providers/Eruda/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import dynamic from 'next/dynamic';
4 | import { ReactNode } from 'react';
5 |
6 | const Eruda = dynamic(() => import('./eruda-provider').then((c) => c.Eruda), {
7 | ssr: false,
8 | });
9 |
10 | export const ErudaProvider = (props: { children: ReactNode }) => {
11 | if (process.env.NEXT_PUBLIC_APP_ENV === 'production') {
12 | return props.children;
13 | }
14 | return {props.children} ;
15 | };
16 |
--------------------------------------------------------------------------------
/demo/next-15-template/src/providers/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { MiniKitProvider } from '@worldcoin/minikit-js/minikit-provider';
3 | import { Session } from 'next-auth';
4 | import { SessionProvider } from 'next-auth/react';
5 | import dynamic from 'next/dynamic';
6 | import type { ReactNode } from 'react';
7 |
8 | const ErudaProvider = dynamic(
9 | () => import('@/providers/Eruda').then((c) => c.ErudaProvider),
10 | { ssr: false },
11 | );
12 |
13 | // Define props for ClientProviders
14 | interface ClientProvidersProps {
15 | children: ReactNode;
16 | session: Session | null; // Use the appropriate type for session from next-auth
17 | }
18 |
19 | /**
20 | * ClientProvider wraps the app with essential context providers.
21 | *
22 | * - ErudaProvider:
23 | * - Should be used only in development.
24 | * - Enables an in-browser console for logging and debugging.
25 | *
26 | * - MiniKitProvider:
27 | * - Required for MiniKit functionality.
28 | *
29 | * This component ensures both providers are available to all child components.
30 | */
31 | export default function ClientProviders({
32 | children,
33 | session,
34 | }: ClientProvidersProps) {
35 | return (
36 |
37 |
38 | {children}
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/demo/next-15-template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/demo/with-next/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/demo/with-next/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/demo/with-next/README.md:
--------------------------------------------------------------------------------
1 | This is a demo application to try out minikit. Since this connects to the World App. You will need a corresponding test build of an android app to receive and send events.
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | ### Usage
18 |
19 | 1. Recommended usage to test is to use the corresponding android folder, ask Andy for permission
20 | 2. `pnpm dev`
21 | 3. `ngrok 3000`
22 | 4. Set the Ngrok url to the ngrok generated url
23 |
--------------------------------------------------------------------------------
/demo/with-next/abi/Andy.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputs": [
4 | {
5 | "components": [
6 | {
7 | "components": [
8 | { "internalType": "address", "name": "token", "type": "address" },
9 | { "internalType": "uint256", "name": "amount", "type": "uint256" }
10 | ],
11 | "internalType": "struct ISignatureTransfer.TokenPermissions",
12 | "name": "permitted",
13 | "type": "tuple"
14 | },
15 | { "internalType": "uint256", "name": "nonce", "type": "uint256" },
16 | { "internalType": "uint256", "name": "deadline", "type": "uint256" }
17 | ],
18 | "internalType": "struct ISignatureTransfer.PermitTransferFrom",
19 | "name": "permitTransferFrom",
20 | "type": "tuple"
21 | },
22 | {
23 | "components": [
24 | { "internalType": "address", "name": "to", "type": "address" },
25 | {
26 | "internalType": "uint256",
27 | "name": "requestedAmount",
28 | "type": "uint256"
29 | }
30 | ],
31 | "internalType": "struct ISignatureTransfer.SignatureTransferDetails",
32 | "name": "transferDetails",
33 | "type": "tuple"
34 | },
35 | { "internalType": "bytes", "name": "signature", "type": "bytes" }
36 | ],
37 | "name": "buyNFTWithPermit2",
38 | "outputs": [],
39 | "stateMutability": "nonpayable",
40 | "type": "function"
41 | }
42 | ]
43 |
--------------------------------------------------------------------------------
/demo/with-next/abi/DEX.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "constructor",
4 | "inputs": [
5 | { "name": "_permit2", "type": "address", "internalType": "address" }
6 | ],
7 | "stateMutability": "nonpayable"
8 | },
9 | {
10 | "type": "function",
11 | "name": "permit2",
12 | "inputs": [],
13 | "outputs": [
14 | {
15 | "name": "",
16 | "type": "address",
17 | "internalType": "contract ISignatureTransfer"
18 | }
19 | ],
20 | "stateMutability": "view"
21 | },
22 | {
23 | "type": "function",
24 | "name": "signatureTransfer",
25 | "inputs": [
26 | {
27 | "name": "permitTransferFrom",
28 | "type": "tuple",
29 | "internalType": "struct ISignatureTransfer.PermitTransferFrom",
30 | "components": [
31 | {
32 | "name": "permitted",
33 | "type": "tuple",
34 | "internalType": "struct ISignatureTransfer.TokenPermissions",
35 | "components": [
36 | {
37 | "name": "token",
38 | "type": "address",
39 | "internalType": "address"
40 | },
41 | {
42 | "name": "amount",
43 | "type": "uint256",
44 | "internalType": "uint256"
45 | }
46 | ]
47 | },
48 | { "name": "nonce", "type": "uint256", "internalType": "uint256" },
49 | { "name": "deadline", "type": "uint256", "internalType": "uint256" }
50 | ]
51 | },
52 | {
53 | "name": "transferDetails",
54 | "type": "tuple",
55 | "internalType": "struct ISignatureTransfer.SignatureTransferDetails",
56 | "components": [
57 | { "name": "to", "type": "address", "internalType": "address" },
58 | {
59 | "name": "requestedAmount",
60 | "type": "uint256",
61 | "internalType": "uint256"
62 | }
63 | ]
64 | },
65 | { "name": "signature", "type": "bytes", "internalType": "bytes" }
66 | ],
67 | "outputs": [],
68 | "stateMutability": "nonpayable"
69 | }
70 | ]
71 |
--------------------------------------------------------------------------------
/demo/with-next/abi/Forward.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputs": [
4 | {
5 | "internalType": "address payable",
6 | "name": "recipient",
7 | "type": "address"
8 | }
9 | ],
10 | "name": "pay",
11 | "outputs": [],
12 | "stateMutability": "payable",
13 | "type": "function"
14 | },
15 | {
16 | "inputs": [
17 | {
18 | "internalType": "address",
19 | "name": "sender",
20 | "type": "address"
21 | },
22 | {
23 | "internalType": "uint256",
24 | "name": "amount",
25 | "type": "uint256"
26 | },
27 | {
28 | "internalType": "address",
29 | "name": "recipient",
30 | "type": "address"
31 | }
32 | ],
33 | "name": "Paid",
34 | "type": "event"
35 | }
36 | ]
37 |
--------------------------------------------------------------------------------
/demo/with-next/abi/orb.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "function",
4 | "name": "mint",
5 | "inputs": [
6 | {
7 | "name": "signal",
8 | "type": "address",
9 | "internalType": "address"
10 | },
11 | {
12 | "name": "root",
13 | "type": "uint256",
14 | "internalType": "uint256"
15 | },
16 | {
17 | "name": "nullifierHash",
18 | "type": "uint256",
19 | "internalType": "uint256"
20 | },
21 | {
22 | "name": "proof",
23 | "type": "uint256[8]",
24 | "internalType": "uint256[8]"
25 | }
26 | ],
27 | "outputs": [],
28 | "stateMutability": "nonpayable"
29 | }
30 | ]
31 |
--------------------------------------------------------------------------------
/demo/with-next/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from '@/auth';
2 | export const { GET, POST } = handlers;
3 |
--------------------------------------------------------------------------------
/demo/with-next/app/api/nonce/route.ts:
--------------------------------------------------------------------------------
1 | import { randomUUID } from 'crypto';
2 | import { NextRequest } from 'next/server';
3 |
4 | export async function GET() {
5 | console.log('GET request received');
6 | return new Response(JSON.stringify({ nonce: randomUUID() }));
7 | }
8 | /*
9 | Note: This is not a secure implementation of Wallet Auth.
10 | It is only for demo purposes.
11 | */
12 | export async function POST(request: NextRequest) {
13 | console.log('POST request received');
14 | const body = await request.json();
15 | console.log('body', body);
16 | const localNonce = body.localNonce;
17 |
18 | return new Response(
19 | JSON.stringify({ nonce: randomUUID(), localNonce: localNonce }),
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/demo/with-next/app/api/notifications/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server';
2 |
3 | export const POST = async (request: NextRequest) => {
4 | try {
5 | const { walletAddress } = await request.json();
6 | const title = 'test';
7 | const message = 'test notification';
8 | const path = `worldapp://mini-app?app_id=${process.env.WLD_CLIENT_ID}`;
9 |
10 | if (!title || !message) {
11 | return NextResponse.json(
12 | { error: 'Title and message are required' },
13 | { status: 400 },
14 | );
15 | }
16 |
17 | const response = await fetch(
18 | `${process.env.NEXT_SERVER_DEV_PORTAL_URL}/api/v2/minikit/send-notification`,
19 | {
20 | method: 'POST',
21 | headers: {
22 | Authorization: `Bearer ${process.env.WORLDCOIN_API_KEY}`,
23 | 'Content-Type': 'application/json',
24 | },
25 | body: JSON.stringify({
26 | app_id: process.env.WLD_CLIENT_ID,
27 | wallet_addresses: [walletAddress],
28 | title,
29 | message,
30 | mini_app_path: path || '/',
31 | }),
32 | },
33 | );
34 | console.log('2');
35 |
36 | if (!response.ok) {
37 | const error = await response.json();
38 | console.log('error');
39 | return NextResponse.json(
40 | { error: error.message || 'Failed to send notification' },
41 | { status: response.status },
42 | );
43 | }
44 | console.log('3');
45 | const data = await response.json();
46 | return NextResponse.json(data);
47 | } catch (error: any) {
48 | console.error('Error sending notification:', error.message);
49 | return NextResponse.json(
50 | { error: 'Internal server error' },
51 | { status: 500 },
52 | );
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/demo/with-next/app/api/verify-siwe/route.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MiniAppWalletAuthSuccessPayload,
3 | verifySiweMessage,
4 | } from '@worldcoin/minikit-js';
5 | import { NextRequest, NextResponse } from 'next/server';
6 |
7 | interface IRequestPayload {
8 | payload: MiniAppWalletAuthSuccessPayload;
9 | nonce: string;
10 | }
11 |
12 | export async function POST(req: NextRequest) {
13 | const { payload, nonce } = (await req.json()) as IRequestPayload;
14 |
15 | // const cookieStore = await cookies();
16 | // if (nonce !== cookieStore.get('siwe')?.value) {
17 | // return NextResponse.json({
18 | // status: 'error',
19 | // isValid: false,
20 | // message: 'Invalid nonce',
21 | // });
22 | // }
23 |
24 | try {
25 | console.log('payload', payload);
26 | const validMessage = await verifySiweMessage(payload, nonce);
27 | return NextResponse.json({
28 | status: 'success',
29 | isValid: validMessage.isValid,
30 | });
31 | } catch (error: unknown) {
32 | const errorMessage =
33 | error instanceof Error ? error.message : 'Unknown error';
34 | return NextResponse.json({
35 | status: 'error',
36 | isValid: false,
37 | message: errorMessage,
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/demo/with-next/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/demo/with-next/app/favicon.ico
--------------------------------------------------------------------------------
/demo/with-next/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
29 | @layer utilities {
30 | .text-balance {
31 | text-wrap: balance;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/demo/with-next/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from '@/auth';
2 | import ClientProviders from '@/components/ClientProviders';
3 | import type { Metadata } from 'next';
4 | import { Inter } from 'next/font/google';
5 | import './globals.css';
6 |
7 | const inter = Inter({ subsets: ['latin'] });
8 |
9 | export const metadata: Metadata = {
10 | title: 'Create Next App',
11 | description: 'Generated by create next app',
12 | };
13 |
14 | export default async function RootLayout({
15 | children,
16 | }: Readonly<{
17 | children: React.ReactNode;
18 | }>) {
19 | const session = await auth();
20 | return (
21 |
22 |
23 | {children}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/demo/with-next/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { ClientContent } from '@/components/ClientContent';
2 |
3 | const Home = async () => {
4 | console.log('Server Rendering Works!');
5 | return (
6 |
7 |
8 |
From the Server Side!
9 |
10 | );
11 | };
12 |
13 | export default Home;
14 |
--------------------------------------------------------------------------------
/demo/with-next/app/test/page.tsx:
--------------------------------------------------------------------------------
1 | import { SearchParams } from '@/components/ClientContent/SearchParams';
2 |
3 | export default function TestPage() {
4 | return (
5 |
6 |
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/demo/with-next/auth.ts:
--------------------------------------------------------------------------------
1 | import NextAuth, { NextAuthConfig, NextAuthResult } from 'next-auth';
2 |
3 | // For more information on each option (and a full list of options) go to
4 | // https://next-auth.js.org/configuration/options
5 | export const authOptions: NextAuthConfig = {
6 | session: {
7 | strategy: 'jwt',
8 | },
9 | // https://next-auth.js.org/configuration/providers/oauth
10 | providers: [
11 | {
12 | id: 'worldcoin',
13 | name: 'Worldcoin',
14 | type: 'oidc',
15 | authorization: { params: { scope: 'openid' } },
16 | clientId: process.env.WLD_CLIENT_ID,
17 | clientSecret: process.env.WLD_CLIENT_SECRET,
18 | issuer:
19 | process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
20 | ? 'https://staging.id.worldcoin.org'
21 | : 'https://id.worldcoin.org',
22 | checks: ['state', 'pkce', 'nonce'],
23 | profile(profile) {
24 | return {
25 | id: profile.sub,
26 | name: profile.sub,
27 | verificationLevel:
28 | profile['https://id.worldcoin.org/v1'].verification_level,
29 | };
30 | },
31 | },
32 | ],
33 | callbacks: {
34 | async jwt({ token, account, user }) {
35 | if (account) {
36 | token.accessToken = account.access_token;
37 | token.idToken = account.id_token;
38 | token.provider = account.provider;
39 | }
40 | if (user) {
41 | token.user = user;
42 | }
43 | return token;
44 | },
45 | },
46 | debug: true,
47 | };
48 |
49 | const nextAuth = NextAuth(authOptions);
50 |
51 | export const signIn: NextAuthResult['signIn'] = nextAuth.signIn;
52 | export const auth: NextAuthResult['auth'] = nextAuth.auth;
53 | export const handlers: NextAuthResult['handlers'] = nextAuth.handlers;
54 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/CheckRequests.tsx:
--------------------------------------------------------------------------------
1 | // This component has a button that when clicked, will send a request to the /api/nonce route
2 | // It will then display the nonce in the component
3 |
4 | import { useState } from 'react';
5 | export default function CheckRequests() {
6 | const [nonce, setNonce] = useState(null);
7 | const [localNonce, setLocalNonce] = useState(null);
8 | const [response, setResponse] = useState(null);
9 | const handleClick = async () => {
10 | const response = await fetch('/api/nonce');
11 |
12 | if (response.ok) {
13 | const data = await response.json();
14 | setNonce(data.nonce);
15 | setLocalNonce(null);
16 | setResponse(data);
17 | }
18 | };
19 |
20 | const handlePostClick = async () => {
21 | const localNonce = Math.random().toString(36).substring(2, 15);
22 | const response = await fetch('/api/nonce', {
23 | method: 'POST',
24 | headers: {
25 | 'Content-Type': 'application/json',
26 | },
27 | body: JSON.stringify({ localNonce: localNonce }),
28 | });
29 | const data = await response.json();
30 | setNonce(data.nonce);
31 | setLocalNonce(localNonce);
32 | setResponse(data);
33 | };
34 |
35 | return (
36 |
37 |
Check Requests
38 |
39 | Each time you click the button it should change the nonce. The nonce
40 | should be different for GET and POST requests. Local Nonce is the nonce
41 | that is generated on the client side and returned so you can check that
42 | requests are not being cached
43 |
44 |
45 |
49 | Check Nonce GET
50 |
51 |
55 | Check Nonce POST
56 |
57 |
58 |
59 |
Local Nonce: {localNonce}
60 |
Response: {JSON.stringify(response)}
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/Eruda/eruda-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import eruda from 'eruda';
4 | import { ReactNode, useEffect } from 'react';
5 |
6 | export const Eruda = (props: { children: ReactNode }) => {
7 | useEffect(() => {
8 | if (typeof window !== 'undefined') {
9 | try {
10 | eruda.init();
11 | } catch (error) {
12 | console.log('Eruda failed to initialize', error);
13 | }
14 | }
15 | }, []);
16 |
17 | return <>{props.children}>;
18 | };
19 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/Eruda/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import dynamic from 'next/dynamic';
4 | import { ReactNode } from 'react';
5 |
6 | const Eruda = dynamic(() => import('./eruda-provider').then((c) => c.Eruda), {
7 | ssr: false,
8 | });
9 |
10 | export const ErudaProvider = (props: { children: ReactNode }) => {
11 | if (process.env.NEXT_PUBLIC_APP_ENV === 'production') {
12 | return props.children;
13 | }
14 | return {props.children} ;
15 | };
16 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/ExternalLinks.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export const ExternalLinks = () => {
4 | return (
5 | <>
6 | Test External Links
7 |
8 |
12 | Valid Associated Domain (Link)
13 |
14 |
18 | worldapp:// deep link
19 |
20 |
24 | worldapp:// games tab collection deep link
25 |
26 |
30 | worldapp:// apps tab collection deep link
31 |
32 | {
34 | window.open(
35 | 'https://worldcoin.org/app-store/explore-tab?collection_id=top_games',
36 | '_blank',
37 | );
38 | }}
39 | className="text-white bg-green-500 hover:bg-blue-300 transition p-4 leading-[1] rounded-lg"
40 | >
41 | world.org Games Collection Deep Link
42 |
43 | {
45 | window.open(
46 | 'https://worldcoin.org/app-store/explore-tab?collection_id=new_and_noteworthy',
47 | '_blank',
48 | );
49 | }}
50 | className="text-white bg-green-500 hover:bg-blue-300 transition p-4 leading-[1] rounded-lg"
51 | >
52 | world.org Apps Collection Deep Link
53 |
54 | {
56 | window.open(
57 | 'https://world.org/mini-app?app_id=app_staging_e387587d26a286fb5bea1d436ba0b2a3&path=features',
58 | '_blank',
59 | );
60 | }}
61 | className="text-white bg-green-500 hover:bg-blue-300 transition p-4 leading-[1] rounded-lg"
62 | >
63 | Internal Deep Link Test
64 |
65 |
69 | Valid Subdomain (Link)
70 |
71 | window.open('https://docs.worldcoin.org')}
73 | className="text-white bg-green-500 transition p-4 leading-[1] rounded-lg"
74 | >
75 | Valid Subdomain (Button)
76 |
77 |
81 | Valid double Subdomain (Link)
82 |
83 |
85 | window.open(
86 | 'https://worldcoin-developer-portal.eu.auth0.com/u/login/identifier',
87 | )
88 | }
89 | className="text-white bg-green-500 transition p-4 leading-[1] rounded-lg"
90 | >
91 | Valid double subdomain (Button)
92 |
93 |
94 | navigator.share({ url: 'https://google.com' })}
96 | className="text-white bg-green-500 transition p-4 leading-[1] rounded-lg"
97 | >
98 | Open Share Page
99 |
100 |
102 | window.open(
103 | 'https://docs.worldcoin.org?open_out_of_window=true',
104 | '_blank',
105 | )
106 | }
107 | className="text-white bg-green-500 transition p-4 leading-[1] rounded-lg"
108 | >
109 | Open Outside App
110 |
111 | {
113 | try {
114 | const response = await fetch('/800.jpeg');
115 | const blob = await response.blob();
116 | const file = new File([blob], '800.jpeg', { type: 'image/jpeg' });
117 | await navigator.share({
118 | url: 'https://google.com',
119 | files: [file],
120 | });
121 | } catch (error) {
122 | console.error('Error sharing:', error);
123 | }
124 | }}
125 | className="text-white bg-green-500 transition p-4 leading-[1] rounded-lg"
126 | >
127 | Share Image
128 |
129 | window.open('https://google.com', '_blank')}
131 | className="text-white bg-red-500 transition p-4 leading-[1] rounded-lg"
132 | >
133 | Invalid External Link (Button)
134 |
135 |
139 | Invalid External Link (Link)
140 |
141 |
142 | >
143 | );
144 | };
145 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/GetPermissions.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | GetPermissionsErrorCodes,
3 | MiniKit,
4 | ResponseEvent,
5 | } from '@worldcoin/minikit-js';
6 | import { useCallback, useEffect, useState } from 'react';
7 | import * as yup from 'yup';
8 | import { validateSchema } from './helpers/validate-schema';
9 |
10 | const getPermissionsSuccessPayloadSchema = yup.object({
11 | status: yup.string<'success'>().equals(['success']).required(),
12 | version: yup.number().required(),
13 | permissions: yup.object().required(),
14 | timestamp: yup.string().required(),
15 | });
16 |
17 | const getPermissionsErrorPayloadSchema = yup.object({
18 | error_code: yup
19 | .string()
20 | .oneOf(Object.values(GetPermissionsErrorCodes))
21 | .required(),
22 | details: yup.string().required(),
23 | status: yup.string<'error'>().equals(['error']).required(),
24 | version: yup.number().required(),
25 | });
26 |
27 | export const GetPermissions = () => {
28 | const [getPermissionsAppPayload, setGetPermissionsAppPayload] = useState<
29 | string | undefined
30 | >();
31 |
32 | const [
33 | getPermissionsPayloadValidationMessage,
34 | setGetPermissionsPayloadValidationMessage,
35 | ] = useState();
36 |
37 | const [sentGetPermissionsPayload, setSentGetPermissionsPayload] =
38 | useState | null>(null);
39 |
40 | const [tempInstallFix, setTempInstallFix] = useState(0);
41 |
42 | useEffect(() => {
43 | if (!MiniKit.isInstalled()) {
44 | return;
45 | }
46 |
47 | MiniKit.subscribe(ResponseEvent.MiniAppGetPermissions, async (payload) => {
48 | console.log('MiniAppGetPermissions, SUBSCRIBE PAYLOAD', payload);
49 | setGetPermissionsAppPayload(JSON.stringify(payload, null, 2));
50 | if (payload.status === 'error') {
51 | const errorMessage = await validateSchema(
52 | getPermissionsErrorPayloadSchema,
53 | payload,
54 | );
55 |
56 | if (!errorMessage) {
57 | setGetPermissionsPayloadValidationMessage('Payload is valid');
58 | } else {
59 | setGetPermissionsPayloadValidationMessage(errorMessage);
60 | }
61 | } else {
62 | const errorMessage = await validateSchema(
63 | getPermissionsSuccessPayloadSchema,
64 | payload,
65 | );
66 |
67 | // This checks if the response format is correct
68 | if (!errorMessage) {
69 | setGetPermissionsPayloadValidationMessage('Payload is valid');
70 | } else {
71 | setGetPermissionsPayloadValidationMessage(errorMessage);
72 | }
73 | }
74 | });
75 |
76 | return () => {
77 | MiniKit.unsubscribe(ResponseEvent.MiniAppGetPermissions);
78 | };
79 | }, [tempInstallFix]);
80 |
81 | const onGetPermissions = useCallback(async () => {
82 | const payload = MiniKit.commands.getPermissions();
83 | setSentGetPermissionsPayload({
84 | payload,
85 | });
86 | console.log('payload', payload);
87 | setTempInstallFix((prev) => prev + 1);
88 | }, []);
89 |
90 | return (
91 |
92 |
93 |
Get Permissions
94 |
95 |
96 |
97 |
98 | {JSON.stringify(sentGetPermissionsPayload, null, 2)}
99 |
100 |
101 |
102 |
103 | onGetPermissions()}
106 | >
107 | Get Permissions
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
Message from "{ResponseEvent.MiniAppGetPermissions}"
116 |
117 |
118 |
119 | {getPermissionsAppPayload ?? JSON.stringify(null)}
120 |
121 |
122 |
123 |
124 |
Response Validation:
125 |
126 | {getPermissionsPayloadValidationMessage ?? 'No validation'}
127 |
128 |
129 |
130 |
131 | );
132 | };
133 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/Nav.tsx:
--------------------------------------------------------------------------------
1 | import { signIn, signOut, useSession } from 'next-auth/react';
2 | import { useMemo } from 'react';
3 |
4 | export const Nav = () => {
5 | const { data: session } = useSession();
6 | const user = useMemo(() => session?.user, [session]);
7 |
8 | return (
9 |
10 | MiniKit V1
11 |
12 | signOut() : () => signIn('worldcoin')}
14 | className="text-white bg-black hover:bg-gray-500 transition p-4 leading-[1] rounded-md"
15 | >
16 | {user?.name ? 'Sign Out' : 'Sign In'}
17 |
18 | {/* signOut() : () => signIn('google')}
20 | className="text-white bg-blue-500 hover:bg-blue-300 transition p-4 leading-[1] rounded-md"
21 | >
22 | {user?.name ? 'Google Sign Out' : 'Google Sign In'}
23 | */}
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/Pay.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | MiniKit,
3 | PayCommandInput,
4 | PaymentErrorCodes,
5 | ResponseEvent,
6 | Tokens,
7 | tokenToDecimals,
8 | } from '@worldcoin/minikit-js';
9 | import { useCallback, useState } from 'react';
10 | import * as yup from 'yup';
11 | import { validateSchema } from './helpers/validate-schema';
12 |
13 | const paymentSuccessPayloadSchema = yup.object({
14 | status: yup.string<'success'>().oneOf(['success']),
15 | transaction_status: yup.string<'submitted'>().oneOf(['submitted']),
16 | transaction_id: yup.string().required(),
17 | reference: yup.string().required(),
18 | from: yup.string().optional(),
19 | chain: yup.string().required(),
20 | timestamp: yup.string().required(),
21 | });
22 |
23 | const paymentErrorPayloadSchema = yup.object({
24 | error_code: yup
25 | .string()
26 | .oneOf(Object.values(PaymentErrorCodes))
27 | .required(),
28 | status: yup.string<'error'>().equals(['error']).required(),
29 | });
30 |
31 | /* Asynchronous Implementation
32 | For the purpose of variability some of these commands use async handlers
33 | and some of the commands user synchronous responses.
34 | */
35 | export const Pay = () => {
36 | const [paymentAppPayload, setPaymentAppPayload] = useState<
37 | string | undefined
38 | >();
39 | const [paymentPayloadValidationMessage, setPaymentPayloadValidationMessage] =
40 | useState();
41 |
42 | const [sentPayPayload, setSentPayPayload] = useState | null>(null);
46 |
47 | const validateResponse = async (payload) => {
48 | console.log('MiniAppPayment, SUBSCRIBE PAYLOAD', payload);
49 |
50 | if (payload.status === 'error') {
51 | const errorMessage = await validateSchema(
52 | paymentErrorPayloadSchema,
53 | payload,
54 | );
55 |
56 | if (!errorMessage) {
57 | setPaymentPayloadValidationMessage('Payload is valid');
58 | } else {
59 | setPaymentPayloadValidationMessage(errorMessage);
60 | }
61 | } else {
62 | const errorMessage = await validateSchema(
63 | paymentSuccessPayloadSchema,
64 | payload,
65 | );
66 |
67 | if (!errorMessage) {
68 | setPaymentPayloadValidationMessage('Payload is valid');
69 | } else {
70 | setPaymentPayloadValidationMessage(errorMessage);
71 | }
72 | }
73 |
74 | setPaymentAppPayload(JSON.stringify(payload, null, 2));
75 | };
76 |
77 | const onPayClick = useCallback(
78 | async (amount: number, address: string, token?: Tokens) => {
79 | const wldAmount = tokenToDecimals(amount, Tokens.WLD);
80 | const usdcAmount = tokenToDecimals(amount, Tokens.USDC);
81 |
82 | const tokenPayload = [
83 | {
84 | symbol: Tokens.WLD,
85 | token_amount: wldAmount.toString(),
86 | },
87 | {
88 | symbol: Tokens.USDC,
89 | token_amount: usdcAmount.toString(),
90 | },
91 | ];
92 |
93 | const payPayload: PayCommandInput = {
94 | to: address,
95 | tokens: token
96 | ? [
97 | {
98 | symbol: token,
99 | token_amount:
100 | token === Tokens.WLD
101 | ? wldAmount.toString()
102 | : usdcAmount.toString(),
103 | },
104 | ]
105 | : tokenPayload,
106 | description: 'Test example payment for minikit on Worldchain',
107 | reference: new Date().toISOString(),
108 | };
109 |
110 | const { commandPayload, finalPayload } =
111 | await MiniKit.commandsAsync.pay(payPayload);
112 | setSentPayPayload(commandPayload);
113 | await validateResponse(finalPayload);
114 | },
115 | [],
116 | );
117 |
118 | return (
119 |
120 |
121 |
Pay
122 |
123 |
124 |
Sent payload: Spec is still WIP
125 |
126 |
127 |
128 | {JSON.stringify(sentPayPayload, null, 2)}
129 |
130 |
131 |
132 |
133 |
136 | onPayClick(0.1, '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
137 | }
138 | >
139 | Pay (USDC + WLD)
140 |
141 |
144 | onPayClick(
145 | 0.1,
146 | '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
147 | Tokens.WLD,
148 | )
149 | }
150 | >
151 | Pay Single (WLD)
152 |
153 |
156 | onPayClick(
157 | 0.1,
158 | '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
159 | Tokens.USDC,
160 | )
161 | }
162 | >
163 | Pay Single (USDC)
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
Message from "{ResponseEvent.MiniAppPayment}"
172 |
173 |
174 |
175 | {paymentAppPayload ?? JSON.stringify(null)}
176 |
177 |
178 |
179 |
180 |
Validation message:
181 |
182 | {paymentPayloadValidationMessage ?? 'No validation'}
183 |
184 |
185 |
186 |
187 | );
188 | };
189 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/RequestPermissions.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | MiniKit,
3 | Permission,
4 | RequestPermissionErrorCodes,
5 | RequestPermissionPayload,
6 | ResponseEvent,
7 | } from '@worldcoin/minikit-js';
8 | import { useCallback, useEffect, useState } from 'react';
9 | import * as yup from 'yup';
10 | import { validateSchema } from './helpers/validate-schema';
11 |
12 | const requestPermissionSuccessPayloadSchema = yup.object({
13 | status: yup.string<'success'>().equals(['success']).required(),
14 | version: yup.number().required(),
15 | permission: yup.string().oneOf(Object.values(Permission)),
16 | timestamp: yup.string().required(),
17 | });
18 |
19 | const requestPermissionErrorPayloadSchema = yup.object({
20 | error_code: yup
21 | .string()
22 | .oneOf(Object.values(RequestPermissionErrorCodes))
23 | .required(),
24 | description: yup.string().required(),
25 | status: yup.string<'error'>().equals(['error']).required(),
26 | version: yup.number().required(),
27 | });
28 |
29 | export const RequestPermission = () => {
30 | const [requestPermissionAppPayload, setRequestPermissionAppPayload] =
31 | useState();
32 |
33 | const [
34 | requestPermissionPayloadValidationMessage,
35 | setRequestPermissionPayloadValidationMessage,
36 | ] = useState();
37 |
38 | const [sentRequestPermissionPayload, setSentRequestPermissionPayload] =
39 | useState | null>(null);
40 |
41 | const [tempInstallFix, setTempInstallFix] = useState(0);
42 |
43 | useEffect(() => {
44 | if (!MiniKit.isInstalled()) {
45 | return;
46 | }
47 |
48 | MiniKit.subscribe(
49 | ResponseEvent.MiniAppRequestPermission,
50 | async (payload) => {
51 | console.log('MiniAppRequestPermission, SUBSCRIBE PAYLOAD', payload);
52 | setRequestPermissionAppPayload(JSON.stringify(payload, null, 2));
53 | if (payload.status === 'error') {
54 | const errorMessage = await validateSchema(
55 | requestPermissionErrorPayloadSchema,
56 | payload,
57 | );
58 |
59 | if (!errorMessage) {
60 | setRequestPermissionPayloadValidationMessage('Payload is valid');
61 | } else {
62 | setRequestPermissionPayloadValidationMessage(errorMessage);
63 | }
64 | } else {
65 | const errorMessage = await validateSchema(
66 | requestPermissionSuccessPayloadSchema,
67 | payload,
68 | );
69 |
70 | // This checks if the response format is correct
71 | if (!errorMessage) {
72 | setRequestPermissionPayloadValidationMessage('Payload is valid');
73 | } else {
74 | setRequestPermissionPayloadValidationMessage(errorMessage);
75 | }
76 | }
77 | },
78 | );
79 |
80 | return () => {
81 | MiniKit.unsubscribe(ResponseEvent.MiniAppRequestPermission);
82 | };
83 | }, [tempInstallFix]);
84 |
85 | const onRequestPermission = useCallback(async (permission: Permission) => {
86 | const requestPermissionPayload: RequestPermissionPayload = {
87 | permission,
88 | };
89 |
90 | const payload = MiniKit.commands.requestPermission(
91 | requestPermissionPayload,
92 | );
93 | setSentRequestPermissionPayload({
94 | payload,
95 | });
96 | console.log('payload', payload);
97 | setTempInstallFix((prev) => prev + 1);
98 | }, []);
99 |
100 | return (
101 |
102 |
103 |
Request Permission
104 |
105 |
106 |
107 |
108 | {JSON.stringify(sentRequestPermissionPayload, null, 2)}
109 |
110 |
111 |
112 |
113 | onRequestPermission(Permission.Notifications)}
116 | >
117 | Request Notifications
118 |
119 | onRequestPermission(Permission.Microphone)}
122 | >
123 | Request Microphone
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | Message from "{ResponseEvent.MiniAppRequestPermission}"{' '}
133 |
134 |
135 |
136 |
137 | {requestPermissionAppPayload ?? JSON.stringify(null)}
138 |
139 |
140 |
141 |
142 |
Response Validation:
143 |
144 | {requestPermissionPayloadValidationMessage ?? 'No validation'}
145 |
146 |
147 |
148 |
149 | );
150 | };
151 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/SearchParams.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useSearchParams } from 'next/navigation';
4 |
5 | export const SearchParams = () => {
6 | const searchParams = useSearchParams();
7 | const params = Object.fromEntries(searchParams.entries());
8 |
9 | return (
10 |
11 |
Search Parameters
12 |
13 |
14 | {JSON.stringify(params, null, 2)}
15 |
16 |
17 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/SendHaptic.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | MiniAppSendHapticFeedbackPayload,
3 | MiniKit,
4 | ResponseEvent,
5 | SendHapticFeedbackErrorCodes,
6 | SendHapticFeedbackInput,
7 | } from '@worldcoin/minikit-js';
8 | import { useCallback, useEffect, useState } from 'react';
9 | import * as yup from 'yup';
10 | import { validateSchema } from './helpers/validate-schema';
11 |
12 | const sendHapticFeedbackSuccessPayloadSchema = yup.object({
13 | status: yup.string<'success'>().oneOf(['success']),
14 | });
15 |
16 | const sendHapticFeedbackErrorPayloadSchema = yup.object({
17 | error_code: yup
18 | .string()
19 | .oneOf(Object.values(SendHapticFeedbackErrorCodes))
20 | .required(),
21 | status: yup.string<'error'>().equals(['error']).required(),
22 | version: yup.number().required(),
23 | });
24 |
25 | const allPossibleHaptics: SendHapticFeedbackInput[] = [
26 | { hapticsType: 'impact', style: 'heavy' },
27 | { hapticsType: 'impact', style: 'light' },
28 | { hapticsType: 'impact', style: 'medium' },
29 | { hapticsType: 'notification', style: 'error' },
30 | { hapticsType: 'notification', style: 'success' },
31 | { hapticsType: 'notification', style: 'warning' },
32 | { hapticsType: 'selection-changed' },
33 | ];
34 |
35 | export const SendHapticFeedback = () => {
36 | const [sentHapticFeedbackPayload, setSentHapticFeedbackPayload] =
37 | useState | null>(null);
38 |
39 | useEffect(() => {
40 | if (!MiniKit.isInstalled()) {
41 | return;
42 | }
43 |
44 | MiniKit.subscribe(
45 | ResponseEvent.MiniAppSendHapticFeedback,
46 | async (payload: MiniAppSendHapticFeedbackPayload) => {
47 | console.log('MiniAppSendHapticFeedback, SUBSCRIBE PAYLOAD', payload);
48 |
49 | if (payload.status === 'error') {
50 | const validationErrorMessage = await validateSchema(
51 | sendHapticFeedbackErrorPayloadSchema,
52 | payload,
53 | );
54 |
55 | if (!validationErrorMessage) {
56 | console.log('Payload is valid');
57 | } else {
58 | console.error(validationErrorMessage);
59 | }
60 | } else {
61 | const validationErrorMessage = await validateSchema(
62 | sendHapticFeedbackSuccessPayloadSchema,
63 | payload,
64 | );
65 |
66 | // This checks if the response format is correct
67 | if (!validationErrorMessage) {
68 | console.log('Payload is valid');
69 | } else {
70 | console.error(validationErrorMessage);
71 | }
72 | }
73 | },
74 | );
75 |
76 | return () => {
77 | MiniKit.unsubscribe(ResponseEvent.MiniAppSignTypedData);
78 | };
79 | }, []);
80 |
81 | const onSendHapticFeedback = useCallback(
82 | async (input: SendHapticFeedbackInput) => {
83 | const payload = MiniKit.commands.sendHapticFeedback(input);
84 |
85 | setSentHapticFeedbackPayload({
86 | payload,
87 | });
88 | },
89 | [],
90 | );
91 |
92 | return (
93 |
94 |
95 |
Send Haptics
96 |
97 |
98 |
99 |
100 | {JSON.stringify(sentHapticFeedbackPayload, null, 2)}
101 |
102 |
103 |
104 |
105 | {allPossibleHaptics.map((haptic, i) => (
106 | onSendHapticFeedback(haptic)}
110 | >
111 | {`Send ${haptic.hapticsType}-${haptic?.style}`}
112 |
113 | ))}
114 |
115 |
116 |
117 | );
118 | };
119 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/ShareContacts.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Contact,
3 | MiniKit,
4 | ResponseEvent,
5 | ShareContactsErrorCodes,
6 | ShareContactsPayload,
7 | } from '@worldcoin/minikit-js';
8 | import { useCallback, useEffect, useState } from 'react';
9 | import * as yup from 'yup';
10 | import { validateSchema } from './helpers/validate-schema';
11 |
12 | const shareContactsSuccessPayloadSchema = yup.object({
13 | status: yup.string<'success'>().equals(['success']).required(),
14 | version: yup.number().required(),
15 | contacts: yup.array().of(yup.object().required()),
16 | });
17 |
18 | const shareContactsErrorPayloadSchema = yup.object({
19 | error_code: yup
20 | .string()
21 | .oneOf(Object.values(ShareContactsErrorCodes))
22 | .required(),
23 | status: yup.string<'error'>().equals(['error']).required(),
24 | version: yup.number().required(),
25 | });
26 |
27 | export const ShareContacts = () => {
28 | const [shareContactsAppPayload, setShareContactsAppPayload] = useState<
29 | string | undefined
30 | >();
31 |
32 | const [
33 | shareContactsPayloadValidationMessage,
34 | setShareContactsPayloadValidationMessage,
35 | ] = useState();
36 |
37 | const [sentShareContactsPayload, setSentShareContactsPayload] =
38 | useState | null>(null);
39 |
40 | const [tempInstallFix, setTempInstallFix] = useState(0);
41 |
42 | useEffect(() => {
43 | if (!MiniKit.isInstalled()) {
44 | return;
45 | }
46 |
47 | MiniKit.subscribe(ResponseEvent.MiniAppShareContacts, async (payload) => {
48 | console.log('MiniAppShareContacts, SUBSCRIBE PAYLOAD', payload);
49 | setShareContactsAppPayload(JSON.stringify(payload, null, 2));
50 | if (payload.status === 'error') {
51 | const errorMessage = await validateSchema(
52 | shareContactsErrorPayloadSchema,
53 | payload,
54 | );
55 |
56 | if (!errorMessage) {
57 | setShareContactsPayloadValidationMessage('Payload is valid');
58 | } else {
59 | setShareContactsPayloadValidationMessage(errorMessage);
60 | }
61 | } else {
62 | const errorMessage = await validateSchema(
63 | shareContactsSuccessPayloadSchema,
64 | payload,
65 | );
66 |
67 | // This checks if the response format is correct
68 | if (!errorMessage) {
69 | setShareContactsPayloadValidationMessage('Payload is valid');
70 | } else {
71 | setShareContactsPayloadValidationMessage(errorMessage);
72 | }
73 | }
74 | });
75 |
76 | return () => {
77 | MiniKit.unsubscribe(ResponseEvent.MiniAppShareContacts);
78 | };
79 | }, [tempInstallFix]);
80 |
81 | const onShareContacts = useCallback(
82 | async (isMultiSelectEnabled: boolean = false, inviteMessage?: string) => {
83 | const shareContactsPayload: ShareContactsPayload = {
84 | isMultiSelectEnabled,
85 | inviteMessage,
86 | };
87 |
88 | const payload = MiniKit.commands.shareContacts(shareContactsPayload);
89 | setSentShareContactsPayload({
90 | payload,
91 | });
92 | console.log('payload', payload);
93 | setTempInstallFix((prev) => prev + 1);
94 | },
95 | [],
96 | );
97 |
98 | return (
99 |
100 |
101 |
Share Contacts
102 |
103 |
104 |
105 |
106 | {JSON.stringify(sentShareContactsPayload, null, 2)}
107 |
108 |
109 |
110 |
111 | onShareContacts(true)}
114 | >
115 | Share Contacts (Multi enabled)
116 |
117 | onShareContacts(false)}
120 | >
121 | Share Contacts (multi disabled)
122 |
123 |
124 |
125 | onShareContacts(false, 'hello join worldcoin!')}
128 | >
129 | Share Contacts Invite Message
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
Message from "{ResponseEvent.MiniAppShareContacts}"
138 |
139 |
140 |
141 | {shareContactsAppPayload ?? JSON.stringify(null)}
142 |
143 |
144 |
145 |
146 |
Response Validation:
147 |
148 | {shareContactsPayloadValidationMessage ?? 'No validation'}
149 |
150 |
151 |
152 |
153 | );
154 | };
155 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/SignMessage.tsx:
--------------------------------------------------------------------------------
1 | import Safe, { hashSafeMessage } from '@safe-global/protocol-kit';
2 | import {
3 | MiniKit,
4 | ResponseEvent,
5 | SignMessageErrorCodes,
6 | SignMessageInput,
7 | } from '@worldcoin/minikit-js';
8 | import { useEffect, useState } from 'react';
9 | import * as yup from 'yup';
10 | import { validateSchema } from './helpers/validate-schema';
11 |
12 | const signMessageSuccessPayloadSchema = yup.object({
13 | status: yup.string<'success'>().oneOf(['success']),
14 | signature: yup.string().required(),
15 | address: yup.string().required(),
16 | });
17 |
18 | const signMessageErrorPayloadSchema = yup.object({
19 | error_code: yup
20 | .string()
21 | .oneOf(Object.values(SignMessageErrorCodes))
22 | .required(),
23 | status: yup.string<'error'>().equals(['error']).required(),
24 | version: yup.number().required(),
25 | });
26 |
27 | export const SignMessage = () => {
28 | const [signMessageAppPayload, setSignMessageAppPayload] = useState<
29 | string | undefined
30 | >();
31 |
32 | const [
33 | signMessagePayloadValidationMessage,
34 | setSignMessagePayloadValidationMessage,
35 | ] = useState();
36 |
37 | const [
38 | signMessagePayloadVerificationMessage,
39 | setSignMessagePayloadVerificationMessage,
40 | ] = useState();
41 |
42 | const [sentSignMessagePayload, setSentSignMessagePayload] = useState | null>(null);
46 | const [tempInstallFix, setTempInstallFix] = useState(0);
47 | const [messageToSign, setMessageToSign] = useState('hello world');
48 |
49 | useEffect(() => {
50 | if (!MiniKit.isInstalled()) {
51 | return;
52 | }
53 |
54 | MiniKit.subscribe(ResponseEvent.MiniAppSignMessage, async (payload) => {
55 | console.log('MiniAppSignMessage, SUBSCRIBE PAYLOAD', payload);
56 | setSignMessageAppPayload(JSON.stringify(payload, null, 2));
57 | if (payload.status === 'error') {
58 | const errorMessage = await validateSchema(
59 | signMessageErrorPayloadSchema,
60 | payload,
61 | );
62 |
63 | if (!errorMessage) {
64 | setSignMessagePayloadValidationMessage('Payload is valid');
65 | } else {
66 | setSignMessagePayloadValidationMessage(errorMessage);
67 | }
68 | } else {
69 | const errorMessage = await validateSchema(
70 | signMessageSuccessPayloadSchema,
71 | payload,
72 | );
73 |
74 | // This checks if the response format is correct
75 | if (!errorMessage) {
76 | setSignMessagePayloadValidationMessage('Payload is valid');
77 | } else {
78 | setSignMessagePayloadValidationMessage(errorMessage);
79 | }
80 |
81 | const messageHash = hashSafeMessage(messageToSign);
82 |
83 | const isValid = await (
84 | await Safe.init({
85 | provider: 'https://worldchain-mainnet.g.alchemy.com/public',
86 | safeAddress: payload.address,
87 | })
88 | ).isValidSignature(messageHash, payload.signature);
89 |
90 | // Checks functionally if the signature is correct
91 | if (isValid) {
92 | setSignMessagePayloadVerificationMessage('Signature is valid');
93 | } else {
94 | setSignMessagePayloadVerificationMessage('Signature is invalid');
95 | }
96 | }
97 | });
98 |
99 | return () => {
100 | MiniKit.unsubscribe(ResponseEvent.MiniAppSignMessage);
101 | };
102 | }, [messageToSign, tempInstallFix]);
103 |
104 | const onSignMessage = async (message: string) => {
105 | const signMessagePayload: SignMessageInput = {
106 | message,
107 | };
108 |
109 | setMessageToSign(message);
110 | const payload = MiniKit.commands.signMessage(signMessagePayload);
111 | setSentSignMessagePayload({
112 | payload,
113 | });
114 | setTempInstallFix((prev) => prev + 1);
115 | };
116 |
117 | return (
118 |
119 |
120 |
Sign Message
121 |
122 |
123 |
124 |
125 | {JSON.stringify(sentSignMessagePayload, null, 2)}
126 |
127 |
128 |
129 |
130 | onSignMessage('hello world')}
133 | >
134 | Sign Message
135 |
136 | {
139 | await onSignMessage('world-chat-authentication:test');
140 | }}
141 | >
142 | Fail Message
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
Message from "{ResponseEvent.MiniAppSignMessage}"
151 |
152 |
153 |
154 | {signMessageAppPayload ?? JSON.stringify(null)}
155 |
156 |
157 |
158 |
159 |
Response Validation:
160 |
161 | {signMessagePayloadValidationMessage ?? 'No validation'}
162 |
163 |
164 |
165 |
Check does signature verify:
166 |
167 | {signMessagePayloadVerificationMessage ?? 'No verification'}
168 |
169 |
170 |
171 |
172 | );
173 | };
174 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/User.tsx:
--------------------------------------------------------------------------------
1 | import { useSession } from 'next-auth/react';
2 | import { useMemo } from 'react';
3 |
4 | export const User = () => {
5 | const { data: session } = useSession();
6 | const user = useMemo(() => session?.user, [session]);
7 |
8 | return (
9 |
10 |
Session User:
11 | {user?.name ? (
12 |
13 | User name:{' '}
14 |
15 | {user?.name}
16 |
17 |
18 | ) : (
19 |
No user
20 | )}
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/VerifyAction/verify-cloud-proof.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import {
4 | ISuccessResult,
5 | IVerifyResponse,
6 | verifyCloudProof,
7 | } from '@worldcoin/minikit-js';
8 |
9 | export const verifyProof = async (params: {
10 | app_id: `app_${string}`;
11 | action: string;
12 | signal?: string;
13 | payload: ISuccessResult;
14 | }) => {
15 | const { app_id, action, payload, signal } = params;
16 | let verifyResponse: IVerifyResponse | null = null;
17 | const stagingEndpoint = `${process.env.NEXT_SERVER_DEV_PORTAL_URL}/api/v2/verify/${app_id}`;
18 |
19 | try {
20 | verifyResponse = await verifyCloudProof(
21 | payload,
22 | app_id,
23 | action,
24 | signal,
25 |
26 | process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
27 | ? stagingEndpoint
28 | : undefined,
29 | );
30 |
31 | console.log('verifyResponse', verifyResponse);
32 | } catch (error) {
33 | console.log('Error in verifyCloudProof', error);
34 | }
35 |
36 | return verifyResponse;
37 | };
38 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/VerifyAction/verify-onchain.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { MiniKit } from '@worldcoin/minikit-js';
4 | import { useState } from 'react';
5 | import { decodeAbiParameters, parseAbiParameters } from 'viem';
6 |
7 | // ABI fragment for the verify function
8 | const testVerifyAbi = [
9 | {
10 | inputs: [
11 | { name: 'signal', type: 'address' },
12 | { name: 'root', type: 'uint256' },
13 | { name: 'nullifierHash', type: 'uint256' },
14 | { name: 'proof', type: 'uint256[8]' },
15 | ],
16 | name: 'verify',
17 | outputs: [],
18 | stateMutability: 'nonpayable',
19 | type: 'function',
20 | },
21 | ];
22 |
23 | /** works on Prod QA App, app_id: app_dfbe55706a640c82dce839bb0ecae74d */
24 | export const TEST_VERIFY_CONTRACT_ADDRESS =
25 | '0x793dda8ec2aff37945627ab64dd4e8b4e8ea4cb1';
26 |
27 | /**
28 | * Calls the TestVerify contract's verify function
29 | */
30 | export const verifyOnchain = async (payload: {
31 | signal: string;
32 | root: string;
33 | nullifierHash: string;
34 | proof: string;
35 | }): Promise<{
36 | success: boolean;
37 | transactionHash?: string;
38 | error?: string;
39 | }> => {
40 | const signal = payload.signal;
41 |
42 | try {
43 | const root = BigInt(payload.root);
44 | const nullifierHash = BigInt(payload.nullifierHash);
45 | const proof = decodeAbiParameters(
46 | parseAbiParameters('uint256[8]'),
47 | payload.proof as `0x${string}`,
48 | )[0];
49 |
50 | console.log('Sending transaction to verify proof on-chain:');
51 | console.log('Signal:', signal);
52 | console.log('Root:', root);
53 | console.log('NullifierHash:', nullifierHash);
54 | console.log('Proof:', proof);
55 |
56 | const { finalPayload } = await MiniKit.commandsAsync.sendTransaction({
57 | transaction: [
58 | {
59 | address: TEST_VERIFY_CONTRACT_ADDRESS,
60 | abi: testVerifyAbi,
61 | functionName: 'verify',
62 | args: [signal, root, nullifierHash, proof],
63 | },
64 | ],
65 | });
66 |
67 | if (finalPayload.status === 'success') {
68 | return {
69 | success: true,
70 | transactionHash: finalPayload.transaction_id,
71 | };
72 | } else {
73 | return {
74 | success: false,
75 | error: `Transaction failed: ${finalPayload.error_code || 'Unknown error'} \n ${JSON.stringify(finalPayload.details)}`,
76 | };
77 | }
78 | } catch (error) {
79 | console.error('Error verifying on-chain:', error);
80 | return {
81 | success: false,
82 | error: error instanceof Error ? error.message : 'Unknown error',
83 | };
84 | }
85 | };
86 |
87 | export const VerifyOnchainProof = () => {
88 | const [onchainVerifyResult, setOnchainVerifyResult] = useState<{
89 | success?: boolean;
90 | transactionHash?: string;
91 | error?: string;
92 | isLoading?: boolean;
93 | }>({});
94 |
95 | const handleOnchainVerify = async () => {
96 | let signal = MiniKit.user.walletAddress;
97 | if (!signal) {
98 | const { finalPayload } = await MiniKit.commandsAsync.walletAuth({
99 | nonce: 'i-trust-you',
100 | });
101 | if (finalPayload.status === 'success') {
102 | signal = finalPayload.address;
103 | } else {
104 | return { success: false, error: 'No wallet address' };
105 | }
106 | }
107 | const { finalPayload } = await MiniKit.commandsAsync.verify({
108 | action: 'onchain-verify-test',
109 | signal: signal,
110 | });
111 | if (finalPayload.status === 'success') {
112 | const merkleRoot = finalPayload.merkle_root;
113 | const nullifierHash = finalPayload.nullifier_hash;
114 | const proof = finalPayload.proof;
115 |
116 | try {
117 | // Using a fixed signal address for simplicity
118 | const result = await verifyOnchain({
119 | signal: signal,
120 | root: merkleRoot,
121 | nullifierHash: nullifierHash,
122 | proof: proof,
123 | });
124 |
125 | setOnchainVerifyResult({
126 | ...result,
127 | isLoading: false,
128 | });
129 | } catch (error) {
130 | console.error('Error in onchain verification:', error);
131 | setOnchainVerifyResult({
132 | success: false,
133 | error: error instanceof Error ? error.message : 'Unknown error',
134 | isLoading: false,
135 | });
136 | }
137 | }
138 | };
139 | if (process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging') {
140 | return <>>;
141 | }
142 | return (
143 |
144 |
Onchain Verification Test
145 |
146 | Tests the verification proof on-chain using the TestVerify contract at
147 | address: {TEST_VERIFY_CONTRACT_ADDRESS}
148 |
149 | This will only work on the prod QA App -
150 |
155 | app_dfbe55706a640c82dce839bb0ecae74d
156 |
157 |
158 |
159 |
160 |
166 | {onchainVerifyResult.isLoading
167 | ? 'Verifying on-chain...'
168 | : 'Verify on-chain with TestVerify contract'}
169 |
170 |
171 | {onchainVerifyResult.success !== undefined && (
172 |
175 | {onchainVerifyResult.success ? (
176 |
177 |
178 | Verification successful!
179 |
180 |
181 | Transaction hash: {onchainVerifyResult.transactionHash}
182 |
183 |
184 | ) : (
185 |
186 |
Verification failed
187 |
188 | {onchainVerifyResult.error}
189 |
190 |
191 | )}
192 |
193 | )}
194 |
195 | );
196 | };
197 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/Versions.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | MiniKit,
5 | MiniKitInstallErrorCodes,
6 | MiniKitInstallErrorMessage,
7 | } from '@worldcoin/minikit-js';
8 |
9 | const appId = 'your-app-id';
10 |
11 | export const Versions = () => {
12 | const isValid = () => {
13 | if (
14 | typeof window === 'undefined' ||
15 | typeof window.WorldApp === 'undefined'
16 | ) {
17 | return { isValid: false, error: 'window.WorldApp is undefined' };
18 | }
19 |
20 | try {
21 | // @ts-ignore
22 | if (MiniKit.commandsValid(window.WorldApp?.supported_commands)) {
23 | return { isValid: true };
24 | } else {
25 | return {
26 | isValid: false,
27 | error:
28 | MiniKitInstallErrorMessage[MiniKitInstallErrorCodes.AppOutOfDate],
29 | };
30 | }
31 | } catch (error) {
32 | return {
33 | isValid: false,
34 | error: 'Something went wrong on version validation',
35 | };
36 | }
37 | };
38 |
39 | const reinstall = () => {
40 | MiniKit.install(appId);
41 | JSON.stringify(isValid() ?? null, null, 2);
42 | };
43 | return (
44 |
45 |
Versions
46 |
47 |
48 |
window.WorldApp:
49 |
Install
50 |
51 |
55 | {JSON.stringify(window?.WorldApp ?? null, null, 2)}
56 |
57 |
58 |
59 |
60 |
61 |
Is versions Valid:
62 |
63 |
64 |
65 | {JSON.stringify(isValid() ?? null, null, 2)}
66 |
67 |
68 |
69 |
70 |
71 |
MiniKit.user:
72 |
73 |
74 | {JSON.stringify(MiniKit.user ?? null, null, 2)}
75 |
76 |
77 |
78 |
79 |
Device Properties:
80 |
81 |
82 | {JSON.stringify(MiniKit.deviceProperties ?? null, null, 2)}
83 |
84 |
85 |
86 |
87 | );
88 | };
89 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/helpers/validate-schema.ts:
--------------------------------------------------------------------------------
1 | import * as yup from 'yup';
2 |
3 | export const validateSchema = async (
4 | schema: yup.ObjectSchema,
5 | payload: any,
6 | ): Promise => {
7 | let errorMessage: string | null = null;
8 |
9 | try {
10 | await schema.validate(payload);
11 | } catch (error) {
12 | if (!(error instanceof yup.ValidationError)) {
13 | errorMessage = 'Unknown error';
14 | return errorMessage;
15 | }
16 |
17 | errorMessage = error.message;
18 | }
19 |
20 | return errorMessage;
21 | };
22 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientContent/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { MiniKit } from '@worldcoin/minikit-js';
4 | import {
5 | GetSearchedUsernameResult,
6 | UsernameSearch,
7 | } from '@worldcoin/minikit-react';
8 | import dynamic from 'next/dynamic';
9 | import Image from 'next/image';
10 | import { useState } from 'react';
11 | import { CameraComponent } from './Camera';
12 | import CheckRequests from './CheckRequests';
13 | import { ExternalLinks } from './ExternalLinks';
14 | import { GetPermissions } from './GetPermissions';
15 | import { Nav } from './Nav';
16 | import { Pay } from './Pay';
17 | import { RequestPermission } from './RequestPermissions';
18 | import { SearchParams } from './SearchParams';
19 | import { SendHapticFeedback } from './SendHaptic';
20 | import { Share } from './Share';
21 | import { ShareContacts } from './ShareContacts';
22 | import { SignMessage } from './SignMessage';
23 | import { SignTypedData } from './SignTypedMessage';
24 | import { SendTransaction } from './Transaction';
25 | import { User } from './User';
26 | import { VerifyAction } from './VerifyAction';
27 | import { WalletAuth } from './WalletAuth';
28 | const VersionsNoSSR = dynamic(
29 | () => import('./Versions').then((comp) => comp.Versions),
30 | { ssr: false },
31 | );
32 |
33 | export const ClientContent = () => {
34 | const [searchValue, setSearchValue] = useState('');
35 | const [searchResults, setSearchResults] =
36 | useState();
37 | const isProduction = process.env.NEXT_PUBLIC_ENVIRONMENT === 'production';
38 |
39 | const handleChange = (e: React.ChangeEvent) => {
40 | setSearchValue(e.target.value);
41 | };
42 |
43 | const sendNotification = async () => {
44 | if (!MiniKit.user?.walletAddress) {
45 | console.error('No wallet address found, do wallet auth first');
46 | return;
47 | }
48 | const response = await fetch(`/api/notifications`, {
49 | method: 'POST',
50 | body: JSON.stringify({
51 | walletAddress: MiniKit.user?.walletAddress ?? '',
52 | }),
53 | });
54 | const data = await response.json();
55 | console.log(data);
56 | };
57 | return (
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
75 |
76 | {/* Display search results */}
77 | {searchResults && (
78 |
79 | {searchResults.status === 200 ? (
80 |
81 | {searchResults.data?.map((user) => (
82 |
86 | {user.profile_picture_url && (
87 |
94 | )}
95 | {user.username} - {user.address}
96 |
97 | ))}
98 |
99 | ) : (
100 |
Error: {searchResults.error}
101 | )}
102 |
103 | )}
104 |
105 |
106 | {isProduction && (
107 |
111 | Send Notification (auth and turn on notifications first)
112 |
113 | )}
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | );
150 | };
151 |
--------------------------------------------------------------------------------
/demo/with-next/components/ClientProviders.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { MiniKitProvider } from '@worldcoin/minikit-js/minikit-provider';
3 | import dynamic from 'next/dynamic';
4 | import type { ReactNode } from 'react';
5 | import SessionProvider from './SessionProvider'; // Assuming SessionProvider is also client-side or compatible
6 |
7 | const ErudaProvider = dynamic(
8 | () => import('./ClientContent/Eruda').then((c) => c.ErudaProvider),
9 | { ssr: false },
10 | );
11 |
12 | // Define props for ClientProviders
13 | interface ClientProvidersProps {
14 | children: ReactNode;
15 | session: any; // Use the appropriate type for session from next-auth
16 | }
17 |
18 | export default function ClientProviders({
19 | children,
20 | session,
21 | }: ClientProvidersProps) {
22 | return (
23 |
24 |
25 | {children}
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/demo/with-next/components/SessionProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { SessionProvider } from 'next-auth/react';
3 | export default SessionProvider;
4 |
--------------------------------------------------------------------------------
/demo/with-next/components/config.ts:
--------------------------------------------------------------------------------
1 | import { createConfig, http } from '@wagmi/core';
2 | import { mainnet, optimism, sepolia } from '@wagmi/core/chains';
3 |
4 | export const config = createConfig({
5 | chains: [mainnet, sepolia, optimism],
6 | transports: {
7 | [mainnet.id]: http(),
8 | [sepolia.id]: http(),
9 | [optimism.id]: http(),
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/demo/with-next/contracts/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiler files
2 | cache/
3 | out/
4 |
5 | # Ignores development broadcast logs
6 | !/broadcast
7 | /broadcast/*/31337/
8 | /broadcast/**/dry-run/
9 |
10 | # Docs
11 | docs/
12 |
13 | # Dotenv file
14 | .env
15 |
--------------------------------------------------------------------------------
/demo/with-next/contracts/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = "src"
3 | out = "out"
4 | libs = ["lib"]
5 |
6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
7 |
--------------------------------------------------------------------------------
/demo/with-next/contracts/src/ByteHasher.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.10;
3 |
4 | library ByteHasher {
5 | /// @dev Creates a keccak256 hash of a bytestring.
6 | /// @param value The bytestring to hash
7 | /// @return The hash of the specified value
8 | /// @dev `>> 8` makes sure that the result is included in our field
9 | function hashToField(bytes memory value) internal pure returns (uint256) {
10 | return uint256(keccak256(abi.encodePacked(value))) >> 8;
11 | }
12 | }
--------------------------------------------------------------------------------
/demo/with-next/contracts/src/IWorldID.sol:
--------------------------------------------------------------------------------
1 | //SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.10;
3 |
4 | interface IWorldID {
5 | /// @notice Reverts if the zero-knowledge proof is invalid.
6 | /// @param root The of the Merkle tree
7 | /// @param groupId The id of the Semaphore group
8 | /// @param signalHash A keccak256 hash of the Semaphore signal
9 | /// @param nullifierHash The nullifier hash
10 | /// @param externalNullifierHash A keccak256 hash of the external nullifier
11 | /// @param proof The zero-knowledge proof
12 | /// @dev Note that a double-signaling check is not included here, and should be carried by the caller.
13 | function verifyProof(
14 | uint256 root,
15 | uint256 groupId,
16 | uint256 signalHash,
17 | uint256 nullifierHash,
18 | uint256 externalNullifierHash,
19 | uint256[8] calldata proof
20 | ) external view;
21 | }
--------------------------------------------------------------------------------
/demo/with-next/contracts/src/TestVerify.sol:
--------------------------------------------------------------------------------
1 |
2 | // SPDX-License-Identifier: MIT
3 | pragma solidity ^0.8.13;
4 |
5 | import { ByteHasher } from './ByteHasher.sol';
6 | import { IWorldID } from './IWorldID.sol';
7 |
8 | contract TestVerify {
9 | using ByteHasher for bytes;
10 |
11 | /// @dev The World ID instance that will be used for verifying proofs
12 | IWorldID internal immutable worldId;
13 |
14 | /// @dev The contract's external nullifier hash
15 | uint256 internal immutable externalNullifier;
16 |
17 | /// @dev The World ID group ID (always 1)
18 | uint256 internal immutable groupId = 1;
19 |
20 | /// @param nullifierHash The nullifier hash for the verified proof
21 | /// @dev A placeholder event that is emitted when a user successfully verifies with World ID
22 | event Verified(uint256 nullifierHash);
23 |
24 | /// @param _worldId The WorldID router that will verify the proofs
25 | /// @param _appId The World ID app ID
26 | /// @param _actionId The World ID action ID
27 | constructor(IWorldID _worldId, string memory _appId, string memory _actionId) {
28 | worldId = _worldId;
29 | externalNullifier = abi.encodePacked(abi.encodePacked(_appId).hashToField(), _actionId).hashToField();
30 | }
31 |
32 | /// @param signal An arbitrary input from the user, usually the user's wallet address (check README for further details)
33 | /// @param root The root of the Merkle tree
34 | /// @param nullifierHash The nullifier hash for this proof, preventing double signaling
35 | /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID
36 | function verify(address signal, uint256 root, uint256 nullifierHash, uint256[8] calldata proof) public {
37 |
38 | // We now verify the provided proof is valid and the user is verified by World ID
39 | worldId.verifyProof(
40 | root,
41 | groupId,
42 | abi.encodePacked(signal).hashToField(),
43 | nullifierHash,
44 | externalNullifier,
45 | proof
46 | );
47 |
48 | emit Verified(nullifierHash);
49 | }
50 | }
--------------------------------------------------------------------------------
/demo/with-next/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | webkit?: {
3 | messageHandlers?: {
4 | minikit?: {
5 | postMessage?: (payload: Record) => void;
6 | };
7 | };
8 | };
9 | __stopAllMiniAppMicrophoneStreams?: () => void;
10 | Android?: {
11 | postMessage?: (payload: string) => void;
12 | };
13 |
14 | MiniKit?: import('@worldcoin/minikit-js').MiniKit;
15 |
16 | WorldApp?: {
17 | world_app_version: number;
18 | device_os: 'ios' | 'android';
19 |
20 | supported_commands: Array<{
21 | name: import('@worldcoin/minikit-js').Command;
22 | supported_versions: Array;
23 | }>;
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/demo/with-next/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | domains: ['static.usernames.app-backend.toolsforhumanity.com'],
5 | },
6 | };
7 |
8 | export default nextConfig;
9 |
--------------------------------------------------------------------------------
/demo/with-next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "minikit-monorepo-demo-with-next",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "type-check": "tsc"
11 | },
12 | "dependencies": {
13 | "@safe-global/protocol-kit": "^5.0.3",
14 | "@uniswap/permit2-sdk": "^1.3.0",
15 | "@wagmi/core": "^2.16.3",
16 | "@worldcoin/minikit-js": "workspace:*",
17 | "@worldcoin/minikit-react": "workspace:*",
18 | "clsx": "^2.1.1",
19 | "eruda": "^3.4.1",
20 | "next": "15.2.3",
21 | "next-auth": "^5.0.0-beta.25",
22 | "permit2-sdk": "link:@uniswap/v3-sdk/permit2-sdk",
23 | "prettier-plugin-sort-imports-desc": "^1.0.0",
24 | "react-dom": "^19.0.0",
25 | "viem": "2.23.5",
26 | "yup": "^1.4.0"
27 | },
28 | "devDependencies": {
29 | "@types/node": "^20",
30 | "@types/react": "^19.0.0",
31 | "@types/react-dom": "^19.0.0",
32 | "eslint": "^8",
33 | "eslint-config-next": "15.2.3",
34 | "postcss": "^8",
35 | "react": "^19.0.0",
36 | "tailwindcss": "^3.4.1",
37 | "typescript": "^5"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/demo/with-next/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/demo/with-next/public/800.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/demo/with-next/public/800.jpeg
--------------------------------------------------------------------------------
/demo/with-next/public/dummy.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/demo/with-next/public/dummy.pdf
--------------------------------------------------------------------------------
/demo/with-next/public/marble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/demo/with-next/public/marble.png
--------------------------------------------------------------------------------
/demo/with-next/public/money.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/demo/with-next/public/money.mp3
--------------------------------------------------------------------------------
/demo/with-next/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/with-next/public/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/demo/with-next/public/test.png
--------------------------------------------------------------------------------
/demo/with-next/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/with-next/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/demo/with-next/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["node", "next"],
5 | "moduleResolution": "bundler",
6 | "module": "ESNext",
7 | "typeRoots": ["node_modules/@types"],
8 | "noEmit": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "jsx": "preserve",
13 | "rootDir": ".",
14 | "baseUrl": ".",
15 | "paths": {
16 | "@/*": ["./*"]
17 | },
18 | "allowJs": true,
19 | "incremental": true,
20 | "plugins": [
21 | {
22 | "name": "next"
23 | }
24 | ]
25 | },
26 | "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules", ".next", ".turbo"]
28 | }
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "version": "1.9.0",
4 | "name": "minikit-monorepo",
5 | "scripts": {
6 | "dev": "turbo run dev",
7 | "lint": "prettier --check .",
8 | "format": "prettier --write .",
9 | "build": "turbo run build",
10 | "prepublishOnly": "npm run build",
11 | "type-check": "pnpm --recursive type-check",
12 | "release": "turbo build && changeset publish"
13 | },
14 | "devDependencies": {
15 | "@changesets/cli": "^2.29.2",
16 | "prettier-plugin-organize-imports": "^4.1.0",
17 | "turbo": "^2.3.3"
18 | },
19 | "engines": {
20 | "node": ">=16"
21 | },
22 | "pnpm": {
23 | "overrides": {
24 | "ws": ">=7.5.10",
25 | "rollup": ">=4.22.4"
26 | }
27 | },
28 | "packageManager": "pnpm@9.9.0"
29 | }
30 |
--------------------------------------------------------------------------------
/packages/core/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | node: true,
5 | es6: true,
6 | },
7 | ignorePatterns: [
8 | '.eslintrc.cjs',
9 | 'postcss.config.cjs',
10 | 'tsup.config.ts',
11 | '/build',
12 | ],
13 | extends: [
14 | 'eslint:recommended',
15 | 'plugin:@typescript-eslint/recommended',
16 | 'plugin:@typescript-eslint/recommended-requiring-type-checking',
17 | 'plugin:react/recommended',
18 | 'plugin:react/jsx-runtime',
19 | 'plugin:jsx-a11y/recommended',
20 | 'plugin:react-hooks/recommended',
21 | 'prettier',
22 | ],
23 | parser: '@typescript-eslint/parser',
24 | plugins: ['@typescript-eslint', 'import', 'prettier'],
25 | parserOptions: {
26 | ecmaVersion: 2019,
27 | sourceType: 'module',
28 | project: ['./tsconfig.json'],
29 | tsconfigRootDir: __dirname,
30 | },
31 | rules: {
32 | 'import/prefer-default-export': 'off',
33 | 'import/no-default-export': 'off',
34 | 'no-empty': ['error', { allowEmptyCatch: true }],
35 |
36 | '@typescript-eslint/no-unused-vars': [
37 | 'warn',
38 | {
39 | vars: 'all',
40 | args: 'after-used',
41 | ignoreRestSiblings: true,
42 | argsIgnorePattern: '^_',
43 | destructuredArrayIgnorePattern: '^_',
44 | },
45 | ],
46 | '@typescript-eslint/no-misused-promises': 'warn',
47 |
48 | // not included into recommended set rules
49 | '@typescript-eslint/restrict-template-expressions': 'off',
50 | '@typescript-eslint/consistent-type-imports': [
51 | 'warn',
52 | { prefer: 'type-imports' },
53 | ],
54 | '@typescript-eslint/require-await': 'off',
55 | 'sort-imports': 'off', // we use TypeScripts' organize imports feature
56 | '@typescript-eslint/no-var-requires': 'error',
57 | '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
58 | '@typescript-eslint/no-unnecessary-condition': 'warn',
59 | '@typescript-eslint/non-nullable-type-assertion-style': 'warn',
60 | '@typescript-eslint/prefer-for-of': 'error',
61 | '@typescript-eslint/prefer-includes': 'error',
62 | '@typescript-eslint/prefer-optional-chain': 'error',
63 | '@typescript-eslint/prefer-nullish-coalescing': 'error',
64 | '@typescript-eslint/prefer-reduce-type-parameter': 'error',
65 | '@typescript-eslint/prefer-string-starts-ends-with': 'error',
66 | '@typescript-eslint/promise-function-async': [
67 | 'error',
68 | { checkArrowFunctions: false },
69 | ],
70 | '@typescript-eslint/sort-type-constituents': 'warn',
71 |
72 | 'jsx-a11y/media-has-caption': 0,
73 | },
74 |
75 | settings: {
76 | react: {
77 | version: 'detect',
78 | },
79 |
80 | polyfills: ['fetch'],
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # minikit-js
2 |
3 | ## 🚀 Getting Started
4 |
5 | MiniKit is currently available on npm. To install, run:
6 | `pnpm i @worldcoin/minikit-js`
7 |
8 | or use the CDN:
9 | `https://cdn.jsdelivr.net/npm/@worldcoin/minikit-js@[version]/+esm`
10 |
11 | For comprehensive setup instructions and usage examples, visit our [developer documentation](https://docs.world.org/mini-apps).
12 |
13 | ## 🛠 ️Developing Locally
14 |
15 | To run the example mini app locally:
16 |
17 | ```
18 | pnpm i
19 | cd demo/with-next
20 | pnpm dev
21 | ```
22 |
23 | This will launch a demo mini app with all essential commands implemented, allowing you to explore and test the features.
24 |
25 | ## 📦 Installation
26 |
27 | To quick start with a template, run:
28 | `npx @worldcoin/create-mini-app my-first-mini-app`
29 |
30 | This will create a new directory called `my-first-mini-app` with a basic template setup.
31 |
32 | Take a look at the in the template for more information.
33 |
--------------------------------------------------------------------------------
/packages/core/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | webkit?: {
3 | messageHandlers?: {
4 | minikit?: {
5 | postMessage?: (payload: Record) => void;
6 | };
7 | };
8 | };
9 |
10 | Android?: {
11 | postMessage?: (payload: string) => void;
12 | };
13 |
14 | MiniKit?: import('./minikit').MiniKit;
15 | __stopAllMiniAppMicrophoneStreams?: () => void;
16 |
17 | WorldApp?: {
18 | world_app_version: number;
19 | device_os: 'ios' | 'android';
20 | is_optional_analytics: boolean;
21 | supported_commands: Array<{
22 | name: import('./types/commands').Command;
23 | supported_versions: Array;
24 | }>;
25 | safe_area_insets: {
26 | top: number;
27 | right: number;
28 | bottom: number;
29 | left: number;
30 | };
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/packages/core/helpers/address-book/index.ts:
--------------------------------------------------------------------------------
1 | import { createPublicClient, http } from 'viem';
2 | import { worldchain } from 'viem/chains';
3 |
4 | const worldIdAddressBookContractAddress =
5 | '0x57b930D551e677CC36e2fA036Ae2fe8FdaE0330D';
6 | const addressVerifiedUntilAbi = [
7 | {
8 | inputs: [
9 | {
10 | internalType: 'address',
11 | name: '',
12 | type: 'address',
13 | },
14 | ],
15 | name: 'addressVerifiedUntil',
16 | outputs: [
17 | {
18 | internalType: 'uint256',
19 | name: '',
20 | type: 'uint256',
21 | },
22 | ],
23 | stateMutability: 'view',
24 | type: 'function',
25 | },
26 | ];
27 |
28 | export const getIsUserVerified = async (
29 | walletAddress: string,
30 | rpcUrl?: string,
31 | ): Promise => {
32 | const publicClient = createPublicClient({
33 | chain: worldchain,
34 | transport: http(
35 | rpcUrl || 'https://worldchain-mainnet.g.alchemy.com/public',
36 | ),
37 | });
38 |
39 | try {
40 | const verifiedUntilResponse = (await publicClient.readContract({
41 | address: worldIdAddressBookContractAddress,
42 | abi: addressVerifiedUntilAbi,
43 | functionName: 'addressVerifiedUntil',
44 | args: [walletAddress],
45 | })) as BigInt;
46 |
47 | const verifiedUntil = Number(verifiedUntilResponse.toString());
48 |
49 | if (!Number.isFinite(verifiedUntil)) {
50 | console.warn('Invalid verifiedUntil value:', verifiedUntil);
51 | return false;
52 | }
53 |
54 | const currentTime = Math.floor(Date.now() / 1000);
55 | return verifiedUntil > currentTime;
56 | } catch (error) {
57 | console.error('Error verifying user:', error);
58 | return false;
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/packages/core/helpers/microphone/index.ts:
--------------------------------------------------------------------------------
1 | import { sendWebviewEvent } from 'helpers/send-webview-event';
2 | import { MiniKit } from 'minikit';
3 | import { MicrophoneErrorCodes } from 'types/errors';
4 | import { ResponseEvent } from 'types/responses';
5 |
6 | let microphoneSetupDone = false;
7 |
8 | export const setupMicrophone = () => {
9 | if (microphoneSetupDone) {
10 | return;
11 | }
12 |
13 | if (typeof navigator !== 'undefined' && !navigator.mediaDevices?.getUserMedia)
14 | return;
15 |
16 | // We need to do this on iOS since ended is not fired when the track is stopped.
17 | const originalStop = MediaStreamTrack.prototype.stop;
18 | MediaStreamTrack.prototype.stop = function () {
19 | originalStop.call(this);
20 | if (this.readyState === 'ended') {
21 | setTimeout(() => this.dispatchEvent(new Event('ended')), 0);
22 | }
23 | };
24 |
25 | const realGUM = navigator.mediaDevices.getUserMedia.bind(
26 | navigator.mediaDevices,
27 | );
28 | const live = new Set();
29 |
30 | async function wrapped(constraints: MediaStreamConstraints) {
31 | const stream = await realGUM(constraints);
32 | sendWebviewEvent({
33 | command: 'microphone-stream-started',
34 | version: 1,
35 | payload: {
36 | streamId: stream.id,
37 | },
38 | });
39 | live.add(stream);
40 | stream.getTracks().forEach((t) => {
41 | t.addEventListener('ended', () => {
42 | sendWebviewEvent({
43 | command: 'microphone-stream-ended',
44 | version: 1,
45 | payload: {
46 | streamId: stream.id,
47 | },
48 | });
49 | live.delete(stream);
50 | });
51 | });
52 | return stream;
53 | }
54 |
55 | // We lock down the navigator.mediaDevices.getUserMedia property so that it cannot be overridden.
56 | Object.defineProperty(navigator.mediaDevices, 'getUserMedia', {
57 | value: wrapped,
58 | writable: false,
59 | configurable: false,
60 | enumerable: true,
61 | });
62 | Object.freeze(navigator.mediaDevices);
63 |
64 | const stopAllMiniAppMicrophoneStreams = () => {
65 | live.forEach((s: MediaStream) => {
66 | s.getTracks().forEach((t) => {
67 | t.stop();
68 | sendWebviewEvent({
69 | command: 'microphone-stream-ended',
70 | version: 1,
71 | payload: {
72 | streamId: s.id,
73 | },
74 | });
75 | });
76 | });
77 | live.clear();
78 | };
79 |
80 | MiniKit.subscribe(ResponseEvent.MiniAppMicrophone, (payload) => {
81 | // If the miniapp has requested the microphone and it has not been granted,
82 | // we stop all streams.
83 | if (
84 | payload.status === 'error' &&
85 | (payload.error_code ===
86 | MicrophoneErrorCodes.MiniAppPermissionNotEnabled ||
87 | payload.error_code ===
88 | MicrophoneErrorCodes.WorldAppPermissionNotEnabled)
89 | ) {
90 | console.log('stopping all microphone streams', payload);
91 | stopAllMiniAppMicrophoneStreams();
92 | }
93 | });
94 |
95 | window.__stopAllMiniAppMicrophoneStreams = stopAllMiniAppMicrophoneStreams;
96 | microphoneSetupDone = true;
97 | };
98 |
--------------------------------------------------------------------------------
/packages/core/helpers/payment/client.ts:
--------------------------------------------------------------------------------
1 | import { PayCommandInput } from 'types/commands';
2 | import { TokenDecimals, Tokens } from 'types/payment';
3 |
4 | // This is a helper function to convert token amount to decimals for payment
5 | // Amount should be in expected amount ie $25.12 should be 25.12
6 | export const tokenToDecimals = (amount: number, token: Tokens): number => {
7 | const decimals = TokenDecimals[token];
8 | if (decimals === undefined) {
9 | throw new Error(`Invalid token: ${token}`);
10 | }
11 | const factor = 10 ** decimals;
12 | const result = amount * factor;
13 | if (!Number.isInteger(result)) {
14 | throw new Error(`The resulting amount is not a whole number: ${result}`);
15 | }
16 | return result;
17 | };
18 |
19 | export const validatePaymentPayload = (payload: PayCommandInput): boolean => {
20 | if (
21 | payload.tokens.some(
22 | (token) => token.symbol == 'USDC' && parseFloat(token.token_amount) < 0.1,
23 | )
24 | ) {
25 | console.error('USDC amount should be greater than $0.1');
26 | return false; // reject
27 | }
28 |
29 | if (payload.reference.length > 36) {
30 | console.error('Reference must not exceed 36 characters');
31 | return false;
32 | }
33 |
34 | if (typeof payload.reference !== 'string') {
35 | throw new Error('Reference must be a string');
36 | }
37 |
38 | return true; // accept
39 | };
40 |
--------------------------------------------------------------------------------
/packages/core/helpers/proof/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createPublicClient,
3 | decodeAbiParameters,
4 | encodeAbiParameters,
5 | http,
6 | } from 'viem';
7 | import { worldchain } from 'viem/chains';
8 |
9 | const semaphoreVerifierAddress = '0x79f46b94d134109EbcbbddBAeD0E88790409A0e4';
10 | const semaphoreVerifierAbi = [
11 | {
12 | inputs: [
13 | {
14 | internalType: 'uint256[8]',
15 | name: 'proof',
16 | type: 'uint256[8]',
17 | },
18 | ],
19 | name: 'compressProof',
20 | outputs: [
21 | {
22 | internalType: 'uint256[4]',
23 | name: 'compressed',
24 | type: 'uint256[4]',
25 | },
26 | ],
27 | stateMutability: 'view',
28 | type: 'function',
29 | },
30 | ];
31 |
32 | export const compressAndPadProof = async (
33 | proof: `0x${string}`,
34 | rpcUrl?: string,
35 | ): Promise<`0x${string}`> => {
36 | try {
37 | const publicClient = createPublicClient({
38 | chain: worldchain,
39 | transport: http(
40 | rpcUrl || 'https://worldchain-mainnet.g.alchemy.com/public',
41 | ),
42 | });
43 |
44 | const decodedProof = decodeAbiParameters(
45 | [{ type: 'uint256[8]' }],
46 | proof,
47 | )[0] as readonly [
48 | bigint,
49 | bigint,
50 | bigint,
51 | bigint,
52 | bigint,
53 | bigint,
54 | bigint,
55 | bigint,
56 | ];
57 |
58 | const compressedProof = (await publicClient.readContract({
59 | address: semaphoreVerifierAddress,
60 | abi: semaphoreVerifierAbi,
61 | functionName: 'compressProof',
62 | args: [decodedProof],
63 | })) as [bigint, bigint, bigint, bigint];
64 |
65 | const paddedProof: [
66 | bigint,
67 | bigint,
68 | bigint,
69 | bigint,
70 | bigint,
71 | bigint,
72 | bigint,
73 | bigint,
74 | ] = [...compressedProof, 0n, 0n, 0n, 0n];
75 |
76 | return encodeAbiParameters([{ type: 'uint256[8]' }], [paddedProof]);
77 | } catch (e) {
78 | return proof;
79 | }
80 | };
81 |
--------------------------------------------------------------------------------
/packages/core/helpers/send-webview-event.ts:
--------------------------------------------------------------------------------
1 | export const sendWebviewEvent = <
2 | T extends Record = Record,
3 | >(
4 | payload: T,
5 | ) => {
6 | if (window.webkit) {
7 | window.webkit?.messageHandlers?.minikit?.postMessage?.(payload);
8 | } else if (window.Android) {
9 | window.Android.postMessage?.(JSON.stringify(payload));
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/packages/core/helpers/share/index.ts:
--------------------------------------------------------------------------------
1 | import { ShareInput, SharePayload } from '../../types/commands';
2 |
3 | const MAX_FILES = 10;
4 | const MAX_TOTAL_SIZE_MB = 50;
5 | const MAX_TOTAL_SIZE_BYTES = MAX_TOTAL_SIZE_MB * 1024 * 1024;
6 |
7 | // Helper function to process a single file to base64
8 | const processFile = async (
9 | file: File,
10 | ): Promise<{ name: string; type: string; data: string }> => {
11 | const buffer = await file.arrayBuffer();
12 | const uint8Array = new Uint8Array(buffer);
13 | let binaryString = '';
14 | const K_CHUNK_SIZE = 0x8000; // 32K chunks
15 |
16 | for (let i = 0; i < uint8Array.length; i += K_CHUNK_SIZE) {
17 | const chunk = uint8Array.subarray(
18 | i,
19 | Math.min(i + K_CHUNK_SIZE, uint8Array.length),
20 | );
21 | binaryString += String.fromCharCode.apply(
22 | null,
23 | Array.from(chunk), // Convert Uint8Array chunk to number[]
24 | );
25 | }
26 |
27 | const base64Data = btoa(binaryString);
28 | return {
29 | name: file.name,
30 | type: file.type,
31 | data: base64Data,
32 | };
33 | };
34 |
35 | export const formatShareInput = async (
36 | input: ShareInput,
37 | ): Promise => {
38 | if (!input.files) {
39 | return {
40 | title: input.title,
41 | text: input.text,
42 | url: input.url,
43 | };
44 | }
45 |
46 | // Ensure input.files is an array if it's truthy
47 | if (!Array.isArray(input.files)) {
48 | throw new Error('The "files" property must be an array.');
49 | }
50 |
51 | if (input.files.length === 0) {
52 | // Handle case with no files, if navigator.share allows title/text/url sharing without files
53 | // Or throw an error if files are always required by your use case
54 | // For now, proceed assuming title/text/url can be shared alone
55 | } else {
56 | if (input.files.length > MAX_FILES) {
57 | throw new Error(`Cannot share more than ${MAX_FILES} files.`);
58 | }
59 |
60 | let totalSize = 0;
61 | for (const file of input.files) {
62 | // Ensure each item in the 'files' array is a File object
63 | if (!(file instanceof File)) {
64 | throw new Error(
65 | `Each item in the 'files' array must be a File object. Received: ${typeof file}`,
66 | );
67 | }
68 | totalSize += file.size; // File.size is in bytes
69 | }
70 |
71 | if (totalSize > MAX_TOTAL_SIZE_BYTES) {
72 | throw new Error(`Total file size cannot exceed ${MAX_TOTAL_SIZE_MB}MB.`);
73 | }
74 | }
75 |
76 | const fileProcessingPromises = input.files.map((file) => processFile(file));
77 | const processedFiles = await Promise.all(fileProcessingPromises);
78 |
79 | return {
80 | files: processedFiles,
81 | title: input.title,
82 | text: input.text,
83 | url: input.url,
84 | };
85 | };
86 |
--------------------------------------------------------------------------------
/packages/core/helpers/siwe/validate-wallet-auth-command-input.ts:
--------------------------------------------------------------------------------
1 | import { WalletAuthInput } from 'types/commands';
2 |
3 | type ValidationResult =
4 | | {
5 | valid: true;
6 | }
7 | | {
8 | valid: false;
9 | message: string;
10 | };
11 |
12 | export const validateWalletAuthCommandInput = (
13 | params: WalletAuthInput,
14 | ): ValidationResult => {
15 | if (!params.nonce) {
16 | return { valid: false, message: "'nonce' is required" };
17 | }
18 |
19 | if (params.nonce.length < 8) {
20 | return { valid: false, message: "'nonce' must be at least 8 characters" };
21 | }
22 |
23 | if (params.statement && params.statement.includes('\n')) {
24 | return { valid: false, message: "'statement' must not contain newlines" };
25 | }
26 |
27 | if (params.expirationTime && new Date(params.expirationTime) < new Date()) {
28 | return { valid: false, message: "'expirationTime' must be in the future" };
29 | }
30 |
31 | if (
32 | params.expirationTime &&
33 | new Date(params.expirationTime) >
34 | new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
35 | ) {
36 | return { valid: false, message: "'expirationTime' must be within 7 days" };
37 | }
38 |
39 | if (
40 | params.notBefore &&
41 | new Date(params.notBefore) > new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
42 | ) {
43 | return { valid: false, message: "'notBefore' must be within 7 days" };
44 | }
45 |
46 | return { valid: true };
47 | };
48 |
--------------------------------------------------------------------------------
/packages/core/helpers/transaction/validate-payload.ts:
--------------------------------------------------------------------------------
1 | import { SendTransactionInput } from 'types/commands';
2 |
3 | const isValidHex = (str: string): boolean => {
4 | return /^0x[0-9A-Fa-f]+$/.test(str);
5 | };
6 |
7 | /**
8 | * Recursively converts an object into an array of its values,
9 | * preserving the nested structure but discarding keys.
10 | * Arrays within the structure are processed element by element.
11 | *
12 | * @param input The object, array, or primitive value to convert.
13 | * @returns An array representing the values of the object in their original structure,
14 | * or the original value if it's not a plain object or array.
15 | */
16 | export const objectValuesToArrayRecursive = (input: any): any => {
17 | if (input === null || typeof input !== 'object') {
18 | return input;
19 | }
20 |
21 | if (Array.isArray(input)) {
22 | return input.map((item) => objectValuesToArrayRecursive(item));
23 | }
24 |
25 | const values = Object.values(input);
26 | return values.map((value) => objectValuesToArrayRecursive(value));
27 | };
28 |
29 | /**
30 | * Recursively processes a payload to ensure all values are valid for transaction processing.
31 | * Handles primitives, numbers, arrays, and objects.
32 | *
33 | * @param payload The payload to process.
34 | * @returns A processed payload with all values validated.
35 | */
36 | const processPayload = (payload: T): T => {
37 | // Handle primitives directly
38 | if (
39 | typeof payload === 'boolean' ||
40 | typeof payload === 'string' ||
41 | payload === null ||
42 | payload === undefined
43 | ) {
44 | return payload;
45 | }
46 |
47 | // Convert numbers to strings to prevent overflow issues
48 | if (typeof payload === 'number' || typeof payload === 'bigint') {
49 | return String(payload) as unknown as T;
50 | }
51 |
52 | // Handle arrays by processing each element
53 | if (Array.isArray(payload)) {
54 | return payload.map((value) => processPayload(value)) as unknown as T;
55 | }
56 |
57 | // Handle objects
58 | if (typeof payload === 'object') {
59 | const result = { ...payload } as any;
60 |
61 | // Special handling for transaction value fields
62 | if ('value' in result && result.value !== undefined) {
63 | // For transaction value, we need to ensure it's a valid hex string
64 | if (typeof result.value !== 'string') {
65 | result.value = String(result.value);
66 | }
67 |
68 | if (!isValidHex(result.value)) {
69 | console.error(
70 | 'Transaction value must be a valid hex string',
71 | result.value,
72 | );
73 | throw new Error(
74 | `Transaction value must be a valid hex string: ${result.value}`,
75 | );
76 | }
77 | }
78 |
79 | // Process all object properties recursively
80 | for (const key in result) {
81 | if (Object.prototype.hasOwnProperty.call(result, key)) {
82 | result[key] = processPayload(result[key]);
83 | }
84 | }
85 |
86 | return result;
87 | }
88 |
89 | // Fallback for any other types
90 | return payload;
91 | };
92 |
93 | export const validateSendTransactionPayload = (
94 | payload: SendTransactionInput,
95 | ): SendTransactionInput => {
96 | if (payload.formatPayload) {
97 | const formattedPayload = processPayload(payload);
98 | formattedPayload.transaction = formattedPayload.transaction.map((tx) => {
99 | const args = objectValuesToArrayRecursive(tx.args);
100 | return {
101 | ...tx,
102 | args,
103 | };
104 | });
105 | return formattedPayload;
106 | }
107 |
108 | return payload;
109 | };
110 |
--------------------------------------------------------------------------------
/packages/core/helpers/usernames/index.ts:
--------------------------------------------------------------------------------
1 | export const getUserProfile = async (address: string) => {
2 | const res = await fetch('https://usernames.worldcoin.org/api/v1/query', {
3 | method: 'POST',
4 | headers: {
5 | 'Content-Type': 'application/json',
6 | },
7 | body: JSON.stringify({
8 | addresses: [address],
9 | }),
10 | });
11 |
12 | const usernames = await res.json();
13 | return usernames?.[0] ?? { username: null, profile_picture_url: null };
14 | };
15 |
--------------------------------------------------------------------------------
/packages/core/index.ts:
--------------------------------------------------------------------------------
1 | export { MiniKit } from './minikit';
2 |
3 | export * from './types/commands';
4 | export * from './types/errors';
5 | export * from './types/init';
6 | export * from './types/payment';
7 | export * from './types/responses';
8 | export * from './types/wallet-auth';
9 |
10 | export { tokenToDecimals } from 'helpers/payment/client';
11 |
12 | export { VerificationLevel, type ISuccessResult } from '@worldcoin/idkit-core';
13 | export {
14 | verifyCloudProof,
15 | type IVerifyResponse,
16 | } from '@worldcoin/idkit-core/backend';
17 |
18 | export { parseSiweMessage, verifySiweMessage } from 'helpers/siwe/siwe';
19 |
20 | export { getIsUserVerified } from 'helpers/address-book';
21 |
--------------------------------------------------------------------------------
/packages/core/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'jest';
2 |
3 | const config: Config = {
4 | preset: 'ts-jest',
5 | moduleDirectories: ['node_modules', ''],
6 | modulePathIgnorePatterns: ['/deploy/cdk.out'],
7 | testMatch: ['**/*.test.ts'],
8 | testPathIgnorePatterns: ['node_modules'],
9 | collectCoverageFrom: ['**/*.(t|j)s'],
10 | reporters: ['default'],
11 | };
12 |
13 | export default config;
14 |
--------------------------------------------------------------------------------
/packages/core/minikit-provider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | createContext,
5 | ReactNode,
6 | useContext,
7 | useEffect,
8 | useState,
9 | } from 'react';
10 | import { MiniKit } from './minikit';
11 |
12 | type MiniKitProps = {
13 | appId: string;
14 | };
15 |
16 | const MiniKitContext = createContext<{ isInstalled: boolean }>({
17 | isInstalled: false,
18 | });
19 |
20 | export const MiniKitProvider = ({
21 | children,
22 | props,
23 | }: {
24 | children: ReactNode;
25 | props?: MiniKitProps;
26 | }) => {
27 | const [isInstalled, setIsInstalled] = useState(false);
28 |
29 | useEffect(() => {
30 | MiniKit.install(props?.appId);
31 | MiniKit.commandsAsync
32 | .getPermissions()
33 | .then(({ commandPayload: _, finalPayload }) => {
34 | if (finalPayload.status === 'success') {
35 | MiniKit.user.permissions = {
36 | notifications: finalPayload.permissions.notifications,
37 | contacts: finalPayload.permissions.contacts,
38 | };
39 | }
40 | });
41 | setIsInstalled(true);
42 | }, [props?.appId]);
43 |
44 | return (
45 |
46 | {children}
47 |
48 | );
49 | };
50 |
51 | // Custom hook to see when minikit is installed
52 | export const useMiniKit = () => {
53 | const context = useContext(MiniKitContext);
54 | if (context === undefined) {
55 | throw new Error('useMiniKit must be used within a MiniKitProvider');
56 | }
57 | return context;
58 | };
59 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@worldcoin/idkit-core": "^2.0.2",
4 | "abitype": "^1.0.6"
5 | },
6 | "description": "minikit-js is our SDK for building mini-apps.",
7 | "devDependencies": {
8 | "@types/jest": "^29.5.14",
9 | "@types/node": "^20",
10 | "@types/react": "^17.0.0",
11 | "@typescript-eslint/eslint-plugin": "^7.7.0",
12 | "@typescript-eslint/parser": "^7.7.0",
13 | "jest": "^29.7.0",
14 | "prettier": "^3.2.5",
15 | "prettier-plugin-sort-imports-desc": "^1.0.0",
16 | "react": "^17.0.0",
17 | "siwe": "^3.0.0",
18 | "ts-jest": "^29.2.5",
19 | "ts-node": "^10.9.2",
20 | "tsup": "^8.0.2",
21 | "ethers": "^6.13.5",
22 | "typescript": "^5.4.5",
23 | "viem": "2.23.5"
24 | },
25 | "engines": {
26 | "node": ">= 16"
27 | },
28 | "exports": {
29 | ".": {
30 | "import": {
31 | "types": "./build/index.d.ts",
32 | "default": "./build/index.js"
33 | },
34 | "require": {
35 | "types": "./build/index.d.cts",
36 | "default": "./build/index.cjs"
37 | }
38 | },
39 | "./minikit-provider": {
40 | "import": {
41 | "types": "./build/minikit-provider.d.ts",
42 | "default": "./build/minikit-provider.js"
43 | },
44 | "require": {
45 | "types": "./build/minikit-provider.d.cts",
46 | "default": "./build/minikit-provider.cjs"
47 | }
48 | }
49 | },
50 | "files": [
51 | "./build/**",
52 | "README.md"
53 | ],
54 | "homepage": "https://docs.worldcoin.org/mini-apps",
55 | "keywords": [
56 | "minikit",
57 | "miniapps"
58 | ],
59 | "license": "MIT",
60 | "main": "index.ts",
61 | "name": "@worldcoin/minikit-js",
62 | "peerDependencies": {
63 | "react": "^17 || ^18 || ^19",
64 | "viem": "^2.23.5"
65 | },
66 | "private": false,
67 | "scripts": {
68 | "build": "tsup",
69 | "dev": "tsup --watch",
70 | "lint": "prettier --check .",
71 | "prepublishOnly": "npm run build",
72 | "test": "jest",
73 | "type-check": "tsc --noEmit"
74 | },
75 | "type": "module",
76 | "types": "index.ts",
77 | "typesVersions": {
78 | "*": {
79 | "*": [
80 | "./build/*/index.d.ts",
81 | "./build/index.d.ts"
82 | ]
83 | }
84 | },
85 | "version": "1.9.5"
86 | }
87 |
--------------------------------------------------------------------------------
/packages/core/tests/siwe.test.ts:
--------------------------------------------------------------------------------
1 | import { JsonRpcProvider } from 'ethers';
2 | import { parseSiweMessage, verifySiweMessage } from 'helpers/siwe/siwe';
3 | import { SiweMessage } from 'siwe';
4 | import { MiniAppWalletAuthSuccessPayload } from 'types/responses';
5 | import { createPublicClient, http } from 'viem';
6 | import { worldchain } from 'viem/chains';
7 |
8 | const siweMessage = `https://test.com wants you to sign in with your Ethereum account:\n\
9 | {{address}}\n\n\
10 | statement\n\n\
11 | URI: https://test.com\n\
12 | Version: 1\n\
13 | Chain ID: 10\n\
14 | Nonce: 12345678\n\
15 | Issued At: ${new Date().toISOString()}\n\
16 | Expiration Time: ${new Date(
17 | new Date().getTime() + 1000 * 60 * 60 * 24 * 7,
18 | ).toISOString()}\n\
19 | Not Before: ${new Date(
20 | new Date().getTime() - 1000 * 60 * 60 * 24 * 7,
21 | ).toISOString()}\n\
22 | Request ID: 0`;
23 |
24 | const incompleteSiweMessage = `https://test.com wants you to sign in with your Ethereum account:\n\
25 | {{address}}\n\n\n\
26 | URI: https://test.com\n\
27 | Version: 1\n\
28 | Chain ID: 10\n\
29 | Nonce: 12345678\n\
30 | Issued At: ${new Date().toISOString()}\n\
31 | Expiration Time: 2024-05-03T00:00:00Z\n\
32 | Request ID: 0`;
33 |
34 | const invalidSiweMessage = `https://test.com wants you to sign in with your Ethereum account:\n\
35 | {{address}}\n\n\n\
36 | URI: https://test.com\n\
37 | Version: 1\n\
38 | Chain ID: 10\n\
39 | Issued At: ${new Date().toISOString()}\n\
40 | Expiration Time: 2024-05-03T00:00:00Z\n\
41 | Request ID: 0`;
42 |
43 | const signedMessagePayload = `test.com wants you to sign in with your Ethereum account:\n0x619525ED4E862B62cFEDACCc4dA5a9864D6f4A97\n\nstatement\n\nURI: https://test.com\nVersion: 1\nChain ID: 480\nNonce: 12345678\nIssued At: 2025-04-09T17:55:41Z\nExpiration Time: 2027-03-10T17:55:41Z\nNot Before: 2025-04-09T17:55:41Z\nRequest ID: 0`;
44 |
45 | const signatureSiweMessage = (
46 | issuedAt = new Date(),
47 | expirationDays = 7,
48 | notBeforeDays = -1,
49 | ) =>
50 | `http://localhost:3000 wants you to sign in with your Ethereum account:\n0xd809de3086ea4f53ed3979cead25e1ff72b564a3\n\n\nURI: http://localhost:3000/\nVersion: 1\nChain ID: 10\nNonce: 814434bd-ed2c-412e-aa2c-c4b266a42027\nIssued At: ${issuedAt.toISOString()}\nExpiration Time: ${new Date(issuedAt.getTime() + 1000 * 60 * 60 * 24 * expirationDays).toISOString()}\nNot Before: ${new Date(issuedAt.getTime() + 1000 * 60 * 60 * 24 * notBeforeDays).toISOString()}\nRequest ID: 0\n`;
51 |
52 | const signature =
53 | '0x4daac02daec8852202bba0694da942b1f4e20d1795cbb1c6740a71ee4660f1d77c4fd7fabfd4416d7e987030d41841c575a363a95e496a3264d282863ce5dc4d1b';
54 |
55 | describe('Test SIWE Message Parsing', () => {
56 | test('Correctly parses full SIWE message', () => {
57 | parseSiweMessage(siweMessage);
58 | });
59 |
60 | test('Correctly parses incomplete SIWE message', () => {
61 | parseSiweMessage(incompleteSiweMessage);
62 | });
63 |
64 | test('Correctly rejects missing required values', () => {
65 | console.log(invalidSiweMessage);
66 | expect(() => parseSiweMessage(invalidSiweMessage)).toThrow(
67 | "Missing 'Nonce: '",
68 | );
69 | });
70 | });
71 |
72 | describe('Test SIWE Message Verification', () => {
73 | it('should validate SIWE v2', async () => {
74 | const result = await verifySiweMessage(
75 | {
76 | status: 'success',
77 | message: signedMessagePayload,
78 | signature: signature,
79 | address: '0x619525ED4E862B62cFEDACCc4dA5a9864D6f4A97',
80 | version: 2,
81 | },
82 | '12345678',
83 | undefined,
84 | undefined,
85 | );
86 |
87 | expect(result.isValid).toBe(true);
88 | expect(result.siweMessageData).toBeDefined();
89 |
90 | const publicClient = createPublicClient({
91 | chain: worldchain,
92 | transport: http(),
93 | });
94 | const valid = await publicClient.verifySiweMessage({
95 | message: signedMessagePayload,
96 | signature,
97 | });
98 | expect(valid).toBe(true);
99 | });
100 |
101 | it('should validate SIWE v2', async () => {
102 | const result = await verifySiweMessage(
103 | {
104 | status: 'success',
105 | message: signedMessagePayload,
106 | signature: signature,
107 | address: '0x619525ED4E862B62cFEDACCc4dA5a9864D6f4A97',
108 | version: 2,
109 | },
110 | '12345678',
111 | undefined,
112 | undefined,
113 | );
114 |
115 | expect(result.isValid).toBe(true);
116 | expect(result.siweMessageData).toBeDefined();
117 | });
118 |
119 | it('should validate SIWE using SIWE', async () => {
120 | const m = new SiweMessage(signedMessagePayload);
121 | const provider = new JsonRpcProvider(
122 | 'https://worldchain-mainnet.g.alchemy.com/public',
123 | );
124 |
125 | const isValid = await m.verify(
126 | {
127 | signature,
128 | nonce: '12345678',
129 | // @ts-ignore
130 | },
131 | // @ts-ignore
132 | { provider: provider },
133 | );
134 |
135 | expect(isValid.success).toBe(true);
136 | });
137 |
138 | test('Verify SIWE Message with invalid signature', async () => {
139 | const payload: MiniAppWalletAuthSuccessPayload = {
140 | status: 'success',
141 | message: signatureSiweMessage(new Date(), 7, -1),
142 | signature: 'random_signature',
143 | address: '0xd809de3086ea4f53ed3979cead25e1ff72b564a3',
144 | version: 1,
145 | };
146 | await expect(
147 | verifySiweMessage(payload, '814434bd-ed2c-412e-aa2c-c4b266a42027'),
148 | ).rejects.toThrow('Signature verification failed');
149 | });
150 |
151 | test('Verify SIWE Message with invalid address', async () => {
152 | const payload: MiniAppWalletAuthSuccessPayload = {
153 | status: 'success' as const,
154 | message: signatureSiweMessage(new Date(), 7, -1),
155 | signature: signature,
156 | address: '0x0000000000000000000000000000000000000000',
157 | version: 1,
158 | };
159 |
160 | await expect(
161 | verifySiweMessage(payload, '814434bd-ed2c-412e-aa2c-c4b266a42027'),
162 | ).rejects.toThrow('Signature verification failed');
163 | });
164 | });
165 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["node", "jest"],
5 | "moduleResolution": "bundler",
6 | "rootDir": ".",
7 | "baseUrl": ".",
8 | "typeRoots": ["./node_modules/@types"],
9 | "noEmit": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "skipLibCheck": true
13 | },
14 | "include": ["**/*.ts", "**/*.tsx"],
15 | "exclude": ["node_modules", "build", ".turbo", "coverage"]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig({
4 | dts: true,
5 | clean: true,
6 | outDir: 'build',
7 | format: ['esm', 'cjs'],
8 | external: ['@worldcoin/idkit-core'],
9 | entry: ['index.ts', 'minikit-provider.tsx'],
10 | define: { 'process.env.NODE_ENV': '"production"' },
11 | });
12 |
--------------------------------------------------------------------------------
/packages/core/types/commands.ts:
--------------------------------------------------------------------------------
1 | import { IDKitConfig, VerificationLevel } from '@worldcoin/idkit-core';
2 | import type { TypedData, TypedDataDomain } from 'abitype';
3 | import { MiniKitInstallErrorCodes, MiniKitInstallErrorMessage } from './errors';
4 | import { Network, Tokens } from './payment';
5 | import { Permit2, Transaction } from './transactions';
6 |
7 | export enum Command {
8 | Verify = 'verify',
9 | Pay = 'pay',
10 | WalletAuth = 'wallet-auth',
11 | SendTransaction = 'send-transaction',
12 | SignMessage = 'sign-message',
13 | SignTypedData = 'sign-typed-data',
14 | ShareContacts = 'share-contacts',
15 | RequestPermission = 'request-permission',
16 | GetPermissions = 'get-permissions',
17 | SendHapticFeedback = 'send-haptic-feedback',
18 | Share = 'share',
19 | }
20 |
21 | export type WebViewBasePayload = {
22 | command: Command;
23 | version: number;
24 | payload: Record;
25 | };
26 |
27 | export type AsyncHandlerReturn = Promise<{
28 | commandPayload: CommandPayload;
29 | finalPayload: FinalPayload;
30 | }>;
31 |
32 | // Values developers can specify
33 | export type VerifyCommandInput = {
34 | action: IDKitConfig['action'];
35 | signal?: IDKitConfig['signal'];
36 | verification_level?: VerificationLevel;
37 | };
38 |
39 | // Full list of values sent to the app
40 | export type VerifyCommandPayload = VerifyCommandInput & {
41 | timestamp: string;
42 | };
43 |
44 | export type TokensPayload = {
45 | symbol: Tokens;
46 | token_amount: string;
47 | };
48 |
49 | export type PayCommandInput = {
50 | reference: string;
51 | to: `0x${string}` | string; // Address or World Username
52 | tokens: TokensPayload[];
53 | network?: Network; // Optional
54 | description: string;
55 | };
56 |
57 | export type PayCommandPayload = PayCommandInput;
58 |
59 | export type WalletAuthInput = {
60 | nonce: string;
61 | statement?: string;
62 | requestId?: string;
63 | expirationTime?: Date;
64 | notBefore?: Date;
65 | };
66 |
67 | export type WalletAuthPayload = {
68 | siweMessage: string;
69 | };
70 |
71 | export type MiniKitInstallReturnType =
72 | | { success: true }
73 | | {
74 | success: false;
75 | errorCode: MiniKitInstallErrorCodes;
76 | errorMessage: (typeof MiniKitInstallErrorMessage)[MiniKitInstallErrorCodes];
77 | };
78 |
79 | export type SendTransactionInput = {
80 | transaction: Transaction[];
81 | permit2?: Permit2[]; // Optional
82 | formatPayload?: boolean; // Optional - If true, the payload will be formatted using objectvalues. Defaults to true if omitted.
83 | };
84 |
85 | export type SendTransactionPayload = SendTransactionInput;
86 |
87 | export type SignMessageInput = {
88 | message: string;
89 | };
90 |
91 | export type SignMessagePayload = SignMessageInput;
92 |
93 | export type SignTypedDataInput = {
94 | types: TypedData;
95 | primaryType: string;
96 | message: Record;
97 | domain?: TypedDataDomain;
98 | chainId?: number;
99 | };
100 |
101 | export type SignTypedDataPayload = SignTypedDataInput;
102 |
103 | // Anchor: Share Contacts Payload
104 | export type ShareContactsInput = {
105 | isMultiSelectEnabled: boolean;
106 | inviteMessage?: string;
107 | };
108 | export type ShareContactsPayload = ShareContactsInput;
109 |
110 | // Anchor: Request Permission Payload
111 | export enum Permission {
112 | Notifications = 'notifications',
113 | Contacts = 'contacts',
114 | Microphone = 'microphone',
115 | }
116 |
117 | export type RequestPermissionInput = {
118 | permission: Permission;
119 | };
120 |
121 | export type RequestPermissionPayload = RequestPermissionInput;
122 |
123 | // Anchor: Get Permissions Payload
124 | export type GetPermissionsInput = {};
125 |
126 | export type GetPermissionsPayload = GetPermissionsInput;
127 |
128 | // Anchor: Send Haptic Feedback Payload
129 | export type SendHapticFeedbackInput =
130 | | {
131 | hapticsType: 'notification';
132 | style: 'error' | 'success' | 'warning';
133 | }
134 | | {
135 | hapticsType: 'selection-changed';
136 | // never necessary or used but improves DX
137 | style?: never;
138 | }
139 | | {
140 | hapticsType: 'impact';
141 | style: 'light' | 'medium' | 'heavy';
142 | };
143 |
144 | export type SendHapticFeedbackPayload = SendHapticFeedbackInput;
145 |
146 | // Anchor: Share Files Payload
147 |
148 | export type ShareInput = {
149 | files?: File[];
150 | title?: string;
151 | text?: string;
152 | url?: string;
153 | };
154 |
155 | export type SharePayload = {
156 | files?: Array<{
157 | name: string;
158 | type: string; // MIME type of the file (e.g., from File.type)
159 | data: string; // Base64 encoded content of the file
160 | }>;
161 | title?: string;
162 | text?: string;
163 | url?: string;
164 | };
165 |
166 | type CommandReturnPayloadMap = {
167 | [Command.Verify]: VerifyCommandPayload;
168 | [Command.Pay]: PayCommandPayload;
169 | [Command.WalletAuth]: WalletAuthPayload;
170 | [Command.SendTransaction]: SendTransactionPayload;
171 | [Command.SignMessage]: SignMessagePayload;
172 | [Command.SignTypedData]: SignTypedDataPayload;
173 | [Command.ShareContacts]: ShareContactsPayload;
174 | [Command.RequestPermission]: RequestPermissionPayload;
175 | [Command.GetPermissions]: GetPermissionsPayload;
176 | [Command.SendHapticFeedback]: SendHapticFeedbackPayload;
177 | [Command.Share]: SharePayload;
178 | };
179 | export type CommandReturnPayload =
180 | T extends keyof CommandReturnPayloadMap ? CommandReturnPayloadMap[T] : never;
181 |
--------------------------------------------------------------------------------
/packages/core/types/init.ts:
--------------------------------------------------------------------------------
1 | export type User = {
2 | walletAddress?: string;
3 | username?: string;
4 | profilePictureUrl?: string;
5 | permissions?: {
6 | notifications: boolean;
7 | contacts: boolean;
8 | };
9 | // verificationStatus: {
10 | // orb: {
11 | // isVerified: boolean;
12 | // verifiedUntil: number;
13 | // };
14 | // device: {
15 | // isVerified: boolean;
16 | // verifiedUntil: number;
17 | // };
18 | // };
19 | optedIntoOptionalAnalytics?: boolean;
20 | /** @deprecated Moved to DeviceProperties */
21 | worldAppVersion?: number;
22 | /** @deprecated Moved to DeviceProperties */
23 | deviceOS?: string;
24 | };
25 |
26 | export type DeviceProperties = {
27 | safeAreaInsets?: {
28 | top: number;
29 | right: number;
30 | bottom: number;
31 | left: number;
32 | };
33 | deviceOS?: string;
34 | worldAppVersion?: number;
35 | };
36 |
37 | export type UserNameService = {
38 | walletAddress: string;
39 | username?: string;
40 | profilePictureUrl?: string;
41 | };
42 |
--------------------------------------------------------------------------------
/packages/core/types/payment.ts:
--------------------------------------------------------------------------------
1 | export enum Tokens {
2 | USDC = 'USDC',
3 | WLD = 'WLD',
4 | }
5 |
6 | export const TokenDecimals: { [key in Tokens]: number } = {
7 | [Tokens.USDC]: 6,
8 | [Tokens.WLD]: 18,
9 | };
10 |
11 | export enum Network {
12 | Optimism = 'optimism',
13 | WorldChain = 'worldchain',
14 | }
15 |
--------------------------------------------------------------------------------
/packages/core/types/transactions.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Abi,
3 | AbiParametersToPrimitiveTypes,
4 | AbiStateMutability,
5 | ExtractAbiFunction,
6 | ExtractAbiFunctionNames,
7 | } from 'abitype';
8 |
9 | export type Permit2 = {
10 | permitted: {
11 | token: string;
12 | amount: string | unknown;
13 | };
14 | spender: string;
15 | nonce: string | unknown;
16 | deadline: string | unknown;
17 | };
18 |
19 | export type Transaction = {
20 | address: string;
21 | abi: Abi | readonly unknown[];
22 | functionName: ContractFunctionName<
23 | Abi | readonly unknown[],
24 | 'payable' | 'nonpayable'
25 | >;
26 | value?: string | undefined;
27 | args: ContractFunctionArgs<
28 | Abi | readonly unknown[],
29 | 'payable' | 'nonpayable',
30 | ContractFunctionName
31 | >;
32 | };
33 |
34 | export type ContractFunctionName<
35 | abi extends Abi | readonly unknown[] = Abi,
36 | mutability extends AbiStateMutability = AbiStateMutability,
37 | > =
38 | ExtractAbiFunctionNames<
39 | abi extends Abi ? abi : Abi,
40 | mutability
41 | > extends infer functionName extends string
42 | ? [functionName] extends [never]
43 | ? string
44 | : functionName
45 | : string;
46 |
47 | export type ContractFunctionArgs<
48 | abi extends Abi | readonly unknown[] = Abi,
49 | mutability extends AbiStateMutability = AbiStateMutability,
50 | functionName extends ContractFunctionName<
51 | abi,
52 | mutability
53 | > = ContractFunctionName,
54 | > =
55 | AbiParametersToPrimitiveTypes<
56 | ExtractAbiFunction<
57 | abi extends Abi ? abi : Abi,
58 | functionName,
59 | mutability
60 | >['inputs'],
61 | 'inputs'
62 | > extends infer args
63 | ? [args] extends [never]
64 | ? readonly unknown[]
65 | : args
66 | : readonly unknown[];
67 |
--------------------------------------------------------------------------------
/packages/core/types/wallet-auth.ts:
--------------------------------------------------------------------------------
1 | export type SiweMessage = {
2 | scheme?: string;
3 | domain: string;
4 | address?: string;
5 | statement?: string;
6 | uri: string;
7 | version: string;
8 | chain_id: number;
9 | nonce: string;
10 | issued_at: string;
11 | expiration_time?: string;
12 | not_before?: string;
13 | request_id?: string;
14 | };
15 |
--------------------------------------------------------------------------------
/packages/create-mini-app/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worldcoin/minikit-js/d844bcc1da2118617bb9943b159cf9b64d978f81/packages/create-mini-app/.gitignore
--------------------------------------------------------------------------------
/packages/create-mini-app/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @worldcoin/create-mini-app
2 |
3 | ## 0.4.0
4 |
5 | ### Minor Changes
6 |
7 | - b239460: fix npx auth secret
8 |
9 | ## 0.3.0
10 |
11 | ### Minor Changes
12 |
13 | - 5248854: allow asking for auth from the get go
14 |
15 | ## 0.2.0
16 |
17 | ### Minor Changes
18 |
19 | - 35ba818: improvements
20 |
--------------------------------------------------------------------------------
/packages/create-mini-app/README.md:
--------------------------------------------------------------------------------
1 | # Create World Mini App
2 |
3 | Create a new World Mini App project based on the Next.js 15 template.
4 |
5 | ## Usage
6 |
7 | ```bash
8 | npx @worldcoin/create-mini-app@latest my-mini-app
9 | ```
10 |
11 | ## Options
12 |
13 | - `-i, --install`: Install dependencies after cloning the template.
14 | - `--no-install`: Do not install dependencies after cloning the template.
15 | - `-p, --project-name`: The name of the project.
16 | - `-a, --auth`: Run `npx auth` to set up next-auth.
17 |
18 | ## License
19 |
20 | [MIT](LICENSE)
21 |
--------------------------------------------------------------------------------
/packages/create-mini-app/dist/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import e from"chalk";import{Command as u}from"commander";import g from"degit";import{execa as p}from"execa";import s from"fs-extra";import a from"node:path";import m from"prompts";var c=new u;async function y(){c.name("create-mini-app").description("Bootstrap a new Worldcoin Mini App project.").argument("[project-name]","The name for the new project directory").option("-i, --install","Install dependencies after cloning",!0).option("--no-install","Do not install dependencies after cloning").option("-a, --auth","Runs npx auth secret to set up next-auth",!0).parse(process.argv);let r=c.opts(),o=c.args[0];o||(o=(await m({type:"text",name:"projectName",message:"What is the name of your project?",initial:"my-world-app"})).projectName),o||(console.error(e.red("Project name is required.")),process.exit(1));let n=a.resolve(process.cwd(),o);if(s.existsSync(n)){let{overwrite:t}=await m({type:"confirm",name:"overwrite",message:`Directory ${e.cyan(o)} already exists. Overwrite?`,initial:!1});t||(console.log(e.yellow("Aborted.")),process.exit(0)),console.log(e.yellow(`Overwriting directory ${e.cyan(o)}...`)),await s.remove(n)}console.log(`Creating project ${e.cyan(o)}...`);try{let t=g("github:worldcoin/minikit-js/demo/next-15-template#main",{cache:!1,force:!0,verbose:!1});t.on("info",l=>{console.log(l.message)}),await t.clone(n),console.log(e.green("Template cloned successfully!"));let i=a.join(n,".env.sample"),d=a.join(n,".env.local");if(s.existsSync(i)&&(await s.copy(i,d),console.log(e.blue("Created .env.local from .env.sample"))),r.install){console.log("Installing dependencies...");try{await p("npm",["install"],{cwd:n,stdio:"inherit"}),console.log(e.green("Dependencies installed successfully!"))}catch(l){console.error(e.red("Failed to install dependencies:"),l),console.log(e.yellow("Please install dependencies manually by running:")),console.log(e.cyan(` cd ${o}`)),console.log(e.cyan(" npm install"))}}if(r.auth)try{console.log("Setting up next-auth..."),await p("npx",["auth","secret"],{cwd:n,stdio:"inherit"}),console.log(e.green("next-auth setup successfully!"))}catch(l){console.error(e.yellow("Failed to setup next-auth, install will continue, you will need to run npx auth secret after install"),l)}console.log(`
3 | ${e.green("Success!")} Created ${e.cyan(o)} at ${e.cyan(n)}
4 | `),console.log(`Inside that directory, you can run several commands:
5 | `),console.log(e.cyan(" npm run dev")),console.log(`We suggest that you begin by typing:
6 | `),console.log(e.cyan(` cd ${o}`)),r.install||console.log(e.cyan(" npm install")),console.log(e.cyan(` npm run dev
7 | `)),console.log(e.blue("Check the .env.local file and follow the setup instructions in README.md")),process.exit(0)}catch(t){console.error(e.red("Failed to create project:"),t),await s.remove(n),process.exit(1)}}y().catch(r=>{console.error(e.red("An unexpected error occurred:"),r),process.exit(1)});
8 |
--------------------------------------------------------------------------------
/packages/create-mini-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@worldcoin/create-mini-app",
3 | "version": "0.4.0",
4 | "description": "Create a new World Mini App project based on the Next.js 15 template.",
5 | "license": "MIT",
6 | "private": false,
7 | "type": "module",
8 | "bin": {
9 | "create-mini-app": "./dist/index.js"
10 | },
11 | "engines": {
12 | "node": ">=18"
13 | },
14 | "scripts": {
15 | "build": "tsup",
16 | "dev": "tsup --watch",
17 | "start": "node ./build/index.js",
18 | "prepublishOnly": "npm run build"
19 | },
20 | "dependencies": {
21 | "chalk": "^5.3.0",
22 | "commander": "^12.1.0",
23 | "degit": "^2.8.4",
24 | "execa": "^9.3.0",
25 | "fs-extra": "^11.2.0",
26 | "prompts": "^2.4.2"
27 | },
28 | "devDependencies": {
29 | "@types/degit": "^2.8.6",
30 | "@types/fs-extra": "^11.0.4",
31 | "@types/node": "^20",
32 | "@types/prompts": "^2.4.9",
33 | "tsup": "^8.0.2",
34 | "typescript": "^5.4.5"
35 | },
36 | "files": [
37 | "build",
38 | "README.md"
39 | ],
40 | "publishConfig": {
41 | "access": "public"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/create-mini-app/src/index.ts:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import { Command } from 'commander';
3 | import degit from 'degit';
4 | import { execa } from 'execa';
5 | import fs from 'fs-extra';
6 | import path from 'node:path';
7 | import prompts from 'prompts';
8 |
9 | const program = new Command();
10 |
11 | interface CLIOptions {
12 | projectName?: string;
13 | install: boolean;
14 | auth: boolean;
15 | }
16 |
17 | async function run(): Promise {
18 | program
19 | .name('create-mini-app')
20 | .description('Bootstrap a new Worldcoin Mini App project.')
21 | .argument('[project-name]', 'The name for the new project directory')
22 | .option('-i, --install', 'Install dependencies after cloning', true)
23 | .option('--no-install', 'Do not install dependencies after cloning')
24 | .option('-a, --auth', 'Runs npx auth secret to set up next-auth', true)
25 | .parse(process.argv);
26 |
27 | const options = program.opts();
28 | let projectName = program.args[0];
29 |
30 | if (!projectName) {
31 | const response = await prompts({
32 | type: 'text',
33 | name: 'projectName',
34 | message: 'What is the name of your project?',
35 | initial: 'my-world-app',
36 | });
37 | projectName = response.projectName;
38 | }
39 |
40 | if (!projectName) {
41 | console.error(chalk.red('Project name is required.'));
42 | process.exit(1);
43 | }
44 |
45 | const targetDir = path.resolve(process.cwd(), projectName);
46 |
47 | if (fs.existsSync(targetDir)) {
48 | const { overwrite } = await prompts({
49 | type: 'confirm',
50 | name: 'overwrite',
51 | message: `Directory ${chalk.cyan(projectName)} already exists. Overwrite?`,
52 | initial: false,
53 | });
54 |
55 | if (!overwrite) {
56 | console.log(chalk.yellow('Aborted.'));
57 | process.exit(0);
58 | }
59 | console.log(
60 | chalk.yellow(`Overwriting directory ${chalk.cyan(projectName)}...`),
61 | );
62 | await fs.remove(targetDir);
63 | }
64 |
65 | console.log(`Creating project ${chalk.cyan(projectName)}...`);
66 |
67 | try {
68 | // Use degit to clone the specific template directory
69 | const emitter = degit(
70 | 'github:worldcoin/minikit-js/demo/next-15-template#main',
71 | {
72 | cache: false,
73 | force: true,
74 | verbose: false,
75 | },
76 | );
77 |
78 | emitter.on('info', (info) => {
79 | console.log(info.message);
80 | });
81 |
82 | await emitter.clone(targetDir);
83 |
84 | console.log(chalk.green('Template cloned successfully!'));
85 |
86 | // Prepare .env file
87 | const envSamplePath = path.join(targetDir, '.env.sample');
88 | const envLocalPath = path.join(targetDir, '.env.local');
89 | if (fs.existsSync(envSamplePath)) {
90 | await fs.copy(envSamplePath, envLocalPath);
91 | console.log(chalk.blue('Created .env.local from .env.sample'));
92 | }
93 |
94 | if (options.install) {
95 | console.log('Installing dependencies...');
96 | try {
97 | await execa('npm', ['install'], { cwd: targetDir, stdio: 'inherit' });
98 | console.log(chalk.green('Dependencies installed successfully!'));
99 | } catch (error) {
100 | console.error(chalk.red('Failed to install dependencies:'), error);
101 | console.log(
102 | chalk.yellow('Please install dependencies manually by running:'),
103 | );
104 | console.log(chalk.cyan(` cd ${projectName}`));
105 | console.log(chalk.cyan(' npm install'));
106 | }
107 | }
108 |
109 | if (options.auth) {
110 | try {
111 | console.log('Setting up next-auth...');
112 | await execa('npx', ['auth', 'secret'], {
113 | cwd: targetDir,
114 | stdio: 'inherit',
115 | });
116 | console.log(chalk.green('next-auth setup successfully!'));
117 | } catch (error) {
118 | console.error(
119 | chalk.yellow(
120 | 'Failed to setup next-auth, install will continue, you will need to run npx auth secret after install',
121 | ),
122 | error,
123 | );
124 | }
125 | }
126 |
127 | console.log(
128 | `\n${chalk.green('Success!')} Created ${chalk.cyan(projectName)} at ${chalk.cyan(targetDir)}\n`,
129 | );
130 | console.log('Inside that directory, you can run several commands:\n');
131 | console.log(chalk.cyan(' npm run dev'));
132 | console.log('We suggest that you begin by typing:\n');
133 | console.log(chalk.cyan(` cd ${projectName}`));
134 | if (!options.install) {
135 | console.log(chalk.cyan(' npm install'));
136 | }
137 | console.log(chalk.cyan(' npm run dev\n'));
138 | console.log(
139 | chalk.blue(
140 | 'Check the .env.local file and follow the setup instructions in README.md',
141 | ),
142 | );
143 | process.exit(0);
144 | } catch (error) {
145 | console.error(chalk.red('Failed to create project:'), error);
146 | await fs.remove(targetDir); // Clean up directory on failure
147 | process.exit(1);
148 | }
149 | }
150 |
151 | run().catch((e) => {
152 | console.error(chalk.red('An unexpected error occurred:'), e);
153 | process.exit(1);
154 | });
155 |
--------------------------------------------------------------------------------
/packages/create-mini-app/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig({
4 | entry: ['src/index.ts'],
5 | format: ['esm'], // Output ES Module
6 | target: 'node18', // Target Node.js 18
7 | platform: 'node',
8 | splitting: false,
9 | sourcemap: false, // No sourcemaps needed for CLI tool
10 | minify: true, // Minify the output
11 | clean: true, // Clean the output directory before building
12 | dts: false, // No declaration files needed for this CLI
13 | banner: {
14 | js: '#!/usr/bin/env node', // Add shebang to make it executable
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/packages/react/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.9.6
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [b00f22c]
8 | - @worldcoin/minikit-js@1.9.5
9 |
10 | ## 1.9.5
11 |
12 | ### Patch Changes
13 |
14 | - Updated dependencies [90d7027]
15 | - @worldcoin/minikit-js@1.9.4
16 |
17 | ## 1.9.4
18 |
19 | ### Patch Changes
20 |
21 | - Updated dependencies [fd4acdc]
22 | - @worldcoin/minikit-js@1.9.3
23 |
24 | ## 1.9.3
25 |
26 | ### Patch Changes
27 |
28 | - Updated dependencies [0af5f97]
29 | - @worldcoin/minikit-js@1.9.2
30 |
31 | ## 1.9.2
32 |
33 | ### Patch Changes
34 |
35 | - a581c5d: Make hooks more consistent and prevent duplicate race conditions
36 |
37 | ## 1.9.0
38 |
39 | ### Minor Changes
40 |
41 | - 35ba818: improvements
42 |
43 | ### Patch Changes
44 |
45 | - Updated dependencies [35ba818]
46 | - @worldcoin/minikit-js@1.9.0
47 |
48 | ## [1.7.0](https://github.com/worldcoin/minikit-js/compare/minikit-react-v1.6.2...minikit-react-v1.7.0) (2025-03-12)
49 |
50 | ### Features
51 |
52 | - 1.6 Release (viem upgrade and eth support) ([#142](https://github.com/worldcoin/minikit-js/issues/142)) ([37175d8](https://github.com/worldcoin/minikit-js/commit/37175d8dfff7430d40e07193f8dbb148182dfb66))
53 | - isUserVerified helper & react bindings ([#118](https://github.com/worldcoin/minikit-js/issues/118)) ([ab8ba8d](https://github.com/worldcoin/minikit-js/commit/ab8ba8da23709a7e5ee4fad7620d91f011735c49))
54 |
55 | ### Bug Fixes
56 |
57 | - Add Readmes to prepare for open source ([#84](https://github.com/worldcoin/minikit-js/issues/84)) ([4d2f5a0](https://github.com/worldcoin/minikit-js/commit/4d2f5a01a392d8ab7743747ce3ca5ba481999db5))
58 |
59 | ### Dependencies
60 |
61 | - The following workspace dependencies were updated
62 | - dependencies
63 | - @worldcoin/minikit-js bumped to 1.7.0
64 |
65 | ## [1.6.0](https://github.com/worldcoin/minikit-js/compare/minikit-react-v1.5.0...minikit-react-v1.6.0) (2025-02-11)
66 |
67 | ### Features
68 |
69 | - 1.6 Release (viem upgrade and eth support) ([#142](https://github.com/worldcoin/minikit-js/issues/142)) ([37175d8](https://github.com/worldcoin/minikit-js/commit/37175d8dfff7430d40e07193f8dbb148182dfb66))
70 |
71 | ### Dependencies
72 |
73 | - The following workspace dependencies were updated
74 | - dependencies
75 | - @worldcoin/minikit-js bumped to 1.6.0
76 |
77 | ## [1.5.0](https://github.com/worldcoin/minikit-js/compare/minikit-react-v1.4.0...minikit-react-v1.5.0) (2025-01-09)
78 |
79 | ### Features
80 |
81 | - isUserVerified helper & react bindings ([#118](https://github.com/worldcoin/minikit-js/issues/118)) ([ab8ba8d](https://github.com/worldcoin/minikit-js/commit/ab8ba8da23709a7e5ee4fad7620d91f011735c49))
82 |
83 | ### Bug Fixes
84 |
85 | - Add Readmes to prepare for open source ([#84](https://github.com/worldcoin/minikit-js/issues/84)) ([4d2f5a0](https://github.com/worldcoin/minikit-js/commit/4d2f5a01a392d8ab7743747ce3ca5ba481999db5))
86 |
87 | ### Dependencies
88 |
89 | - The following workspace dependencies were updated
90 | - dependencies
91 | - @worldcoin/minikit-js bumped to 1.5.0
92 |
93 | ## [1.4.0](https://github.com/worldcoin/minikit-js/compare/react-v1.3.0...react-v1.4.0) (2024-12-03)
94 |
95 | ### Features
96 |
97 | - isUserVerified helper & react bindings ([#118](https://github.com/worldcoin/minikit-js/issues/118)) ([ab8ba8d](https://github.com/worldcoin/minikit-js/commit/ab8ba8da23709a7e5ee4fad7620d91f011735c49))
98 |
99 | ### Bug Fixes
100 |
101 | - Add Readmes to prepare for open source ([#84](https://github.com/worldcoin/minikit-js/issues/84)) ([4d2f5a0](https://github.com/worldcoin/minikit-js/commit/4d2f5a01a392d8ab7743747ce3ca5ba481999db5))
102 |
103 | ### Dependencies
104 |
105 | - The following workspace dependencies were updated
106 | - dependencies
107 | - @worldcoin/minikit-js bumped to 1.4.0
108 |
109 | ## [1.3.0](https://github.com/worldcoin/minikit-js/compare/react-v1.2.0...react-v1.3.0) (2024-11-26)
110 |
111 | ### Bug Fixes
112 |
113 | - Add Readmes to prepare for open source ([#84](https://github.com/worldcoin/minikit-js/issues/84)) ([4d2f5a0](https://github.com/worldcoin/minikit-js/commit/4d2f5a01a392d8ab7743747ce3ca5ba481999db5))
114 |
115 | ### Dependencies
116 |
117 | - The following workspace dependencies were updated
118 | - dependencies
119 | - @worldcoin/minikit-js bumped to 1.3.0
120 |
121 | ## [1.2.0](https://github.com/worldcoin/minikit-js/compare/react-v1.1.1...react-v1.2.0) (2024-11-01)
122 |
123 | ### Bug Fixes
124 |
125 | - Add Readmes to prepare for open source ([#84](https://github.com/worldcoin/minikit-js/issues/84)) ([4d2f5a0](https://github.com/worldcoin/minikit-js/commit/4d2f5a01a392d8ab7743747ce3ca5ba481999db5))
126 |
127 | ### Dependencies
128 |
129 | - The following workspace dependencies were updated
130 | - dependencies
131 | - @worldcoin/minikit-js bumped to 1.2.0
132 |
--------------------------------------------------------------------------------
/packages/react/README.md:
--------------------------------------------------------------------------------
1 | # minikit-react
2 |
3 | This package contains helper functions and components for building mini apps with React. This is not required to run a mini app and is only a convenience package for developers as it contains nice abstractions.
4 |
5 | ## 🚀 Getting Started
6 |
7 | MiniKit is currently available on npm. To install, run:
8 |
9 | ```
10 | pnpm i @worldcoin/minikit-react
11 | ```
12 |
13 | For comprehensive setup instructions and usage examples, visit our [developer documentation](https://docs.world.org/mini-apps).
14 |
15 | ## 🛠 ️Developing Locally
16 |
17 | To run the example mini app locally:
18 |
19 | ```
20 | pnpm i
21 | cd demo/with-next
22 | pnpm dev
23 | ```
24 |
25 | This will launch a demo mini app with all essential commands implemented, allowing you to explore and test the features.
26 |
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@worldcoin/minikit-react",
3 | "version": "1.9.6",
4 | "homepage": "https://docs.worldcoin.org/mini-apps",
5 | "description": "minikit-react is a set of hooks for building mini-apps",
6 | "license": "MIT",
7 | "private": false,
8 | "type": "module",
9 | "exports": {
10 | ".": {
11 | "import": {
12 | "types": "./build/index.d.ts",
13 | "default": "./build/index.js"
14 | },
15 | "require": {
16 | "types": "./build/index.d.cts",
17 | "default": "./build/index.cjs"
18 | }
19 | }
20 | },
21 | "typesVersions": {
22 | "*": {
23 | "*": [
24 | "./build/*/index.d.ts",
25 | "./build/index.d.ts"
26 | ]
27 | }
28 | },
29 | "main": "index.ts",
30 | "types": "index.ts",
31 | "engines": {
32 | "node": ">= 16"
33 | },
34 | "files": [
35 | "./build/**",
36 | "README.md"
37 | ],
38 | "keywords": [
39 | "minikit",
40 | "miniapps"
41 | ],
42 | "scripts": {
43 | "build": "tsup",
44 | "dev": "tsup --watch",
45 | "lint": "eslint ./ --ext .ts",
46 | "prepublishOnly": "npm run build",
47 | "type-check": "tsc --noEmit"
48 | },
49 | "dependencies": {
50 | "@worldcoin/minikit-js": "workspace:*",
51 | "abitype": "^1.0.6",
52 | "turbo": "^2.3.3"
53 | },
54 | "devDependencies": {
55 | "@types/node": "^20",
56 | "@typescript-eslint/eslint-plugin": "^7.7.0",
57 | "@typescript-eslint/parser": "^7.7.0",
58 | "prettier": "^3.2.5",
59 | "prettier-plugin-sort-imports-desc": "^1.0.0",
60 | "tsup": "^8.0.2",
61 | "typescript": "^5.4.5",
62 | "viem": "2.23.5",
63 | "react": "^18.2.0",
64 | "@types/react": "^18.0.25",
65 | "react-dom": "^18.2.0",
66 | "@types/react-dom": "^18.0.9",
67 | "eslint-plugin-react": "^7.34.3",
68 | "eslint-plugin-react-hooks": "^4.6.2"
69 | },
70 | "peerDependencies": {
71 | "viem": "^2.23.5",
72 | "react": "^18 || ^19"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/react/src/address-book/index.ts:
--------------------------------------------------------------------------------
1 | export { useIsUserVerified } from './is-verified';
2 |
--------------------------------------------------------------------------------
/packages/react/src/address-book/is-verified.tsx:
--------------------------------------------------------------------------------
1 | import { getIsUserVerified } from '@worldcoin/minikit-js';
2 | import { useEffect, useState } from 'react';
3 |
4 | /**
5 | * Checks if a user is Orb verified
6 | *
7 | * @param walletAddress - The wallet address of the user
8 | * @param rpcUrl - Your preferred RPC node URL, https://worldchain-mainnet.g.alchemy.com/public by default
9 | */
10 | export const useIsUserVerified = (walletAddress: string, rpcUrl?: string) => {
11 | const [isUserVerified, setIsUserVerified] = useState(null);
12 | const [isLoading, setIsLoading] = useState(true);
13 | const [isError, setIsError] = useState(null);
14 |
15 | useEffect(() => {
16 | const fetchIsUserVerified = async () => {
17 | try {
18 | const data = await getIsUserVerified(walletAddress);
19 | setIsUserVerified(data);
20 | } catch (err) {
21 | setIsError(err);
22 | } finally {
23 | setIsLoading(false);
24 | }
25 | };
26 |
27 | fetchIsUserVerified();
28 | }, [walletAddress]);
29 |
30 | return { isUserVerified, isLoading, isError };
31 | };
32 |
--------------------------------------------------------------------------------
/packages/react/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | UsernameSearch,
3 | type GetSearchedUsernameResult,
4 | } from './username-search';
5 |
--------------------------------------------------------------------------------
/packages/react/src/components/username-search.tsx:
--------------------------------------------------------------------------------
1 | const createDebounce = () => {
2 | let timeoutId: NodeJS.Timeout;
3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
4 | return any>(fn: T, delay: number) => {
5 | return (...args: Parameters) => {
6 | if (timeoutId) {
7 | clearTimeout(timeoutId);
8 | }
9 | timeoutId = setTimeout(() => {
10 | fn(...args);
11 | }, delay);
12 | };
13 | };
14 | };
15 |
16 | const DEBOUNCE_DELAY_MS = 300;
17 | const debounce = createDebounce();
18 |
19 | type SearchUsernameResponseBodySuccess = Array<{
20 | address: string;
21 | profile_picture_url: string | null;
22 | username: string;
23 | }>;
24 |
25 | export type GetSearchedUsernameResult = Awaited<
26 | ReturnType
27 | >;
28 |
29 | const getSearchedUsername = async (username: string) => {
30 | const response = await fetch(
31 | `https://usernames.worldcoin.org/api/v1/search/${username}`,
32 | );
33 |
34 | if (response.status === 200) {
35 | const json = (await response.json()) as SearchUsernameResponseBodySuccess;
36 | return { status: response.status, data: json };
37 | }
38 |
39 | return { status: response.status, error: 'Error fetching data' };
40 | };
41 |
42 | type Props = {
43 | value: string;
44 | handleChange: (e: React.ChangeEvent) => void;
45 | setSearchedUsernames: (searchedUsernames: GetSearchedUsernameResult) => void;
46 | className?: string;
47 | inputProps?: React.InputHTMLAttributes;
48 | };
49 |
50 | /**
51 | * Simple component that allows users to search for usernames.
52 | *
53 | * It is encouraged to build your own components, using this as a base/reference
54 | *
55 | * You can add more props/override existing ones by passing inputProps.
56 | * Debounce delay is 300ms.
57 | */
58 | export const UsernameSearch = ({
59 | value,
60 | handleChange,
61 | setSearchedUsernames,
62 | className,
63 | inputProps,
64 | }: Props) => {
65 | const debouncedSearch = debounce(
66 | async (e: React.ChangeEvent) => {
67 | const username = e.target.value;
68 | const data = await getSearchedUsername(username);
69 |
70 | setSearchedUsernames(data);
71 | },
72 | DEBOUNCE_DELAY_MS,
73 | );
74 |
75 | const onChange = (e: React.ChangeEvent) => {
76 | debouncedSearch(e);
77 | handleChange(e);
78 | };
79 |
80 | return (
81 |
88 | );
89 | };
90 |
--------------------------------------------------------------------------------
/packages/react/src/index.ts:
--------------------------------------------------------------------------------
1 | export { useIsUserVerified } from './address-book/is-verified';
2 | export * from './components';
3 | export { useWaitForTransactionReceipt } from './transaction/hooks';
4 | export * from './types/client';
5 |
--------------------------------------------------------------------------------
/packages/react/src/transaction/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useMemo, useState } from 'react';
2 | import {
3 | type Account,
4 | type Address,
5 | type Chain,
6 | type ParseAccount,
7 | type PublicClient,
8 | type RpcSchema,
9 | type TransactionReceipt,
10 | type Transport,
11 | } from 'viem';
12 | import { fetchTransactionHash, TransactionStatus } from '.';
13 | import { AppConfig } from '../types/client';
14 |
15 | interface UseTransactionReceiptOptions<
16 | transport extends Transport = Transport,
17 | chain extends Chain | undefined = Chain | undefined,
18 | accountOrAddress extends Account | Address | undefined = undefined,
19 | rpcSchema extends RpcSchema | undefined = undefined,
20 | > {
21 | client: PublicClient<
22 | transport,
23 | chain,
24 | ParseAccount,
25 | rpcSchema
26 | >;
27 | appConfig: AppConfig;
28 | transactionId: string;
29 | confirmations?: number;
30 | timeout?: number;
31 | pollingInterval?: number;
32 | }
33 |
34 | interface UseTransactionReceiptResult {
35 | transactionHash?: `0x${string}`;
36 | receipt?: TransactionReceipt;
37 | isError: boolean;
38 | isLoading: boolean;
39 | isSuccess: boolean;
40 | error?: Error;
41 | retrigger: () => void;
42 | }
43 |
44 | export function useWaitForTransactionReceipt<
45 | transport extends Transport = Transport,
46 | chain extends Chain | undefined = Chain | undefined,
47 | accountOrAddress extends Account | Address | undefined = undefined,
48 | rpcSchema extends RpcSchema | undefined = undefined,
49 | >(
50 | options: UseTransactionReceiptOptions<
51 | transport,
52 | chain,
53 | accountOrAddress,
54 | rpcSchema
55 | >,
56 | ): UseTransactionReceiptResult {
57 | const {
58 | client,
59 | appConfig: _appConfig,
60 | transactionId,
61 | confirmations = 1,
62 | timeout,
63 | pollingInterval = 1000,
64 | } = options;
65 |
66 | const appConfig = useMemo(() => _appConfig, [_appConfig]);
67 |
68 | const [transactionHash, setTransactionHash] = useState<
69 | `0x${string}` | undefined
70 | >(undefined);
71 | const [receipt, setReceipt] = useState(
72 | undefined,
73 | );
74 | const [isLoading, setIsLoading] = useState(false);
75 | const [isError, setIsError] = useState(false);
76 | const [error, setError] = useState(undefined);
77 | const [pollCount, setPollCount] = useState(0);
78 | const [transactionStatus, setTransactionStatus] = useState<
79 | TransactionStatus | undefined
80 | >(undefined);
81 |
82 | const retrigger = useCallback(() => {
83 | reset();
84 | setIsLoading(false);
85 | setPollCount((count) => count + 1);
86 | }, []);
87 |
88 | const reset = useCallback(() => {
89 | setTransactionHash(undefined);
90 | setReceipt(undefined);
91 | setIsLoading(false);
92 | setPollCount(0);
93 | setIsError(false);
94 | setError(undefined);
95 | setTransactionStatus(undefined);
96 | }, []);
97 |
98 | const fetchStatus = useCallback(async () => {
99 | return await fetchTransactionHash(appConfig, transactionId);
100 | }, [appConfig, transactionId]);
101 |
102 | useEffect(() => {
103 | if (!transactionId) {
104 | reset();
105 | return;
106 | }
107 |
108 | console.log(
109 | '[Effect] Running for txId:',
110 | transactionId,
111 | 'Poll count:',
112 | pollCount,
113 | );
114 |
115 | const abortController = new AbortController();
116 | const signal = abortController.signal;
117 | let timeoutId: NodeJS.Timeout | null = null;
118 |
119 | const fetchReceipt = async (hashToWaitFor: `0x${string}`) => {
120 | if (signal.aborted) return;
121 | try {
122 | const txnReceipt = await client.waitForTransactionReceipt({
123 | hash: hashToWaitFor,
124 | confirmations,
125 | timeout,
126 | });
127 |
128 | if (signal.aborted) return;
129 | setReceipt(txnReceipt);
130 | setIsLoading(false);
131 | } catch (err) {
132 | if (signal.aborted) return;
133 | setIsError(true);
134 | setError(err instanceof Error ? err : new Error(String(err)));
135 | setIsLoading(false);
136 | }
137 | };
138 |
139 | const pollHash = async () => {
140 | if (signal.aborted) return;
141 |
142 | try {
143 | // If we already have the hash, don't poll
144 | if (transactionHash) return;
145 | if (signal.aborted) return;
146 |
147 | const status = await fetchStatus();
148 | setTransactionStatus(status);
149 | // If we have the hash, fetch the receipt
150 | if (status.transactionHash) {
151 | setTransactionHash(status.transactionHash);
152 | await fetchReceipt(status.transactionHash);
153 | } else {
154 | // Otherwise, poll again
155 | timeoutId = setTimeout(pollHash, pollingInterval);
156 | }
157 | } catch (err) {
158 | if (signal.aborted) return;
159 | setIsError(true);
160 | setError(err instanceof Error ? err : new Error(String(err)));
161 | setIsLoading(false);
162 | }
163 | };
164 |
165 | pollHash();
166 |
167 | return () => {
168 | abortController.abort();
169 | if (timeoutId) {
170 | clearTimeout(timeoutId);
171 | }
172 | };
173 | }, [transactionId]);
174 |
175 | const isSuccess =
176 | (receipt !== undefined && receipt.status === 'success') ||
177 | transactionStatus?.transactionStatus === 'mined';
178 |
179 | return {
180 | transactionHash,
181 | receipt,
182 | isError,
183 | isLoading,
184 | isSuccess,
185 | error,
186 | retrigger,
187 | };
188 | }
189 |
--------------------------------------------------------------------------------
/packages/react/src/transaction/index.ts:
--------------------------------------------------------------------------------
1 | import { AppConfig } from 'src/types/client';
2 | export interface TransactionStatus {
3 | transactionHash: `0x${string}`;
4 | transactionStatus: 'pending' | 'mined' | 'failed';
5 | }
6 |
7 | export async function fetchTransactionHash(
8 | appConfig: AppConfig,
9 | transactionId: string,
10 | ): Promise {
11 | try {
12 | const response = await fetch(
13 | `https://developer.worldcoin.org/api/v2/minikit/transaction/${transactionId}?app_id=${appConfig.app_id}&type=transaction`,
14 | {
15 | method: 'GET',
16 | },
17 | );
18 | if (!response.ok) {
19 | const error = await response.json();
20 | console.log(error);
21 | throw new Error(`Failed to fetch transaction status: ${error.message}`);
22 | }
23 |
24 | const data: TransactionStatus = await response.json();
25 |
26 | return data;
27 | } catch (error) {
28 | console.log('Error fetching transaction status', error);
29 | throw new Error('Failed to fetch transaction status');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/react/src/types/client.ts:
--------------------------------------------------------------------------------
1 | export type AppConfig = {
2 | app_id: string;
3 | };
4 |
--------------------------------------------------------------------------------
/packages/react/src/types/errors.ts:
--------------------------------------------------------------------------------
1 | // Error format from developer portal
2 | export type DeveloperPortalError = {
3 | statusCode?: number;
4 | code?: string;
5 | details?: string;
6 | attribute?: string;
7 | };
8 |
--------------------------------------------------------------------------------
/packages/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "types": ["node", "react"],
5 | "moduleResolution": "bundler",
6 | "rootDir": ".",
7 | "baseUrl": ".",
8 | "typeRoots": ["./node_modules/@types"],
9 | "noEmit": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "skipLibCheck": true,
13 | "jsx": "react-jsx",
14 | "paths": {
15 | "@worldcoin/minikit-js": ["../core"]
16 | }
17 | },
18 | "include": ["./**/*.ts", "./**/*.tsx"],
19 | "exclude": ["node_modules", "build", ".turbo", "coverage"]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/react/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig({
4 | dts: true,
5 | clean: true,
6 | outDir: 'build',
7 | format: ['esm', 'cjs'],
8 | external: ['@worldcoin/idkit-core'],
9 | entry: ['src/index.ts'],
10 | define: { 'process.env.NODE_ENV': '"production"' },
11 | });
12 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'demo/*'
3 | - 'packages/*'
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "declaration": true,
5 | "declarationDir": "build",
6 | "module": "ESNext",
7 | "moduleResolution": "node",
8 | "target": "ES2020",
9 | "lib": ["es6", "dom", "es2016", "es2017", "es2021"],
10 | "sourceMap": false,
11 | "allowSyntheticDefaultImports": true,
12 | "esModuleInterop": true,
13 | "jsx": "react-jsx",
14 | "noImplicitAny": false,
15 | "skipLibCheck": true,
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": ["."]
19 | }
20 | },
21 | "include": ["packages/**/*", "demo/**/*"],
22 | "exclude": ["node_modules/**", "build"],
23 | "watchOptions": {
24 | "watchFile": "useFsEvents",
25 | "watchDirectory": "useFsEvents",
26 | "fallbackPolling": "dynamicPriority",
27 | "synchronousWatchDirectory": true,
28 | "excludeDirectories": ["../node_modules", "build"]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "outputs": ["build/**", ".next/**", "!.next/cache/**"]
7 | },
8 | "dev": {
9 | "cache": false,
10 | "persistent": true
11 | },
12 | "lint": {
13 | "cache": false
14 | },
15 | "type-check": {
16 | "dependsOn": ["^check-types"]
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------