{"id":31817708,"url":"https://github.com/Crossmint/global-wallets","last_synced_at":"2025-10-11T10:23:36.544Z","repository":{"id":296471379,"uuid":"992819030","full_name":"Crossmint/global-wallets","owner":"Crossmint","description":null,"archived":false,"fork":false,"pushed_at":"2025-07-15T16:47:46.000Z","size":531,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-16T12:54:14.990Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://global-wallets.vercel.app","language":"TypeScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Crossmint.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-29T19:03:30.000Z","updated_at":"2025-07-15T16:47:43.000Z","dependencies_parsed_at":"2025-05-31T10:09:00.507Z","dependency_job_id":"09c4a7f6-e2c1-49bf-afbe-5004740e2962","html_url":"https://github.com/Crossmint/global-wallets","commit_stats":null,"previous_names":["crossmint/evm-wallet-delegation-quickstart"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/Crossmint/global-wallets","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crossmint%2Fglobal-wallets","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crossmint%2Fglobal-wallets/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crossmint%2Fglobal-wallets/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crossmint%2Fglobal-wallets/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Crossmint","download_url":"https://codeload.github.com/Crossmint/global-wallets/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Crossmint%2Fglobal-wallets/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279006851,"owners_count":26084204,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-11T10:23:28.417Z","updated_at":"2025-10-11T10:23:36.539Z","avatar_url":"https://github.com/Crossmint.png","language":"TypeScript","funding_links":[],"categories":["Starter Code"],"sub_categories":["Wallets"],"readme":"\u003cdiv align=\"center\"\u003e\n\u003cimg width=\"200\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/8b617791-cd37-4a5a-8695-a7c9018b7c70\" /\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003ch1\u003ePortal Wallets Integration Quickstart\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n\u003ca href=\"https://docs.crossmint.com/introduction/platform/wallets\"\u003eDocs\u003c/a\u003e | \u003ca href=\"https://github.com/crossmint\"\u003eSee all quickstarts\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cimg src=\"https://github.com/user-attachments/assets/c7a87267-0e07-44b6-8e85-deeb960b7600\" alt=\"Image\" width=\"full\"\u003e\n\n\u003c/div\u003e\n\n## Introduction\nThis quickstart demonstrates the **Portal Wallets Integration** - enabling Portal users to connect and utilize wallets from other Story ecosystem projects. This implementation shows how Portal can integrate with external DApps through delegated signers using iframe communication.\n\n**High-level Flow:**\n- User logs in to Portal → gets a non-custodial signer and smart wallet created\n- User clicks \"Connect to DApp\" button → Portal opens popup to DApp's main page\n- Portal sends its non-custodial signer as a delegated signer to the DApp via popup\n- DApp user logs in and adds the delegated signer to their smart wallet\n- DApp sends its wallet address back to Portal\n- Portal can now use the DApp's smart wallet using Portal's signer\n\n**What you'll learn:**\n- How to implement popup communication between Portal and DApps\n- Delegated signer flow for cross-application wallet usage\n- Crossmint wallet integration in a monorepo structure\n- Message-based communication between parent and popup windows\n\n## Architecture\nThis is a **pnpm monorepo** with two Next.js applications:\n\n- **Portal App** (`apps/portal/`) - Runs on `http://localhost:3000`\n  - Main Portal platform where users log in\n  - \"Connect to DApp\" button opens popup window to DApp\n  - Sends delegated signer via postMessage\n  - Receives wallet address from DApp\n  \n- **DApp App** (`apps/dapp/`) - Runs on `http://localhost:3001`  \n  - Represents an external DApp (like Magma)\n  - Main page detects if it's in popup and adapts UI accordingly\n  - Receives delegated signers from Portal via postMessage\n  - Sends wallet address back to Portal after adding delegated signer\n\n## Setup\n1. Clone the repository and navigate to the project folder:\n```bash\ngit clone https://github.com/Crossmint/evm-wallet-delegation-quickstart.git \u0026\u0026 cd evm-wallet-delegation-quickstart\n```\n\n2. Install all dependencies:\n```bash\npnpm install\n```\n\n3. Get a Crossmint client API key from [here](https://docs.crossmint.com/introduction/platform/api-keys/client-side) and set it in both apps. Make sure your API key has the following scopes: `users.create`, `users.read`, `wallets.read`, `wallets.create`, `wallets:transactions.create`, `wallets:transactions.sign`, `wallets:balance.read`, `wallets.fund`.\n\nCreate `.env.local` files in both app directories:\n```bash\n# apps/portal/.env.local\nNEXT_PUBLIC_CROSSMINT_API_KEY=your_api_key\nNEXT_PUBLIC_CHAIN=story-testnet\n\n# apps/dapp/.env.local  \nNEXT_PUBLIC_CROSSMINT_API_KEY=your_api_key\nNEXT_PUBLIC_CHAIN=story-testnet\n```\n\n4. Run both applications:\n\n**Option A: Run both apps simultaneously**\n```bash\n# Terminal 1 - Portal\npnpm portal:dev\n\n# Terminal 2 - DApp  \npnpm dapp:dev\n```\n\n**Option B: Use individual commands**\n```bash\n# Portal only\npnpm portal:dev\n\n# DApp only  \npnpm dapp:dev\n```\n\n## Usage Flow\n\n### 1. **Portal Side (localhost:3000)**\n- User logs in to Portal with their wallet\n- Portal creates a Crossmint smart wallet\n- User clicks \"Connect to DApp\" button\n- Portal opens popup window showing DApp (localhost:3001)\n- Portal sends delegated signer to DApp via postMessage\n- Portal waits for DApp wallet address response\n- Shows \"Connected DApp\" card when successful\n\n### 2. **DApp Side (localhost:3001)**\n- When accessed directly: shows normal DApp interface\n- When accessed in popup: shows \"Connect DApp to Portal\" interface\n- User logs in to create DApp wallet\n- DApp receives delegated signer from Portal\n- User clicks \"Add Delegated Signer\" to confirm\n- DApp sends its wallet address back to Portal\n- Shows success message when connected\n\n### 3. **Testing the Integration**\n1. Start both apps\n2. Open Portal at `http://localhost:3000`\n3. Log in to Portal\n4. Click \"Connect to DApp\" button\n5. In the popup, log in to DApp\n6. Click \"Add Delegated Signer\" when prompted\n7. See success messages on both sides\n8. Portal now shows \"Connected DApp\" card with wallet address\n\n## Implementation Details\n\n### Popup Communication\nThe implementation uses native **postMessage API** for secure cross-origin communication:\n\n**Portal (Parent) - manages popup window:**\n```javascript\n// Open popup window\nconst popup = window.open(\n  DAPP_URL,\n  \"dapp-connection\",\n  \"width=500,height=600,scrollbars=yes,resizable=yes,top=100,left=100\"\n);\n\n// Handle incoming messages from popup\nconst handleMessage = (event: MessageEvent) =\u003e {\n  if (event.origin !== DAPP_ORIGIN) return;\n  \n  if (isValidReadyMessage(event.data)) {\n    setIsPopupReady(true);\n  } else if (isValidPopupMessage(event.data)) {\n    setConnectedWallet(event.data.wallet);\n  }\n};\n\n// Send delegated signer when popup is ready\nuseEffect(() =\u003e {\n  if (isPopupReady \u0026\u0026 popupWindow \u0026\u0026 signerAddress) {\n    const message: ParentToPopupMessage = { delegatedSigner: signerAddress };\n    \n    const interval = setInterval(() =\u003e {\n      popupWindow.postMessage(message, DAPP_ORIGIN);\n    }, 1000);\n    \n    return () =\u003e clearInterval(interval);\n  }\n}, [isPopupReady, popupWindow, signerAddress]);\n```\n\n**DApp (Popup) - communicates with parent:**\n```javascript\n// Send ready message to parent on mount\nuseEffect(() =\u003e {\n  const readyMessage: ReadyMessage = { type: \"ready\" };\n  window.opener?.postMessage(readyMessage, PORTAL_ORIGIN);\n}, []);\n\n// Handle messages from parent window\nconst handleMessage = (event: MessageEvent) =\u003e {\n  if (event.origin !== PORTAL_ORIGIN) return;\n  \n  if (isValidParentMessage(event.data)) {\n    setReceivedSigner(event.data.delegatedSigner);\n  }\n};\n\n// Send wallet address back to parent\nconst response: PopupToParentMessage = { wallet: walletAddress };\nwindow.opener?.postMessage(response, PORTAL_ORIGIN);\n```\n\n**Message Types (Type-Safe with TypeScript):**\n```javascript\ninterface ParentToPopupMessage {\n  delegatedSigner: string;\n}\n\ninterface PopupToParentMessage {\n  wallet: string;\n}\n\ninterface ReadyMessage {\n  type: 'ready';\n}\n```\n\n### Popup Detection\nDApp detects if it's running in a popup:\n```javascript\nconst isInPopup = window.opener !== null;\n```\n\n### Responsive Design\n- Both apps are fully responsive\n- iframe modal adapts to different screen sizes\n- Clean, modern UI following best practices\n\n## Building for Production\n```bash\n# Build both apps\npnpm portal:build\npnpm dapp:build\n\n# Or build individually\npnpm portal:build\npnpm dapp:build\n```\n\n## Using in production\n1. Create a [production API key](https://docs.crossmint.com/introduction/platform/api-keys/client-side).\n2. Update the popup URLs in Portal to point to your production DApp URL.\n3. Update the origin validation in both Portal and DApp for your production domains.\n4. Deploy both applications to your preferred hosting platform.\n\n## Key Files\n- `apps/portal/app/page.tsx` - Portal home with Connect button and popup window using native postMessage\n- `apps/dapp/app/page.tsx` - DApp main page with popup detection and postMessage communication  \n- `pnpm-workspace.yaml` - Monorepo configuration with shared dependencies\n\n## Production Ready\nThis implementation uses native **postMessage API** for secure, type-safe popup communication between Portal and external DApps. The implementation provides:\n\n- **Type Safety**: TypeScript interfaces with runtime validation for all messages\n- **Ready Protocol**: Secure connection establishment with ready handshake\n- **Error Handling**: Built-in popup lifecycle monitoring and error handling\n- **Cross-Origin Security**: Proper origin validation for all messages\n- **Event Management**: Clean event listener management with automatic cleanup\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrossmint%2Fglobal-wallets","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCrossmint%2Fglobal-wallets","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrossmint%2Fglobal-wallets/lists"}