├── .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 | 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 | 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 | 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 | 78 | 79 | 90 | 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 | 51 | 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 | 43 | 54 | 65 | 69 | Valid Subdomain (Link) 70 | 71 | 77 | 81 | Valid double Subdomain (Link) 82 | 83 | 93 | 94 | 100 | 111 | 129 | 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 | 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 | 18 | {/* */} 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 | 141 | 153 | 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 | 119 | 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 | 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 | 117 | 123 |
124 |
125 | 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 | 136 | 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 | 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 | 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 |
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 | --------------------------------------------------------------------------------