Skip to content

Commit 44fd384

Browse files
committed
feat: farcaster miniapp support
1 parent 009b123 commit 44fd384

File tree

14 files changed

+562
-9
lines changed

14 files changed

+562
-9
lines changed

.github/workflows/netlify.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ jobs:
4242
CUSTOM_ORGANIZATION_DOMAINS: ${{ secrets.CUSTOM_ORGANIZATION_DOMAINS }}
4343
STRIPE_PUBLIC_KEY: ${{ secrets.STRIPE_PUBLIC_KEY }}
4444
CALCOM_EVENT_SLUG: ${{ secrets.CALCOM_EVENT_SLUG }}
45+
APP_DOMAIN: vocdoni-app-dev.netlify.app
46+
FC_ACCOUNT_ASSOCIATION: ${{ secrets.FC_ACCOUNT_ASSOCIATION }}
4547

4648
- name: Deploy to Netlify (dev)
4749
uses: nwtgck/[email protected]
@@ -93,6 +95,8 @@ jobs:
9395
STRIPE_PUBLIC_KEY: ${{ secrets.STRIPE_PUBLIC_KEY }}
9496
CALCOM_EVENT_SLUG: ${{ secrets.CALCOM_EVENT_SLUG }}
9597
SAAS_URL: https://saas-api-stg.vocdoni.net
98+
APP_DOMAIN: vocdoni-app-stg.netlify.app
99+
FC_ACCOUNT_ASSOCIATION: ${{ secrets.FC_ACCOUNT_ASSOCIATION }}
96100

97101
- name: Deploy to Netlify (stg)
98102
uses: nwtgck/[email protected]

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"@ethersproject/abstract-signer": "^5.7.0",
2222
"@ethersproject/address": "^5.7.0",
2323
"@ethersproject/providers": "^5.7.0",
24+
"@farcaster/miniapp-sdk": "^0.2.1",
25+
"@farcaster/miniapp-wagmi-connector": "^1.1.0",
2426
"@fontsource/inter": "^5.2.5",
2527
"@gsap/react": "^2.1.2",
2628
"@lexical/code": "^0.34.0",

pnpm-lock.yaml

Lines changed: 112 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.well-known

src/Providers.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { AuthProvider } from '~components/Auth/AuthContext'
1313
import { SubscriptionProvider } from '~components/Auth/Subscription'
1414
import { CookieConsent } from '~components/shared/CookieConsent'
1515
import { walletClientToSigner } from '~constants/wagmi-adapters'
16+
import { useFarcasterSDK } from '~utils/farcaster'
1617
import { VocdoniEnvironment } from './constants'
1718
import { wagmiConfig } from './constants/rainbow'
1819
import { translations } from './i18n/components'
@@ -59,6 +60,9 @@ export const AppProviders = () => {
5960
const { address } = useAccount()
6061
const { t, i18n } = useTranslation()
6162

63+
// Initialize Farcaster SDK early (if in Farcaster miniapp)
64+
useFarcasterSDK()
65+
6266
let signer = null
6367
if (data && address && data.account.address === address) {
6468
signer = walletClientToSigner(data)

src/components/Process/Aside.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Link as ReactRouterLink } from 'react-router-dom'
99
import { useAccount } from 'wagmi'
1010
import { CensusMeta, CensusTypes } from './Census/CensusType'
1111
import { CspAuth } from './CSP/CSPAuthModal'
12+
import { isFarcasterMiniapp } from '~utils/farcaster'
1213
import LogoutButton from './LogoutButton'
1314

1415
const results = (result: number, decimals?: number) =>
@@ -179,13 +180,25 @@ export const CensusConnectButton = () => {
179180
const isCSP = election.census.type === CensusType.CSP
180181
const isSpreadsheet = census?.type === CensusTypes.Spreadsheet
181182
const isWeb3 = census?.type === CensusTypes.Web3
183+
const isInFarcaster = isFarcasterMiniapp()
182184

183185
return (
184186
<>
185187
{isWeb3 && !connected && (
186-
<Button colorScheme='black' onClick={openConnectModal} w='full'>
187-
{t('menu.connect').toString()}
188-
</Button>
188+
<>
189+
{isInFarcaster ? (
190+
// In Farcaster miniapp: show status message
191+
// The connection happens automatically in FarcasterAwareClientProvider
192+
<Text fontSize='sm' textAlign='center' color='texts.subtle'>
193+
{t('farcaster.connecting')}
194+
</Text>
195+
) : (
196+
// Not in Farcaster: show RainbowKit connect button
197+
<Button colorScheme='black' onClick={openConnectModal} w='full'>
198+
{t('menu.connect').toString()}
199+
</Button>
200+
)}
201+
</>
189202
)}
190203
{isCSP && !connected && <CspAuth />}
191204
{isSpreadsheet && !connected && <SpreadsheetAccess />}

src/elements/processes/view.tsx

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
import { Signer } from '@ethersproject/abstract-signer'
12
import { ConnectButton } from '@rainbow-me/rainbowkit'
2-
import { ElectionProvider, OrganizationProvider, useElection } from '@vocdoni/react-providers'
3-
import { PublishedElection } from '@vocdoni/sdk'
3+
import { ClientProvider, ElectionProvider, OrganizationProvider, useElection } from '@vocdoni/react-providers'
4+
import { EnvOptions, PublishedElection } from '@vocdoni/sdk'
5+
import { PropsWithChildren, useEffect, useState } from 'react'
6+
import { useTranslation } from 'react-i18next'
47
import { useLoaderData } from 'react-router-dom'
8+
import { useAccount, useWalletClient } from 'wagmi'
59
import { ProcessView as ProcessViewComponent } from '~components/Process/View'
10+
import { farcasterSignerAdapter, isFarcasterMiniapp } from '~utils/farcaster'
11+
import { VocdoniEnvironment } from '~src/constants'
12+
import { walletClientToSigner } from '~src/constants/wagmi-adapters'
13+
import { translations } from '~src/i18n/components'
14+
import { datesLocale } from '~src/i18n/locales'
615
import { useDocumentTitle } from '~src/use-document-title'
716

817
const ProcessView = () => {
@@ -15,14 +24,74 @@ const ProcessView = () => {
1524
return <ProcessViewComponent />
1625
}
1726

27+
/**
28+
* Farcaster-aware ClientProvider wrapper
29+
* Automatically uses Farcaster wallet when running in Farcaster miniapp context,
30+
* otherwise falls back to Wagmi wallet
31+
*/
32+
const FarcasterAwareClientProvider = ({ children }: PropsWithChildren) => {
33+
const { data: wagmiWalletClient } = useWalletClient()
34+
const { address } = useAccount()
35+
const { t, i18n } = useTranslation()
36+
const [farcasterSigner, setFarcasterSigner] = useState<Signer | null>(null)
37+
const [isInitialized, setIsInitialized] = useState(false)
38+
39+
// Get Farcaster signer if in miniapp
40+
useEffect(() => {
41+
const initFarcaster = async () => {
42+
if (isFarcasterMiniapp()) {
43+
try {
44+
// Get Farcaster signer (SDK already initialized in AppProviders)
45+
const signer = await farcasterSignerAdapter()
46+
setFarcasterSigner(signer)
47+
console.log('✅ Using Farcaster wallet for voting')
48+
} catch (error) {
49+
console.error('❌ Farcaster signer creation failed, falling back to Wagmi:', error)
50+
}
51+
}
52+
setIsInitialized(true)
53+
}
54+
55+
initFarcaster()
56+
}, [])
57+
58+
// Determine which signer to use
59+
let signer: Signer | null = null
60+
if (farcasterSigner) {
61+
// Use Farcaster signer if available
62+
signer = farcasterSigner
63+
} else if (wagmiWalletClient && address && wagmiWalletClient.account.address === address) {
64+
// Fall back to Wagmi wallet
65+
signer = walletClientToSigner(wagmiWalletClient)
66+
}
67+
68+
// Don't render until Farcaster initialization is complete
69+
if (!isInitialized) {
70+
return null
71+
}
72+
73+
return (
74+
<ClientProvider
75+
env={VocdoniEnvironment as EnvOptions}
76+
signer={signer as Signer}
77+
locale={translations(t)}
78+
datesLocale={datesLocale(i18n.language)}
79+
>
80+
{children}
81+
</ClientProvider>
82+
)
83+
}
84+
1885
const Process = () => {
1986
const election = useLoaderData() as PublishedElection
2087

2188
return (
2289
<OrganizationProvider id={election.organizationId}>
23-
<ElectionProvider election={election} ConnectButton={ConnectButton} fetchCensus autoUpdate>
24-
<ProcessView />
25-
</ElectionProvider>
90+
<FarcasterAwareClientProvider>
91+
<ElectionProvider election={election} ConnectButton={ConnectButton} fetchCensus autoUpdate>
92+
<ProcessView />
93+
</ElectionProvider>
94+
</FarcasterAwareClientProvider>
2695
</OrganizationProvider>
2796
)
2897
}

src/i18n/locales/ca.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@
315315
},
316316
"error_removing_code": "Hi ha hagut un error eliminant el codi",
317317
"error_text": "Ho sentim, no s'ha trobat la pàgina que cerqueu",
318+
"farcaster": {
319+
"connecting": "Connectant amb la cartera de Farcaster..."
320+
},
318321
"features": {
319322
"anonymous": "Votacions anònimes",
320323
"anonymous_voting": "Votació anònima",

src/i18n/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@
312312
},
313313
"error_removing_code": "Error removing code",
314314
"error_text": "Sorry, the page you were looking for was not found",
315+
"farcaster": {
316+
"connecting": "Connecting with Farcaster wallet..."
317+
},
315318
"features": {
316319
"anonymous": "Anonymous voting",
317320
"anonymous_voting": "Anonymous voting",

src/i18n/locales/es.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@
315315
},
316316
"error_removing_code": "Error eliminando el código",
317317
"error_text": "Lo sentimos, la página que buscabas no se ha encontrado",
318+
"farcaster": {
319+
"connecting": "Conectando con la cartera de Farcaster..."
320+
},
318321
"features": {
319322
"anonymous": "Votación anónima",
320323
"anonymous_voting": "Votación anónima",

0 commit comments

Comments
 (0)