Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: World ID Bridge #127

Closed
wants to merge 18 commits into from
6 changes: 5 additions & 1 deletion idkit/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 4 additions & 7 deletions idkit/src/components/IDKitWidget/States/WorldID/QRState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import LoadingIcon from '@/components/Icons/LoadingIcon'
import WorldcoinIcon from '@/components/Icons/WorldcoinIcon'

type Props = {
qrData: {
default: string
mobile: string
} | null
qrData: string | null
showQR: boolean
setShowQR: (show: boolean | ((state: boolean) => boolean)) => void
}
Expand All @@ -23,7 +20,7 @@ const QRState: FC<Props> = ({ qrData, showQR, setShowQR }) => {
const [copiedLink, setCopiedLink] = useState(false)

const copyLink = useCallback(() => {
copy(qrData?.default ?? '')
copy(qrData ?? '')

setCopiedLink(true)
setTimeout(() => setCopiedLink(false), 2000)
Expand Down Expand Up @@ -59,7 +56,7 @@ const QRState: FC<Props> = ({ qrData, showQR, setShowQR }) => {
{qrData ? (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div onClick={copyLink} className="cursor-pointer">
<Qrcode data={qrData.default} size={244} />
<Qrcode data={qrData} size={244} />
</div>
) : (
<div className="flex h-[244px] w-[244px] items-center justify-center">
Expand All @@ -72,11 +69,11 @@ const QRState: FC<Props> = ({ qrData, showQR, setShowQR }) => {
)}
<div className="space-y-4">
<motion.a
href={qrData ?? ''}
whileTap={{ scale: 0.95 }}
whileHover={{ scale: 1.05 }}
transition={{ layout: { duration: 0.15 } }}
layoutId={media == 'desktop' ? undefined : 'worldid-button'}
href={qrData?.mobile}
className={classNames(
'flex w-full space-x-2 items-center px-4 py-4 border border-transparent font-medium rounded-2xl shadow-sm',
'bg-0d151d dark:bg-white text-white dark:text-0d151d md:hidden'
Expand Down
33 changes: 12 additions & 21 deletions idkit/src/components/IDKitWidget/States/WorldIDState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ import { shallow } from 'zustand/shallow'
import { useEffect, useState } from 'react'
import type { IDKitStore } from '@/store/idkit'
import AboutWorldID from '@/components/AboutWorldID'
import useAppConnection from '@/services/walletconnect'
pdtfh marked this conversation as resolved.
Show resolved Hide resolved
import { useWorldBridge } from '@/services/wld-bridge'
import LoadingIcon from '@/components/Icons/LoadingIcon'
import WorldcoinIcon from '@/components/Icons/WorldcoinIcon'
import { AppErrorCodes, VerificationState } from '@/types/app'
import { AppErrorCodes, VerificationState } from '@/types/bridge'
import DevicePhoneMobileIcon from '@/components/Icons/DevicePhoneMobileIcon'

const getOptions = (store: IDKitStore) => ({
signal: store.signal,
app_id: store.app_id,
action: store.action,
setStage: store.setStage,
bridgeUrl: store.bridgeUrl,
handleVerify: store.handleVerify,
setErrorState: store.setErrorState,
showAbout: store.methods.length == 1,
credential_types: store.credential_types,
isExperimental: store.methods.length > 0,
hasPhone: store.methods.includes('phone'),
action_description: store.action_description,
walletConnectProjectId: store.walletConnectProjectId,
usePhone: () => store.setStage(IDKITStage.ENTER_PHONE),
})

Expand All @@ -40,20 +40,21 @@ const WorldIDState = () => {
usePhone,
handleVerify,
isExperimental,
bridgeUrl,
action_description,
credential_types,
hasPhone,
walletConnectProjectId,
setErrorState,
} = useIDKitStore(getOptions, shallow)

const { result, qrData, verificationState, reset, errorCode } = useAppConnection(
const { connectorURI, reset, errorCode, result, verificationState } = useWorldBridge(
app_id,
action,
signal,
bridgeUrl,
credential_types,
action_description,
walletConnectProjectId
action_description
)

useEffect(() => reset, [reset])
Expand All @@ -80,28 +81,18 @@ const WorldIDState = () => {
<WorldcoinIcon className="text-0d151d h-8 dark:text-white" />
</div>
<p className="font-sora text-center text-2xl font-semibold text-gray-900 dark:text-white">
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{verificationState === VerificationState.AwaitingVerification
? __('Confirm in World App')
: __('Continue with Worldcoin')}
{__('Continue with Worldcoin')}
</p>
{verificationState === VerificationState.AwaitingVerification && (
<p className="text-70868f dark:text-9eafc0 mt-3 text-center md:mt-2">
Please confirm the request in your app to continue.
</p>
)}
</div>
{verificationState === VerificationState.AwaitingVerification ? (
{verificationState === VerificationState.PreparingClient ? (
<div className="flex items-center justify-center">
<LoadingIcon className="h-20 w-20" />
</div>
) : (
<QRState showQR={showQR} setShowQR={setShowQR} qrData={qrData} />
<QRState showQR={showQR} setShowQR={setShowQR} qrData={connectorURI} />
)}
{(media == 'desktop' || !showQR) &&
(verificationState === VerificationState.AwaitingConnection ||
verificationState === VerificationState.LoadingWidget) && <AboutWorldID />}
{hasPhone && verificationState == VerificationState.AwaitingConnection && (
{(media == 'desktop' || !showQR) && <AboutWorldID />}
{hasPhone && (
<div className="hidden space-y-3 md:block">
<div className="flex items-center justify-between space-x-6">
<div className="bg-f2f5f9 dark:bg-29343f h-px flex-1" />
Expand Down
32 changes: 32 additions & 0 deletions idkit/src/lib/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { buffer_decode, buffer_encode } from './utils'

const encoder = new TextEncoder()
const decoder = new TextDecoder()

export const generateKey = async (): Promise<{ key: CryptoKey; iv: Uint8Array }> => {
return {
iv: window.crypto.getRandomValues(new Uint8Array(12)),
key: await window.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']),
}
}

export const exportKey = async (key: CryptoKey): Promise<string> => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: make it explicit this is used to export the private key?

return buffer_encode(await window.crypto.subtle.exportKey('raw', key))
}

export const encryptRequest = async (
key: CryptoKey,
iv: ArrayBuffer,
request: string
): Promise<{ payload: string; iv: string }> => {
return {
iv: buffer_encode(iv),
payload: buffer_encode(
await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoder.encode(request))
),
}
}

export const decryptResponse = async (key: CryptoKey, iv: ArrayBuffer, payload: string): Promise<string> => {
return decoder.decode(await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, buffer_decode(payload)))
}
2 changes: 1 addition & 1 deletion idkit/src/lib/hashing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { arrayify, concat, hexlify, isBytesLike } from '@ethersproject/bytes'

export interface HashFunctionOutput {
hash: BigInt
digest: string
digest: `0x${string}`
}

/**
Expand Down
8 changes: 8 additions & 0 deletions idkit/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ export const classNames = (...classes: unknown[]): string => {
export const randomNumber = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min + 1) + min)
}

export const buffer_encode = (buffer: ArrayBuffer): string => {
return Buffer.from(buffer).toString('base64')
}

export const buffer_decode = (encoded: string): ArrayBuffer => {
return Buffer.from(encoded, 'base64')
}
Loading