Skip to content

Commit

Permalink
feat: siwx solana verifier (#3172)
Browse files Browse the repository at this point in the history
  • Loading branch information
zoruka authored Oct 31, 2024
1 parent 3e27610 commit 2338332
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 10 deletions.
24 changes: 24 additions & 0 deletions .changeset/fair-badgers-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@reown/appkit-adapter-solana': patch
'@reown/appkit-siwx': patch
'@apps/demo': patch
'@apps/gallery': patch
'@apps/laboratory': patch
'@reown/appkit-adapter-ethers': patch
'@reown/appkit-adapter-ethers5': patch
'@reown/appkit-adapter-polkadot': patch
'@reown/appkit-adapter-wagmi': patch
'@reown/appkit': 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-siwe': patch
'@reown/appkit-ui': patch
'@reown/appkit-wallet': patch
---

Adds SIWX Solana verifier
3 changes: 2 additions & 1 deletion packages/adapters/solana/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { W3mFrameProviderSingleton } from '@reown/appkit/auth-provider'
import { ConstantsUtil, ErrorUtil } from '@reown/appkit-utils'
import { createSendTransaction } from './utils/createSendTransaction.js'
import { CoinbaseWalletProvider } from './providers/CoinbaseWalletProvider.js'
import base58 from 'bs58'

export interface AdapterOptions {
connectionSettings?: Commitment | ConnectionConfig
Expand Down Expand Up @@ -197,7 +198,7 @@ export class SolanaAdapter implements ChainAdapter {

const signature = await provider.signMessage(new TextEncoder().encode(message))

return new TextDecoder().decode(signature)
return base58.encode(signature)
},

estimateGas: async params => {
Expand Down
2 changes: 2 additions & 0 deletions packages/siwx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"dependencies": {
"@reown/appkit-common": "workspace:*",
"@reown/appkit-core": "workspace:*",
"bs58": "6.0.0",
"tweetnacl": "1.0.3",
"viem": "2.21.34"
},
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions packages/siwx/src/configs/DefaultSIWX.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SIWXConfig } from '../core/SIWXConfig.js'
import { InformalMessenger } from '../messengers/index.js'
import { LocalStorage } from '../storages/index.js'
import { EIP155Verifier } from '../verifiers/index.js'
import { EIP155Verifier, SolanaVerifier } from '../verifiers/index.js'

const DEFAULTS = {
getDefaultMessenger: () =>
Expand All @@ -11,7 +11,7 @@ const DEFAULTS = {
getNonce: async () => Promise.resolve(Math.round(Math.random() * 10000).toString())
}),

getDefaultVerifiers: () => [new EIP155Verifier()],
getDefaultVerifiers: () => [new EIP155Verifier(), new SolanaVerifier()],

getDefaultStorage: () => new LocalStorage({ key: '@appkit/siwx' })
}
Expand Down
6 changes: 1 addition & 5 deletions packages/siwx/src/core/SIWXVerifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import type { SIWXSession } from '@reown/appkit-core'

export abstract class SIWXVerifier {
public abstract readonly chainNamespace: ChainNamespace
public abstract readonly messageVersion: string

public shouldVerify(session: SIWXSession): boolean {
return (
session.message.version === this.messageVersion &&
session.message.chainId.startsWith(this.chainNamespace)
)
return session.message.chainId.startsWith(this.chainNamespace)
}

abstract verify(session: SIWXSession): Promise<boolean>
Expand Down
4 changes: 2 additions & 2 deletions packages/siwx/src/verifiers/EIP155Verifier.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { SIWXSession } from '@reown/appkit-core'
import { SIWXVerifier } from '../core/SIWXVerifier.js'
import { verifyMessage } from 'viem'
import { ConstantsUtil } from '@reown/appkit-common'

export class EIP155Verifier extends SIWXVerifier {
public readonly chainNamespace = 'eip155'
public readonly messageVersion = '1'
public readonly chainNamespace = ConstantsUtil.CHAIN.EVM

public async verify(session: SIWXSession): Promise<boolean> {
try {
Expand Down
23 changes: 23 additions & 0 deletions packages/siwx/src/verifiers/SolanaVerifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { SIWXVerifier } from '../core/SIWXVerifier.js'
import type { SIWXSession } from '@reown/appkit-core'
import nacl from 'tweetnacl'
import bs58 from 'bs58'
import { ConstantsUtil } from '@reown/appkit-common'

export class SolanaVerifier extends SIWXVerifier {
public readonly chainNamespace = ConstantsUtil.CHAIN.SOLANA

public async verify(session: SIWXSession): Promise<boolean> {
try {
const publicKey = bs58.decode(session.message.accountAddress)
const signature = bs58.decode(session.signature)
const message = new TextEncoder().encode(session.message.toString())

const isValid = nacl.sign.detached.verify(message, signature, publicKey)

return Promise.resolve(isValid)
} catch (error) {
return Promise.resolve(false)
}
}
}
1 change: 1 addition & 0 deletions packages/siwx/src/verifiers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './EIP155Verifier.js'
export * from './SolanaVerifier.js'
22 changes: 22 additions & 0 deletions packages/siwx/tests/verifiers/EIP155Verifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ describe('EIP155Verifier', () => {
expect(verifier.chainNamespace).toBe('eip155')
})

test('should verify only eip155 chain id', () => {
expect(
verifier.shouldVerify(
mockSession({
message: {
chainId: 'eip155:1'
}
})
)
).toBe(true)

expect(
verifier.shouldVerify(
mockSession({
message: {
chainId: 'solana:mainnet'
}
})
)
).toBe(false)
})

test.each(cases)(`should verify $reason`, async ({ session, expected }) => {
expect(await verifier.verify(session)).toBe(expected)
})
Expand Down
80 changes: 80 additions & 0 deletions packages/siwx/tests/verifiers/SolanaVerifier.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, test, expect } from 'vitest'
import { type SIWXSession } from '@reown/appkit-core'
import { mockSession } from '../mocks/mockSession.js'
import { SolanaVerifier } from '../../src/verifiers/SolanaVerifier.js'

type Case = {
reason: string
session: SIWXSession
expected: boolean
}

const cases: Case[] = [
{
reason: 'valid session',
session: mockSession({
message: {
accountAddress: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgpU'
},
signature:
'2ZpgpUKF6RtmbrE8uBmPwRiBqRnsCKiBKkjsPSpf6c64r4XdDoevjhjNX35X7GeuSwwRhmbB2Ro4NfHWAeXWNhDL'
}),
expected: true
},
{
reason: 'invalid session with an invalid signature',
session: mockSession({
message: {
accountAddress: '2VqKhjZ766ZN3uBtBpb7Ls3cN4HrocP1rzxzekhVEgpU'
},
signature:
'3ErkFZkvhSJVR7E1uakGwj8icgfxvRSS6AwW5bq4CZsXPZ83XrT1H9xcCWLvhsYCLYzFc7WSMQEJxGgpZvtgqbdE'
}),
expected: false
},
{
reason: 'invalid session with an invalid account address',
session: mockSession({
message: {
accountAddress: 'C6ydkvKcRdXz3ZTEYy6uWAAyZgyUF49qP4XPdaDB2nqS'
},
signature:
'2ZpgpUKF6RtmbrE8uBmPwRiBqRnsCKiBKkjsPSpf6c64r4XdDoevjhjNX35X7GeuSwwRhmbB2Ro4NfHWAeXWNhDL'
}),
expected: false
}
]

describe('SolanaVerifier', () => {
const verifier = new SolanaVerifier()

test('should have solana as the chain namespace', () => {
expect(verifier.chainNamespace).toBe('solana')
})

test('should verify only solana chain id', () => {
expect(
verifier.shouldVerify(
mockSession({
message: {
chainId: 'solana:mainnet'
}
})
)
).toBe(true)

expect(
verifier.shouldVerify(
mockSession({
message: {
chainId: 'eip155:1'
}
})
)
).toBe(false)
})

test.each(cases)(`should verify $reason`, async ({ session, expected }) => {
expect(await verifier.verify(session)).toBe(expected)
})
})
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

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

0 comments on commit 2338332

Please sign in to comment.