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.
- package/CHANGELOG.md +10 -0
- package/dist/index.js +20 -20
- package/package.json +1 -1
- package/template/openfort-templates/firebase/AGENTS.md +1 -1
- package/template/openfort-templates/firebase/package.json +3 -3
- package/template/openfort-templates/firebase/src/App.tsx +2 -1
- package/template/openfort-templates/firebase/src/integrations/openfort/providers.tsx +36 -35
- package/template/openfort-templates/firebase/src/lib/contracts.ts +34 -0
- package/template/openfort-templates/firebase/src/ui/openfort/blockchain/ActionsCard.tsx +25 -13
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletCreation.tsx +108 -63
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletListCard.tsx +211 -41
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletPasswordSheets.tsx +45 -21
- package/template/openfort-templates/headless/AGENTS.md +1 -1
- package/template/openfort-templates/headless/package.json +2 -2
- package/template/openfort-templates/headless/src/components/cards/actions.tsx +30 -21
- package/template/openfort-templates/headless/src/components/cards/profile.tsx +0 -2
- package/template/openfort-templates/headless/src/components/cards/wallets.tsx +230 -67
- package/template/openfort-templates/headless/src/components/createWallet.tsx +115 -73
- package/template/openfort-templates/headless/src/components/passwordRecovery.tsx +48 -23
- package/template/openfort-templates/headless/src/components/providers.tsx +30 -25
- package/template/openfort-templates/headless/src/lib/contracts.ts +43 -0
- package/template/openfort-templates/openfort-ui/AGENTS.md +1 -1
- package/template/openfort-templates/openfort-ui/package.json +3 -3
- package/template/openfort-templates/openfort-ui/src/components/cards/actions.tsx +30 -15
- package/template/openfort-templates/openfort-ui/src/components/cards/auth.tsx +1 -1
- package/template/openfort-templates/openfort-ui/src/components/cards/wallets.tsx +232 -67
- package/template/openfort-templates/openfort-ui/src/components/createWallet.tsx +115 -73
- package/template/openfort-templates/openfort-ui/src/components/passwordRecovery.tsx +48 -23
- package/template/openfort-templates/openfort-ui/src/components/providers.tsx +34 -30
- package/template/openfort-templates/openfort-ui/src/lib/contracts.ts +35 -0
- package/template/openfort-templates/solana-headless/biome.json +70 -0
- package/template/openfort-templates/solana-headless/index.html +16 -0
- package/template/openfort-templates/solana-headless/package.json +34 -0
- package/template/openfort-templates/solana-headless/public/githubLogo.svg +5 -0
- package/template/openfort-templates/solana-headless/public/openfort.svg +13 -0
- package/template/openfort-templates/solana-headless/public/solanaLogo.svg +11 -0
- package/template/openfort-templates/solana-headless/src/App.tsx +7 -0
- package/template/openfort-templates/solana-headless/src/components/cards/auth.tsx +167 -0
- package/template/openfort-templates/solana-headless/src/components/cards/head.tsx +359 -0
- package/template/openfort-templates/solana-headless/src/components/cards/history.tsx +134 -0
- package/template/openfort-templates/solana-headless/src/components/cards/main.tsx +140 -0
- package/template/openfort-templates/solana-headless/src/components/cards/profile.tsx +80 -0
- package/template/openfort-templates/solana-headless/src/components/cards/send.tsx +242 -0
- package/template/openfort-templates/solana-headless/src/components/cards/sign.tsx +48 -0
- package/template/openfort-templates/solana-headless/src/components/cards/wallets.tsx +199 -0
- package/template/openfort-templates/solana-headless/src/components/createWallet.tsx +117 -0
- package/template/openfort-templates/solana-headless/src/components/passwordRecovery.tsx +167 -0
- package/template/openfort-templates/solana-headless/src/components/providers.tsx +23 -0
- package/template/openfort-templates/solana-headless/src/components/ui/Sheet.tsx +47 -0
- package/template/openfort-templates/solana-headless/src/components/ui/Tabs.tsx +111 -0
- package/template/openfort-templates/solana-headless/src/components/ui/TruncateData.tsx +31 -0
- package/template/openfort-templates/solana-headless/src/hooks/useSolanaMessageSigner.ts +37 -0
- package/template/openfort-templates/solana-headless/src/index.css +180 -0
- package/template/openfort-templates/solana-headless/src/lib/errors.ts +4 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/balance.ts +17 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/index.ts +4 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/kora.ts +137 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/transaction.ts +146 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/transactionHistory.ts +39 -0
- package/template/openfort-templates/solana-headless/src/main.tsx +13 -0
- package/template/openfort-templates/solana-headless/src/vite-env.d.ts +1 -0
- package/template/openfort-templates/solana-headless/tsconfig.app.json +24 -0
- package/template/openfort-templates/solana-headless/tsconfig.json +7 -0
- package/template/openfort-templates/solana-headless/tsconfig.node.json +22 -0
- 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 {
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
-
|
|
56
|
-
<
|
|
57
|
-
className="
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
className="wallet-option
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
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
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
try {
|
|
135
|
+
await setActive({
|
|
136
|
+
address: wallet.address,
|
|
115
137
|
recoveryMethod: RecoveryMethod.PASSWORD,
|
|
116
138
|
password,
|
|
117
|
-
}
|
|
118
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
<
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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": "
|
|
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:
|
|
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:
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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
|
-
{!
|
|
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
|
|
127
|
-
<b>{chains[0].name}</b>. Set the <code>
|
|
128
|
-
environment variable with the
|
|
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 />
|