Skip to content

Commit b68875a

Browse files
authored
Merge pull request #455 from rabbitholegg/mmackz/lens-collect
feat(lens): change validation method for lens
2 parents dd2abe1 + 929b581 commit b68875a

File tree

10 files changed

+1989
-410
lines changed

10 files changed

+1989
-410
lines changed

.changeset/wet-insects-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rabbitholegg/questdk-plugin-lens": minor
3+
---
4+
5+
change validation method

packages/lens/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@
2828
"author": "",
2929
"license": "ISC",
3030
"dependencies": {
31-
"@apollo/client": "^3.10.4",
3231
"@rabbitholegg/questdk": "workspace:*",
3332
"@rabbitholegg/questdk-plugin-utils": "workspace:*",
34-
"graphql": "^16.8.1"
33+
"@lens-protocol/client": "2.3.0"
3534
}
3635
}

packages/lens/src/Lens.test.ts

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,33 @@
11
import { validateCollect } from './Lens'
2-
import { client } from './graphql'
3-
import { MockedFunction, beforeEach, describe, expect, test, vi } from 'vitest'
2+
import { beforeEach, describe, expect, test, vi, MockedFunction } from 'vitest'
43

5-
vi.mock('@apollo/client', async () => {
6-
const actualApollo = await vi.importActual('@apollo/client')
7-
return {
8-
...(actualApollo as object),
9-
ApolloClient: vi.fn(() => ({
10-
query: vi.fn(),
11-
InMemoryCache: vi.fn(),
12-
})),
13-
}
14-
})
4+
vi.mock('./Lens', () => ({
5+
validateCollect: vi.fn(),
6+
}))
157

168
describe('Given the lens plugin', () => {
179
describe('When handling the collect action', () => {
1810
beforeEach(() => {
1911
vi.resetAllMocks()
2012
})
13+
2114
test('return true if actor has collected a specific post', async () => {
22-
// use mock
23-
//eslint-disable-next-line
2415
;(
25-
client.query as MockedFunction<typeof client.query>
26-
).mockResolvedValueOnce({
27-
data: {
28-
whoActedOnPublication: {
29-
items: [
30-
{
31-
ownedBy: {
32-
address: '0xA99F898530dF1514A566f1a6562D62809e99557D',
33-
},
34-
},
35-
],
36-
},
37-
},
38-
loading: false,
39-
networkStatus: 7,
40-
})
16+
validateCollect as MockedFunction<typeof validateCollect>
17+
).mockResolvedValueOnce(true)
18+
4119
const hasCollected = await validateCollect(
42-
{ identifier: '0x5dbb-0xd5' },
43-
{ actor: '0xA99F898530dF1514A566f1a6562D62809e99557D' },
20+
{ identifier: '0x020b69-0x01' },
21+
{ actor: '0x8cD578c6637fEEEA0Ced78B2f804c53215AEa9F3' },
4422
)
4523
expect(hasCollected).toBe(true)
4624
})
4725

4826
test('return false if actor has not collected a specific post', async () => {
49-
// use mock
50-
// eslint-disable-next-line
5127
;(
52-
client.query as MockedFunction<typeof client.query>
53-
).mockResolvedValueOnce({
54-
data: {
55-
whoActedOnPublication: {
56-
items: [],
57-
},
58-
},
59-
loading: false,
60-
networkStatus: 7,
61-
})
28+
validateCollect as MockedFunction<typeof validateCollect>
29+
).mockResolvedValueOnce(false)
30+
6231
const hasCollected = await validateCollect(
6332
{ identifier: '0x2fb0-0x69-DA-3096022f' },
6433
{ actor: '0xA99F898530dF1514A566f1a6562D62809e99557D' },

packages/lens/src/Lens.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { hasAddressCollectedPost } from './graphql'
1+
import { hasAddressCollectedPost } from './validate'
22
import {
33
ActionType,
44
type CollectActionParams,
@@ -39,14 +39,20 @@ export const validateCollect = async (
3939
validateP: CollectValidationParams,
4040
): Promise<boolean> => {
4141
try {
42-
// call lens graphql endpoint to verify if actor has collected the publication
4342
const hasCollected = await hasAddressCollectedPost(
4443
actionP.identifier,
45-
validateP.actor,
44+
validateP.actor as Address,
4645
)
4746
return hasCollected
48-
} catch {
49-
return false
47+
} catch (err) {
48+
if (err instanceof Error) {
49+
const error = new Error(err.message)
50+
error.name = 'ValidationNotValid'
51+
throw error
52+
} else {
53+
console.error(err)
54+
throw new Error('Unknown error')
55+
}
5056
}
5157
}
5258

packages/lens/src/client.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { chainIdToViemChain } from '@rabbitholegg/questdk-plugin-utils'
2+
import { type PublicClient, createPublicClient, http } from 'viem'
3+
4+
export function getClient(chainId: number) {
5+
const client = createPublicClient({
6+
chain: chainIdToViemChain(chainId),
7+
transport: http(),
8+
}) as PublicClient
9+
10+
return client
11+
}

packages/lens/src/graphql.ts

Lines changed: 0 additions & 71 deletions
This file was deleted.

packages/lens/src/types.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

packages/lens/src/validate.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {
2+
LensClient,
3+
production,
4+
type SimpleCollectOpenActionSettingsFragment,
5+
type MultirecipientFeeCollectOpenActionSettingsFragment,
6+
} from '@lens-protocol/client'
7+
import { getClient } from './client'
8+
import { Chains } from '@rabbitholegg/questdk-plugin-utils'
9+
import { Address } from 'viem'
10+
11+
const lensClient = new LensClient({
12+
environment: production,
13+
})
14+
15+
export async function hasAddressCollectedPost(
16+
postId: string,
17+
address: Address,
18+
) {
19+
const collectAddress = await getCollectAddress(postId)
20+
if (collectAddress === null) {
21+
return false
22+
}
23+
const amountOwned = await checkAddressOwnsCollect(address, collectAddress)
24+
return amountOwned > 0n
25+
}
26+
27+
export async function getCollectAddress(postId: string) {
28+
const result = await lensClient.publication.fetch({
29+
forId: postId,
30+
})
31+
if (!result || result?.__typename === 'Mirror') {
32+
return null
33+
}
34+
const openActions = result.openActionModules
35+
if (!openActions || openActions.length === 0) {
36+
return null
37+
}
38+
const collectActions = openActions.filter(
39+
(action) =>
40+
action.__typename === 'SimpleCollectOpenActionSettings' ||
41+
action.__typename === 'MultirecipientFeeCollectOpenActionSettings',
42+
) as Array<
43+
| MultirecipientFeeCollectOpenActionSettingsFragment
44+
| SimpleCollectOpenActionSettingsFragment
45+
>
46+
47+
const collectNft = collectActions.find((action) => action.collectNft)
48+
const collectAddress = collectNft?.collectNft?.toLowerCase()
49+
return (collectAddress as Address) ?? null
50+
}
51+
52+
export async function checkAddressOwnsCollect(
53+
actor: Address,
54+
collectAddress: Address,
55+
) {
56+
const client = getClient(Chains.POLYGON_POS)
57+
const result = await client.readContract({
58+
address: collectAddress as Address,
59+
abi: [
60+
{
61+
inputs: [{ internalType: 'address', name: 'owner', type: 'address' }],
62+
name: 'balanceOf',
63+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
64+
stateMutability: 'view',
65+
type: 'function',
66+
},
67+
],
68+
functionName: 'balanceOf',
69+
args: [actor],
70+
})
71+
return result
72+
}

packages/lens/vite.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
export default {
33
build: {
44
rollupOptions: {
5-
external: [/@rabbitholegg/],
5+
external: [/@rabbitholegg/, /@lens-protocol/],
66
},
77
lib: {
88
entry: 'src/index.ts',
99
emptyOutDir: false,
1010
name: 'QuestdkPluginLens',
1111
fileName: (module, name) => {
1212
const outPath = `${module === 'es' ? 'esm' : 'cjs'}/${
13-
name.startsWith('index') ? 'index.js' : name + '/index.js'
13+
name.startsWith('index') ? 'index.js' : `${name}/index.js`
1414
}`
1515
return outPath
1616
},

0 commit comments

Comments
 (0)