{"id":50993207,"url":"https://github.com/mhbdev/ton-better-auth","last_synced_at":"2026-06-20T05:32:28.118Z","repository":{"id":357703159,"uuid":"1237990351","full_name":"mhbdev/ton-better-auth","owner":"mhbdev","description":"TonConnect better-auth Plugin","archived":false,"fork":false,"pushed_at":"2026-05-23T10:21:26.000Z","size":424,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T11:35:40.993Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mhbdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-13T17:50:21.000Z","updated_at":"2026-05-23T10:21:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mhbdev/ton-better-auth","commit_stats":null,"previous_names":["mhbdev/ton-better-auth"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mhbdev/ton-better-auth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhbdev%2Fton-better-auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhbdev%2Fton-better-auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhbdev%2Fton-better-auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhbdev%2Fton-better-auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mhbdev","download_url":"https://codeload.github.com/mhbdev/ton-better-auth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhbdev%2Fton-better-auth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34558894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"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":"2026-06-20T05:32:27.633Z","updated_at":"2026-06-20T05:32:28.112Z","avatar_url":"https://github.com/mhbdev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ton-better-auth\n\n\u003cimg width=\"200\" height=\"200\" alt=\"image\" src=\"https://github.com/user-attachments/assets/40ad78aa-5621-4afd-8277-145fc843930a\" /\u003e\n\n\nSign in with TON Connect for [Better Auth](https://better-auth.com). Ships\na server plugin that verifies `ton_proof` payloads end-to-end (replayable\nnonce challenge, signature verification, wallet state-init parsing for v1\nthrough v5, and on-chain `get_public_key` fallback) plus a matching\nclient plugin that gives you typed helpers on your Better Auth client.\n\n## Why\n\nTON Connect returns a `ton_proof` item to your dApp after the user picks\na wallet. The TON protocol docs describe the exact verification steps:\n\n- https://docs.ton.org/v3/guidelines/ton-connect/verifying-signed-in-users\n- https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md#address-proof-signature-ton_proof\n\nThis package implements the server side of that flow and plugs it into\nBetter Auth's session machinery, so you get a normal Better Auth session\ncookie after a successful TON wallet sign-in.\n\n## Install\n\n```bash\nnpm install ton-better-auth\n```\n\nPeer dependencies: `better-auth` (\u003e= 1.3) and `@better-auth/core`.\nOptional peer for `ton-better-auth/react`: `react` (\u003e= 18).\n\n## Server setup\n\n```ts\n// src/auth.ts\nimport { betterAuth } from \"better-auth\";\nimport { tonConnect } from \"ton-better-auth\";\n\nexport const auth = betterAuth({\n  database: /* your adapter */,\n  plugins: [\n    tonConnect({\n      // Domain policy can be global string[] or per-network object.\n      // Supports wildcard patterns (e.g. *.example.com).\n      allowedDomains: {\n        default: [\"example.com\", \"*.example.com\"],\n        mainnet: [\"app.example.com\"],\n        testnet: [\"localhost:5173\", \"*.staging.example.com\"],\n      },\n      // Optional additive policy by network id.\n      allowedDomainsByNetwork: {\n        \"-3\": [\"*.dev.example.com\"],\n      },\n      // Optional tweaks:\n      validAuthTimeSec: 15 * 60,  // signature TTL, defaults to 15 min\n      challengeTtlSec: 10 * 60,   // nonce TTL, defaults to 10 min\n      emailDomain: \"ton.local\",   // used to synth an email for new users\n      antiAbuse: {\n        verify: { maxPerIp: 20, maxPerAddress: 8, windowSec: 60 },\n        failedVerifyCooldown: {\n          enabled: true,\n          threshold: 5,\n          windowSec: 10 * 60,\n          cooldownSec: 10 * 60,\n          keying: \"ip+address\",\n        },\n      },\n      authRules: {\n        onlyPrimaryCanSignIn: false,\n        allowOnlyLinkedWallets: false,\n        autoLinkOnVerify: true,\n      },\n      events: {\n        onVerifySuccess: async (event) =\u003e {\n          console.log(\"TON verify success\", event.userId, event.address);\n        },\n      },\n      // Optional: fallback when state-init parsing fails (rare).\n      // Called as getWalletPublicKey(address, network).\n      getWalletPublicKey: async (address) =\u003e {\n        // e.g. use @ton/ton's TonClient to call the wallet's get_public_key.\n        return null;\n      },\n      // Optional: enrich new users with a name / avatar from TON DNS.\n      addressLookup: async ({ address }) =\u003e ({ name: \"Alice\", image: \"…\" }),\n    }),\n  ],\n});\n```\n\nRun the CLI to generate / apply the `tonWallet` table:\n\n```bash\nnpx @better-auth/cli@latest migrate\n# or, for Prisma / Drizzle:\nnpx @better-auth/cli@latest generate\n```\n\n## Client setup\n\n```ts\n// src/auth-client.ts\nimport { createAuthClient } from \"better-auth/client\";\nimport { tonConnectClient } from \"ton-better-auth/client\";\n\nexport const authClient = createAuthClient({\n  plugins: [tonConnectClient()],\n});\n```\n\n## Sign-in flow\n\nThe plugin exposes these endpoints under `/ton-connect`:\n\n| Method | Path                      | Auth | Purpose                                    |\n|--------|---------------------------|------|--------------------------------------------|\n| POST   | `/ton-connect/challenge`  | no   | Issue a one-shot `ton_proof` payload (`body.address` optional) |\n| POST   | `/ton-connect/verify`     | no   | Verify a `ton_proof` and start a session   |\n| POST   | `/ton-connect/link`       | yes  | Link an extra TON wallet to the user       |\n| POST   | `/ton-connect/unlink`     | yes  | Remove a linked wallet                     |\n| POST   | `/ton-connect/set-primary`| yes  | Set a linked wallet as primary             |\n| POST   | `/ton-connect/switch-session-wallet` | yes | Rotate active wallet context in session |\n| GET    | `/ton-connect/wallets`    | yes  | List the current user's linked wallets     |\n\nLifecycle hooks:\n\n- `onChallengeIssued`\n- `onVerifySuccess`\n- `onVerifyFail`\n- `onWalletLinked`\n\n### Wiring with `@tonconnect/ui-react` (recommended)\n\nUse the built-in React helper from `ton-better-auth/react`. It wraps\nchallenge refresh + verify lifecycle + typed error states:\n\n```tsx\nimport { useTonConnectUI, useTonWallet } from \"@tonconnect/ui-react\";\nimport { useTonConnectAuth } from \"ton-better-auth/react\";\nimport { authClient } from \"./auth-client\";\n\nexport function SignInButton() {\n  const [tonConnectUI] = useTonConnectUI();\n  const wallet = useTonWallet();\n  const {\n    authenticated,\n    status,\n    error,\n    disconnect,\n    refreshChallenge,\n  } = useTonConnectAuth({\n    tonConnectUI,\n    authClient,\n    refreshIntervalMs: 9 * 60 * 1000,\n    // Optional when server captcha checks are enabled.\n    getCaptchaToken: async ({ phase }) =\u003e\n      phase === \"challenge\" ? window.localStorage.getItem(\"captcha-token\") : null,\n    onError: (e) =\u003e console.error(e.code, e.message),\n  });\n\n  if (authenticated) {\n    return (\n      \u003cbutton onClick={() =\u003e void disconnect()}\u003e\n        Disconnect {wallet?.account.address.slice(0, 6)}...\n      \u003c/button\u003e\n    );\n  }\n\n  return (\n    \u003cbutton\n      onClick={() =\u003e {\n        void refreshChallenge();\n        void tonConnectUI.openModal();\n      }}\n      disabled={status === \"loading-challenge\" || status === \"verifying\"}\n      title={error?.message}\n    \u003e\n      Sign in with TON\n    \u003c/button\u003e\n  );\n}\n```\n\n`useTonConnectAuth` exposes:\n\n- `status`: `idle | loading-challenge | ready | verifying | authenticated | error`\n- `error`: typed error object with stable `code`\n- `refreshChallenge()` and `disconnect()` helpers\n- optional `getCaptchaToken` callback for captcha-protected flows\n- lifecycle callbacks like `onVerified` / `onError`\n\n## What gets stored\n\n- A new user row when a wallet first signs in (email is synthesised as\n  `\u003craw-address\u003e@\u003cemailDomain\u003e`, `emailVerified: false`).\n- A row in `account` with `providerId: \"ton-connect\"` and the raw address\n  as `accountId`.\n- A row in the plugin's `tonWallet` table holding `address`, `publicKey`,\n  `network`, `isPrimary`, `createdAt`, linked to the user via `userId`.\n- Session metadata `activeTonWalletAddress` and `activeTonWalletNetwork`\n  so wallet-scoped dApps can switch active context without switching user.\n\nA user may link additional wallets through `/ton-connect/link`; the first\nis flagged as primary, and the plugin refuses to unlink the last one so\nthe account can't be locked out. Use `/ton-connect/set-primary` to choose\nthe primary wallet explicitly, and `/ton-connect/switch-session-wallet`\nto rotate the active wallet for the current session.\n\n## Security notes\n\n- Challenges are stored in the Better Auth `verification` table and\n  consumed atomically via `consumeVerificationValue`, so a valid\n  `ton_proof` can only be used once.\n- Signatures older than `validAuthTimeSec` are rejected.\n- Domain policy supports exact entries, wildcard patterns (`*`), and\n  per-network rules (`mainnet`/`testnet` or `-239`/`-3`).\n- Anti-abuse controls include per-IP and per-address limits, failed\n  verification cooldown, and optional captcha challenge/verify hooks.\n  Default verify limits: `20/ip/min`, `8/address/min`.\n- The verifier follows the reference implementation from the TON\n  Connect demo dApp — it parses the `walletStateInit` to extract the\n  public key, cross-checks it against the client-reported `public_key`,\n  and re-derives the contract address to match the claimed one.\n\n## Programmatic verification\n\nYou can use the verifier on its own without the Better Auth plugin:\n\n```ts\nimport { verifyTonProof } from \"ton-better-auth\";\n\nconst result = await verifyTonProof(\n  {\n    address: \"0:…\",\n    network: \"-239\",\n    public_key: \"…\",\n    proof: { /* timestamp, domain, payload, signature, state_init */ },\n  },\n  { allowedDomains: [\"example.com\"] },\n);\n\nif (!result.ok) {\n  console.warn(\"ton_proof rejected:\", result.reason);\n}\n```\n\n## Runtime requirements (Buffer and friends)\n\n`ton-better-auth` depends on `@ton/core`, `@ton/crypto`, `@ton/ton`, and\n`tweetnacl`. These libraries expect Node.js primitives — primarily the\nglobal `Buffer` — to be available at runtime.\n\n- **Node.js 18+** — no setup needed.\n- **Bun** — `Buffer` is provided out of the box.\n- **Cloudflare Workers** — enable the Node.js compatibility flag:\n  ```toml\n  # wrangler.toml\n  compatibility_flags = [\"nodejs_compat\"]\n  compatibility_date = \"2024-09-23\"\n  ```\n- **Vercel Edge Runtime** — the edge runtime does not polyfill `Buffer`.\n  Run auth routes on the Node.js runtime (the default) by not setting\n  `export const runtime = \"edge\"` on the handler file, or add a polyfill\n  (see below).\n- **Deno** — `Buffer` is exposed via `node:buffer`; most bundlers handle\n  this automatically, but set `\"nodeModulesDir\": true` in `deno.json`\n  or import it explicitly at entry:\n  ```ts\n  import { Buffer } from \"node:buffer\";\n  globalThis.Buffer ??= Buffer;\n  ```\n- **Browser** — this plugin is designed for **server-side use**. If you\n  are running parts of it in the browser (for example, pre-verifying a\n  signature client-side before submitting it), you need to shim `Buffer`\n  through your bundler:\n  ```bash\n  npm i -D buffer\n  ```\n  Vite:\n  ```ts\n  // vite.config.ts\n  import { defineConfig } from \"vite\";\n  import { nodePolyfills } from \"vite-plugin-node-polyfills\";\n  export default defineConfig({\n    plugins: [nodePolyfills({ globals: { Buffer: true } })],\n  });\n  ```\n  Webpack 5:\n  ```ts\n  // webpack.config.js\n  resolve: {\n    fallback: { buffer: require.resolve(\"buffer/\") },\n  },\n  plugins: [\n    new webpack.ProvidePlugin({ Buffer: [\"buffer\", \"Buffer\"] }),\n  ],\n  ```\n  Next.js (edge-only routes): add a polyfill at the top of the handler:\n  ```ts\n  import { Buffer } from \"buffer\";\n  globalThis.Buffer = globalThis.Buffer ?? Buffer;\n  ```\n\nIf `Buffer` is missing, you will see errors like\n`ReferenceError: Buffer is not defined` the first time the plugin\ntouches `ton_proof`. None of this applies to a normal Next.js / Express\n/ SvelteKit server running on Node.js.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhbdev%2Fton-better-auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmhbdev%2Fton-better-auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhbdev%2Fton-better-auth/lists"}