Skip to content

Commit

Permalink
fix: siwe map to siwx onSignOut callback (#3316)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoruka authored Nov 25, 2024
1 parent c2e0428 commit 1f586a2
Show file tree
Hide file tree
Showing 14 changed files with 773 additions and 128 deletions.
26 changes: 26 additions & 0 deletions .changeset/honest-pets-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@reown/appkit': patch
'@reown/appkit-siwe': patch
'@apps/demo': patch
'@apps/gallery': patch
'@reown/appkit-adapter-ethers': patch
'@reown/appkit-adapter-ethers5': patch
'@reown/appkit-adapter-polkadot': patch
'@reown/appkit-adapter-solana': patch
'@reown/appkit-adapter-wagmi': patch
'@reown/appkit-utils': patch
'@reown/appkit-cdn': patch
'@reown/appkit-common': patch
'@reown/appkit-core': patch
'@reown/appkit-experimental': patch
'@reown/appkit-polyfills': patch
'@reown/appkit-scaffold-ui': patch
'@reown/appkit-siwx': patch
'@reown/appkit-ui': patch
'@reown/appkit-wallet': patch
---

- Fix onSignOut not being called for activeCaipAddress changes;
- Fix signOut/onSignOut not being called on useDisconnect hook calls;
- Fix AppKitSIWEClient signIn and signOut methods to call new SIWX handlers;
- Add tests for mapToSIWX function and usage against AppKit.
12 changes: 12 additions & 0 deletions apps/laboratory/src/components/Siwe/SiweData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ export function SiweData() {
{session?.address}
</Text>
</Box>

<Box>
<Heading size="xs" textTransform="uppercase">
Session Events Called
</Heading>
{['onSignIn', 'onSignOut'].map(event => (
<Box key={event} display="flex" mr="1" pt="2">
<Text fontWeight="bold">{event}:&nbsp;</Text>
<Text data-testid={`siwe-event-${event}`}>false</Text>
</Box>
))}
</Box>
</Stack>
</CardBody>
</Card>
Expand Down
16 changes: 14 additions & 2 deletions apps/laboratory/src/utils/SiweUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { getCsrfToken, signIn, signOut, getSession } from 'next-auth/react'
import type { SIWEVerifyMessageArgs, SIWECreateMessageArgs, SIWESession } from '@reown/appkit-siwe'
import { createSIWEConfig, formatMessage } from '@reown/appkit-siwe'
import { ConstantsUtil } from './ConstantsUtil'

const chains = ConstantsUtil.EvmNetworks

export const siweConfig = createSIWEConfig({
signOutOnAccountChange: true,
signOutOnNetworkChange: true,
signOutOnDisconnect: true,
// We don't require any async action to populate params but other apps might
// eslint-disable-next-line @typescript-eslint/require-await
getMessageParams: async () => ({
Expand All @@ -29,7 +29,7 @@ export const siweConfig = createSIWEConfig({
getSession: async () => {
const session = await getSession()
if (!session) {
throw new Error('Failed to get session!')
return null
}

const { address, chainId } = session as unknown as SIWESession
Expand Down Expand Up @@ -67,5 +67,17 @@ export const siweConfig = createSIWEConfig({
} catch (error) {
return false
}
},
onSignOut() {
const element = document.querySelector("[data-testid='siwe-event-onSignOut']")
if (element) {
element.textContent = 'true'
}
},
onSignIn() {
const element = document?.querySelector("[data-testid='siwe-event-onSignIn']")
if (element) {
element.textContent = 'true'
}
}
})
8 changes: 8 additions & 0 deletions apps/laboratory/tests/shared/validators/ModalValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,21 @@ export class ModalValidator {
).toContainText('authenticated')
}

async expectOnSignInEventCalled(toBe: boolean) {
await expect(this.page.getByTestId('siwe-event-onSignIn')).toContainText(`${toBe}`)
}

async expectUnauthenticated() {
await expect(
this.page.getByTestId('w3m-authentication-status'),
'Authentication status should be: unauthenticated'
).toContainText('unauthenticated')
}

async expectOnSignOutEventCalled(toBe: boolean) {
await expect(this.page.getByTestId('siwe-event-onSignOut')).toContainText(`${toBe}`)
}

async expectSignatureDeclined() {
await expect(
this.page.getByText('Signature declined'),
Expand Down
4 changes: 4 additions & 0 deletions apps/laboratory/tests/siwe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,17 @@ siweWalletTest.afterAll(async () => {

// -- Tests --------------------------------------------------------------------
siweWalletTest('it should be authenticated', async () => {
await modalValidator.expectOnSignInEventCalled(false)
await modalPage.qrCodeFlow(modalPage, walletPage)
await modalValidator.expectConnected()
await modalValidator.expectAuthenticated()
await modalValidator.expectOnSignInEventCalled(true)
})

siweWalletTest('it should require re-authentication when switching networks', async () => {
await modalPage.switchNetwork('Polygon')
await modalValidator.expectUnauthenticated()
await modalValidator.expectOnSignOutEventCalled(true)
await modalPage.promptSiwe()
await walletPage.handleRequest({ accept: true })
await modalValidator.expectAuthenticated()
Expand All @@ -66,6 +69,7 @@ siweWalletTest('it should be authenticated when refresh page', async () => {
await modalPage.page.reload()
await modalValidator.expectConnected()
await modalValidator.expectAuthenticated()
await modalValidator.expectOnSignInEventCalled(false)
await modalPage.sign()
await walletPage.handleRequest({ accept: true })
await modalValidator.expectAcceptedSign()
Expand Down
2 changes: 1 addition & 1 deletion packages/appkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"@reown/appkit-core": "workspace:*",
"@reown/appkit-polyfills": "workspace:*",
"@reown/appkit-scaffold-ui": "workspace:*",
"@reown/appkit-siwe": "workspace:*",
"@reown/appkit-ui": "workspace:*",
"@reown/appkit-utils": "workspace:*",
"@reown/appkit-wallet": "workspace:*",
Expand All @@ -112,7 +113,6 @@
"viem": "2.x"
},
"devDependencies": {
"@reown/appkit-siwe": "workspace:*",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
"@vitest/coverage-v8": "2.1.3",
Expand Down
3 changes: 2 additions & 1 deletion packages/appkit/src/tests/mocks/Adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const mockUniversalAdapter = {
revokePermissions: vi.fn(),
on: vi.fn(),
off: vi.fn(),
emit: vi.fn()
emit: vi.fn(),
construct: vi.fn()
} as unknown as AdapterBlueprint

export default mockUniversalAdapter
237 changes: 237 additions & 0 deletions packages/appkit/src/tests/siwe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ModalController, OptionsController, RouterController, SIWXUtil } from '@reown/appkit-core'
import { AppKit } from '@reown/appkit'
import * as networks from '@reown/appkit/networks'
import { createSIWEConfig, type AppKitSIWEClient } from '@reown/appkit-siwe'
import { mockUniversalAdapter } from './mocks/Adapter'
import mockProvider from './mocks/UniversalProvider'

describe('SIWE mapped to SIWX', () => {
let siweConfig: AppKitSIWEClient
let appkit: AppKit

beforeEach(async () => {
siweConfig = createSIWEConfig({
createMessage: () => {
return 'mock-message'
},
getNonce: async () => {
return 'mock-nonce'
},
getSession: async () => {
return {
address: 'mock-address',
chainId: 1
}
},
verifyMessage: async () => {
return true
},
signOut: async () => {
return true
},
onSignIn: () => {},
onSignOut: () => {},
enabled: true,
getMessageParams: async () => {
return {
chains: [1],
domain: 'mock-domain',
uri: 'mock-uri',
nonce: 'mock-nonce'
}
},
nonceRefetchIntervalMs: 60000,
sessionRefetchIntervalMs: 120000,
signOutOnAccountChange: true,
signOutOnNetworkChange: true,
signOutOnDisconnect: true
})
appkit = new AppKit({
adapters: [mockUniversalAdapter],
projectId: 'mock-project-id',
networks: [networks.mainnet],
defaultNetwork: networks.mainnet,
siweConfig,
sdkVersion: 'html-test-test'
})

// Wait for the appkit to be ready
await new Promise(resolve => setTimeout(resolve, 1))

// Set CAIP address to represent connected state
appkit.setCaipAddress('eip155:1:mock-address', 'eip155')
})

it('should fulfill siwx', () => {
expect(OptionsController.state.siwx).toBeDefined()
})

describe('SIWXUtils against SIWE usage', () => {
it('should have siwx', async () => {
expect(SIWXUtil.getSIWX()).toBeDefined()
})

it('should initializeIfEnabled', async () => {
vi.spyOn(siweConfig.methods, 'getSession').mockResolvedValueOnce(null)
await SIWXUtil.initializeIfEnabled()

expect(RouterController.state.view).toBe('SIWXSignMessage')
expect(ModalController.state.open).toBe(true)
})

it('should requestSignMessage', async () => {
const getNonceSpy = vi.spyOn(siweConfig.methods, 'getNonce')
const createMessageSpy = vi.spyOn(siweConfig.methods, 'createMessage')
const verifyMessageSpy = vi.spyOn(siweConfig.methods, 'verifyMessage')

await SIWXUtil.requestSignMessage()

expect(getNonceSpy).toHaveBeenCalled()
expect(createMessageSpy).toHaveBeenCalled()
expect(verifyMessageSpy).toHaveBeenCalledWith({
data: {
accountAddress: 'mock-address',
chainId: 'eip155:1',
domain: 'mock-domain',
expirationTime: undefined,
issuedAt: expect.any(String),
nonce: 'mock-nonce',
notBefore: undefined,
requestId: undefined,
resources: undefined,
statement: undefined,
toString: expect.any(Function),
uri: 'mock-uri',
version: '1'
},
message: 'mock-message',
signature: ''
})
})

it('should getSessions', async () => {
const getSessionSpy = vi.spyOn(siweConfig.methods, 'getSession')
const sessions = await SIWXUtil.getSessions()

expect(getSessionSpy).toHaveBeenCalled()
expect(sessions).toEqual([
{
data: {
accountAddress: 'mock-address',
chainId: 'eip155:1'
},
message: '',
signature: ''
}
])
})

it('should universalProviderAuthenticate', async () => {
const getNonceSpy = vi.spyOn(siweConfig.methods, 'getNonce')
const verifyMessageSpy = vi.spyOn(siweConfig.methods, 'verifyMessage')

const cacao = {
h: {
t: 'caip122'
},
p: {
aud: 'mock-aud',
iss: 'did:pkh:eip155:1:mock-address',
domain: 'mock-domain',
nonce: 'mock-nonce'
},
s: {
m: 'mock-message',
s: 'mock-signature',
t: 'eip191'
}
} as const

vi.spyOn(mockProvider, 'authenticate').mockResolvedValueOnce({
session: {} as any,
auths: [cacao]
})

await SIWXUtil.universalProviderAuthenticate({
universalProvider: mockProvider,
chains: ['eip155:1'],
methods: ['eth_sign']
})

expect(getNonceSpy).toHaveBeenCalled()
expect(verifyMessageSpy).toHaveBeenCalledWith({
cacao: expect.objectContaining(cacao),
data: expect.objectContaining({
chainId: 'eip155:1',
accountAddress: 'mock-address'
}),
message: 'Formatted auth message',
signature: 'mock-signature'
})
})
})

describe('siweConfig should still work outside of AppKit', () => {
it('should execute signIn', async () => {
const requestSignMessageSpy = vi.spyOn(SIWXUtil, 'requestSignMessage')
const onSignInSpy = vi.spyOn(siweConfig.methods, 'onSignIn')

const session = await siweConfig.signIn()

expect(requestSignMessageSpy).toHaveBeenCalled()
expect(onSignInSpy).toHaveBeenCalled()
expect(session).toEqual({
address: 'mock-address',
chainId: 1
})
})

it('should execute signOut', async () => {
const setSessionsSpy = vi.spyOn(OptionsController.state.siwx!, 'setSessions')
const onSignOutSpy = vi.spyOn(siweConfig.methods, 'onSignOut')
await siweConfig.signOut()

expect(setSessionsSpy).toHaveBeenCalledWith([])
expect(onSignOutSpy).toHaveBeenCalled()
})

it('should execute getNonce', async () => {
const nonce = await siweConfig.getNonce()

expect(nonce).toBe('mock-nonce')
})

it('should execute getMessageParams', async () => {
const messageParams = await siweConfig.getMessageParams?.()

expect(messageParams).toEqual({
chains: [1],
domain: 'mock-domain',
uri: 'mock-uri',
nonce: 'mock-nonce'
})
})

it('should execute createMessage', () => {
const message = siweConfig.createMessage({} as any)

expect(message).toBe('mock-message')
})

it('should execute verifyMessage', async () => {
const isValid = await siweConfig.verifyMessage({} as any)

expect(isValid).toBe(true)
})

it('should execute getSession', async () => {
const session = await siweConfig.getSession()

expect(session).toEqual({
address: 'mock-address',
chainId: 1
})
})
})
})
Loading

0 comments on commit 1f586a2

Please sign in to comment.