create-openfort 0.1.10 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/index.js +20 -20
  3. package/package.json +1 -1
  4. package/template/openfort-templates/firebase/AGENTS.md +1 -1
  5. package/template/openfort-templates/firebase/package.json +3 -3
  6. package/template/openfort-templates/firebase/src/App.tsx +2 -1
  7. package/template/openfort-templates/firebase/src/integrations/openfort/providers.tsx +36 -35
  8. package/template/openfort-templates/firebase/src/lib/contracts.ts +34 -0
  9. package/template/openfort-templates/firebase/src/ui/openfort/blockchain/ActionsCard.tsx +25 -13
  10. package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletCreation.tsx +108 -63
  11. package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletListCard.tsx +211 -41
  12. package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletPasswordSheets.tsx +45 -21
  13. package/template/openfort-templates/headless/AGENTS.md +1 -1
  14. package/template/openfort-templates/headless/package.json +2 -2
  15. package/template/openfort-templates/headless/src/components/cards/actions.tsx +30 -21
  16. package/template/openfort-templates/headless/src/components/cards/profile.tsx +0 -2
  17. package/template/openfort-templates/headless/src/components/cards/wallets.tsx +230 -67
  18. package/template/openfort-templates/headless/src/components/createWallet.tsx +115 -73
  19. package/template/openfort-templates/headless/src/components/passwordRecovery.tsx +48 -23
  20. package/template/openfort-templates/headless/src/components/providers.tsx +30 -25
  21. package/template/openfort-templates/headless/src/lib/contracts.ts +43 -0
  22. package/template/openfort-templates/openfort-ui/AGENTS.md +1 -1
  23. package/template/openfort-templates/openfort-ui/package.json +3 -3
  24. package/template/openfort-templates/openfort-ui/src/components/cards/actions.tsx +30 -15
  25. package/template/openfort-templates/openfort-ui/src/components/cards/auth.tsx +1 -1
  26. package/template/openfort-templates/openfort-ui/src/components/cards/wallets.tsx +232 -67
  27. package/template/openfort-templates/openfort-ui/src/components/createWallet.tsx +115 -73
  28. package/template/openfort-templates/openfort-ui/src/components/passwordRecovery.tsx +48 -23
  29. package/template/openfort-templates/openfort-ui/src/components/providers.tsx +34 -30
  30. package/template/openfort-templates/openfort-ui/src/lib/contracts.ts +35 -0
  31. package/template/openfort-templates/solana-headless/biome.json +70 -0
  32. package/template/openfort-templates/solana-headless/index.html +16 -0
  33. package/template/openfort-templates/solana-headless/package.json +34 -0
  34. package/template/openfort-templates/solana-headless/public/githubLogo.svg +5 -0
  35. package/template/openfort-templates/solana-headless/public/openfort.svg +13 -0
  36. package/template/openfort-templates/solana-headless/public/solanaLogo.svg +11 -0
  37. package/template/openfort-templates/solana-headless/src/App.tsx +7 -0
  38. package/template/openfort-templates/solana-headless/src/components/cards/auth.tsx +167 -0
  39. package/template/openfort-templates/solana-headless/src/components/cards/head.tsx +359 -0
  40. package/template/openfort-templates/solana-headless/src/components/cards/history.tsx +134 -0
  41. package/template/openfort-templates/solana-headless/src/components/cards/main.tsx +140 -0
  42. package/template/openfort-templates/solana-headless/src/components/cards/profile.tsx +80 -0
  43. package/template/openfort-templates/solana-headless/src/components/cards/send.tsx +242 -0
  44. package/template/openfort-templates/solana-headless/src/components/cards/sign.tsx +48 -0
  45. package/template/openfort-templates/solana-headless/src/components/cards/wallets.tsx +199 -0
  46. package/template/openfort-templates/solana-headless/src/components/createWallet.tsx +117 -0
  47. package/template/openfort-templates/solana-headless/src/components/passwordRecovery.tsx +167 -0
  48. package/template/openfort-templates/solana-headless/src/components/providers.tsx +23 -0
  49. package/template/openfort-templates/solana-headless/src/components/ui/Sheet.tsx +47 -0
  50. package/template/openfort-templates/solana-headless/src/components/ui/Tabs.tsx +111 -0
  51. package/template/openfort-templates/solana-headless/src/components/ui/TruncateData.tsx +31 -0
  52. package/template/openfort-templates/solana-headless/src/hooks/useSolanaMessageSigner.ts +37 -0
  53. package/template/openfort-templates/solana-headless/src/index.css +180 -0
  54. package/template/openfort-templates/solana-headless/src/lib/errors.ts +4 -0
  55. package/template/openfort-templates/solana-headless/src/lib/solana/balance.ts +17 -0
  56. package/template/openfort-templates/solana-headless/src/lib/solana/index.ts +4 -0
  57. package/template/openfort-templates/solana-headless/src/lib/solana/kora.ts +137 -0
  58. package/template/openfort-templates/solana-headless/src/lib/solana/transaction.ts +146 -0
  59. package/template/openfort-templates/solana-headless/src/lib/solana/transactionHistory.ts +39 -0
  60. package/template/openfort-templates/solana-headless/src/main.tsx +13 -0
  61. package/template/openfort-templates/solana-headless/src/vite-env.d.ts +1 -0
  62. package/template/openfort-templates/solana-headless/tsconfig.app.json +24 -0
  63. package/template/openfort-templates/solana-headless/tsconfig.json +7 -0
  64. package/template/openfort-templates/solana-headless/tsconfig.node.json +22 -0
  65. package/template/openfort-templates/solana-headless/vite.config.ts +8 -0
@@ -2,119 +2,161 @@ import {
2
2
  FingerPrintIcon,
3
3
  KeyIcon,
4
4
  LockClosedIcon,
5
+ WalletIcon,
5
6
  } from '@heroicons/react/24/outline'
6
- import { RecoveryMethod, useWallets } from '@openfort/react'
7
+ import { AccountTypeEnum, RecoveryMethod } from '@openfort/react'
8
+ import { useEthereumEmbeddedWallet } from '@openfort/react/ethereum'
7
9
  import { useState } from 'react'
8
10
  import { CreateWalletPasswordSheet } from './passwordRecovery'
9
11
  import { Sheet } from './ui/Sheet'
10
12
 
11
- export const CreateWalletSheet = ({
12
- open,
13
- onClose,
14
- }: {
13
+ type CreateStep = 'choose-account-type' | 'choose-recovery-method'
14
+
15
+ type CreateWalletSheetProps = {
15
16
  open: boolean
16
17
  onClose: () => void
17
- }) => {
18
+ onWalletCreated?: () => void
19
+ }
20
+
21
+ export const CreateWalletSheet = ({ open, onClose, onWalletCreated }: CreateWalletSheetProps) => {
18
22
  return (
19
23
  <Sheet
20
24
  open={open}
21
- onClose={() => {
22
- onClose()
23
- }}
25
+ onClose={onClose}
24
26
  title="Create Wallet"
25
27
  description="Please choose a recovery method for your new wallet."
26
28
  >
27
29
  <CreateWallet
28
30
  onWalletCreated={() => {
29
31
  onClose()
32
+ onWalletCreated?.()
30
33
  }}
31
34
  />
32
35
  </Sheet>
33
36
  )
34
37
  }
35
38
 
36
- export const CreateWallet = ({
37
- onWalletCreated,
38
- }: {
39
- onWalletCreated?: () => void
40
- }) => {
39
+ export const CreateWallet = ({ onWalletCreated }: { onWalletCreated?: () => void }) => {
40
+ const { create, status } = useEthereumEmbeddedWallet()
41
+ const [error, setError] = useState<string | null>(null)
42
+ const [step, setStep] = useState<CreateStep>('choose-account-type')
43
+ const [accountType, setAccountType] = useState<AccountTypeEnum>(AccountTypeEnum.SMART_ACCOUNT)
41
44
  const [passwordSheetOpen, setPasswordSheetOpen] = useState(false)
42
45
 
43
- const { isCreating, createWallet, error } = useWallets({
44
- onSuccess: () => {
45
- onWalletCreated?.()
46
- },
47
- })
46
+ const isCreating = status === 'creating'
48
47
 
49
48
  if (isCreating) {
50
49
  return <div>Creating wallet...</div>
51
50
  }
52
51
 
52
+ const handleCreate = async (recoveryMethod: RecoveryMethod, password?: string) => {
53
+ try {
54
+ await create({ recoveryMethod, accountType, ...(password && { password }) })
55
+ onWalletCreated?.()
56
+ } catch (err) {
57
+ setError(err instanceof Error ? err.message : 'Failed to create wallet')
58
+ }
59
+ }
60
+
53
61
  return (
54
62
  <>
55
- <div className="flex flex-col gap-4 wallet-option-group mb-4">
56
- <button
57
- className="wallet-option cursor-pointer"
58
- onClick={() =>
59
- createWallet({
60
- recovery: {
61
- recoveryMethod: RecoveryMethod.PASSKEY,
62
- },
63
- })
64
- }
65
- >
66
- <FingerPrintIcon />
67
- <div className="flex flex-col text-start">
68
- <h4>Passkey</h4>
69
- <p className="text-sm hover-description">
70
- Secure your wallet with biometric authentication.
71
- </p>
72
- </div>
73
- </button>
74
- <button
75
- className="wallet-option cursor-pointer"
76
- onClick={() =>
77
- createWallet({
78
- recovery: {
79
- recoveryMethod: RecoveryMethod.AUTOMATIC,
80
- },
81
- })
82
- }
83
- >
84
- <LockClosedIcon />
85
- <div className="flex flex-col text-start">
86
- <h4>Automatic recovery</h4>
87
- <p className="text-sm hover-description">
88
- Uses encryption session to recover your wallet.
89
- </p>
90
- </div>
91
- </button>
92
- <button
93
- className="wallet-option cursor-pointer"
94
- onClick={() => setPasswordSheetOpen(true)}
95
- >
96
- <KeyIcon />
97
- <div className="flex flex-col text-start">
98
- <h4>Password</h4>
99
- <p className="text-sm hover-description">
100
- Create a strong password to secure your wallet.
101
- </p>
63
+ {step === 'choose-account-type' && (
64
+ <div className="flex flex-col gap-4 wallet-option-group mb-4">
65
+ <p className="text-sm text-zinc-400">Select account type:</p>
66
+ {Object.values(AccountTypeEnum).map((type) => (
67
+ <button
68
+ key={type}
69
+ type="button"
70
+ className="wallet-option cursor-pointer"
71
+ onClick={() => {
72
+ setAccountType(type)
73
+ setStep('choose-recovery-method')
74
+ }}
75
+ >
76
+ <WalletIcon className="h-6 w-6 shrink-0" />
77
+ <div className="flex flex-col text-start">
78
+ <h4>{type}</h4>
79
+ <p className="text-sm hover-description">
80
+ {type === AccountTypeEnum.EOA && 'A standard externally owned account.'}
81
+ {type === AccountTypeEnum.SMART_ACCOUNT &&
82
+ 'A smart contract wallet (also creates an EOA owner).'}
83
+ {type === AccountTypeEnum.DELEGATED_ACCOUNT && 'A delegated smart account.'}
84
+ </p>
85
+ </div>
86
+ </button>
87
+ ))}
88
+ </div>
89
+ )}
90
+
91
+ {step === 'choose-recovery-method' && (
92
+ <>
93
+ <button
94
+ type="button"
95
+ className="text-xs text-zinc-400 mb-4 hover:text-zinc-200 flex items-center gap-1"
96
+ onClick={() => setStep('choose-account-type')}
97
+ >
98
+ ← Back ·{' '}
99
+ <span className="text-zinc-300">{accountType}</span>
100
+ </button>
101
+ <div className="flex flex-col gap-4 wallet-option-group mb-4">
102
+ <p className="text-sm text-zinc-400">Select recovery method:</p>
103
+ <button
104
+ type="button"
105
+ className="wallet-option cursor-pointer"
106
+ onClick={() => handleCreate(RecoveryMethod.PASSKEY)}
107
+ >
108
+ <FingerPrintIcon />
109
+ <div className="flex flex-col text-start">
110
+ <h4>Passkey</h4>
111
+ <p className="text-sm hover-description">
112
+ Secure your wallet with biometric authentication.
113
+ </p>
114
+ </div>
115
+ </button>
116
+ <button
117
+ type="button"
118
+ className="wallet-option cursor-pointer"
119
+ onClick={() => handleCreate(RecoveryMethod.AUTOMATIC)}
120
+ >
121
+ <LockClosedIcon />
122
+ <div className="flex flex-col text-start">
123
+ <h4>Automatic recovery</h4>
124
+ <p className="text-sm hover-description">
125
+ Uses encryption session to recover your wallet.
126
+ </p>
127
+ </div>
128
+ </button>
129
+ <button
130
+ type="button"
131
+ className="wallet-option cursor-pointer"
132
+ onClick={() => setPasswordSheetOpen(true)}
133
+ >
134
+ <KeyIcon />
135
+ <div className="flex flex-col text-start">
136
+ <h4>Password</h4>
137
+ <p className="text-sm hover-description">
138
+ Create a strong password to secure your wallet.
139
+ </p>
140
+ </div>
141
+ </button>
102
142
  </div>
103
- </button>
104
- </div>
105
- {error && (
106
- <p className="text-red-500 text-sm mb-2">Error: {error.message}</p>
143
+ </>
107
144
  )}
145
+
146
+ {error && <p className="text-red-500 text-sm mb-2">Error: {error}</p>}
147
+
108
148
  <p className="mb-4 text-xs text-zinc-400">
109
149
  Disclaimer: This is a demo of Openfort recovery methods. In production,
110
150
  it's best to choose one method for a smoother user experience.
111
151
  </p>
152
+
112
153
  <CreateWalletPasswordSheet
113
154
  open={passwordSheetOpen}
114
155
  onClose={() => setPasswordSheetOpen(false)}
115
- onCreateWallet={() => {
116
- onWalletCreated?.()
117
- }}
156
+ create={create}
157
+ status={status}
158
+ accountType={accountType}
159
+ onCreateWallet={onWalletCreated}
118
160
  />
119
161
  </>
120
162
  )
@@ -1,25 +1,44 @@
1
1
  import { CheckCircleIcon } from '@heroicons/react/24/outline'
2
- import { RecoveryMethod, type UserWallet, useWallets } from '@openfort/react'
2
+ import {
3
+ AccountTypeEnum,
4
+ RecoveryMethod,
5
+ type ConnectedEmbeddedEthereumWallet,
6
+ } from '@openfort/react'
7
+ import type { EmbeddedAccount } from '@openfort/react'
8
+ import { useEthereumEmbeddedWallet } from '@openfort/react/ethereum'
9
+ import { useState } from 'react'
3
10
  import { Sheet } from './ui/Sheet'
4
11
 
5
12
  type CreateWalletPasswordSheetProps = {
6
13
  open: boolean
7
14
  onClose: () => void
8
15
  onCreateWallet?: () => void
16
+ create: (options: {
17
+ recoveryMethod: RecoveryMethod
18
+ accountType?: AccountTypeEnum
19
+ password?: string
20
+ }) => Promise<EmbeddedAccount>
21
+ status: string
22
+ accountType: AccountTypeEnum
9
23
  }
24
+
10
25
  export const CreateWalletPasswordSheet = ({
11
26
  open,
12
27
  onClose,
13
28
  onCreateWallet,
29
+ create,
30
+ status,
31
+ accountType,
14
32
  }: CreateWalletPasswordSheetProps) => {
15
- const { createWallet, error, isCreating, reset } = useWallets()
33
+ const [error, setError] = useState<string | null>(null)
34
+ const isCreating = status === 'creating'
16
35
 
17
36
  return (
18
37
  <Sheet
19
38
  open={open}
20
39
  onClose={() => {
21
40
  onClose()
22
- reset()
41
+ setError(null)
23
42
  }}
24
43
  title="Enter Password"
25
44
  description="Please enter the password of your wallet."
@@ -31,40 +50,41 @@ export const CreateWalletPasswordSheet = ({
31
50
  const formData = new FormData(e.target as HTMLFormElement)
32
51
  const password = formData.get('password') as string
33
52
 
34
- const { error } = await createWallet({
35
- recovery: {
53
+ try {
54
+ await create({
36
55
  recoveryMethod: RecoveryMethod.PASSWORD,
56
+ accountType,
37
57
  password,
38
- },
39
- })
40
-
41
- if (!error) {
58
+ })
42
59
  onCreateWallet?.()
43
60
  onClose()
61
+ } catch (err) {
62
+ setError(err instanceof Error ? err.message : 'Failed to create wallet')
44
63
  }
45
64
  }}
46
65
  >
47
66
  <div className="flex flex-col gap-2 mr-4 mb-4">
48
67
  <div className="flex items-center gap-2">
49
68
  <CheckCircleIcon className="h-5 w-5 text-primary my-4 shrink-0" />
50
- <span>This password will be used to secure your account</span>
69
+ <span>This password will be used to secure your account.</span>
51
70
  </div>
52
71
  <div className="flex items-center gap-2">
53
72
  <CheckCircleIcon className="h-5 w-5 text-primary my-4 shrink-0" />
54
73
  <span>
55
74
  If you lose this password, you will not be able to access your
56
- wallet
75
+ wallet.
57
76
  </span>
58
77
  </div>
59
78
  </div>
60
79
  <input
61
80
  type="password"
62
81
  name="password"
82
+ autoComplete="new-password"
63
83
  placeholder="Enter your wallet's password"
64
84
  className="w-full mt-2 p-2 border border-gray-300 rounded"
65
85
  />
66
86
  {error && (
67
- <span className="text-red-500 text-sm mt-2">{error?.message}</span>
87
+ <span className="text-red-500 text-sm mt-2">{error}</span>
68
88
  )}
69
89
  <button
70
90
  className="mt-4 w-full bg-zinc-700 text-white p-2 rounded cursor-pointer"
@@ -81,7 +101,7 @@ export const CreateWalletPasswordSheet = ({
81
101
  type WalletRecoverPasswordProps = {
82
102
  open: boolean
83
103
  onClose: () => void
84
- wallet: UserWallet | null
104
+ wallet: ConnectedEmbeddedEthereumWallet | null
85
105
  }
86
106
 
87
107
  export const WalletRecoverPasswordSheet = ({
@@ -89,34 +109,38 @@ export const WalletRecoverPasswordSheet = ({
89
109
  onClose,
90
110
  wallet,
91
111
  }: WalletRecoverPasswordProps) => {
92
- const { setActiveWallet, error, isConnecting, reset } = useWallets()
112
+ const { setActive, status } = useEthereumEmbeddedWallet()
113
+ const [error, setError] = useState<string | null>(null)
114
+ const isConnecting = status === 'connecting'
93
115
 
94
116
  return (
95
117
  <Sheet
96
118
  open={open}
97
119
  onClose={() => {
98
120
  onClose()
99
- reset()
121
+ setError(null)
100
122
  }}
101
123
  title="Enter Password"
102
124
  description="Please enter the password of your wallet."
103
125
  >
104
126
  <form
105
127
  className="w-full flex-1 flex flex-col justify-center"
106
- onSubmit={(e) => {
128
+ onSubmit={async (e) => {
107
129
  e.preventDefault()
108
130
  const formData = new FormData(e.target as HTMLFormElement)
109
131
  const password = formData.get('password') as string
110
132
  if (!wallet) throw new Error('No wallet to recover')
111
133
 
112
- setActiveWallet({
113
- walletId: 'xyz.openfort',
114
- recovery: {
134
+ try {
135
+ await setActive({
136
+ address: wallet.address,
115
137
  recoveryMethod: RecoveryMethod.PASSWORD,
116
138
  password,
117
- },
118
- address: wallet.address,
119
- })
139
+ })
140
+ onClose()
141
+ } catch (err) {
142
+ setError(err instanceof Error ? err.message : 'Failed to recover wallet')
143
+ }
120
144
  }}
121
145
  >
122
146
  {wallet && (
@@ -128,11 +152,12 @@ export const WalletRecoverPasswordSheet = ({
128
152
  <input
129
153
  type="password"
130
154
  name="password"
155
+ autoComplete="current-password"
131
156
  placeholder="Enter your wallet's password"
132
157
  className="w-full mt-2 p-2 border border-gray-300 rounded"
133
158
  />
134
159
  {error && (
135
- <span className="text-red-500 text-sm mt-2">{error?.message}</span>
160
+ <span className="text-red-500 text-sm mt-2">{error}</span>
136
161
  )}
137
162
  <button
138
163
  className="mt-4 w-full bg-zinc-700 text-white p-2 rounded cursor-pointer"
@@ -1,40 +1,45 @@
1
- import { getDefaultConfig, OpenfortProvider } from '@openfort/react'
1
+ import { OpenfortProvider } from '@openfort/react'
2
+ import { getDefaultConfig, OpenfortWagmiBridge } from '@openfort/react/wagmi'
2
3
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
3
- import { useState } from 'react'
4
4
  import { beamTestnet, polygonAmoy } from 'viem/chains'
5
5
  import { createConfig, WagmiProvider } from 'wagmi'
6
6
 
7
7
  const config = createConfig(
8
8
  getDefaultConfig({
9
- appName: 'Openfort Next.js demo',
9
+ appName: 'Openfort React demo',
10
10
  chains: [polygonAmoy, beamTestnet], // The chains you want to support
11
11
  walletConnectProjectId: import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID, // The WalletConnect Project ID
12
12
  }),
13
13
  )
14
14
 
15
+ const queryClient = new QueryClient()
16
+
17
+ const walletConfig = {
18
+ shieldPublishableKey: import.meta.env.VITE_SHIELD_PUBLISHABLE_KEY!, // The public key for your Openfort Shield account get it from https://dashboard.openfort.io
19
+ ethereum: {
20
+ ethereumFeeSponsorshipId: import.meta.env.VITE_FEE_SPONSORSHIP_ID, // The fee sponsorship ID for sponsoring transactions
21
+ },
22
+ // If you want to use AUTOMATIC embedded wallet recovery, an encryption session is required.
23
+ // See: https://www.openfort.io/docs/products/embedded-wallet/react-native/quickstart/automatic
24
+ // For backend setup, check: https://github.com/openfort-xyz/openfort-backend-quickstart
25
+ createEncryptedSessionEndpoint: import.meta.env.VITE_CREATE_ENCRYPTED_SESSION_ENDPOINT,
26
+ connectOnLogin: true,
27
+ }
28
+
15
29
  export function Providers({ children }: { children: React.ReactNode }) {
16
- const [queryClient] = useState(() => new QueryClient())
17
30
  return (
18
- <WagmiProvider config={config}>
19
- <QueryClientProvider client={queryClient}>
20
- <OpenfortProvider
21
- debugMode
22
- publishableKey={import.meta.env.VITE_OPENFORT_PUBLISHABLE_KEY!}
23
- // Set the wallet configuration. In this example, we will be using the embedded signer.
24
- walletConfig={{
25
- shieldPublishableKey: import.meta.env.VITE_SHIELD_PUBLISHABLE_KEY!, // The public key for your Openfort Shield account get it from https://dashboard.openfort.io
26
- ethereumProviderPolicyId: import.meta.env.VITE_POLICY_ID, // The policy ID for sponsoring transactions
27
- // If you want to use AUTOMATIC embedded wallet recovery, an encryption session is required.
28
- // See: https://www.openfort.io/docs/products/embedded-wallet/react-native/quickstart/automatic
29
- // For backend setup, check: https://github.com/openfort-xyz/openfort-backend-quickstart
30
- createEncryptedSessionEndpoint: import.meta.env
31
- .VITE_CREATE_ENCRYPTED_SESSION_ENDPOINT,
32
- recoverWalletAutomaticallyAfterAuth: true, // We will manually call create/setActive wallet after auth
33
- }}
34
- >
35
- {children}
36
- </OpenfortProvider>
37
- </QueryClientProvider>
38
- </WagmiProvider>
31
+ <QueryClientProvider client={queryClient}>
32
+ <WagmiProvider config={config}>
33
+ <OpenfortWagmiBridge>
34
+ <OpenfortProvider
35
+ debugMode
36
+ publishableKey={import.meta.env.VITE_OPENFORT_PUBLISHABLE_KEY!}
37
+ walletConfig={walletConfig}
38
+ >
39
+ {children}
40
+ </OpenfortProvider>
41
+ </OpenfortWagmiBridge>
42
+ </WagmiProvider>
43
+ </QueryClientProvider>
39
44
  )
40
45
  }
@@ -0,0 +1,43 @@
1
+ /** Chain IDs for mint contracts (viem/wagmi) */
2
+ const BEAM_CHAIN_ID = 13337
3
+ const POLYGON_CHAIN_ID = 80002
4
+ const BASE_SEPOLIA_CHAIN_ID = 84532
5
+
6
+ /** Fallback addresses when env vars are not set */
7
+ const DEFAULT_POLYGON_MINT = '0xef147ed8bb07a2a0e7df4c1ac09e96dec459ffac'
8
+ const DEFAULT_BEAM_MINT = '0x45238AB60ACA6862a70fe996D1A8baDb71Af5A8f'
9
+
10
+ type MintContractType = 'claim' | 'mint'
11
+
12
+ interface MintContractConfig {
13
+ address: string
14
+ type: MintContractType
15
+ }
16
+
17
+ /**
18
+ * Returns the mint contract address for the given chainId.
19
+ * Uses VITE_BEAM_MINT_CONTRACT and VITE_POLYGON_MINT_CONTRACT when set.
20
+ */
21
+ function getMintContractAddress(chainId: number | undefined): string | undefined {
22
+ if (chainId == null) return undefined
23
+ if (chainId === BEAM_CHAIN_ID) {
24
+ return import.meta.env.VITE_BEAM_MINT_CONTRACT ?? DEFAULT_BEAM_MINT
25
+ }
26
+ if (chainId === POLYGON_CHAIN_ID || chainId === BASE_SEPOLIA_CHAIN_ID) {
27
+ return import.meta.env.VITE_POLYGON_MINT_CONTRACT ?? DEFAULT_POLYGON_MINT
28
+ }
29
+ return import.meta.env.VITE_POLYGON_MINT_CONTRACT ?? DEFAULT_POLYGON_MINT
30
+ }
31
+
32
+ /**
33
+ * Returns MintContractConfig for the given chainId.
34
+ * Beam uses claim(amount), Polygon uses mint(address, amount).
35
+ */
36
+ export function getMintContractConfig(chainId: number | undefined): MintContractConfig | undefined {
37
+ const address = getMintContractAddress(chainId)
38
+ if (!address) return undefined
39
+ return {
40
+ address,
41
+ type: chainId === BEAM_CHAIN_ID ? 'claim' : 'mint',
42
+ }
43
+ }
@@ -96,7 +96,7 @@ Required environment variables with UI configuration:
96
96
  ```env
97
97
  VITE_OPENFORT_PUBLISHABLE_KEY=pk_test_...
98
98
  VITE_SHIELD_PUBLISHABLE_KEY=sk_test_...
99
- VITE_POLICY_ID=pol_...
99
+ VITE_FEE_SPONSORSHIP_ID=pol_...
100
100
  VITE_CREATE_ENCRYPTED_SESSION_ENDPOINT=/api/shield-session
101
101
  VITE_WALLET_CONNECT_PROJECT_ID=your-wallet-connect-project-id
102
102
  VITE_OPENFORT_THEME=light|dark
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@heroicons/react": "^2.2.0",
15
- "@openfort/react": "latest",
15
+ "@openfort/react": "workspace:^",
16
16
  "@tanstack/react-query": ">=5.45.1",
17
17
  "react": "^18.2.0",
18
18
  "react-dom": "^18.2.0",
@@ -21,11 +21,11 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@biomejs/biome": "^2.3.8",
24
+ "@tailwindcss/vite": "^4.1.15",
24
25
  "@types/react": "^19.1.10",
25
26
  "@types/react-dom": "^19.1.7",
26
- "@tailwindcss/vite": "^4.1.15",
27
- "tailwindcss": "^4.1.15",
28
27
  "@vitejs/plugin-react": "^4.3.4",
28
+ "tailwindcss": "^4.1.15",
29
29
  "typescript": "~5.8.3",
30
30
  "vite": "^6.4.1"
31
31
  }
@@ -2,21 +2,25 @@ import { useMemo } from 'react'
2
2
  import { getAddress, parseAbi } from 'viem'
3
3
  import {
4
4
  useAccount,
5
+ useChainId,
5
6
  useChains,
6
7
  useReadContract,
7
8
  useWriteContract
8
9
  } from 'wagmi'
10
+ import { getMintContractConfig } from '../../lib/contracts'
9
11
  import { TruncateData } from '../ui/TruncateData'
10
12
 
11
13
  const MintContract = () => {
12
14
  const { address } = useAccount()
15
+ const chainId = useChainId()
16
+ const config = getMintContractConfig(chainId)
13
17
 
14
18
  const {
15
19
  data: balance,
16
20
  refetch,
17
21
  error: balanceError,
18
22
  } = useReadContract({
19
- address: '0xef147ed8bb07a2a0e7df4c1ac09e96dec459ffac',
23
+ address: (config?.address ?? undefined) as `0x${string}` | undefined,
20
24
  abi: [
21
25
  {
22
26
  type: 'function',
@@ -27,11 +31,11 @@ const MintContract = () => {
27
31
  },
28
32
  ],
29
33
  functionName: 'balanceOf',
30
- args: [address!],
34
+ args: config && address ? [address] : undefined,
31
35
  })
32
36
 
33
37
  const { data: tokenSymbol } = useReadContract({
34
- address: '0xef147ed8bb07a2a0e7df4c1ac09e96dec459ffac',
38
+ address: (config?.address ?? undefined) as `0x${string}` | undefined,
35
39
  abi: [
36
40
  {
37
41
  type: 'function',
@@ -62,12 +66,23 @@ const MintContract = () => {
62
66
  })
63
67
 
64
68
  async function submit({ amount }: { amount: string }) {
65
- writeContract({
66
- address: getAddress('0xef147ed8bb07a2a0e7df4c1ac09e96dec459ffac'),
67
- abi: parseAbi(['function mint(address to, uint256 amount)']),
68
- functionName: 'mint',
69
- args: [address!, BigInt(amount)],
70
- })
69
+ if (!config?.address) return
70
+ const amountWei = BigInt(amount) * BigInt(10 ** 18)
71
+ if (config.type === 'claim') {
72
+ writeContract({
73
+ address: getAddress(config.address),
74
+ abi: parseAbi(['function claim(uint256 amount)']),
75
+ functionName: 'claim',
76
+ args: [amountWei],
77
+ })
78
+ } else {
79
+ writeContract({
80
+ address: getAddress(config.address),
81
+ abi: parseAbi(['function mint(address to, uint256 amount)']),
82
+ functionName: 'mint',
83
+ args: [address!, amountWei],
84
+ })
85
+ }
71
86
  }
72
87
 
73
88
  return (
@@ -90,7 +105,7 @@ const MintContract = () => {
90
105
  className="grow peer"
91
106
  name="amount"
92
107
  />
93
- <button className="btn" disabled={isPending || !address}>
108
+ <button className="btn" disabled={isPending || !address || !config}>
94
109
  {isPending ? 'Minting...' : 'Mint Tokens'}
95
110
  </button>
96
111
  </form>
@@ -102,7 +117,7 @@ const MintContract = () => {
102
117
  }
103
118
 
104
119
  export const Actions = () => {
105
- const hasSponsorPolicy = useMemo(() => !!import.meta.env.VITE_POLICY_ID, [])
120
+ const hasFeeSponsorship = useMemo(() => !!import.meta.env.VITE_FEE_SPONSORSHIP_ID, [])
106
121
  const chains = useChains()
107
122
  return (
108
123
  <div className="flex flex-col w-full">
@@ -110,7 +125,7 @@ export const Actions = () => {
110
125
  <span className="mb-4 text-zinc-400 text-sm">
111
126
  Interact with smart contracts on the blockchain.
112
127
  </span>
113
- {!hasSponsorPolicy && (
128
+ {!hasFeeSponsorship && (
114
129
  <div className="mb-3 p-3 bg-red-800 text-white rounded text-sm">
115
130
  <strong>Warning: Transactions are not sponsored.</strong> Minting may
116
131
  fail because transactions are not being sponsored. To sponsor
@@ -123,9 +138,9 @@ export const Actions = () => {
123
138
  >
124
139
  Openfort Dashboard
125
140
  </a>{' '}
126
- and <b>create a policy</b> sponsoring transactions in{' '}
127
- <b>{chains[0].name}</b>. Set the <code>VITE_POLICY_ID</code>{' '}
128
- environment variable with the policy ID.
141
+ and <b>create a fee sponsorship</b> for transactions in{' '}
142
+ <b>{chains[0].name}</b>. Set the <code>VITE_FEE_SPONSORSHIP_ID</code>{' '}
143
+ environment variable with the fee sponsorship ID.
129
144
  </div>
130
145
  )}
131
146
  <MintContract />