Repository: lens-protocol/lens-sdk
Branch: main
Commit: 342abd5efb3b
Files: 423
Total size: 1.9 MB
Directory structure:
gitextract_d8sflke8/
├── .changeset/
│ ├── README.md
│ ├── config.json
│ ├── giant-buckets-melt.md
│ ├── nice-baboons-protect.md
│ └── pre.json
├── .editorconfig
├── .github/
│ ├── actions/
│ │ ├── setup/
│ │ │ └── action.yml
│ │ └── tests/
│ │ └── action.yml
│ └── workflows/
│ ├── dependency-review.yml
│ ├── pull-request.yml
│ ├── snapshot.yml
│ ├── trigger-tests.yml
│ └── verify.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .vscode/
│ └── settings.json
├── LICENSE
├── README.md
├── biome.json
├── examples/
│ ├── create-app/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── custom-fragments/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── nextjs-client/
│ │ ├── .gitignore
│ │ ├── .stackblitzrc
│ │ ├── README.md
│ │ ├── next.config.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── app/
│ │ │ ├── Web3Providers.tsx
│ │ │ ├── client.ts
│ │ │ ├── globals.css
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── tsconfig.json
│ ├── react-follow/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── AccountToFollow.tsx
│ │ │ ├── App.tsx
│ │ │ ├── FollowButton.tsx
│ │ │ ├── Web3Providers.tsx
│ │ │ ├── client.ts
│ │ │ ├── config.ts
│ │ │ ├── main.tsx
│ │ │ ├── vite-env.d.ts
│ │ │ └── wallet.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── react-login/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ ├── LoginForm.tsx
│ │ │ ├── LogoutButton.tsx
│ │ │ ├── MyAccount.tsx
│ │ │ ├── Web3Providers.tsx
│ │ │ ├── client.ts
│ │ │ ├── config.ts
│ │ │ ├── main.tsx
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── react-post/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ ├── client.ts
│ │ │ ├── config.ts
│ │ │ ├── main.tsx
│ │ │ ├── vite-env.d.ts
│ │ │ └── wallet.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── react-post-action/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ ├── client.ts
│ │ │ ├── config.ts
│ │ │ ├── main.tsx
│ │ │ ├── vite-env.d.ts
│ │ │ └── wallet.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── sponsored-tx/
│ │ ├── .stackblitzrc
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── sponsored-tx-poc/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── thirdweb-onramp/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ └── main.tsx
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ └── user-onboarding/
│ ├── README.md
│ ├── index.html
│ ├── index.ts
│ ├── package.json
│ └── tsconfig.json
├── jest-extended.d.ts
├── package.json
├── packages/
│ ├── client/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── AuthenticatedUser.ts
│ │ │ ├── actions/
│ │ │ │ ├── account.test.ts
│ │ │ │ ├── account.ts
│ │ │ │ ├── accountManager.test.ts
│ │ │ │ ├── accountManager.ts
│ │ │ │ ├── actions.ts
│ │ │ │ ├── admins.ts
│ │ │ │ ├── app.ts
│ │ │ │ ├── authentication.ts
│ │ │ │ ├── feed.ts
│ │ │ │ ├── follow.ts
│ │ │ │ ├── frames.ts
│ │ │ │ ├── funds.e2e.ts
│ │ │ │ ├── funds.ts
│ │ │ │ ├── graph.ts
│ │ │ │ ├── group.e2e.ts
│ │ │ │ ├── group.ts
│ │ │ │ ├── helpers.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── metadata.test.ts
│ │ │ │ ├── metadata.ts
│ │ │ │ ├── misc.ts
│ │ │ │ ├── ml.e2e.ts
│ │ │ │ ├── ml.ts
│ │ │ │ ├── namespace.ts
│ │ │ │ ├── notifications.test.ts
│ │ │ │ ├── notifications.ts
│ │ │ │ ├── onboarding.e2e.ts
│ │ │ │ ├── post.test.ts
│ │ │ │ ├── post.ts
│ │ │ │ ├── posts.test.ts
│ │ │ │ ├── posts.ts
│ │ │ │ ├── sns.ts
│ │ │ │ ├── sponsorship.ts
│ │ │ │ ├── timeline.ts
│ │ │ │ ├── tipping.e2e.ts
│ │ │ │ ├── transactions.ts
│ │ │ │ ├── transfer.ts
│ │ │ │ └── username.ts
│ │ │ ├── authorization.ts
│ │ │ ├── batch.ts
│ │ │ ├── cache.ts
│ │ │ ├── clients.test.ts
│ │ │ ├── clients.ts
│ │ │ ├── config.ts
│ │ │ ├── context.ts
│ │ │ ├── crossRegion.e2e.ts
│ │ │ ├── errors.ts
│ │ │ ├── ethers/
│ │ │ │ ├── __snapshots__/
│ │ │ │ │ └── encoding.test.ts.snap
│ │ │ │ ├── encoding.test.ts
│ │ │ │ ├── encoding.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── signer.test.ts
│ │ │ │ ├── signer.ts
│ │ │ │ ├── sponsorship.test.ts
│ │ │ │ └── sponsorship.ts
│ │ │ ├── fragments.test.ts
│ │ │ ├── fragments.ts
│ │ │ ├── index.ts
│ │ │ ├── logger.ts
│ │ │ ├── sponsorship.ts
│ │ │ ├── test-utils.ts
│ │ │ ├── tokens.ts
│ │ │ ├── types.ts
│ │ │ ├── utils.ts
│ │ │ └── viem/
│ │ │ ├── __snapshots__/
│ │ │ │ └── encoding.test.ts.snap
│ │ │ ├── authorization.test.ts
│ │ │ ├── authorization.ts
│ │ │ ├── encoding.test.ts
│ │ │ ├── encoding.ts
│ │ │ ├── index.ts
│ │ │ ├── signer.test.ts
│ │ │ ├── signer.ts
│ │ │ ├── sponsorship.test.ts
│ │ │ ├── sponsorship.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ ├── env/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ ├── graphql/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── schema.d.ts
│ │ ├── schema.graphql
│ │ ├── scripts/
│ │ │ └── fetch-schema.ts
│ │ ├── src/
│ │ │ ├── accounts/
│ │ │ │ ├── account.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers.ts
│ │ │ │ └── signless.ts
│ │ │ ├── actions.ts
│ │ │ ├── admins.ts
│ │ │ ├── app.ts
│ │ │ ├── authentication.ts
│ │ │ ├── common.ts
│ │ │ ├── enums.ts
│ │ │ ├── feed.ts
│ │ │ ├── follow.ts
│ │ │ ├── fragments/
│ │ │ │ ├── account.ts
│ │ │ │ ├── common.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── media.ts
│ │ │ │ ├── metadata.ts
│ │ │ │ ├── pagination.ts
│ │ │ │ ├── post.ts
│ │ │ │ ├── primitives.ts
│ │ │ │ ├── transactions.ts
│ │ │ │ └── username.ts
│ │ │ ├── frames.ts
│ │ │ ├── funds.ts
│ │ │ ├── graph.ts
│ │ │ ├── graphql-env.d.ts
│ │ │ ├── graphql.ts
│ │ │ ├── group.ts
│ │ │ ├── index.ts
│ │ │ ├── metadata.ts
│ │ │ ├── misc.ts
│ │ │ ├── ml.ts
│ │ │ ├── namespace.ts
│ │ │ ├── notifications.ts
│ │ │ ├── post.ts
│ │ │ ├── refinements.ts
│ │ │ ├── scalars.ts
│ │ │ ├── schema.json
│ │ │ ├── schema.ts
│ │ │ ├── sns.ts
│ │ │ ├── sponsorship.ts
│ │ │ ├── test-utils.ts
│ │ │ ├── timeline.ts
│ │ │ ├── transactions.ts
│ │ │ ├── transferOwnership.ts
│ │ │ └── username.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ ├── react/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── LensProvider.tsx
│ │ │ ├── account/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useAccount.ts
│ │ │ │ ├── useAccountFeedsStats.ts
│ │ │ │ ├── useAccountManagers.ts
│ │ │ │ ├── useAccountStats.ts
│ │ │ │ ├── useAccounts.ts
│ │ │ │ ├── useAccountsBlocked.ts
│ │ │ │ ├── useAccountsBulk.ts
│ │ │ │ ├── useAddAccountManager.ts
│ │ │ │ ├── useBalancesBulk.ts
│ │ │ │ ├── useCreateAccountWithFreeUsername.ts
│ │ │ │ ├── useCreateAccountWithRestrictedUsername.ts
│ │ │ │ ├── useEnableSignless.ts
│ │ │ │ ├── useRemoveAccountManager.ts
│ │ │ │ ├── useRemoveSignless.ts
│ │ │ │ ├── useSetAccountMetadata.ts
│ │ │ │ └── useWhoExecutedActionOnAccount.ts
│ │ │ ├── app/
│ │ │ │ ├── index.ts
│ │ │ │ └── useAppUsers.ts
│ │ │ ├── authentication/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useAccountsAvailable.ts
│ │ │ │ ├── useAuthenticatedUser.test.ts
│ │ │ │ ├── useAuthenticatedUser.ts
│ │ │ │ ├── useLogin.test.ts
│ │ │ │ ├── useLogin.ts
│ │ │ │ ├── useLogout.ts
│ │ │ │ ├── useMeDetails.ts
│ │ │ │ ├── usePublicClient.ts
│ │ │ │ ├── useSessionClient.test.ts
│ │ │ │ ├── useSessionClient.ts
│ │ │ │ └── useSwitchAccount.ts
│ │ │ ├── context.tsx
│ │ │ ├── ethers/
│ │ │ │ ├── index.ts
│ │ │ │ └── useUnknownPostActionEncoder.ts
│ │ │ ├── feed/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useFeed.ts
│ │ │ │ └── useFeeds.ts
│ │ │ ├── follow/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useFollow.ts
│ │ │ │ ├── useFollowStatus.ts
│ │ │ │ ├── useFollowers.ts
│ │ │ │ ├── useFollowersYouKnow.ts
│ │ │ │ ├── useFollowing.ts
│ │ │ │ └── useUnfollow.ts
│ │ │ ├── graph/
│ │ │ │ ├── index.ts
│ │ │ │ └── useGraph.ts
│ │ │ ├── group/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useGroup.ts
│ │ │ │ ├── useGroupBannedAccounts.ts
│ │ │ │ ├── useGroupMembers.ts
│ │ │ │ ├── useGroupMembershipRequests.ts
│ │ │ │ └── useGroups.ts
│ │ │ ├── helpers/
│ │ │ │ ├── index.ts
│ │ │ │ ├── reads.ts
│ │ │ │ ├── results.ts
│ │ │ │ ├── tasks.test.ts
│ │ │ │ └── tasks.ts
│ │ │ ├── index.ts
│ │ │ ├── ml/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useAccountRecommendations.ts
│ │ │ │ ├── useDismissRecommendedAccounts.ts
│ │ │ │ ├── usePostsForYou.ts
│ │ │ │ └── usePostsToExplore.ts
│ │ │ ├── notification/
│ │ │ │ ├── index.ts
│ │ │ │ └── useNotifications.ts
│ │ │ ├── post/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useBookmarkPost.ts
│ │ │ │ ├── useCreatePost.test.ts
│ │ │ │ ├── useCreatePost.ts
│ │ │ │ ├── useExecutePostAction.ts
│ │ │ │ ├── usePost.ts
│ │ │ │ ├── usePostBookmarks.ts
│ │ │ │ ├── usePostReactions.ts
│ │ │ │ ├── usePostReferences.ts
│ │ │ │ ├── usePostTags.ts
│ │ │ │ ├── usePosts.ts
│ │ │ │ ├── useUndoBookmarkPost.ts
│ │ │ │ ├── useWhoExecutedActionOnPost.ts
│ │ │ │ └── useWhoReferencedPost.ts
│ │ │ ├── test-utils.tsx
│ │ │ ├── timeline/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useTimeline.ts
│ │ │ │ └── useTimelineHighlights.ts
│ │ │ ├── tokenDistribution/
│ │ │ │ ├── index.ts
│ │ │ │ └── useTokenDistributions.ts
│ │ │ ├── username/
│ │ │ │ ├── index.ts
│ │ │ │ ├── useCanCreateUsername.ts
│ │ │ │ ├── useNamespace.ts
│ │ │ │ └── useUsernames.ts
│ │ │ └── viem/
│ │ │ ├── index.ts
│ │ │ └── useUnknownPostActionEncoder.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ ├── tsup.config.ts
│ │ └── vitest.setup.ts
│ ├── storage/
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── BaseStorageSchema.ts
│ │ │ ├── CredentialsStorageSchema.ts
│ │ │ ├── IStorage.ts
│ │ │ ├── InMemoryStorageProvider.ts
│ │ │ ├── Storage.test.ts
│ │ │ ├── Storage.ts
│ │ │ ├── __helpers__/
│ │ │ │ └── mocks.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ └── types/
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src/
│ │ ├── errors.ts
│ │ ├── helpers/
│ │ │ ├── Deferred.ts
│ │ │ ├── assertions.ts
│ │ │ ├── fail.ts
│ │ │ ├── identity.ts
│ │ │ ├── index.ts
│ │ │ ├── invariant.ts
│ │ │ ├── never.ts
│ │ │ ├── refinements.ts
│ │ │ └── typeguards.ts
│ │ ├── hex.ts
│ │ ├── id.ts
│ │ ├── index.ts
│ │ ├── jwt.ts
│ │ ├── misc.ts
│ │ ├── number.ts
│ │ ├── tag.ts
│ │ └── uri.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ └── tsup.config.ts
├── plopfile.ts
├── pnpm-workspace.yaml
├── renovate.json
├── templates/
│ ├── example-react/
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── App.tsx
│ │ │ ├── client.ts
│ │ │ ├── main.tsx
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ └── lib/
│ ├── README.md.hbs
│ ├── package.json.hbs
│ ├── src/
│ │ └── index.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ └── tsup.config.ts
├── tsconfig.base.json
├── tsconfig.json
├── turbo.json
├── vite-env.d.ts
├── vitest.config.ts
├── vitest.d.ts
└── vitest.setup.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .changeset/README.md
================================================
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
================================================
FILE: .changeset/config.json
================================================
{
"$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["@lens-protocol/client", "@lens-protocol/react"]],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
================================================
FILE: .changeset/giant-buckets-melt.md
================================================
---
"@lens-protocol/client": minor
"@lens-protocol/react": minor
---
**feat**: add post action encoder and execution hooks
================================================
FILE: .changeset/nice-baboons-protect.md
================================================
---
"@lens-protocol/client": major
"@lens-protocol/react": major
"@lens-protocol/storage": minor
"@lens-protocol/env": minor
"@lens-protocol/graphql": minor
"@lens-protocol/types": minor
---
**chore**: transition to new major release
================================================
FILE: .changeset/pre.json
================================================
{
"mode": "pre",
"tag": "alpha",
"initialVersions": {
"@lens-protocol/client": "2.3.2",
"@lens-protocol/env": "0.0.1",
"@lens-protocol/graphql": "0.0.1",
"@lens-protocol/react": "2.3.2",
"@lens-protocol/storage": "0.8.1",
"@lens-protocol/types": "0.0.1"
},
"changesets": [
"nice-baboons-protect"
]
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 100
================================================
FILE: .github/actions/setup/action.yml
================================================
name: 'Setup'
description: 'Setup repo and install dependencies'
runs:
using: 'composite'
steps:
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install Dependencies
shell: bash
run: pnpm install --frozen-lockfile
================================================
FILE: .github/actions/tests/action.yml
================================================
name: 'Test Workflow'
description: 'Run tests against a specified environment'
inputs:
environment:
description: 'Environment to run tests against'
required: true
default: 'staging'
private_key:
description: 'Private key for authentication'
required: true
global_sponsorship:
description: 'The Global Sponsorship address'
required: true
sponsorship_approver_private_key:
description: 'Private key for a Global Sponsorship Signer'
required: true
publish_results:
description: 'Publish test results'
required: false
default: 'false'
test_app:
description: 'A valid Lens App address'
required: true
test_account:
description: 'A Lens Account address'
required: true
test_erc20:
description: 'An ERC20 token address'
required: true
runs:
using: 'composite'
steps:
- uses: actions/checkout@v4
- name: Setup Repository
uses: ./.github/actions/setup
- name: Build
shell: bash
run: pnpm build
- name: Setup Environment Variables
shell: bash
run: |
echo "PRIVATE_KEY=${{ inputs.private_key }}" >> .env
echo "TEST_APP=${{ inputs.test_app }}" >> .env
echo "TEST_ACCOUNT=${{ inputs.test_account }}" >> .env
echo "TEST_ERC20=${{ inputs.test_erc20 }}" >> .env
echo "ENVIRONMENT=${{ inputs.environment }}" >> .env
echo "GLOBAL_SPONSORSHIP=${{ inputs.global_sponsorship }}" >> .env
echo "SPONSORSHIP_APPROVER_PRIVATE_KEY=${{ inputs.sponsorship_approver_private_key }}" >> .env
- name: Run Tests
shell: bash
run: pnpm test
- name: Publish Test Results
if: ${{ inputs.publish_results == 'true' }}
shell: bash
run: |
echo "Publishing test results..."
# Add logic to upload test results to a service or save artifacts
echo "Test results published."
================================================
FILE: .github/workflows/dependency-review.yml
================================================
name: Dependency Review
on:
- pull_request
permissions:
contents: read
pull-requests: write
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v5
- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
comment-summary-in-pr: on-failure
fail-on-severity: moderate
license-check: false
================================================
FILE: .github/workflows/pull-request.yml
================================================
name: Pull Request
on:
workflow_dispatch:
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
verify:
name: Verify
uses: ./.github/workflows/verify.yml
secrets: inherit
================================================
FILE: .github/workflows/snapshot.yml
================================================
name: Canary Release
on:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
verify:
name: Verify
uses: ./.github/workflows/verify.yml
secrets: inherit
publish:
name: Publish Snapshot
runs-on: ubuntu-latest
needs: verify
steps:
- uses: actions/checkout@v4
- name: Setup Repository
uses: ./.github/actions/setup
- name: Configure NPM Auth Token
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NODE_AUTH_TOKEN }}" >> ~/.npmrc
- name: Create Snapshot
id: create_snapshot
run: pnpm changeset version --snapshot canary || echo "No unreleased changesets found"
- name: Check for Changeset
id: check_changeset
run: echo "changeset_exists=$(grep -q 'No unreleased changesets found' <<< '${{ steps.create_snapshot.outputs.stdout }}' && echo false || echo true)" >> $GITHUB_ENV
- name: Build
if: env.changeset_exists == 'true'
run: pnpm build
- name: Publish Snapshot
if: env.changeset_exists == 'true'
run: pnpm changeset publish --snapshot --tag canary --no-git-tag
================================================
FILE: .github/workflows/trigger-tests.yml
================================================
name: Run Tests on Demand
on:
repository_dispatch:
types: [on-demand-test]
workflow_dispatch:
inputs:
environment:
description: 'Environment to run tests against'
required: true
default: 'staging'
type: choice
options:
- staging
- testnet
publish_results:
description: 'Publish test results'
required: false
default: 'true'
type: string
jobs:
tests:
name: Full Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Tests
uses: ./.github/actions/tests
with:
environment: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment || github.event.client_payload.lens_env || 'staging' }}
private_key: ${{ secrets.PRIVATE_KEY }}
test_app: ${{ vars.TEST_APP }}
test_account: ${{ vars.TEST_ACCOUNT }}
test_erc20: ${{ vars.TEST_ERC20 }}
global_sponsorship: ${{ secrets.GLOBAL_SPONSORSHIP }}
sponsorship_approver_private_key: ${{ secrets.SPONSORSHIP_APPROVER_PRIVATE_KEY }}
publish_results: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.publish_results || 'true' }}
================================================
FILE: .github/workflows/verify.yml
================================================
name: 'Verify'
on:
workflow_call:
workflow_dispatch:
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: 2.0.6
- name: Run Biome
run: biome ci .
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Tests
uses: ./.github/actions/tests
with:
environment: 'staging'
private_key: ${{ secrets.PRIVATE_KEY }}
test_app: ${{ vars.TEST_APP }}
test_account: ${{ vars.TEST_ACCOUNT }}
test_erc20: ${{ vars.TEST_ERC20 }}
global_sponsorship: ${{ secrets.GLOBAL_SPONSORSHIP }}
sponsorship_approver_private_key: ${{ secrets.SPONSORSHIP_APPROVER_PRIVATE_KEY }}
publish_results: 'false'
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
.pnp
.pnp.js
# build output
**/dist
/docs
*/graphql-cache.d.ts
# misc
.DS_Store
*.pem
# turbo
.turbo
# lockfiles
package-lock.json
yarn.lock
examples/*/pnpm-lock.yaml
# debug
.pnpm-debug.log*
# local env files
.env
# typescript
*.tsbuildinfo
# cursor
.cursorignore
================================================
FILE: .npmrc
================================================
engine-strict=true
auto-install-peers=false
strict-peer-dependencies=false
link-workspace-packages=false
================================================
FILE: .nvmrc
================================================
20.13.1
================================================
FILE: .prettierignore
================================================
# use biome instead
*
================================================
FILE: .vscode/settings.json
================================================
{
"[graphql]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": false
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.removeUnused.ts": "never",
"source.removeUnusedImports": "never",
"source.organizeImports.biome": "explicit"
},
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.removeUnused.ts": "never",
"source.removeUnusedImports": "never",
"source.organizeImports.biome": "explicit"
},
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.quickSuggestions": {
"strings": true
},
"editor.formatOnSave": true,
"editor.suggest.insertMode": "replace"
},
"disabledExtensions": ["Orta.vscode-jest"],
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) Avara Labs Cayman Holdings SEZC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Lens SDK
The official SDK for the Lens 🌿.
## Table of Contents
- [Installation](#installation)
- [Development Workflow](#development-workflow)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)
## Installation
## Development Workflow
This section is for developers who want to contribute to the SDK.
### Initial Setup
Clone the repository:
```bash
git clone https://github.com/lens-network/sdk.git
```
Install the dependencies:
```bash
pnpm install
```
### Pre-requisites:
- Node.js: >= v20. See [installation guide](https://nodejs.org/en/download/package-manager).
- pnpm: v9.1.2. See [installation guide](https://pnpm.io/installation).
Use [nvm](https://github.com/nvm-sh/nvm) to manage your Node.js versions. Run the following command in the project root folder:
```bash
nvm use
```
to switch to the correct Node.js version.
Enable [corepack](https://www.totaltypescript.com/how-to-use-corepack) to use the the correct version of `pnpm`.
Run the following command in the project root folder:
```bash
corepack install
```
to install the correct version once. After that corepack will automatically use the correct version of `pnpm` when entering the project folder.
### Usage
Run the tests:
- `pnpm test:client`: Run the tests for the `@lens-protocol/client` package.
Lint the code:
```bash
pnpm lint
```
Compile the code:
```bash
pnpm build
```
Clean the build:
```bash
pnpm clean
```
Create a new package:
```bash
pnpm new:package
```
### IDE Setup
The project uses [Biome](https://biomejs.dev/) to format and lint the code. You can install the Biome extension for your IDE: https://biomejs.dev/guides/editors/first-party-extensions/
### Publishing
1. Create a new release branch using the `release/X.Y.Z` naming convention.
2. Bumps up version number and updates the changelog.
```bash
pnpm changeset version
```
3. Commit the changes using `chore: bumps up version number` as the commit message.
4. Push the changes to the remote repository.
5. Open a pull request to the `main` branch.
6. Wait for all checks to pass and for the pull request to be approved.
7. Publish the package.
```bash
pnpm changeset publish
```
8. Push tags to the remote repository.
```bash
git push --follow-tags
```
9. Merge the pull request to the `main` branch.
## Troubleshooting
### Incompatible Types Across Packages
Working within a monorepo can sometimes lead to type incompatibilities across packages. If you encounter an error like:
```bash
Type 'import("[...]/packages/client/dist/index").PublicClient' is not assignable to type 'import("[...]/packages/client/src/clients").PublicClient'.
```
This usually indicates that TypeScript is picking up types from different versions of the same package. To resolve this, make sure you have configured the entry points correctly as aliases in the top level `tsconfig.json` file.
```json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"skipLibCheck": true,
"types": ["node"],
"paths": {
"@lens-protocol/client": ["./packages/client/src"],
"@lens-protocol/client/actions": ["./packages/client/src/actions"],
"@lens-protocol/client/test-utils": ["./packages/client/src/test-utils"],
"@lens-protocol/env": ["./packages/env/src"],
"@lens-protocol/graphql": ["./packages/graphql/src"],
"@lens-protocol/react": ["./packages/react/src"],
"@lens-protocol/storage": ["./packages/storage/src"],
"@lens-protocol/types": ["./packages/types/src"]
}
},
"include": ["**/*.ts"],
"exclude": ["dist", "node_modules"]
}
```
## Contributing
We are currently focused on launching Lens Network mainnet and Lens Protocol v3. We are not able to accept contributions at this time. We will update this section in due course.
If you have a pressing issue or feature request, please open an issue on GitHub.
## License
Lens SDK is [MIT licensed](./LICENSE).
================================================
FILE: biome.json
================================================
{
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"includes": ["**"]
},
"formatter": {
"enabled": true,
"useEditorconfig": true
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "single"
}
},
"json": {
"formatter": {
"enabled": false
},
"linter": {
"enabled": false
}
},
"overrides": [
{
"includes": ["**/*.tsx"],
"linter": {
"domains": {
"react": "all"
}
}
},
{
"includes": ["templates/**/*"],
"formatter": {
"enabled": false
},
"linter": {
"enabled": false
}
},
{
"includes": ["packages/**/*/dist/**"],
"formatter": {
"enabled": false
},
"linter": {
"enabled": false
}
},
{
"includes": ["packages/**/*.e2e.ts","packages/**/*.test.ts"],
"linter": {
"rules": {
"style": {
"noNonNullAssertion": "off"
}
}
}
},
{
"includes": ["packages/graphql/**/*.graphql"],
"formatter": {
"enabled": false
},
"linter": {
"enabled": false
}
},
{
"includes": ["packages/graphql/src/graphql-*.d.ts"],
"formatter": {
"enabled": false
},
"linter": {
"enabled": false
},
"assist": { "actions": { "source": { "organizeImports": "off" } } }
},
{
"includes": ["examples/**/*.ts", "examples/**/*.tsx"],
"linter": {
"rules": {
"style": {
"noNonNullAssertion": "off"
}
}
}
}
]
}
================================================
FILE: examples/create-app/README.md
================================================
# Create an App
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/create-app)
================================================
FILE: examples/create-app/index.html
================================================
Create an App
Loading...
================================================
FILE: examples/create-app/index.ts
================================================
import 'viem/window';
import { chains } from '@lens-chain/sdk/viem';
import { immutable, StorageClient } from '@lens-chain/storage-client';
import { PublicClient, testnet, uri } from '@lens-protocol/client';
import { createApp, fetchApp } from '@lens-protocol/client/actions';
import { handleOperationWith } from '@lens-protocol/client/viem';
import { app, Platform } from '@lens-protocol/metadata';
import { type Address, createWalletClient, custom } from 'viem';
const chain = chains.testnet;
// hoist account
const [address] = (await window.ethereum!.request({
method: 'eth_requestAccounts',
})) as [Address];
const walletClient = createWalletClient({
account: address,
chain,
transport: custom(window.ethereum!),
});
const client = PublicClient.create({
environment: testnet,
});
const sessionClient = await client
.login({
builder: {
address: walletClient.account.address,
},
signMessage: async (message) => walletClient.signMessage({ message }),
})
.match(
(result) => result,
(error) => {
throw error;
},
);
const storageClient = StorageClient.create();
const metadata = app({
name: 'My App',
url: 'https://example.com',
description: 'My app description',
platforms: [Platform.WEB],
developer: 'me@example.com',
});
const resource = await storageClient.uploadAsJson(metadata, {
acl: immutable(chain.id),
});
const created = await createApp(sessionClient, {
metadataUri: uri(resource.uri),
defaultFeed: {
globalFeed: true,
},
graph: {
globalGraph: true,
},
namespace: {
globalNamespace: true,
},
})
.andThen(handleOperationWith(walletClient))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchApp(sessionClient, { txHash }))
.match(
(result) => result,
(error) => {
throw error;
},
);
export default [
`${created?.metadata?.name}
`,
`Address: ${await created?.address}
`,
];
================================================
FILE: examples/create-app/package.json
================================================
{
"name": "example-create-app",
"private": true,
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "latest",
"@lens-protocol/client": "canary",
"@lens-protocol/metadata": "latest",
"@lens-chain/storage-client": "latest",
"viem": "^2.21.55"
},
"devDependencies": {
"typescript": "^5.6.3",
"vite": "^5.4.11"
}
}
================================================
FILE: examples/create-app/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM"],
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["./"]
}
================================================
FILE: examples/custom-fragments/README.md
================================================
# Custom Fragments
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/custom-fragments)
================================================
FILE: examples/custom-fragments/index.html
================================================
Custom Fragments
Loading...
================================================
FILE: examples/custom-fragments/index.ts
================================================
import {
type Account,
ArticleMetadataFragment,
AudioMetadataFragment,
evmAddress,
type FragmentOf,
graphql,
ImageMetadataFragment,
PublicClient,
TextOnlyMetadataFragment,
testnet,
UsernameFragment,
VideoMetadataFragment,
} from '@lens-protocol/client';
import { fetchAccount } from '@lens-protocol/client/actions';
const AccountFragment = graphql(
`fragment Account on Account {
__typename
handle: username {
...Username
}
}`,
[UsernameFragment],
);
const PostMetadataFragment = graphql(
`fragment PostMetadata on PostMetadata {
__typename
... on ArticleMetadata {
content
}
... on AudioMetadata {
content
}
... on TextOnlyMetadata {
content
}
... on ImageMetadata {
content
}
... on VideoMetadata {
content
}
}`,
[
ArticleMetadataFragment,
AudioMetadataFragment,
TextOnlyMetadataFragment,
ImageMetadataFragment,
VideoMetadataFragment,
],
);
const PostFieldsFragment = graphql(
`fragment PostFields on Post {
metadata {
...PostMetadata
}
}`,
[PostMetadataFragment],
);
declare module '@lens-protocol/client' {
export interface Account extends FragmentOf {}
export interface PostFields extends FragmentOf {}
export type PostMetadata = FragmentOf;
}
const client = PublicClient.create({
environment: testnet,
fragments: [AccountFragment, PostFieldsFragment],
});
const account: Account | null = await fetchAccount(client, {
address: evmAddress('0x57b62a1571F4F09CDB4C3d93dA542bfe142D9F81'),
}).unwrapOr(null);
export default [
`${account?.handle?.value}
`,
`${JSON.stringify(account, null, 2)}`,
];
================================================
FILE: examples/custom-fragments/package.json
================================================
{
"name": "example-custom-fragments",
"private": true,
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-protocol/client": "canary",
"viem": "^2.21.55"
},
"devDependencies": {
"typescript": "^5.6.3",
"vite": "^5.4.11"
}
}
================================================
FILE: examples/custom-fragments/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM"],
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["./"]
}
================================================
FILE: examples/nextjs-client/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: examples/nextjs-client/.stackblitzrc
================================================
{
"startCommand": "npm run dev",
"env": {}
}
================================================
FILE: examples/nextjs-client/README.md
================================================
# Next.js - Lens Client Integration Example
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/nextjs-client)
================================================
FILE: examples/nextjs-client/next.config.ts
================================================
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
================================================
FILE: examples/nextjs-client/package.json
================================================
{
"name": "nextjs-client",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@lens-protocol/react": "canary",
"next": "15.4.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"simpledotcss": "^2.3.3"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"typescript": "^5"
}
}
================================================
FILE: examples/nextjs-client/src/app/Web3Providers.tsx
================================================
'use client';
import { LensProvider } from '@lens-protocol/react';
import { client } from './client';
export function Web3Providers({ children }: { children: React.ReactNode }) {
return {children};
}
================================================
FILE: examples/nextjs-client/src/app/client.ts
================================================
import { PublicClient, testnet } from '@lens-protocol/react';
export const client = PublicClient.create({
environment: testnet,
});
================================================
FILE: examples/nextjs-client/src/app/globals.css
================================================
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
================================================
FILE: examples/nextjs-client/src/app/layout.tsx
================================================
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import 'simpledotcss/simple.min.css';
import './globals.css';
import { Suspense } from 'react';
import { Web3Providers } from './Web3Providers';
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
});
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
});
export const metadata: Metadata = {
title: 'Next.js - Lens Client Integration',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
Loading...}>{children}
);
}
================================================
FILE: examples/nextjs-client/src/app/page.tsx
================================================
'use client';
import { AccountsOrderBy, PageSize, useAccounts } from '@lens-protocol/react';
export default function Home() {
const { data } = useAccounts({
orderBy: AccountsOrderBy.AccountScore,
pageSize: PageSize.Ten,
suspense: true,
});
return (
Top 10 Accounts by Account Score:
{data.items.map((account) => (
- {account.username?.value}
))}
);
}
================================================
FILE: examples/nextjs-client/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: examples/react-follow/README.md
================================================
# Follow/Unfollow Accounts on Lens
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/react-follow)
================================================
FILE: examples/react-follow/index.html
================================================
Log in to Lens
================================================
FILE: examples/react-follow/package.json
================================================
{
"name": "react-follow",
"description": "Follow/Unfollow accounts on Lens",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "latest",
"@lens-protocol/react": "canary",
"@tanstack/react-query": "^5.63.0",
"connectkit": "^1.9.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"viem": "^2.22.4",
"wagmi": "^2.14.6"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.8.1",
"typescript": "^5.6.3",
"vite": "^5.4.9"
}
}
================================================
FILE: examples/react-follow/src/AccountToFollow.tsx
================================================
import { PageSize, useAccounts } from '@lens-protocol/react';
import { FollowButton } from './FollowButton';
export function AccountsToFollow() {
const { data } = useAccounts({
filter: { searchBy: { localNameQuery: 'test' } },
pageSize: PageSize.Ten,
suspense: true,
});
return (
);
}
================================================
FILE: examples/react-follow/src/App.tsx
================================================
import { AccountsToFollow } from './AccountToFollow';
export function App() {
return (
);
}
================================================
FILE: examples/react-follow/src/FollowButton.tsx
================================================
import { type Account, useFollow, useUnfollow } from '@lens-protocol/react';
import { handleOperationWith } from '@lens-protocol/react/viem';
import { useWalletClient } from 'wagmi';
export function FollowButton({ account }: { account: Account }) {
const { data: wallet } = useWalletClient();
const { execute: follow, loading: followLoading } = useFollow({
handler: handleOperationWith(wallet),
});
const { execute: unfollow, loading: unfollowLoading } = useUnfollow({
handler: handleOperationWith(wallet),
});
const loading = followLoading || unfollowLoading;
const handleFollowToggle = async () => {
const result = account.operations?.isFollowedByMe
? await unfollow({ account: account.address })
: await follow({ account: account.address });
if (result.isErr()) {
alert(result.error.message);
return;
}
};
return (
);
}
================================================
FILE: examples/react-follow/src/Web3Providers.tsx
================================================
import { LensProvider } from '@lens-protocol/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ConnectKitProvider } from 'connectkit';
import type React from 'react';
import { WagmiProvider } from 'wagmi';
import { client } from './client';
import { config } from './config';
const queryClient = new QueryClient();
export function Web3Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
================================================
FILE: examples/react-follow/src/client.ts
================================================
import { fetchAccountsAvailable } from '@lens-protocol/client/actions';
import {
assertOk,
cache,
invariant,
PublicClient,
testnet,
} from '@lens-protocol/react';
import { signMessageWith } from '@lens-protocol/react/viem';
import { walletClient } from './wallet';
export const client = PublicClient.create({
environment: testnet,
cache: cache,
storage: window.localStorage,
});
const result = await client.resumeSession().orElse(() =>
fetchAccountsAvailable(client, {
managedBy: walletClient.account.address,
}).andThen(({ items }) => {
invariant(items.length > 0, 'No available accounts found');
const loginAs =
items[0].__typename === 'AccountOwned'
? {
accountOwner: {
owner: walletClient.account.address,
account: items[0].account.address,
},
}
: {
accountManager: {
manager: walletClient.account.address,
account: items[0].account.address,
},
};
return client.login({
...loginAs,
signMessage: signMessageWith(walletClient),
});
}),
);
assertOk(result);
================================================
FILE: examples/react-follow/src/config.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { getDefaultConfig } from 'connectkit';
import { createConfig, http } from 'wagmi';
export const config = createConfig(
getDefaultConfig({
chains: [chains.testnet],
transports: {
// [chains.mainnet.id]: http(chains.mainnet.rpcUrls.default.http[0]!),
[chains.testnet.id]: http(chains.testnet.rpcUrls.default.http[0]!),
},
walletConnectProjectId: '',
appName: 'Lens + ConnectKit Example',
appDescription: 'A sample app integrating ConnectKit and Lens React SDK.',
appUrl: `${import.meta.env.BASE_URL}`,
appIcon: `${import.meta.env.BASE_URL}/lens.svg`,
}),
);
================================================
FILE: examples/react-follow/src/main.tsx
================================================
import { Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import { Web3Providers } from './Web3Providers';
createRoot(document.getElementById('root')!).render(
Loading...
}>
,
);
================================================
FILE: examples/react-follow/src/vite-env.d.ts
================================================
///
================================================
FILE: examples/react-follow/src/wallet.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { type Address, createWalletClient, custom } from 'viem';
const chain = chains.testnet;
// hoist account
const [address] = (await window.ethereum!.request({
method: 'eth_requestAccounts',
})) as [Address];
export const walletClient = createWalletClient({
account: address,
chain,
transport: custom(window.ethereum!),
});
const chainId = await walletClient.getChainId();
if (chainId !== chain.id) {
try {
await walletClient.switchChain({ id: chain.id });
} catch {
await walletClient.addChain({ chain });
}
}
================================================
FILE: examples/react-follow/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src", "vite.config.ts"]
}
================================================
FILE: examples/react-follow/vite.config.ts
================================================
import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/react-login/README.md
================================================
# Log in to Lens
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/react-login)
================================================
FILE: examples/react-login/index.html
================================================
Log in to Lens
================================================
FILE: examples/react-login/package.json
================================================
{
"name": "react-login",
"description": "Log in to Lens",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "latest",
"@lens-protocol/react": "canary",
"@tanstack/react-query": "^5.63.0",
"connectkit": "^1.9.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"viem": "^2.22.4",
"wagmi": "^2.14.6"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.8.1",
"typescript": "^5.6.3",
"vite": "^5.4.9"
}
}
================================================
FILE: examples/react-login/src/App.tsx
================================================
import { useAuthenticatedUser } from '@lens-protocol/react';
import { LoginForm } from './LoginForm';
import { MyAccount } from './MyAccount';
export function App() {
const { data } = useAuthenticatedUser({ suspense: true });
if (data) {
return ;
}
return ;
}
================================================
FILE: examples/react-login/src/LoginForm.tsx
================================================
import {
type AccountAvailable,
type EvmAddress,
evmAddress,
useAccountsAvailable,
useLogin,
} from '@lens-protocol/react';
import { signMessageWith } from '@lens-protocol/react/viem';
import { useModal } from 'connectkit';
import { useAccount, useWalletClient } from 'wagmi';
function LoginWith({
signer,
value,
}: {
signer: EvmAddress;
value: AccountAvailable;
}) {
const { execute } = useLogin();
const { data } = useWalletClient();
const loginAs =
value.__typename === 'AccountManaged'
? {
accountManager: {
account: value.account.address,
manager: signer,
},
}
: {
accountOwner: {
account: value.account.address,
owner: signer,
},
};
return (
);
}
function LoginOptions({ address }: { address: string }) {
const { data } = useAccountsAvailable({
managedBy: evmAddress(address),
suspense: true,
});
return (
<>
Select an account
{data.items.map((item) => (
-
))}
>
);
}
export function LoginForm() {
const { address } = useAccount();
const { setOpen } = useModal();
if (!address) {
return (
);
}
return ;
}
================================================
FILE: examples/react-login/src/LogoutButton.tsx
================================================
import { useLogout } from '@lens-protocol/react';
import { useDisconnect } from 'wagmi';
export function LogoutButton() {
const { execute } = useLogout();
const { disconnectAsync } = useDisconnect();
const onClick = async () => {
await execute();
await disconnectAsync();
};
return (
);
}
================================================
FILE: examples/react-login/src/MyAccount.tsx
================================================
import { type EvmAddress, useAccount } from '@lens-protocol/react';
import { LogoutButton } from './LogoutButton';
export function MyAccount({ address }: { address: EvmAddress }) {
const { data } = useAccount({ address, suspense: true });
return (
Welcome,{' '}
{data?.metadata?.name ?? data?.username?.value ?? data?.address}!
Created on: {data?.createdAt}
Account Score: {data?.score}
);
}
================================================
FILE: examples/react-login/src/Web3Providers.tsx
================================================
import { LensProvider } from '@lens-protocol/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ConnectKitProvider } from 'connectkit';
import type React from 'react';
import { WagmiProvider } from 'wagmi';
import { client } from './client';
import { config } from './config';
const queryClient = new QueryClient();
export function Web3Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
================================================
FILE: examples/react-login/src/client.ts
================================================
import { mainnet, PublicClient } from '@lens-protocol/react';
export const client = PublicClient.create({
environment: mainnet,
storage: window.localStorage,
});
================================================
FILE: examples/react-login/src/config.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { getDefaultConfig } from 'connectkit';
import { createConfig, http } from 'wagmi';
export const config = createConfig(
getDefaultConfig({
chains: [chains.mainnet, chains.testnet],
transports: {
[chains.mainnet.id]: http(chains.mainnet.rpcUrls.default.http[0]!),
[chains.testnet.id]: http(chains.testnet.rpcUrls.default.http[0]!),
},
walletConnectProjectId: '',
appName: 'Lens + ConnectKit Example',
appDescription: 'A sample app integrating ConnectKit and Lens React SDK.',
appUrl: `${import.meta.env.BASE_URL}`,
appIcon: `${import.meta.env.BASE_URL}/lens.svg`,
}),
);
================================================
FILE: examples/react-login/src/main.tsx
================================================
import { Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import { Web3Providers } from './Web3Providers';
createRoot(document.getElementById('root')!).render(
Loading...}>
,
);
================================================
FILE: examples/react-login/src/vite-env.d.ts
================================================
///
================================================
FILE: examples/react-login/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src", "vite.config.ts"]
}
================================================
FILE: examples/react-login/vite.config.ts
================================================
import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/react-post/README.md
================================================
# Create a Lens Post with Lens React Hooks
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/react-post)
================================================
FILE: examples/react-post/index.html
================================================
Create a Lens Post with Lens React Hooks
================================================
FILE: examples/react-post/package.json
================================================
{
"name": "react-post",
"description": "Create a Lens Post with Lens React Hooks",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "^1.0.3",
"@lens-protocol/client": "canary",
"@lens-protocol/metadata": "^2.0.0",
"@lens-protocol/react": "canary",
"@tanstack/react-query": "^5.74.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"viem": "2.x",
"wagmi": "^2.14.16"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.7.2",
"typescript": "^5.6.3",
"vite": "^5.4.9"
}
}
================================================
FILE: examples/react-post/src/App.tsx
================================================
import { textOnly } from '@lens-protocol/metadata';
import { useCreatePost } from '@lens-protocol/react';
import { handleOperationWith } from '@lens-protocol/react/viem';
import { useWalletClient } from 'wagmi';
export function App() {
const { data: wallet } = useWalletClient();
const {
execute,
loading,
data: post,
} = useCreatePost({ handler: handleOperationWith(wallet) });
const onSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const content = formData.get('content') as string;
const metadata = textOnly({
content,
});
const result = await execute({
contentUri: `data:application/json,${JSON.stringify(metadata)}`,
});
if (result.isErr()) {
alert(result.error.message);
}
};
return (
Post Example
{post && (
Post Created
Slug: {post.slug}
Created At: {post.timestamp.toString()}
Content:{' '}
{post.metadata.__typename === 'TextOnlyMetadata' &&
post.metadata.content}
)}
);
}
================================================
FILE: examples/react-post/src/client.ts
================================================
import { fetchAccountsAvailable } from '@lens-protocol/client/actions';
import {
assertOk,
invariant,
PublicClient,
testnet,
} from '@lens-protocol/react';
import { signMessageWith } from '@lens-protocol/react/viem';
import { walletClient } from './wallet';
export const client = PublicClient.create({
environment: testnet,
storage: window.localStorage,
});
const result = await client.resumeSession().orElse(() =>
fetchAccountsAvailable(client, {
managedBy: walletClient.account.address,
}).andThen(({ items }) => {
invariant(items.length > 0, 'No available accounts found');
const loginAs =
items[0].__typename === 'AccountOwned'
? {
accountOwner: {
owner: walletClient.account.address,
account: items[0].account.address,
},
}
: {
accountManager: {
manager: walletClient.account.address,
account: items[0].account.address,
},
};
return client.login({
...loginAs,
signMessage: signMessageWith(walletClient),
});
}),
);
assertOk(result);
================================================
FILE: examples/react-post/src/config.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { createConfig, http, injected } from 'wagmi';
export const config = createConfig({
chains: [chains.testnet],
connectors: [injected()],
transports: {
[chains.testnet.id]: http(),
},
});
================================================
FILE: examples/react-post/src/main.tsx
================================================
import { LensProvider } from '@lens-protocol/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createRoot } from 'react-dom/client';
import { WagmiProvider } from 'wagmi';
import { App } from './App';
import { client } from './client';
import { config } from './config';
const queryClient = new QueryClient();
createRoot(document.getElementById('root')!).render(
,
);
================================================
FILE: examples/react-post/src/vite-env.d.ts
================================================
///
import 'viem/window';
================================================
FILE: examples/react-post/src/wallet.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { type Address, createWalletClient, custom } from 'viem';
const chain = chains.testnet;
// hoist account
const [address] = (await window.ethereum!.request({
method: 'eth_requestAccounts',
})) as [Address];
export const walletClient = createWalletClient({
account: address,
chain,
transport: custom(window.ethereum!),
});
const chainId = await walletClient.getChainId();
if (chainId !== chain.id) {
try {
await walletClient.switchChain({ id: chain.id });
} catch {
await walletClient.addChain({ chain });
}
}
================================================
FILE: examples/react-post/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src", "vite.config.ts"]
}
================================================
FILE: examples/react-post/vite.config.ts
================================================
import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/react-post-action/README.md
================================================
# Execute a Lens Post Action with Lens React Hooks
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/react-post-action)
================================================
FILE: examples/react-post-action/index.html
================================================
Create a Lens Post with Lens React Hooks
================================================
FILE: examples/react-post-action/package.json
================================================
{
"name": "react-post-action",
"description": "Execute a Lens Post Action with Lens React Hooks",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "^1.0.3",
"@lens-protocol/client": "workspace:*",
"@lens-protocol/metadata": "^2.0.0",
"@lens-protocol/react": "workspace:*",
"@tanstack/react-query": "^5.74.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"viem": "^2.23.0",
"wagmi": "^2.14.6"
},
"devDependencies": {
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@vitejs/plugin-react-swc": "^3.7.2",
"typescript": "^5.6.3",
"vite": "^5.4.9"
}
}
================================================
FILE: examples/react-post-action/src/App.tsx
================================================
import {
evmAddress,
type Post,
postId,
useExecutePostAction,
usePost,
} from '@lens-protocol/react';
import {
handleOperationWith,
useUnknownPostActionEncoder,
} from '@lens-protocol/react/viem';
import { useWalletClient } from 'wagmi';
const POST_SLUG = 'b84rn3awqztera37ek';
const ACTION_ADDRESS = '0xE34b5bF6e385084F43F827077E49EdAa33a3c9Dd';
export function App() {
const { data: wallet } = useWalletClient();
const { data: post, loading } = usePost({
post: postId(POST_SLUG),
}) as { data: Post | null; loading: boolean };
const { execute: executePostAction, loading: executing } =
useExecutePostAction({
handler: handleOperationWith(wallet),
});
const encodeParams = useUnknownPostActionEncoder(
post,
evmAddress(ACTION_ADDRESS),
);
const handleVote = async (event: React.FormEvent) => {
event.preventDefault();
if (!post || !wallet) return;
const formData = new FormData(event.currentTarget);
const params = {
'lens.param.vote': formData.get('lens.param.vote') === 'true',
};
const encodedParams = encodeParams(params);
const result = await executePostAction({
post: postId(post.id),
action: {
unknown: {
address: evmAddress(ACTION_ADDRESS),
params: encodedParams,
},
},
});
if (result.isErr()) {
console.error(result.error);
alert(`Failed to vote: ${result.error.message}`);
return;
}
alert(
`Successfully voted!\nVote: ${formData.get('lens.param.vote') === 'true' ? 'Yes' : 'No'}`,
);
};
return (
Custom Action Example
{loading &&
Loading post...
}
{!post && !loading &&
Post not found
}
{post && (
)}
);
}
================================================
FILE: examples/react-post-action/src/client.ts
================================================
import { fetchAccountsAvailable } from '@lens-protocol/client/actions';
import {
assertOk,
invariant,
PublicClient,
testnet,
} from '@lens-protocol/react';
import { signMessageWith } from '@lens-protocol/react/viem';
import { walletClient } from './wallet';
export const client = PublicClient.create({
environment: testnet,
storage: window.localStorage,
});
const result = await client.resumeSession().orElse(() =>
fetchAccountsAvailable(client, {
managedBy: walletClient.account.address,
}).andThen(({ items }) => {
invariant(items.length > 0, 'No available accounts found');
const loginAs =
items[0].__typename === 'AccountOwned'
? {
accountOwner: {
owner: walletClient.account.address,
account: items[0].account.address,
},
}
: {
accountManager: {
manager: walletClient.account.address,
account: items[0].account.address,
},
};
return client.login({
...loginAs,
signMessage: signMessageWith(walletClient),
});
}),
);
assertOk(result);
================================================
FILE: examples/react-post-action/src/config.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { createConfig, http, injected } from 'wagmi';
export const config = createConfig({
chains: [chains.testnet],
connectors: [injected()],
transports: {
[chains.testnet.id]: http(),
},
});
================================================
FILE: examples/react-post-action/src/main.tsx
================================================
import { LensProvider } from '@lens-protocol/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createRoot } from 'react-dom/client';
import { WagmiProvider } from 'wagmi';
import { App } from './App';
import { client } from './client';
import { config } from './config';
const queryClient = new QueryClient();
createRoot(document.getElementById('root')!).render(
,
);
================================================
FILE: examples/react-post-action/src/vite-env.d.ts
================================================
///
import 'viem/window';
================================================
FILE: examples/react-post-action/src/wallet.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { type Address, createWalletClient, custom } from 'viem';
const chain = chains.testnet;
// hoist account
const [address] = (await window.ethereum!.request({
method: 'eth_requestAccounts',
})) as [Address];
export const walletClient = createWalletClient({
account: address,
chain,
transport: custom(window.ethereum!),
});
const chainId = await walletClient.getChainId();
if (chainId !== chain.id) {
try {
await walletClient.switchChain({ id: chain.id });
} catch {
await walletClient.addChain({ chain });
}
}
================================================
FILE: examples/react-post-action/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src", "vite.config.ts"]
}
================================================
FILE: examples/react-post-action/vite.config.ts
================================================
import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/sponsored-tx/.stackblitzrc
================================================
{
"startCommand": "pnpm start",
"env": {
"ENVIRONMENT": "testnet",
"WALLET_PRIVATE_KEY": "0x…",
"SPONSORSHIP_ADDRESS": "0x…",
"SPONSORSHIP_SIGNER_PRIVATE_KEY": "0x…"
}
}
================================================
FILE: examples/sponsored-tx/README.md
================================================
# Sponsored Transaction Example
This example demonstrates how to sponsor a transaction on Lens Chain using the Lens Protocol SDK.
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/sponsored-tx)
================================================
FILE: examples/sponsored-tx/index.ts
================================================
import { chains } from '@lens-chain/sdk/viem';
import { evmAddress } from '@lens-protocol/client';
import { SponsorshipApprovalSigner } from '@lens-protocol/client/viem';
import {
type Address,
createWalletClient,
type Hash,
type Hex,
http,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { waitForTransactionReceipt } from 'viem/actions';
import { sendTransaction } from 'viem/zksync';
const chain =
process.env.ENVIRONMENT === 'mainnet' ? chains.mainnet : chains.testnet;
console.log(`Network: ${chain.name}`);
const wallet = createWalletClient({
account: privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as Hex),
chain: chain,
transport: http(),
});
console.log(`Wallet: ${wallet.account.address}`);
const signer = createWalletClient({
account: privateKeyToAccount(
process.env.SPONSORSHIP_SIGNER_PRIVATE_KEY as Hex,
),
chain: chain,
transport: http(),
});
console.log(`Sponsorship Signer: ${signer.account.address}`);
const approver = new SponsorshipApprovalSigner({
signer,
sponsorship: evmAddress(process.env.SPONSORSHIP_ADDRESS as Address),
});
export interface SponsorRequest {
to: Address;
value: string | number;
data?: Hex;
}
async function sendSponsoredTransaction({
to,
value,
data,
}: SponsorRequest): Promise {
const tx = await approver.approveSponsorship({
account: wallet.account,
to,
value,
data,
});
// biome-ignore lint/suspicious/noExplicitAny: keep it simple
return await sendTransaction(wallet, tx as any);
}
const hash = await sendSponsoredTransaction({
to: wallet.account.address,
value: 1, // 1 wei
});
console.log(`Transaction hash: ${hash}`);
await waitForTransactionReceipt(wallet, { hash });
================================================
FILE: examples/sponsored-tx/package.json
================================================
{
"name": "example-sponsored-tx",
"private": true,
"type": "module",
"scripts": {
"start": "vite-node index.ts"
},
"dependencies": {
"@lens-chain/sdk": "latest",
"@lens-protocol/client": "canary",
"viem": "~2.22.4"
},
"devDependencies": {
"typescript": "^5.8.3",
"vite-node": "^1.6.1"
}
}
================================================
FILE: examples/sponsored-tx/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ESNext"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["./"]
}
================================================
FILE: examples/sponsored-tx-poc/README.md
================================================
# Sponsored Transaction via RPC Wallet
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/sponsored-tx-poc)
================================================
FILE: examples/sponsored-tx-poc/index.html
================================================
Sponsored Transaction
Loading...
================================================
FILE: examples/sponsored-tx-poc/index.ts
================================================
import 'viem/window';
import { chains } from '@lens-chain/sdk/viem';
import { evmAddress } from '@lens-protocol/client';
import { SponsorshipApprovalSigner } from '@lens-protocol/client/viem';
import { createWalletClient, custom, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { sendTransaction } from 'viem/zksync';
const chain = chains.testnet;
const [address] = (await window.ethereum!.request({
method: 'eth_requestAccounts',
})) as [Address];
const wallet = createWalletClient({
account: address,
chain,
transport: custom(window.ethereum!),
});
const signer = createWalletClient({
account: privateKeyToAccount(window.prompt('Sponsorship Signer PK')),
chain: chain,
transport: http(),
});
const approver = new SponsorshipApprovalSigner({
signer,
sponsorship: evmAddress(window.prompt('Sponsorship Address')),
});
const tx = await approver.approveSponsorship({
account: wallet.account,
to: wallet.account.address,
data: '0x',
value: 1, // 1 wei
});
console.log('tx', tx);
const hash = await sendTransaction(wallet, tx);
export default [
`Network: ${chain.name}
`,
`Wallet: ${wallet.account.address}
`,
`Sponsorship Signer: ${signer.account.address}
`,
`Transaction hash: ${hash}
`,
];
================================================
FILE: examples/sponsored-tx-poc/package.json
================================================
{
"name": "example-sponsored-tx-poc",
"private": true,
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "latest",
"@lens-protocol/client": "canary",
"viem": "^2.21.55"
},
"devDependencies": {
"typescript": "^5.6.3",
"vite": "^5.4.11"
}
}
================================================
FILE: examples/sponsored-tx-poc/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM"],
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["./"]
}
================================================
FILE: examples/thirdweb-onramp/README.md
================================================
# thirdweb On-Ramp
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/thirdweb-onramp)
================================================
FILE: examples/thirdweb-onramp/index.html
================================================
thirdweb On-Ramp
================================================
FILE: examples/thirdweb-onramp/package.json
================================================
{
"name": "example-thirdweb-onramp",
"private": true,
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "latest",
"@lens-protocol/client": "canary",
"@lens-protocol/metadata": "latest",
"@lens-protocol/storage-node-client": "next",
"@tanstack/react-query": "^5.66.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"thirdweb": "^5.89.0",
"viem": "^2.21.55",
"wagmi": "^2.14.11"
},
"devDependencies": {
"@types/react": "18",
"@types/react-dom": "18",
"@vitejs/plugin-react-swc": "^3.8.0",
"typescript": "^5.6.3",
"vite": "^5.4.11"
},
"resolutions": {
"viem": "^2.21.55"
},
"pnpm": {
"overrides": {
"viem": "^2.21.55"
}
}
}
================================================
FILE: examples/thirdweb-onramp/src/App.tsx
================================================
import 'viem/window';
import { chains } from '@lens-chain/sdk/viem';
import { createThirdwebClient } from 'thirdweb';
import { viemAdapter } from 'thirdweb/adapters/viem';
import { ethereum } from 'thirdweb/chains';
import { PayEmbed } from 'thirdweb/react';
import { type Address, createWalletClient, custom } from 'viem';
const chain = chains.testnet;
// hoist account
const [address] = (await window.ethereum!.request({
method: 'eth_requestAccounts',
})) as [Address];
// create wallet client
// this example assume you might not use thirdweb wallet already, if you
// do you can skip this step and use the wallet from thirdweb
const walletClient = createWalletClient({
account: address,
chain: chain,
transport: custom(window.ethereum!),
});
// create thirdweb wallet
const thirdwebWallet = await viemAdapter.wallet.fromViem({
walletClient: walletClient,
});
const client = createThirdwebClient({
clientId: '44323e7868feac3bd3ea4d91c9e879d4',
});
await thirdwebWallet.connect({ client });
export function App() {
return (
{
console.log('Purchase success', purchase);
},
}}
/>
);
}
================================================
FILE: examples/thirdweb-onramp/src/main.tsx
================================================
import { chains } from '@lens-chain/sdk/viem';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createRoot } from 'react-dom/client';
import { ThirdwebProvider } from 'thirdweb/react';
import { createConfig, http, injected, WagmiProvider } from 'wagmi';
import { App } from './App';
export const config = createConfig({
chains: [chains.testnet],
connectors: [injected()],
transports: {
[chains.testnet.id]: http(),
},
});
const queryClient = new QueryClient();
createRoot(document.getElementById('root')!).render(
,
);
================================================
FILE: examples/thirdweb-onramp/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src", "vite.config.ts"]
}
================================================
FILE: examples/thirdweb-onramp/vite.config.ts
================================================
import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react()],
});
================================================
FILE: examples/user-onboarding/README.md
================================================
# User Onboarding
[](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/main/examples/user-onboarding)
================================================
FILE: examples/user-onboarding/index.html
================================================
User Onboarding Example
Loading...
================================================
FILE: examples/user-onboarding/index.ts
================================================
import 'viem/window';
import { chains } from '@lens-chain/sdk/viem';
import { immutable, StorageClient } from '@lens-chain/storage-client';
import { PublicClient, testnet } from '@lens-protocol/client';
import {
createAccountWithUsername,
fetchAccount,
} from '@lens-protocol/client/actions';
import { handleOperationWith } from '@lens-protocol/client/viem';
import { account } from '@lens-protocol/metadata';
import { type Address, createWalletClient, custom } from 'viem';
const chain = chains.testnet;
// hoist account
const [address] = (await window.ethereum!.request({
method: 'eth_requestAccounts',
})) as [Address];
const walletClient = createWalletClient({
account: address,
chain,
transport: custom(window.ethereum!),
});
const client = PublicClient.create({
environment: testnet,
});
const sessionClient = await client
.login({
onboardingUser: {
wallet: walletClient.account.address,
app: '0xe5439696f4057aF073c0FB2dc6e5e755392922e1',
},
signMessage: async (message) => walletClient.signMessage({ message }),
})
.match(
(result) => result,
(error) => {
throw error;
},
);
const storageClient = StorageClient.create();
const metadata = account({
name: 'John Doe',
});
const { uri } = await storageClient.uploadFile(
new File([JSON.stringify(metadata)], 'metadata.json', {
type: 'application/json',
}),
{ acl: immutable(chain.id) },
);
const created = await createAccountWithUsername(sessionClient, {
metadataUri: uri,
username: {
localName: `john-doe-${Date.now()}`,
},
})
.andThen(handleOperationWith(walletClient))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchAccount(sessionClient, { txHash }))
.match(
(result) => result,
(error) => {
throw error;
},
);
export default [
`${created?.username?.value}
`,
`Address: ${await created?.address}
`,
];
================================================
FILE: examples/user-onboarding/package.json
================================================
{
"name": "example-user-onboarding",
"private": true,
"type": "module",
"scripts": {
"dev": "vite"
},
"dependencies": {
"@lens-chain/sdk": "latest",
"@lens-protocol/client": "canary",
"@lens-protocol/metadata": "latest",
"@lens-chain/storage-client": "latest",
"viem": "^2.21.55"
},
"devDependencies": {
"typescript": "^5.6.3",
"vite": "^5.4.11"
}
}
================================================
FILE: examples/user-onboarding/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM"],
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["./"]
}
================================================
FILE: jest-extended.d.ts
================================================
import type CustomMatchers from 'jest-extended';
import 'vitest';
declare module 'vitest' {
interface Assertion extends CustomMatchers {}
interface AsymmetricMatchersContaining
extends CustomMatchers {}
interface ExpectStatic extends CustomMatchers {}
}
================================================
FILE: package.json
================================================
{
"name": "lens-sdk",
"version": "0.0.0",
"description": "The quickest way to build on top of Lens.",
"private": true,
"type": "module",
"workspaces": [
"packages/*"
],
"engines": {
"node": ">=20",
"pnpm": ">=9.1.2"
},
"scripts": {
"build": "turbo build",
"dev": "turbo watch build",
"clean": "rimraf .turbo packages/*/dist",
"lint": "biome check",
"lint:fix": "biome check --write",
"new:package": "NODE_OPTIONS='--import tsx' plop --plopfile=plopfile.ts",
"prepublish": "pnpm run build",
"test:client": "vitest --project client",
"test:e2e": "vitest --project e2e",
"test:react": "vitest --project react",
"test:storage": "vitest --project storage",
"test": "vitest"
},
"devDependencies": {
"@biomejs/biome": "2.0.6",
"@changesets/cli": "^2.29.1",
"@types/node": "^22.15.29",
"jest-extended": "^4.0.2",
"plop": "^4.0.1",
"rimraf": "^6.0.1",
"tsx": "^4.19.3",
"turbo": "^2.2.3",
"typescript": "^5.6.3",
"vite": "^6.3.5",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^3.2.0"
},
"license": "MIT",
"packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
}
================================================
FILE: packages/client/CHANGELOG.md
================================================
# @lens-protocol/client
## 3.0.0-alpha.0
### Major Changes
- 8073fb7: **chore**: transition to new major release
### Patch Changes
- Updated dependencies [8073fb7]
- @lens-protocol/storage@0.9.0-alpha.0
- @lens-protocol/env@0.1.0-alpha.0
- @lens-protocol/graphql@0.1.0-alpha.0
- @lens-protocol/types@0.1.0-alpha.0
================================================
FILE: packages/client/README.md
================================================
# Lens JavaScript SDK
The official framework-agnostic JavaScript SDK for Lens Protocol.
---
This package enables you to interact with the Lens API via a type safe interface that abstracts away some of the GraphQL intricacies.
================================================
FILE: packages/client/package.json
================================================
{
"name": "@lens-protocol/client",
"version": "3.0.0-alpha.0",
"description": "Low-level Lens API client",
"repository": {
"directory": "packages/client",
"type": "git",
"url": "git://github.com/lens-protocol/lens-sdk.git"
},
"type": "module",
"types": "dist/index.d.ts",
"main": "dist/index.cjs",
"module": "dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./actions": {
"import": "./dist/actions/index.js",
"require": "./dist/actions/index.cjs",
"types": "./dist/actions/index.d.cts"
},
"./ethers": {
"import": "./dist/ethers/index.js",
"require": "./dist/ethers/index.cjs",
"types": "./dist/ethers/index.d.cts"
},
"./viem": {
"import": "./dist/viem/index.js",
"require": "./dist/viem/index.cjs",
"types": "./dist/viem/index.d.cts"
},
"./test-utils": {
"import": "./dist/test-utils.js",
"require": "./dist/test-utils.cjs",
"types": "./dist/test-utils.d.cts"
}
},
"typesVersions": {
"*": {
"actions": [
"./dist/actions/index.d.ts"
],
"ethers": [
"./dist/ethers/index.d.ts"
],
"viem": [
"./dist/viem/index.d.ts"
],
"test-utils": [
"./dist/test-utils.d.ts"
]
}
},
"files": [
"dist"
],
"sideEffects": false,
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@lens-chain/sdk": "^1.0.0",
"@lens-protocol/env": "workspace:*",
"@lens-protocol/graphql": "workspace:*",
"@lens-protocol/storage": "workspace:*",
"@lens-protocol/types": "workspace:*",
"@urql/core": "^5.0.8",
"@urql/exchange-auth": "^2.2.0",
"@urql/exchange-graphcache": "^7.2.4",
"graphql": "^16.9.0",
"jwt-decode": "^4.0.0"
},
"peerDependencies": {
"ethers": "^6.13.4",
"viem": "^2.21.53",
"zksync-ethers": "^6.15.3"
},
"peerDependenciesMeta": {
"ethers": {
"optional": true
},
"viem": {
"optional": true
},
"zksync-ethers": {
"optional": true
}
},
"devDependencies": {
"@lens-chain/storage-client": "^1.0.5",
"@lens-protocol/metadata": "^2.0.0",
"@types/big.js": "^6.2.2",
"big.js": "^7.0.1",
"ethers": "^6.13.4",
"msw": "^2.7.0",
"tsup": "^8.3.5",
"typescript": "^5.6.3",
"viem": "^2.22.4",
"zksync-ethers": "^6.15.3",
"zod": "^3.23.8"
},
"license": "MIT"
}
================================================
FILE: packages/client/src/AuthenticatedUser.ts
================================================
import { Role } from '@lens-protocol/graphql';
import {
type EvmAddress,
err,
never,
ok,
type Result,
type UUID,
} from '@lens-protocol/types';
import { UnexpectedError } from './errors';
import { type IdTokenClaims, ROLE_CLAIM, SPONSORED_CLAIM } from './tokens';
export type AuthenticatedUser = {
address: EvmAddress;
app: EvmAddress;
authenticationId: UUID;
role: Role;
signer: EvmAddress;
sponsored: boolean;
};
/**
* @internal
*/
export function authenticatedUser(
claims: IdTokenClaims,
): Result {
switch (claims[ROLE_CLAIM]) {
case Role.AccountManager:
return ok({
address:
claims.act?.sub ?? never('Account Manager must have an Actor Claim'),
app: claims.aud,
authenticationId: claims.sid,
role: Role.AccountManager,
signer: claims.sub,
sponsored: claims[SPONSORED_CLAIM],
});
case Role.AccountOwner:
return ok({
address:
claims.act?.sub ?? never('Account Owner must have an Actor Claim'),
app: claims.aud,
authenticationId: claims.sid,
role: Role.AccountOwner,
signer: claims.sub,
sponsored: claims[SPONSORED_CLAIM],
});
case Role.OnboardingUser:
case Role.Builder:
return ok({
address: claims.sub,
app: claims.aud,
authenticationId: claims.sid,
role: claims[ROLE_CLAIM],
signer: claims.sub,
sponsored: claims[SPONSORED_CLAIM],
});
default:
return err(
UnexpectedError.from(`Unexpected role: ${claims[ROLE_CLAIM]}`),
);
}
}
================================================
FILE: packages/client/src/actions/account.test.ts
================================================
import { assertOk, evmAddress } from '@lens-protocol/types';
import { describe, it } from 'vitest';
import { createPublicClient } from '../test-utils';
import { fetchAccount } from './account';
describe('Given the Account query actions', () => {
const client = createPublicClient();
describe(`When invoking the '${fetchAccount.name}' action`, () => {
it('Then it should not fail w/ a GQL BadRequest error', async () => {
const result = await fetchAccount(client, {
address: evmAddress(import.meta.env.TEST_ACCOUNT),
});
assertOk(result);
});
});
});
================================================
FILE: packages/client/src/actions/account.ts
================================================
import type {
Account,
AccountAvailable,
AccountBlocked,
AccountFeedsStats,
AccountFeedsStatsRequest,
AccountGraphsFollowStats,
AccountGraphsStatsRequest,
AccountRequest,
AccountStats,
AccountStatsRequest,
AccountsAvailableRequest,
AccountsBlockedRequest,
AccountsBulkRequest,
AccountsRequest,
BlockRequest,
BlockResult,
CreateAccountRequest,
CreateAccountResult,
CreateAccountWithUsernameRequest,
CreateAccountWithUsernameResult,
EnableSignlessResult,
MuteRequest,
Paginated,
RecommendAccountRequest,
RemoveSignlessResult,
ReportAccountRequest,
SetAccountMetadataRequest,
SetAccountMetadataResult,
UnblockRequest,
UnblockResult,
UndoRecommendAccountRequest,
UnmuteRequest,
UpdateAccountFollowRulesRequest,
UpdateAccountFollowRulesResult,
} from '@lens-protocol/graphql';
import {
AccountFeedsStatsQuery,
AccountGraphsStatsQuery,
AccountQuery,
AccountStatsQuery,
AccountsAvailableQuery,
AccountsBlockedQuery,
AccountsBulkQuery,
AccountsQuery,
BlockMutation,
CreateAccountMutation,
CreateAccountWithUsernameMutation,
EnableSignlessMutation,
MuteAccountMutation,
RecommendAccountMutation,
RemoveSignlessMutation,
ReportAccountMutation,
SetAccountMetadataMutation,
UnblockMutation,
UndoRecommendAccountMutation,
UnmuteAccountMutation,
UpdateAccountFollowRulesMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch an Account.
*
* Using a {@link SessionClient} will yield {@link Account#operations} specific to the authenticated Account.
*
* ```ts
* const result = await fetchAccount(anyClient, {
* address?: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The Account query request.
* @returns The Account or `null` if it does not exist.
*/
export function fetchAccount(
client: AnyClient,
request: AccountRequest,
): ResultAsync {
return client.query(AccountQuery, { request });
}
/**
* Fetch an Accounts.
*
* Using a {@link SessionClient} will yield {@link Account#operations} specific to the authenticated Account.
*
* ```ts
* const result = await fetchAccounts(anyClient);
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of accounts.
*/
export function fetchAccounts(
client: AnyClient,
request: AccountsRequest = {},
): ResultAsync, UnexpectedError> {
return client.query(AccountsQuery, { request });
}
/**
* Fetch an Accounts Bulk.
*
* ```ts
* const result = await fetchAccountsBulk(anyClient, {
* addresses: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of accounts.
*/
export function fetchAccountsBulk(
client: AnyClient,
request: AccountsBulkRequest = {},
): ResultAsync {
return client.query(AccountsBulkQuery, { request });
}
/**
* Fetch an Account Stats.
*
* ```ts
* const result = await fetchAccountStats(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The stats for the account or `null` if it does not exist.
*/
export function fetchAccountStats(
client: AnyClient,
request: AccountStatsRequest,
): ResultAsync {
return client.query(AccountStatsQuery, { request });
}
/**
* Fetch an Account Feed Stats.
*
* ```ts
* const result = await fetchAccountFeedStats(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The feed stats for the account or `null` if it does not exist.
*/
export function fetchAccountFeedStats(
client: AnyClient,
request: AccountFeedsStatsRequest,
): ResultAsync {
return client.query(AccountFeedsStatsQuery, { request });
}
/**
* Fetch an Account Graph Stats.
*
* ```ts
* const result = await fetchAccountGraphStats(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The follow stats for the account or `null` if it does not exist.
*/
export function fetchAccountGraphStats(
client: AnyClient,
request: AccountGraphsStatsRequest,
): ResultAsync {
return client.query(AccountGraphsStatsQuery, { request });
}
/**
* Fetch Accounts Available.
*
* ```ts
* const result = await fetchAccountsAvailable(anyClient, {
* managedBy: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of available accounts.
*/
export function fetchAccountsAvailable(
client: AnyClient,
request: AccountsAvailableRequest,
): ResultAsync, UnexpectedError> {
return client.query(AccountsAvailableQuery, { request });
}
/**
* Fetch Blocked Accounts.
*
* ```ts
* const result = await fetchAccountsBlocked(sessionClient);
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The query request.
* @returns The list of blocked accounts.
*/
export function fetchAccountsBlocked(
client: SessionClient,
request: AccountsBlockedRequest = {},
): ResultAsync, UnexpectedError> {
return client.query(AccountsBlockedQuery, { request });
}
/**
* Set Account metadata for the authenticated Account.
*
* ```ts
* const result = await setAccountMetadata(sessionClient, {
* metadataURI: uri('ar://abc123def456gh…'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setAccountMetadata(
client: SessionClient,
request: SetAccountMetadataRequest,
): ResultAsync<
SetAccountMetadataResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SetAccountMetadataMutation, { request });
}
/**
* Create an account with a given username.
*
* ```ts
* const result = await createAccountWithUsername(sessionClient, {
* username: {
* localname: 'wagmi'
* },
* metadataUri: uri('lens://bafybxiky5jf…'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createAccountWithUsername(
client: SessionClient,
request: CreateAccountWithUsernameRequest,
): ResultAsync<
CreateAccountWithUsernameResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(CreateAccountWithUsernameMutation, { request });
}
/**
* Create an account without username.
*
* @alpha This is an alpha API and may be subject to breaking changes.
*
* ```ts
* const result = await createAccount(sessionClient, {
* metadataUri: uri('lens://bafybxiky5jf…'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createAccount(
client: SessionClient,
request: CreateAccountRequest,
): ResultAsync {
return client.mutation(CreateAccountMutation, { request });
}
/**
* Get transaction to enable signless.
*
* ```ts
* const result = await enableSignless(sessionClient);
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function enableSignless(
client: SessionClient,
): ResultAsync {
return client.mutation(EnableSignlessMutation, {});
}
/**
* Get transaction to remove signless.
*
* ```ts
* const result = await removeSignless(sessionClient);
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function removeSignless(
client: SessionClient,
): ResultAsync {
return client.mutation(RemoveSignlessMutation, {});
}
/**
* Mute an account.
*
* ```ts
* const result = await muteAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns void.
*/
export function muteAccount(
client: SessionClient,
request: MuteRequest,
): ResultAsync {
return client.mutation(MuteAccountMutation, { request });
}
/**
* Unmute an account.
*
* ```ts
* const result = await unmuteAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns void.
*/
export function unmuteAccount(
client: SessionClient,
request: UnmuteRequest,
): ResultAsync {
return client.mutation(UnmuteAccountMutation, { request });
}
/**
* Report an account.
*
* ```ts
* const result = await reportAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* reason: AccountReportReason.RepetitiveSpam,
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns void.
*/
export function reportAccount(
client: SessionClient,
request: ReportAccountRequest,
): ResultAsync {
return client.mutation(ReportAccountMutation, { request });
}
/**
* Block an account.
*
* ```ts
* const result = await blockAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function blockAccount(
client: SessionClient,
request: BlockRequest,
): ResultAsync {
return client.mutation(BlockMutation, { request });
}
/**
* Unblock an account.
*
* ```ts
* const result = await unblockAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function unblockAccount(
client: SessionClient,
request: UnblockRequest,
): ResultAsync {
return client.mutation(UnblockMutation, { request });
}
/**
* Recommend an account.
*
* ```ts
* const result = await recommendAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns void.
*/
export function recommendAccount(
client: SessionClient,
request: RecommendAccountRequest,
): ResultAsync {
return client.mutation(RecommendAccountMutation, { request });
}
/**
* Undo recommendation of an account.
*
* ```ts
* const result = await undoRecommendAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns void.
*/
export function undoRecommendAccount(
client: SessionClient,
request: UndoRecommendAccountRequest,
): ResultAsync {
return client.mutation(UndoRecommendAccountMutation, { request });
}
/**
* Update account follow rules
*
* ```ts
* const result = await updateAccountFollowRules(sessionClient, {
* toRemove: ['1234'],
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateAccountFollowRules(
client: SessionClient,
request: UpdateAccountFollowRulesRequest,
): ResultAsync<
UpdateAccountFollowRulesResult,
UnauthenticatedError | UnexpectedError
> {
return client.mutation(UpdateAccountFollowRulesMutation, { request });
}
================================================
FILE: packages/client/src/actions/accountManager.test.ts
================================================
import { immutable } from '@lens-chain/storage-client';
import { type Account, assertTypename } from '@lens-protocol/graphql';
import * as metadata from '@lens-protocol/metadata';
import { assertOk, never, uri } from '@lens-protocol/types';
import { beforeAll, describe, expect, it } from 'vitest';
import type { SessionClient } from '../clients';
import {
CHAIN,
loginAsOnboardingUser,
storageClient,
wallet,
} from '../test-utils';
import { handleOperationWith } from '../viem';
import {
createAccountWithUsername,
fetchAccount,
setAccountMetadata,
} from './account';
import { fetchMeDetails } from './authentication';
describe(
`Given the '${createAccountWithUsername.name}' action`,
{ timeout: 10000 },
() => {
let newAccount: Account;
let sessionClient: SessionClient;
beforeAll(async () => {
const initialMetadata = metadata.account({
name: 'John Doe',
});
const result = await loginAsOnboardingUser().andThen((sessionClient) =>
createAccountWithUsername(sessionClient, {
username: { localName: `testname${Date.now()}` },
metadataUri: uri(
`data:application/json,${JSON.stringify(initialMetadata)}`,
), // empty at first
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchAccount(sessionClient, { txHash }))
.andThen((account) => {
newAccount = account ?? never('Account not found');
return sessionClient.switchAccount({
account: newAccount.address,
});
}),
);
assertOk(result);
sessionClient = result.value;
});
describe('When creating a new Account', () => {
it('Then it should have Signless enabled by default', async () => {
const result = await fetchMeDetails(sessionClient);
assertOk(result);
expect(result.value).toMatchObject({
isSignless: true,
});
});
it('Then it should be able to perform social operations in a signless fashion (e.g., updating Account metadata)', async () => {
const updated = metadata.account({
name: 'Bruce Wayne',
});
const resource = await storageClient.uploadAsJson(updated, {
acl: immutable(CHAIN.id),
});
const result = await setAccountMetadata(sessionClient, {
metadataUri: resource.uri,
});
assertOk(result);
assertTypename(result.value, 'SetAccountMetadataResponse');
await sessionClient.waitForTransaction(result.value.hash);
const account = await fetchAccount(sessionClient, {
address: newAccount.address,
}).unwrapOr(null);
expect(account).toMatchObject({
metadata: {
name: 'Bruce Wayne',
},
});
});
});
},
);
================================================
FILE: packages/client/src/actions/accountManager.ts
================================================
import type {
AccountManager,
AccountManagerPermissionsInput,
AccountManagersRequest,
AddAccountManagerResult,
HideManagedAccountRequest,
Paginated,
RemoveAccountManagerRequest,
RemoveAccountManagerResult,
UnhideManagedAccountRequest,
UpdateAccountManagerRequest,
UpdateAccountManagerResult,
} from '@lens-protocol/graphql';
import {
AccountManagersQuery,
AddAccountManagerMutation,
HideManagedAccountMutation,
RemoveAccountManagerMutation,
UnhideManagedAccountMutation,
UpdateAccountManagerMutation,
} from '@lens-protocol/graphql';
import type { EvmAddress, ResultAsync } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch Account Managers.
*
* ```ts
* const result = await fetchAccountManagers(sessionClient);
* ```
*
* @param client - Lens SessionClient.
* @param request - The query request.
* @returns List of Account Managers.
*/
export function fetchAccountManagers(
client: SessionClient,
request: AccountManagersRequest = {},
): ResultAsync<
Paginated,
UnexpectedError | UnauthenticatedError
> {
return client.query(AccountManagersQuery, { request });
}
/**
* Add an account manager to the authenticated account.
*
* ```ts
* const result = await addAccountManager(sessionClient, {
* address: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
* ```
*
* @param client - Lens SessionClient.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function addAccountManager(
client: SessionClient,
request: {
address: EvmAddress;
permissions?: AccountManagerPermissionsInput;
},
): ResultAsync<
AddAccountManagerResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(AddAccountManagerMutation, {
request: {
address: request.address,
permissions: request.permissions ?? {
canExecuteTransactions: false,
canTransferTokens: false,
canTransferNative: false,
canSetMetadataUri: false,
},
},
});
}
/**
* Remove manager from the authenticated account.
*
* ```ts
* const result = await removeAccountManager(sessionClient, {
* manager: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1");
* });
* ```
*
* @param client - Lens SessionClient.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function removeAccountManager(
client: SessionClient,
request: RemoveAccountManagerRequest,
): ResultAsync<
RemoveAccountManagerResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(RemoveAccountManagerMutation, { request });
}
/**
* Update permissions for an account manager.
*
* ```ts
* const result = await updateAccountManager(sessionClient, {
* permissions: {
* canSetMetadataUri: true;
* canTransferNative: false;
* canTransferTokens: true;
* canExecuteTransactions: false;
* },
* manager: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1");
* });
* ```
*
* @param client - Lens SessionClient.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateAccountManager(
client: SessionClient,
request: UpdateAccountManagerRequest,
): ResultAsync<
UpdateAccountManagerResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(UpdateAccountManagerMutation, { request });
}
/**
* Hide a managed account.
*
* ```ts
* const result = await muteAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1");
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns void.
*/
export function hideManagedAccount(
client: SessionClient,
request: HideManagedAccountRequest,
): ResultAsync {
return client.mutation(HideManagedAccountMutation, { request });
}
/**
* Unhide a managed account.
*
* ```ts
* const result = await unhideManagedAccount(sessionClient, {
* account: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1");
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns void.
*/
export function unhideManagedAccount(
client: SessionClient,
request: UnhideManagedAccountRequest,
): ResultAsync {
return client.mutation(UnhideManagedAccountMutation, { request });
}
================================================
FILE: packages/client/src/actions/actions.ts
================================================
import type {
ConfigureAccountActionRequest,
ConfigureAccountActionResult,
ConfigurePostActionRequest,
ConfigurePostActionResult,
DisableAccountActionRequest,
DisableAccountActionResult,
DisablePostActionRequest,
DisablePostActionResult,
EnableAccountActionRequest,
EnableAccountActionResult,
EnablePostActionRequest,
EnablePostActionResult,
ExecuteAccountActionRequest,
ExecuteAccountActionResult,
ExecutePostActionRequest,
ExecutePostActionResult,
} from '@lens-protocol/graphql';
import {
ConfigureAccountActionMutation,
ConfigurePostActionMutation,
DisableAccountActionMutation,
DisablePostActionMutation,
EnableAccountActionMutation,
EnablePostActionMutation,
ExecuteAccountActionMutation,
ExecutePostActionMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Configure post action.
*
* ```ts
* const result = await configurePostAction(sessionClient, {
* post: postId('1234…'),
* params: {
* simpleCollect: {
* payToCollect: {
* amount: {
* value: '100',
* currency: evmAddress('0x5678…'),
* },
* },
* },
* },
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function configurePostAction(
client: SessionClient,
request: ConfigurePostActionRequest,
): ResultAsync<
ConfigurePostActionResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(ConfigurePostActionMutation, { request });
}
/**
* Enable post action.
*
* ```ts
* const result = await enablePostAction(sessionClient, {
* post: postId('1234…'),
* action: { simpleCollect: true }
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function enablePostAction(
client: SessionClient,
request: EnablePostActionRequest,
): ResultAsync {
return client.mutation(EnablePostActionMutation, { request });
}
/**
* Disable post action.
*
* ```ts
* const result = await disablePostAction(sessionClient, {
* post: postId('1234…'),
* action: { simpleCollect: true }
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function disablePostAction(
client: SessionClient,
request: DisablePostActionRequest,
): ResultAsync<
DisablePostActionResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(DisablePostActionMutation, { request });
}
/**
* Execute post action.
*
* ```ts
* const result = await executePostAction(sessionClient, {
* post: postId('1234…'),
* action: {
* simpleCollect: {
* selected: true,
* }
* }
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function executePostAction(
client: SessionClient,
request: ExecutePostActionRequest,
): ResultAsync<
ExecutePostActionResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(ExecutePostActionMutation, { request });
}
/**
* Configure account action, only available for configure custom actions.
* By default the tipping action is configured for all accounts.
* Any user can tip any other user and any token.
*
* ```ts
* const result = await configureAccountAction(sessionClient, {
* action: {
* unknown: {
* address: evmAddress('0x1234…'),
* params: [{
* key: 'usd',
* value: '100',
* }],
* },
* },
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function configureAccountAction(
client: SessionClient,
request: ConfigureAccountActionRequest,
): ResultAsync<
ConfigureAccountActionResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(ConfigureAccountActionMutation, { request });
}
/**
* Enable account action.
* The tipping action is not possible to modify.
*
* ```ts
* const result = await enableAccountAction(sessionClient, {
* unknown: {
* params: [{
* key: 'usd',
* value: '100'
* }],
* address: evmAddress('0x1234…'),
* }
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function enableAccountAction(
client: SessionClient,
request: EnableAccountActionRequest,
): ResultAsync<
EnableAccountActionResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(EnableAccountActionMutation, { request });
}
/**
* Disable account action.
* Not possible to disable the tipping action.
*
* ```ts
* const result = await disableAccountAction(sessionClient, {
* unknown: {
* address: evmAddress('0x1234…'),
* }
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function disableAccountAction(
client: SessionClient,
request: DisableAccountActionRequest,
): ResultAsync<
DisableAccountActionResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(DisableAccountActionMutation, { request });
}
/**
* Execute account action.
*
* ```ts
* const result = await executeAccountAction(sessionClient, {
* targetAccount: evmAddress('0x1234…'),
* params: {
* tipping: {
* value: '100',
* currency: evmAddress('0x5678…')
* }
* }
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function executeAccountAction(
client: SessionClient,
request: ExecuteAccountActionRequest,
): ResultAsync<
ExecuteAccountActionResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(ExecuteAccountActionMutation, { request });
}
================================================
FILE: packages/client/src/actions/admins.ts
================================================
import type {
AddAdminsRequest,
AddAdminsResult,
Admin,
AdminsForRequest,
Paginated,
RemoveAdminsRequest,
RemoveAdminsResult,
} from '@lens-protocol/graphql';
import {
AddAdminsMutation,
AdminsForQuery,
RemoveAdminsMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Add Admins
*
* ```ts
* const result = await addAdmins(sessionClient{
* admins: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function addAdmins(
client: SessionClient,
request: AddAdminsRequest,
): ResultAsync {
return client.mutation(AddAdminsMutation, { request });
}
/**
* Remove admins
*
* ```ts
* const result = await removeAdmins(sessionClient{
* admins: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function removeAdmins(
client: SessionClient,
request: RemoveAdminsRequest,
): ResultAsync {
return client.mutation(RemoveAdminsMutation, { request });
}
/**
* Fetch admins for.
*
* ```ts
* const result = await fetchAdminsFor(anyClient, {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of admins or empty if it does not exist.
*/
export function fetchAdminsFor(
client: AnyClient,
request: AdminsForRequest,
): ResultAsync, UnexpectedError> {
return client.query(AdminsForQuery, { request });
}
================================================
FILE: packages/client/src/actions/app.ts
================================================
import type {
AddAppAuthorizationEndpointRequest,
AddAppFeedsRequest,
AddAppFeedsResult,
AddAppGroupsRequest,
AddAppGroupsResult,
AddAppSignersRequest,
AddAppSignersResult,
App,
AppFeed,
AppFeedsRequest,
AppGroupsRequest,
AppRequest,
AppServerApiKeyRequest,
AppSigner,
AppSignersRequest,
AppsRequest,
AppUser,
AppUsersRequest,
CreateAppRequest,
CreateAppResult,
GenerateNewAppServerApiKeyRequest,
Group,
Paginated,
RemoveAppAuthorizationEndpointRequest,
RemoveAppFeedsRequest,
RemoveAppFeedsResult,
RemoveAppGroupsRequest,
RemoveAppGroupsResult,
RemoveAppSignersRequest,
RemoveAppSignersResult,
ServerAPIKey,
SetAppGraphRequest,
SetAppGraphResult,
SetAppMetadataRequest,
SetAppMetadataResult,
SetAppSponsorshipRequest,
SetAppSponsorshipResult,
SetAppTreasuryRequest,
SetAppTreasuryResult,
SetAppUsernameNamespaceRequest,
SetAppUsernameNamespaceResult,
SetAppVerificationRequest,
SetAppVerificationResult,
SetDefaultAppFeedRequest,
SetDefaultAppFeedResult,
} from '@lens-protocol/graphql';
import {
AddAppAuthorizationEndpointMutation,
AddAppFeedsMutation,
AddAppGroupsMutation,
AddAppSignersMutation,
AppFeedsQuery,
AppGroupsQuery,
AppQuery,
AppServerApiKeyQuery,
AppSignersQuery,
AppsQuery,
AppUsersQuery,
CreateAppMutation,
GenerateNewAppServerApiKeyMutation,
RemoveAppAuthorizationEndpointMutation,
RemoveAppFeedsMutation,
RemoveAppGroupsMutation,
RemoveAppSignersMutation,
SetAppGraphMutation,
SetAppMetadataMutation,
SetAppSponsorshipMutation,
SetAppTreasuryMutation,
SetAppUsernameNamespaceMutation,
SetAppVerificationMutation,
SetDefaultAppFeedMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch an App.
*
* Using a {@link SessionClient} will yield {@link App#operations}
*
* ```ts
* const result = await fetchApp(anyClient, {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The App query request.
* @returns The App or `null` if it does not exist.
*/
export function fetchApp(
client: AnyClient,
request: AppRequest,
): ResultAsync {
return client.query(AppQuery, { request });
}
/**
* Fetch Apps.
*
* ```ts
* const result = await fetchApps(anyClient, {
* filter: {
* managedBy: {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* }
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of Apps or empty if it does not exist.
*/
export function fetchApps(
client: AnyClient,
request: AppsRequest,
): ResultAsync, UnexpectedError> {
return client.query(AppsQuery, { request });
}
/**
* Fetch Groups linked to an App.
*
* ```ts
* const result = await fetchAppGroups(anyClient, {
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of groups for an App or empty if it does not exist.
*/
export function fetchAppGroups(
client: AnyClient,
request: AppGroupsRequest,
): ResultAsync, UnexpectedError> {
return client.query(AppGroupsQuery, { request });
}
/**
* Fetch Feeds linked to an App.
*
* ```ts
* const result = await fetchAppFeeds(anyClient, {
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of feeds for an App or empty if it does not exist.
*/
export function fetchAppFeeds(
client: AnyClient,
request: AppFeedsRequest,
): ResultAsync, UnexpectedError> {
return client.query(AppFeedsQuery, { request });
}
/**
* Fetch Signers linked to an App.
*
* ```ts
* const result = await fetchAppSigners(anyClient, {
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of signers for an App or empty if it does not exist.
*/
export function fetchAppSigners(
client: AnyClient,
request: AppSignersRequest,
): ResultAsync, UnexpectedError> {
return client.query(AppSignersQuery, { request });
}
/**
* Fetch users using an App.
*
* ```ts
* const result = await fetchAppUsers(anyClient, {
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of users for an App or empty if it does not exist.
*/
export function fetchAppUsers(
client: AnyClient,
request: AppUsersRequest,
): ResultAsync | null, UnexpectedError> {
return client.query(AppUsersQuery, { request });
}
/**
* Create an App
*
* ```ts
* const result = await createApp(sessionClient, {
* defaultFeed: {
* globalFeed: true,
* },
* graph: {
* globalGraph: true,
* },
* namespace: {
* globalNamespace: true,
* },
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createApp(
client: SessionClient,
request: CreateAppRequest,
): ResultAsync {
return client.mutation(CreateAppMutation, { request });
}
/**
* Add feeds to an App
*
* ```ts
* const result = await addAppFeeds(sessionClient, {
* feeds: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function addAppFeeds(
client: SessionClient,
request: AddAppFeedsRequest,
): ResultAsync {
return client.mutation(AddAppFeedsMutation, { request });
}
/**
* Add groups to an App
*
* ```ts
* const result = await addAppGroups(sessionClient, {
* groups: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function addAppGroups(
client: SessionClient,
request: AddAppGroupsRequest,
): ResultAsync {
return client.mutation(AddAppGroupsMutation, { request });
}
/**
* Add signers to an App
*
* ```ts
* const result = await addAppSigners(sessionClient, {
* signers: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function addAppSigners(
client: SessionClient,
request: AddAppSignersRequest,
): ResultAsync {
return client.mutation(AddAppSignersMutation, { request });
}
/**
* Remove feeds from an App
*
* ```ts
* const result = await removeAppFeeds(sessionClient, {
* feeds: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function removeAppFeeds(
client: SessionClient,
request: RemoveAppFeedsRequest,
): ResultAsync {
return client.mutation(RemoveAppFeedsMutation, { request });
}
/**
* Remove groups from an App
*
* ```ts
* const result = await removeAppGroups(sessionClient, {
* groups: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function removeAppGroups(
client: SessionClient,
request: RemoveAppGroupsRequest,
): ResultAsync {
return client.mutation(RemoveAppGroupsMutation, { request });
}
/**
* Remove signers from an App
*
* ```ts
* const result = await removeAppSigners(sessionClient, {
* signers: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function removeAppSigners(
client: SessionClient,
request: RemoveAppSignersRequest,
): ResultAsync {
return client.mutation(RemoveAppSignersMutation, { request });
}
/**
* Set app Graph
*
* ```ts
* const result = await setAppGraph(sessionClient, {
* graph: { globalGraph: true },
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setAppGraph(
client: SessionClient,
request: SetAppGraphRequest,
): ResultAsync {
return client.mutation(SetAppGraphMutation, { request });
}
/**
* Set default Feed for app
*
* ```ts
* const result = await setDefaultAppFeed(sessionClient, {
* feed: { globalFeed: true },
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setDefaultAppFeed(
client: SessionClient,
request: SetDefaultAppFeedRequest,
): ResultAsync<
SetDefaultAppFeedResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SetDefaultAppFeedMutation, { request });
}
/**
* Set metadata for app
*
* ```ts
* const result = await setAppMetadata(sessionClient, {
* metadataUri: uri("lens://4f91..."),
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setAppMetadata(
client: SessionClient,
request: SetAppMetadataRequest,
): ResultAsync {
return client.mutation(SetAppMetadataMutation, { request });
}
/**
* Set verification status for app
*
* ```ts
* const result = await setAppVerification(sessionClient, {
* enabled: true,
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setAppVerification(
client: SessionClient,
request: SetAppVerificationRequest,
): ResultAsync<
SetAppVerificationResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SetAppVerificationMutation, { request });
}
/**
* Set sponsorship for app
*
* ```ts
* const result = await setAppSponsorship(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setAppSponsorship(
client: SessionClient,
request: SetAppSponsorshipRequest,
): ResultAsync<
SetAppSponsorshipResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SetAppSponsorshipMutation, { request });
}
/**
* Set treasury for app
*
* ```ts
* const result = await setAppTreasury(sessionClient, {
* treasury: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setAppTreasury(
client: SessionClient,
request: SetAppTreasuryRequest,
): ResultAsync {
return client.mutation(SetAppTreasuryMutation, { request });
}
/**
* Set username namespace for app
*
* ```ts
* const result = await setAppUsernameNamespace(sessionClient, {
* usernameNamespace: {
* custom: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* },
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setAppUsernameNamespace(
client: SessionClient,
request: SetAppUsernameNamespaceRequest,
): ResultAsync<
SetAppUsernameNamespaceResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SetAppUsernameNamespaceMutation, { request });
}
/**
* Add an authorization endpoint to an App
*
* ```ts
* const result = await addAppAuthorizationEndpoint(sessionClient, {
* endpoint: uri('https://example.com/auth'),
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* bearerToken: 'Qdy136748…',
* });
* ```
*
* @param client - The session client logged as a builder.
* @param request - The mutation request.
* @returns void.
*/
export function addAppAuthorizationEndpoint(
client: SessionClient,
request: AddAppAuthorizationEndpointRequest,
): ResultAsync {
return client.mutation(AddAppAuthorizationEndpointMutation, {
// biome-ignore lint/suspicious/noExplicitAny: work around and issue with endpoint: URL using browser URL instead of specified scalar
request: request as any,
});
}
/**
* Remove an authorization endpoint to an App
*
* ```ts
* const result = await removeAppAuthorizationEndpoint(sessionClient, {
* app: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged as a builder.
* @param request - The mutation request.
* @returns void.
*/
export function removeAppAuthorizationEndpoint(
client: SessionClient,
request: RemoveAppAuthorizationEndpointRequest,
): ResultAsync {
return client.mutation(RemoveAppAuthorizationEndpointMutation, { request });
}
/**
* Checks if the App has a server API key allocated.
*
* The actual value is redacted for security reasons.
*
* You MUST be logged-in as Builder and be the owner of the App.
*
* @param client - The session client logged as a builder.
* @param request - The query request.
* @returns The server API key for the App.
*/
export function fetchAppServerAPiKey(
client: SessionClient,
request: AppServerApiKeyRequest,
): ResultAsync {
return client.query(AppServerApiKeyQuery, { request });
}
/**
* Generate a new server API key for an App.
*
* You MUST be logged-in as Builder and be the owner of the App.
*
* @param client - The session client logged as a builder.
* @param request - The mutation request.
* @returns The new server API key for the App.
*/
export function generateNewAppServerApiKey(
client: SessionClient,
request: GenerateNewAppServerApiKeyRequest,
): ResultAsync {
return client.mutation(GenerateNewAppServerApiKeyMutation, { request });
}
================================================
FILE: packages/client/src/actions/authentication.ts
================================================
import type {
Account,
AuthenticatedSession,
AuthenticatedSessionsRequest,
LastLoggedInAccountRequest,
MeResult,
Paginated,
RefreshRequest,
RefreshResult,
RevokeAuthenticationRequest,
RolloverRefreshRequest,
SwitchAccountRequest,
SwitchAccountResult,
} from '@lens-protocol/graphql';
import {
AuthenticatedSessionsQuery,
CurrentSessionQuery,
LastLoggedInAccountQuery,
LegacyRolloverRefreshMutation,
MeQuery,
RefreshMutation,
RevokeAuthenticationMutation,
SwitchAccountMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Get the AuthenticatedSession associated with the authenticated Account.
*
* ```ts
* const result = await currentSession(sessionClient);
* ```
*
* @param client - The session client.
* @returns The current AuthenticatedSession details.
*/
export function currentSession(
client: SessionClient,
): ResultAsync {
return client.query(CurrentSessionQuery, {});
}
/**
* Revoke the authentication from the provided authentication ID.
*
* @remarks Use the {@link SessionClient#logout} method to logout instead.
* It's unlikely you'll need to use this action directly.
*
* ```ts
* const result = await revokeAuthentication(sessionClient, {
* authenticationId: 'f4dd2ea1-58d4-4210-86a2-67b7571f241a',
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Void if the operation was successful.
*/
export function revokeAuthentication(
client: SessionClient,
request: RevokeAuthenticationRequest,
): ResultAsync {
return client.mutation(RevokeAuthenticationMutation, { request });
}
/**
* Refresh the authentication tokens of the authenticated Account.
*
* ```ts
* const result = await refresh(anyClient, {
* refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5c…'
* });
* ```
*
* @param client - Any Lens client.
* @param request - The mutation request.
* @returns The refreshed authentication tokens if the operation was successful.
*/
export function refresh(
client: AnyClient,
request: RefreshRequest,
): ResultAsync {
return client.mutation(RefreshMutation, { request });
}
/**
* Fetch the authenticated sessions associated with the authenticated Account.
*
* ```ts
* const result = await fetchAuthenticatedSessions(sessionClient);
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The query request.
* @returns The paginated authenticated sessions associated with the authenticated Account.
*/
export function fetchAuthenticatedSessions(
client: SessionClient,
request: AuthenticatedSessionsRequest = {},
): ResultAsync, UnexpectedError> {
return client.query(AuthenticatedSessionsQuery, { request });
}
/**
* Issue new authentication tokens from a valid Lens API v2 refresh token.
*
* Use this to seamlessly transition your users from Lens API v2 to Lens API v3 without
* requiring them to re-authenticate.
*
* The HTTP Origin header MUST be present and match the app's domain.
*
* ```ts
* const result = await legacyRolloverRefresh(sessionClient, {
* refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5c…',
* app: evmAddress("0xe5439696f4057aF073c0FB2dc6e5e755392922e1"),
* });
* ```
*
* @param client - The client to use for the rollover refresh operation.
* @param request - The mutation request.
* @returns The refreshed authentication tokens if the operation was successful.
*/
export function legacyRolloverRefresh(
client: SessionClient,
request: RolloverRefreshRequest,
): ResultAsync {
return client.mutation(LegacyRolloverRefreshMutation, { request });
}
/**
* Switch to another managed account.
*
* @remarks Use the {@link SessionClient#switchAccount} method to switch to an account instead.
* It's unlikely you'll need to use this action directly.
*
* @param client - The current session client.
* @param request - The query request.
* @returns The authenticated tokens for the switched account.
*/
export function switchAccount(
client: SessionClient,
request: SwitchAccountRequest,
): ResultAsync {
return client.mutation(SwitchAccountMutation, { request });
}
/**
* Retrieve the details of the authenticated Account.
*
* @param client - The session client for the authenticated Account.
* @returns The details of the authenticated Account.
*/
export function fetchMeDetails(
client: SessionClient,
): ResultAsync {
return client.query(MeQuery, {});
}
/**
* Get the last logged in account.
*
* ```ts
* const result = await lastLoggedInAccount(anyClient, {
* address: evmAddress('0x90c8c68d0Abfb40D4fCD72316A65e42161520BC3'),
* });
* ```
*
* @param client - The session client.
* @param request - The query request.
* @returns The last logged in account.
*/
export function lastLoggedInAccount(
client: AnyClient,
request: LastLoggedInAccountRequest,
): ResultAsync {
return client.query(LastLoggedInAccountQuery, { request });
}
================================================
FILE: packages/client/src/actions/feed.ts
================================================
import type {
CreateFeedRequest,
CreateFeedResult,
Feed,
FeedRequest,
FeedsRequest,
Paginated,
SetFeedMetadataRequest,
SetFeedMetadataResult,
UpdateFeedRulesRequest,
UpdateFeedRulesResult,
} from '@lens-protocol/graphql';
import {
CreateFeedMutation,
FeedQuery,
FeedsQuery,
SetFeedMetadataMutation,
UpdateFeedRulesMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Create a Feed
*
* ```ts
* const result = await createFeed(sessionClient);
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createFeed(
client: SessionClient,
request: CreateFeedRequest,
): ResultAsync {
return client.mutation(CreateFeedMutation, { request });
}
/**
* Set Feed Metadata
*
* ```ts
* const result = await setFeedMetadata(sessionClient, {
* feed: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* metadataUri: uri("lens://4f91..."),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setFeedMetadata(
client: SessionClient,
request: SetFeedMetadataRequest,
): ResultAsync {
return client.mutation(SetFeedMetadataMutation, { request });
}
/**
* Fetch a Feed.
*
* ```ts
* const result = await fetchFeed(anyClient, {
* feed: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The Feed query request.
* @returns The Feed or `null` if it does not exist.
*/
export function fetchFeed(
client: AnyClient,
request: FeedRequest,
): ResultAsync {
return client.query(FeedQuery, { request });
}
/**
* Fetch Feeds.
*
* ```ts
* const result = await fetchFeeds(anyClient, {
* filter: {
* managedBy: {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* }
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The Feeds query request.
* @returns The list of Feeds or empty list if none exist.
*/
export function fetchFeeds(
client: AnyClient,
request: FeedsRequest,
): ResultAsync, UnexpectedError> {
return client.query(FeedsQuery, { request });
}
/**
* Update feed rules.
*
* ```ts
* const result = await updateFeedRules(sessionClient, {
* feed: evmAddress('0x1234…'),
* toAdd: {
* required: [{
* tokenGatedRule: {
* standard: TokenStandard.Erc20,
* currency: evmAddress('0x5678…'),
* value: '1.5', // Token value in its main unit
* }
* }],
* anyOf: [],
* }
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateFeedRules(
client: SessionClient,
request: UpdateFeedRulesRequest,
): ResultAsync {
return client.mutation(UpdateFeedRulesMutation, { request });
}
================================================
FILE: packages/client/src/actions/follow.ts
================================================
import type {
CreateFollowRequest,
CreateUnfollowRequest,
Follower,
FollowersRequest,
FollowersYouKnowRequest,
Following,
FollowingRequest,
FollowResult,
FollowStatusRequest,
FollowStatusResult,
Paginated,
UnfollowResult,
} from '@lens-protocol/graphql';
import {
FollowersQuery,
FollowersYouKnowQuery,
FollowingQuery,
FollowMutation,
FollowStatusQuery,
UnfollowMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Follow an Account
*
* ```ts
* const result = await follow(sessionClient, {
* account: evmAddress('0x90c8c68d0Abfb40D4fCD72316A65e42161520BC3')
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function follow(
client: SessionClient,
request: CreateFollowRequest,
): ResultAsync {
return client.mutation(FollowMutation, { request });
}
/**
* Unfollow an Account
*
* ```ts
* const result = await unfollow(sessionClient, {
* account: evmAddress('0x90c8c68d0Abfb40D4fCD72316A65e42161520BC3')
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function unfollow(
client: SessionClient,
request: CreateUnfollowRequest,
): ResultAsync {
return client.mutation(UnfollowMutation, { request });
}
/**
* Fetch followers accounts.
*
* ```ts
* const result = await fetchFollowers(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns List of followers accounts.
*/
export function fetchFollowers(
client: AnyClient,
request: FollowersRequest,
): ResultAsync, UnexpectedError> {
return client.query(FollowersQuery, { request });
}
/**
* Fetch following accounts for an account.
*
* ```ts
* const result = await fetchFollowing(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns List of accounts following.
*/
export function fetchFollowing(
client: AnyClient,
request: FollowingRequest,
): ResultAsync, UnexpectedError> {
return client.query(FollowingQuery, { request });
}
/**
* Fetch followers you know.
*
* ```ts
* const result = await fetchFollowersYouKnow(anyClient, {
* observer: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* target: evmAddress('0xe5439696f4057aF073c0FB2dc6e5e755392922e1'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns List of accounts following.
*/
export function fetchFollowersYouKnow(
client: AnyClient,
request: FollowersYouKnowRequest,
): ResultAsync, UnexpectedError> {
return client.query(FollowersYouKnowQuery, { request });
}
/**
* Fetch follow status.
*
* ```ts
* const result = await fetchFollowStatus(anyClient,
* pairs: [
* {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* follower: evmAddress('0xe5439696f4057aF073c0FB2dc6e5e755392922e1'),
* }
* ],
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns Status of the follow action.
*/
export function fetchFollowStatus(
client: AnyClient,
request: FollowStatusRequest,
): ResultAsync {
return client.query(FollowStatusQuery, { request });
}
================================================
FILE: packages/client/src/actions/frames.ts
================================================
import type {
CreateFrameEip712TypedDataFragment,
CreateFrameTypedDataRequest,
FrameLensManagerSignatureResultFragment,
SignFrameActionRequest,
VerifyFrameSignatureRequest,
} from '@lens-protocol/graphql';
import {
CreateFrameTypedDataQuery,
type FrameVerifySignatureResult,
SignFrameActionMutation,
VerifyFrameSignatureQuery,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Create Frame action typed data to be signed by user wallet
*
* @param request - The request object
* @returns Typed data for Frame request
* @experimental This function might change in the future release
*
* @example
* ```ts
* const result = await createFrameTypedData(anyClient, {
* transactionId: '0x0000000000000000000000000000000000000000',
* buttonIndex: 2,
* deadline: 1711038973,
* inputText: 'Hello, World!',
* account: '0x0000000000000000000000000000000000000000',
* post: '0x01-0x01',
* app: '0x0000000000000000000000000000000000000000,
* specVersion: '1.1.0',
* state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}',
* url: 'https://mylensframe.xyz',
* });
* ```
*/
export function createFrameTypedData(
client: AnyClient,
request: CreateFrameTypedDataRequest,
): ResultAsync {
return client.query(CreateFrameTypedDataQuery, { request });
}
/**
* Sign Frame action with Lens Manager if enabled
*
* ⚠️ Requires authenticated SessionClient.
*
* @param request - The request object
* @returns Signature result
* @experimental This function might change in the future release
*
* @example
* ```ts
* const result = await signFrameAction(sessionClient, {
* transactionId: '0x0000000000000000000000000000000000000000',
* buttonIndex: 2,
* inputText: 'Hello, World!',
* account: '0x0000000000000000000000000000000000000000',
* post: '0x01-0x01',
* app: '0x0000000000000000000000000000000000000000,
* specVersion: '1.1.0',
* state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}',
* url: 'https://mylensframe.xyz',
* });
* ```
*/
export function signFrameAction(
client: SessionClient,
request: SignFrameActionRequest,
): ResultAsync<
FrameLensManagerSignatureResultFragment,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SignFrameActionMutation, { request });
}
/**
* Verify Frame signature
*
* @param request - The request object
* @returns Verification result
* @experimental This function might change in the future release
*
* @example
* ```ts
* const result = await verifyFrameSignature(anyClient, {
* identityToken: identityToken,
* signature: data.signature,
* signedTypedData: data.signedTypedData,
* });
* ```
*/
export function verifyFrameSignature(
client: AnyClient,
request: VerifyFrameSignatureRequest,
): ResultAsync {
return client.query(VerifyFrameSignatureQuery, { request });
}
================================================
FILE: packages/client/src/actions/funds.e2e.ts
================================================
import type { Erc20Amount, NativeAmount } from '@lens-protocol/graphql';
import { assertOk, bigDecimal, evmAddress, Result } from '@lens-protocol/types';
import { Big } from 'big.js';
import { zeroAddress } from 'viem';
import { beforeAll, describe, expect, it } from 'vitest';
import type { SessionClient } from '../clients';
import {
CHAIN,
loginAsAccountOwner,
TEST_ACCOUNT,
TEST_ERC20,
wallet,
} from '../test-utils';
import { handleOperationWith } from '../viem';
import {
deposit,
fetchBalancesBulk,
unwrapTokens,
withdraw,
wrapTokens,
} from './funds';
import { findErc20Amount, findNativeAmount } from './helpers';
async function fetchBalances(
sessionClient: SessionClient,
): Promise<[NativeAmount, Erc20Amount]> {
const result = await fetchBalancesBulk(sessionClient, {
address: TEST_ACCOUNT,
includeNative: true,
tokens: [TEST_ERC20],
}).andThen((balances) =>
Result.combine([
findNativeAmount(balances),
findErc20Amount(TEST_ERC20, balances),
]),
);
assertOk(result);
return result.value;
}
// TODO: remove this once LENS-1212 is fixed
async function retryBalanceCheck(
sessionClient: SessionClient,
assertion: (balances: [NativeAmount, Erc20Amount]) => void,
): Promise {
let lastError: Error | null = null;
let attempt = 0;
do {
try {
const balances = await fetchBalances(sessionClient);
assertion(balances);
return;
} catch (error) {
lastError = error as Error;
}
attempt++;
await new Promise((resolve) => setTimeout(resolve, 250));
} while (attempt < 4);
throw lastError || new Error('Balance check failed after all retries');
}
describe('Given a Lens Account', { timeout: 20_000 }, () => {
describe(`When calling the '${fetchBalancesBulk.name}' action`, () => {
it('Then it should return the requested balance amounts', async () => {
const result = await loginAsAccountOwner().andThen((sessionClient) =>
fetchBalancesBulk(sessionClient, {
address: TEST_ACCOUNT,
includeNative: true,
tokens: [TEST_ERC20],
}),
);
assertOk(result);
expect(result.value).toMatchObject([
{
__typename: 'NativeAmount',
asset: {
symbol: 'GRASS',
},
value: expect.any(String),
},
{
__typename: 'Erc20Amount',
asset: {
contract: {
chainId: CHAIN.id,
address: TEST_ERC20,
},
},
value: expect.any(String),
},
]);
});
it('Then it should be resilient and have a local error just for the failed balance', async () => {
const result = await loginAsAccountOwner().andThen((sessionClient) =>
fetchBalancesBulk(sessionClient, {
address: TEST_ACCOUNT,
includeNative: true,
tokens: [evmAddress(zeroAddress), TEST_ERC20],
}),
);
assertOk(result);
expect(result.value).toMatchObject([
{
__typename: 'NativeAmount',
asset: {
symbol: 'GRASS',
},
value: expect.any(String),
},
{
__typename: 'Erc20BalanceError',
reason: expect.any(String),
token: zeroAddress,
},
{
__typename: 'Erc20Amount',
asset: {
contract: {
chainId: CHAIN.id,
address: TEST_ERC20,
},
},
value: expect.any(String),
},
]);
});
});
describe('When managing Account funds', () => {
let sessionClient: SessionClient;
beforeAll(async () => {
await loginAsAccountOwner().andTee((client) => {
sessionClient = client;
});
});
it.sequential(
`Then it should be possible to deposit native tokens via the '${deposit.name}' action`,
async () => {
const [native] = await fetchBalances(sessionClient);
const result = await deposit(sessionClient, {
native: bigDecimal(1),
}).andThen(handleOperationWith(wallet));
assertOk(result);
await retryBalanceCheck(sessionClient, ([newNative]) => {
expect(Big(newNative.value)).toEqual(Big(native.value).add(1));
});
},
);
it.sequential(
`Then it should be possible to wrap native tokens via the '${wrapTokens.name}' action`,
async () => {
const [native, wrapped] = await fetchBalances(sessionClient);
const result = await wrapTokens(sessionClient, {
amount: bigDecimal(1),
}).andThen(handleOperationWith(wallet));
assertOk(result);
await retryBalanceCheck(sessionClient, ([newNative, newWrapped]) => {
expect(Big(newNative.value)).toEqual(Big(native.value).sub(1));
expect(Big(newWrapped.value)).toEqual(Big(wrapped.value).add(1));
});
},
);
it.sequential(
`Then it should be possible to withdraw ERC20 tokens via the '${withdraw.name}' action`,
async () => {
const [, wrapped] = await fetchBalances(sessionClient);
const result = await withdraw(sessionClient, {
erc20: {
currency: TEST_ERC20,
value: bigDecimal(1),
},
}).andThen(handleOperationWith(wallet));
assertOk(result);
await retryBalanceCheck(sessionClient, ([, newWrapped]) => {
expect(Big(newWrapped.value)).toEqual(Big(wrapped.value).sub(1));
});
},
);
it.sequential(
`Then it should be possible to deposit ERC20 tokens via the '${deposit.name}' action`,
async () => {
const [, wrapped] = await fetchBalances(sessionClient);
const result = await deposit(sessionClient, {
erc20: {
currency: TEST_ERC20,
value: bigDecimal(1),
},
}).andThen(handleOperationWith(wallet));
assertOk(result);
await retryBalanceCheck(sessionClient, ([, newWrapped]) => {
expect(Big(newWrapped.value)).toEqual(Big(wrapped.value).add(1));
});
},
);
it.sequential(
`Then it should be possible to unwrap wrapped native tokens via the '${unwrapTokens.name}' action`,
async () => {
const [native, wrapped] = await fetchBalances(sessionClient);
const result = await unwrapTokens(sessionClient, {
amount: bigDecimal(1),
}).andThen(handleOperationWith(wallet));
assertOk(result);
await retryBalanceCheck(sessionClient, ([newNative, newWrapped]) => {
expect(Big(newNative.value)).toEqual(Big(native.value).add(1));
expect(Big(newWrapped.value)).toEqual(Big(wrapped.value).sub(1));
});
},
);
it.sequential(
`Then it should be possible to withdraw native tokens via the '${withdraw.name}' action`,
async () => {
const [native] = await fetchBalances(sessionClient);
const result = await withdraw(sessionClient, {
native: bigDecimal(1),
}).andThen(handleOperationWith(wallet));
assertOk(result);
await retryBalanceCheck(sessionClient, ([newNative]) => {
expect(Big(newNative.value)).toEqual(Big(native.value).sub(1));
});
},
);
});
});
================================================
FILE: packages/client/src/actions/funds.ts
================================================
import type {
AccountBalancesRequest,
AnyAccountBalance,
AnyBalance,
BalancesBulkRequest,
DepositRequest,
DepositResult,
UnwrapTokensRequest,
UnwrapTokensResult,
WithdrawRequest,
WithdrawResult,
WrapTokensRequest,
WrapTokensResult,
} from '@lens-protocol/graphql';
import {
AccountBalancesQuery,
BalancesBulkQuery,
DepositMutation,
UnwrapTokensMutation,
WithdrawMutation,
WrapTokensMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* @deprecated Use `fetchBalancesBulk` instead.
*
* Fetch balances for the authenticated Account.
*
* ```ts
* const result = await fetchBalancesBulk(sessionClient, {
* includeNative: true,
* tokens: [
* evmAddress("0x12345…"),
* evmAddress("0x67890…"),
* ]
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The query request.
* @returns The list of balance results.
*/
export function fetchAccountBalances(
client: SessionClient,
request: AccountBalancesRequest,
): ResultAsync {
return client.query(AccountBalancesQuery, { request });
}
/**
* Fetch balances for the provided addresses. Needs to be authenticated to execute.
*
* ```ts
* const result = await fetchBalancesBulk(anyClient, {
* address: evmAddress("0x12345…"),
* includeNative: true,
* tokens: [
* evmAddress("0x45678…"),
* evmAddress("0x90123…"),
* ],
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The query request.
* @returns The list of balance results for the provided addresses.
*/
export function fetchBalancesBulk(
client: SessionClient,
request: BalancesBulkRequest,
): ResultAsync {
return client.query(BalancesBulkQuery, { request });
}
/**
* Withdraw funds from the authenticated Account.
*
* ```ts
* const result = await withdraw(sessionClient, {
* native: bigDecimal(42.5),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function withdraw(
sessionClient: SessionClient,
request: WithdrawRequest,
): ResultAsync {
return sessionClient.mutation(WithdrawMutation, { request });
}
/**
* Deposit funds into the authenticated Account.
*
* ```ts
* const result = await deposit(sessionClient, {
* native: bigDecimal(42.5),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function deposit(
sessionClient: SessionClient,
request: DepositRequest,
): ResultAsync {
return sessionClient.mutation(DepositMutation, { request });
}
/**
* Convert native tokens held in the authenticated Lens Account to their ERC20 equivalent. For example:
* - Mainnet: GHO -> WGHO
* - Testnet: GRASS -> WGRASS
*
* ```ts
* const result = await wrapTokens(sessionClient, {
* amount: bigDecimal(42.5),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function wrapTokens(
sessionClient: SessionClient,
request: WrapTokensRequest,
): ResultAsync {
return sessionClient.mutation(WrapTokensMutation, { request });
}
/**
* Unwrap wrapped native tokens held in the authenticated Lens Account. For example:
* - Mainnet: WGHO -> GHO
* - Testnet: WGRASS -> GRASS
*
* ```ts
* const result = await unwrapTokens(sessionClient, {
* amount: bigDecimal(42.5),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function unwrapTokens(
sessionClient: SessionClient,
request: UnwrapTokensRequest,
): ResultAsync {
return sessionClient.mutation(UnwrapTokensMutation, { request });
}
================================================
FILE: packages/client/src/actions/graph.ts
================================================
import type {
CreateGraphRequest,
CreateGraphResult,
Graph,
GraphRequest,
GraphsRequest,
Paginated,
SetGraphMetadataRequest,
SetGraphMetadataResult,
UpdateGraphRulesRequest,
UpdateGraphRulesResult,
} from '@lens-protocol/graphql';
import {
CreateGraphMutation,
GraphQuery,
GraphsQuery,
SetGraphMetadataMutation,
UpdateGraphRulesMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Create a Graph
*
* ```ts
* const result = await createGraph(sessionClient);
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createGraph(
client: SessionClient,
request: CreateGraphRequest,
): ResultAsync {
return client.mutation(CreateGraphMutation, { request });
}
/**
* Set Graph Metadata
*
* ```ts
* const result = await setGraphMetadata(sessionClient, {
* graph: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* metadataUri: uri("lens://4f91..."),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setGraphMetadata(
client: SessionClient,
request: SetGraphMetadataRequest,
): ResultAsync {
return client.mutation(SetGraphMetadataMutation, { request });
}
/**
* Fetch a Graph.
*
* ```ts
* const result = await fetchGraph(anyClient, {
* graph: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The Graph query request.
* @returns The Graph or `null` if it does not exist.
*/
export function fetchGraph(
client: AnyClient,
request: GraphRequest,
): ResultAsync {
return client.query(GraphQuery, { request });
}
/**
* Fetch Graphs.
*
* ```ts
* const result = await fetchGraphs(anyClient, {
* filter: {
* managedBy: {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* }
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The Graphs query request.
* @returns The list of Graphs or empty list if none exist.
*/
export function fetchGraphs(
client: AnyClient,
request: GraphsRequest,
): ResultAsync, UnexpectedError> {
return client.query(GraphsQuery, { request });
}
/**
* Update graph rules.
*
* ```ts
* const result = await updateGraphRules(sessionClient, {
* graph: evmAddress('0x1234…'),
* toAdd: {
* required: [{
* tokenGatedRule: {
* standard: TokenStandard.Erc20,
* currency: evmAddress('0x5678…'),
* value: '1.5', // Token value in its main unit
* }
* }],
* anyOf: [],
* }
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateGraphRules(
client: SessionClient,
request: UpdateGraphRulesRequest,
): ResultAsync {
return client.mutation(UpdateGraphRulesMutation, { request });
}
================================================
FILE: packages/client/src/actions/group.e2e.ts
================================================
import type { EvmAddress } from '@lens-chain/storage-client';
import { group } from '@lens-protocol/metadata';
import { assertOk, nonNullable, uri } from '@lens-protocol/types';
import { beforeAll, describe, expect, it } from 'vitest';
import {
loginAsAccountOwner,
loginAsBuilder,
TEST_ACCOUNT,
wallet,
} from '../test-utils';
import { handleOperationWith } from '../viem';
import { createGroup, fetchGroup, joinGroup } from './group';
const metadata = group({
name: 'web3natives',
});
describe('Given a logged-in user', () => {
describe('When joining a Group with SimplePaymentRule in native token denomination', () => {
let groupAddress: EvmAddress;
beforeAll(async () => {
const result = await loginAsBuilder().andThen((sessionClient) =>
createGroup(sessionClient, {
metadataUri: uri(`data:application/json,${JSON.stringify(metadata)}`),
rules: {
required: [
{
simplePaymentRule: {
native: '0.01',
recipient: TEST_ACCOUNT,
},
},
],
},
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchGroup(sessionClient, { txHash }))
.map(nonNullable),
);
assertOk(result);
groupAddress = result.value.address;
}, 15_000);
it('Then the user can join the Group by fulfilling the payment rule with native tokens from the Lens Account', async () => {
const result = await loginAsAccountOwner().andThen((sessionClient) =>
joinGroup(sessionClient, {
group: groupAddress,
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen(() =>
fetchGroup(sessionClient, { group: groupAddress }).map(nonNullable),
),
);
assertOk(result);
expect(result.value.operations?.isMember).toBe(true);
});
});
});
================================================
FILE: packages/client/src/actions/group.ts
================================================
import type {
ApproveGroupMembershipRequest,
ApproveGroupMembershipResult,
BanGroupAccountsRequest,
BanGroupAccountsResult,
CancelGroupMembershipRequestRequest,
CancelGroupMembershipRequestResult,
CreateGroupRequest,
CreateGroupResult,
Group,
GroupBannedAccount,
GroupBannedAccountsRequest,
GroupMember,
GroupMembershipRequest,
GroupMembershipRequestsRequest,
GroupMembersRequest,
GroupRequest,
GroupStatsRequest,
GroupStatsResponse,
GroupsRequest,
JoinGroupRequest,
JoinGroupResult,
LeaveGroupRequest,
LeaveGroupResult,
Paginated,
RejectGroupMembershipRequest,
RejectGroupMembershipResult,
RemoveGroupMembersRequest,
RemoveGroupMembersResult,
RequestGroupMembershipRequest,
RequestGroupMembershipResult,
SetGroupMetadataRequest,
SetGroupMetadataResult,
UnbanGroupAccountsRequest,
UnbanGroupAccountsResult,
UpdateGroupRulesRequest,
UpdateGroupRulesResult,
} from '@lens-protocol/graphql';
import {
ApproveGroupMembershipRequestsMutation,
BanGroupAccountsMutation,
CancelGroupMembershipRequestMutation,
CreateGroupMutation,
GroupBannedAccountsQuery,
GroupMembershipRequestsQuery,
GroupMembersQuery,
GroupQuery,
GroupStatsQuery,
GroupsQuery,
JoinGroupMutation,
LeaveGroupMutation,
RejectGroupMembershipRequestsMutation,
RemoveGroupMembersMutation,
RequestGroupMembershipMutation,
SetGroupMetadataMutation,
UnbanGroupAccountsMutation,
UpdateGroupRulesMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Create a Group
*
* ```ts
* const result = await createGroup(sessionClient, {
* metadataUri: uri("lens://4f91..."),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createGroup(
client: SessionClient,
request: CreateGroupRequest,
): ResultAsync {
return client.mutation(CreateGroupMutation, { request });
}
/**
* Set Group Metadata
*
* ```ts
* const result = await setGroupMetadata(sessionClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* metadataUri: uri("lens://4f91..."),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setGroupMetadata(
client: SessionClient,
request: SetGroupMetadataRequest,
): ResultAsync {
return client.mutation(SetGroupMetadataMutation, { request });
}
/**
* Join a Group
*
* ```ts
* const result = await joinGroup(sessionClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function joinGroup(
client: SessionClient,
request: JoinGroupRequest,
): ResultAsync {
return client.mutation(JoinGroupMutation, { request });
}
/**
* Leave a Group
*
* ```ts
* const result = await leaveGroup(sessionClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function leaveGroup(
client: SessionClient,
request: LeaveGroupRequest,
): ResultAsync {
return client.mutation(LeaveGroupMutation, { request });
}
/**
* Fetch a Group.
*
* ```ts
* const result = await fetchGroup(anyClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The Group or `null` if it does not exist.
*/
export function fetchGroup(
client: AnyClient,
request: GroupRequest,
): ResultAsync {
return client.query(GroupQuery, { request });
}
/**
* Fetch groups.
*
* ```ts
* const result = await fetchGroups(anyClient);
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of groups.
*/
export function fetchGroups(
client: AnyClient,
request: GroupsRequest = {},
): ResultAsync, UnexpectedError> {
return client.query(GroupsQuery, { request });
}
/**
* Fetch group members.
*
* ```ts
* const result = await fetchGroupMembers(anyClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of members for the group.
*/
export function fetchGroupMembers(
client: AnyClient,
request: GroupMembersRequest,
): ResultAsync, UnexpectedError> {
return client.query(GroupMembersQuery, { request });
}
/**
* Fetch stats for a group.
*
* ```ts
* const result = await fetchGroupStats(anyClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The stats for the group.
*/
export function fetchGroupStats(
client: AnyClient,
request: GroupStatsRequest,
): ResultAsync {
return client.query(GroupStatsQuery, { request });
}
/**
* Fetch banned accounts for a group.
*
* ```ts
* const result = await fetchGroupBannedAccounts(anyClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of banned accounts for the group.
*/
export function fetchGroupBannedAccounts(
client: AnyClient,
request: GroupBannedAccountsRequest,
): ResultAsync, UnexpectedError> {
return client.query(GroupBannedAccountsQuery, { request });
}
/**
* Fetch membership requests for a group (only admin/owner).
*
* ```ts
* const result = await fetchGroupMembershipRequests(sessionClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The query request.
* @returns The list of membership requests for the group.
*/
export function fetchGroupMembershipRequests(
client: SessionClient,
request: GroupMembershipRequestsRequest,
): ResultAsync, UnexpectedError> {
return client.query(GroupMembershipRequestsQuery, { request });
}
/**
* Update group rules.
*
* ```ts
* const result = await updateGroupRules(sessionClient, {
* group: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* toAdd: {
* required: [{
* membershipApprovalRule: {
* enable: true
* }
* }]
* anyOf: [],
* }
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateGroupRules(
client: SessionClient,
request: UpdateGroupRulesRequest,
): ResultAsync {
return client.mutation(UpdateGroupRulesMutation, { request });
}
/**
* Approve group membership requests.
*
* ```ts
* const result = await approveGroupMembershipRequests(sessionClient, {
* group: evmAddress('0xe2f2…'),
* accounts: [evmAddress('0x4f91…'), evmAddress('0x4f92…')],
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function approveGroupMembershipRequests(
client: SessionClient,
request: ApproveGroupMembershipRequest,
): ResultAsync<
ApproveGroupMembershipResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(ApproveGroupMembershipRequestsMutation, { request });
}
/**
* Remove account from a group.
*
* ```ts
* const result = await removeGroupMembers(sessionClient, {
* group: evmAddress('0xe2f…'),
* accounts: [evmAddress('0x4f91…'), evmAddress('0x4f92…')],
* ban: true, // contextually bans the accounts from rejoining
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function removeGroupMembers(
client: SessionClient,
request: RemoveGroupMembersRequest,
): ResultAsync<
RemoveGroupMembersResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(RemoveGroupMembersMutation, { request });
}
/**
* Request membership for a group.
*
* ```ts
* const result = await requestGroupMembership(sessionClient, {
* group: evmAddress('0xe2f…'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function requestGroupMembership(
client: SessionClient,
request: RequestGroupMembershipRequest,
): ResultAsync<
RequestGroupMembershipResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(RequestGroupMembershipMutation, { request });
}
/**
* Cancel and existing request to be part of a group.
*
* ```ts
* const result = await cancelGroupMembershipRequest(sessionClient, {
* group: evmAddress('0xe2f…'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function cancelGroupMembershipRequest(
client: SessionClient,
request: CancelGroupMembershipRequestRequest,
): ResultAsync<
CancelGroupMembershipRequestResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(CancelGroupMembershipRequestMutation, { request });
}
/**
* Reject group membership requests.
*
* You must be the owner or admin of the group to reject membership requests.
*
* ```ts
* const result = await rejectGroupMembershipRequests(sessionClient, {
* group: evmAddress('0xe2f…'),
* accounts: [evmAddress('0x4f91…'), evmAddress('0x4f92…')],
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function rejectGroupMembershipRequests(
client: SessionClient,
request: RejectGroupMembershipRequest,
): ResultAsync<
RejectGroupMembershipResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(RejectGroupMembershipRequestsMutation, { request });
}
/**
* Ban accounts from a group. These accounts will not be able to join the group.
*
* You must be the owner or admin of the group to ban accounts.
*
* ```ts
* const result = await banGroupAccount(sessionClient, {
* group: evmAddress('0xe2f…'),
* accounts: [evmAddress('0x4f91…'), evmAddress('0x4f92…')],
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function banGroupAccounts(
client: SessionClient,
request: BanGroupAccountsRequest,
): ResultAsync {
return client.mutation(BanGroupAccountsMutation, { request });
}
/**
* Unban accounts from a group.
*
* You must be the owner or admin of the group to unban accounts.
*
* ```ts
* const result = await unbanGroupAccounts(sessionClient, {
* group: evmAddress('0xe2f…'),
* accounts: [evmAddress('0x4f91…'), evmAddress('0x4f92…')],
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function unbanGroupAccounts(
client: SessionClient,
request: UnbanGroupAccountsRequest,
): ResultAsync<
UnbanGroupAccountsResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(UnbanGroupAccountsMutation, { request });
}
================================================
FILE: packages/client/src/actions/helpers.ts
================================================
import { InvariantError } from '@lens-chain/storage-client';
import type {
AnyAccountBalance,
Erc20Amount,
NativeAmount,
} from '@lens-protocol/graphql';
import { type EvmAddress, err, ok, type Result } from '@lens-protocol/types';
import { UnexpectedError } from '../errors';
/**
* Given a list of account balances, find the native amount.
*
* @experimental This function is subject to change.
* @param balances - A list of account balances as returned by the {@link fetchAccountBalances} action.
* @returns The native amount.
*/
export function findNativeAmount(
balances: AnyAccountBalance[],
): Result {
for (const entry of balances) {
switch (entry.__typename) {
case 'NativeBalanceError':
return err(new UnexpectedError(entry.reason));
case 'NativeAmount':
return ok(entry);
}
}
return err(new InvariantError('No native balance found'));
}
/**
* Given a list of account balances, find the ERC20 entry for a given token.
*
* @experimental This function is subject to change.
* @param token - The token address.
* @param balances - A list of account balances as returned by the {@link fetchAccountBalances} action.
* @returns The ERC20 amount.
*/
export function findErc20Amount(
token: EvmAddress,
balances: AnyAccountBalance[],
): Result {
for (const entry of balances) {
switch (entry.__typename) {
case 'Erc20BalanceError':
if (entry.token === token) {
return err(new UnexpectedError(entry.reason));
}
break;
case 'Erc20Amount':
if (entry.asset.contract.address === token) {
return ok(entry);
}
break;
}
}
return err(new InvariantError(`No balance found for token ${token}`));
}
================================================
FILE: packages/client/src/actions/index.ts
================================================
export * from './account';
export * from './accountManager';
export * from './actions';
export * from './admins';
export * from './app';
export * from './authentication';
export * from './feed';
export * from './follow';
export * from './frames';
export * from './funds';
export * from './graph';
export * from './group';
export * from './helpers';
export * from './metadata';
export * from './misc';
export * from './ml';
export * from './namespace';
export * from './notifications';
export * from './post';
export * from './posts';
export * from './sns';
export * from './sponsorship';
export * from './timeline';
export * from './transactions';
export * from './transfer';
export * from './username';
================================================
FILE: packages/client/src/actions/metadata.test.ts
================================================
import { justPost, type Post } from '@lens-protocol/graphql';
import { textOnly } from '@lens-protocol/metadata';
import { assertOk, nonNullable } from '@lens-protocol/types';
import { beforeAll, describe, expect, it } from 'vitest';
import {
createPublicClient,
loginAsAccountOwner,
updateTextOnlyMetadata,
uploadTextOnlyPostMetadata,
wallet,
} from '../test-utils';
import { handleOperationWith } from '../viem';
import { refreshMetadata, waitForMetadata } from './metadata';
import { post } from './post';
import { fetchPost } from './posts';
describe('Given a Lens Post', () => {
let item: Post;
beforeAll(async () => {
const resources = await uploadTextOnlyPostMetadata();
const result = await loginAsAccountOwner().andThen((sessionClient) =>
post(sessionClient, {
contentUri: resources.uri,
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((tx) => fetchPost(sessionClient, { txHash: tx }))
.map(nonNullable)
.map(justPost),
);
assertOk(result);
item = result.value;
// Make sure the metadata is on IPFS before being able to edit/delete it
await resources.waitForPropagation();
}, 20_000);
describe(`When the metadata at 'contentUri' is updated`, () => {
const client = createPublicClient();
const updates = textOnly({ content: 'This is the new content' });
beforeAll(async () => {
const response = await updateTextOnlyMetadata(item.contentUri, updates);
await response.waitForPropagation();
}, 20_000);
it('Then it should be possible to force a refresh of the metadata', async () => {
const refreshed = await refreshMetadata(client, {
entity: { post: item.id },
}).andThen(({ id }) => waitForMetadata(client, id));
assertOk(refreshed);
const fetched = await fetchPost(client, { post: item.id })
.map(nonNullable)
.map(justPost);
assertOk(fetched);
expect(fetched.value.metadata).toHaveProperty(
'content',
updates.lens.content,
);
});
});
});
================================================
FILE: packages/client/src/actions/metadata.ts
================================================
import type {
RefreshMetadataRequest,
RefreshMetadataResult,
RefreshMetadataStatusRequest,
RefreshMetadataStatusResult,
} from '@lens-protocol/graphql';
import {
IndexingStatus,
RefreshMetadataMutation,
RefreshMetadataStatusQuery,
} from '@lens-protocol/graphql';
import { ResultAsync, type UUID } from '@lens-protocol/types';
import type { AnyClient } from '../clients';
import {
MetadataIndexingError,
type UnauthenticatedError,
UnexpectedError,
} from '../errors';
import { delay } from '../utils';
/**
* Fetch the indexing status of metadata.
*
* ```ts
* const result = await refreshMetadataStatus(anyClient, {
* id: uuid("a0a88a62-377f-46eb-a1ec-ca6597aef164")
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The indexing status of the metadata.
*/
export function refreshMetadataStatus(
client: AnyClient,
request: RefreshMetadataStatusRequest,
): ResultAsync {
return client.query(RefreshMetadataStatusQuery, { request });
}
/**
* Refresh the metadata for a given entity.
*
* ```ts
* const result = await refreshMetadata(anyClient, {
* entity: {
* post: postId('42'),
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The mutation request.
* @returns - UUID to track the metadata refresh.
*/
export function refreshMetadata(
client: AnyClient,
request: RefreshMetadataRequest,
): ResultAsync {
return client.mutation(RefreshMetadataMutation, { request });
}
/**
* Given a metadata id, wait for the metadata to be either confirmed or rejected by the Lens API.
*
* @param client - Any Lens client.
* @param id - The metadata id to wait for.
* @returns The metadata id if the metadata was confirmed or an error if the transaction was rejected.
*/
export function waitForMetadata(
client: AnyClient,
id: UUID,
): ResultAsync {
return ResultAsync.fromPromise(pollMetadataStatus(client, id), (err) => {
if (
err instanceof MetadataIndexingError ||
err instanceof UnexpectedError
) {
return err;
}
return UnexpectedError.from(err);
});
}
async function pollMetadataStatus(client: AnyClient, id: UUID): Promise {
const startedAt = Date.now();
while (Date.now() - startedAt < client.context.environment.indexingTimeout) {
const result = await refreshMetadataStatus(client, { id });
if (result.isErr()) {
throw UnexpectedError.from(result.error);
}
switch (result.value.status) {
case IndexingStatus.Finished:
return result.value.id;
case IndexingStatus.Failed:
throw MetadataIndexingError.from(result.value.reason);
case IndexingStatus.Pending:
await delay(client.context.environment.pollingInterval);
break;
}
}
throw MetadataIndexingError.from(`Timeout waiting for metadata ${id}`);
}
================================================
FILE: packages/client/src/actions/misc.ts
================================================
import {
AccessControlQuery,
type AccessControlRequest,
type AccessControlResult,
HealthQuery,
type Paginated,
type TokenDistribution,
TokenDistributionsQuery,
type TokenDistributionsRequest,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnexpectedError } from '../errors';
/**
* Health check query.
*
* ```ts
* const result = await health(anyClient);
* ```
*
* @param client - Any Lens client.
* @returns True or false
*/
export function health(
client: AnyClient,
): ResultAsync {
return client.query(HealthQuery, {});
}
/**
* Fetch an Access Control details.
*
* ```ts
* const result = await fetchAccessControl(anyClient, {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The Access Control query request.
* @returns The Access Control or `null` if it does not exist.
*/
export function fetchAccessControl(
client: AnyClient,
request: AccessControlRequest,
): ResultAsync {
return client.query(AccessControlQuery, { request });
}
/**
* Fetch list of token distributions received by the authenticated account.
*
* ```ts
* const result = await fetchTokenDistributions(sessionClient);
* ```
*
* @param client - Lens SessionClient.
* @param request - The query request.
* @returns List of token distributions for the authenticated account was rewarded with.
*/
export function fetchTokenDistributions(
client: SessionClient,
request: TokenDistributionsRequest = {},
): ResultAsync, UnexpectedError> {
return client.query(TokenDistributionsQuery, { request });
}
================================================
FILE: packages/client/src/actions/ml.e2e.ts
================================================
import { assertOk } from '@lens-protocol/types';
import { beforeAll, describe, it } from 'vitest';
import type { SessionClient } from '../clients';
import { loginAsAccountOwner, TEST_ACCOUNT } from '../test-utils';
import {
fetchAccountRecommendations,
fetchPostsForYou,
fetchPostsToExplore,
} from './ml';
describe('Given the ML query actions', () => {
let sessionClient: SessionClient;
beforeAll(async () => {
await loginAsAccountOwner().andTee((client) => {
sessionClient = client;
});
});
describe(`When invoking the '${fetchAccountRecommendations.name}' action`, () => {
it('Then it should not fail w/ a GQL BadRequest error', async () => {
const result = await fetchAccountRecommendations(sessionClient, {
account: TEST_ACCOUNT,
});
assertOk(result);
});
});
describe(`When invoking the '${fetchPostsForYou.name}' action`, () => {
it('Then it should not fail w/ a GQL BadRequest error', async () => {
const result = await fetchPostsForYou(sessionClient, {
account: TEST_ACCOUNT,
});
assertOk(result);
});
});
describe(`When invoking the '${fetchPostsToExplore.name}' action`, () => {
it('Then it should not fail w/ a GQL BadRequest error', async () => {
const result = await fetchPostsToExplore(sessionClient, {});
assertOk(result);
});
});
});
================================================
FILE: packages/client/src/actions/ml.ts
================================================
import type {
Account,
AccountRecommendationsRequest,
DismissRecommendedAccountsRequest,
Paginated,
Post,
PostForYou,
PostNotInterestedRequest,
PostsExploreRequest,
PostsForYouRequest,
} from '@lens-protocol/graphql';
import {
AddPostNotInterestedMutation,
MlAccountRecommendationsQuery,
MlDismissRecommendedAccountsMutation,
MlPostsExploreQuery,
MlPostsForYouQuery,
UndoPostNotInterestedMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch account recommendations from ML.
*
* ```ts
* const result = await fetchAccountRecommendations(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list accounts recommended.
*/
export function fetchAccountRecommendations(
client: AnyClient,
request: AccountRecommendationsRequest,
): ResultAsync, UnexpectedError> {
return client.query(MlAccountRecommendationsQuery, { request });
}
/**
* Fetch posts for you from ML.
*
* ```ts
* const result = await fetchPostsForYou(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of recommended posts.
*/
export function fetchPostsForYou(
client: AnyClient,
request: PostsForYouRequest,
): ResultAsync, UnexpectedError> {
return client.query(MlPostsForYouQuery, { request });
}
/**
* Fetch posts to explore.
*
* ```ts
* const result = await fetchPostsToExplore(anyClient);
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of posts to explore.
*/
export function fetchPostsToExplore(
client: AnyClient,
request: PostsExploreRequest,
): ResultAsync, UnexpectedError> {
return client.query(MlPostsExploreQuery, { request });
}
/**
* Dismiss recommended accounts.
*
* ```ts
* const result = await dismissRecommendedAccounts(sessionClient, {
* accounts: [evmAddress('0xe2f2...')],
* });
* ```
*
* @param client - Session Lens client.
* @param request - The list of accounts to dismiss.
* @returns - void
*/
export function dismissRecommendedAccounts(
client: SessionClient,
request: DismissRecommendedAccountsRequest,
): ResultAsync {
return client.mutation(MlDismissRecommendedAccountsMutation, { request });
}
/**
* Flag a post as not of interest.
*
* ```ts
* const result = await addPostNotInterested(sessionClient, {
* post: postID('34fdasd...'),
* });
* ```
*
* @param client - Session Lens client.
* @param request - The post to add as not interested.
* @returns - void
*/
export function addPostNotInterested(
client: SessionClient,
request: PostNotInterestedRequest,
): ResultAsync {
return client.mutation(AddPostNotInterestedMutation, { request });
}
/**
* Undo a previous decision to flag a post as uninteresting.
*
* ```ts
* const result = await undoPostNotInterested(sessionClient, {
* post: postID('34fdasd...'),
* });
* ```
*
* @param client - Session Lens client.
* @param request - The post to remove as not interested.
* @returns - void
*/
export function undoPostNotInterested(
client: SessionClient,
request: PostNotInterestedRequest,
): ResultAsync {
return client.mutation(UndoPostNotInterestedMutation, { request });
}
================================================
FILE: packages/client/src/actions/namespace.ts
================================================
import type {
CreateUsernameNamespaceRequest,
CreateUsernameNamespaceResult,
NamespaceRequest,
NamespaceReservedUsernamesRequest,
NamespacesRequest,
Paginated,
SetNamespaceMetadataRequest,
SetNamespaceMetadataResult,
UpdateNamespaceRulesRequest,
UpdateNamespaceRulesResult,
UpdateReservedUsernamesRequest,
UpdateReservedUsernamesResult,
UsernameNamespace,
UsernameReserved,
} from '@lens-protocol/graphql';
import {
CreateUsernameNamespaceMutation,
NamespaceQuery,
NamespaceReservedUsernamesQuery,
NamespacesQuery,
SetNamespaceMetadataMutation,
UpdateNamespaceRulesMutation,
UpdateReservedUsernamesMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Create a Namespace
*
* ```ts
* const result = await createUsernameNamespace(sessionClient, {
* symbol: 'NAME',
* namespace: 'custom-namespace',
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createUsernameNamespace(
client: SessionClient,
request: CreateUsernameNamespaceRequest,
): ResultAsync<
CreateUsernameNamespaceResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(CreateUsernameNamespaceMutation, { request });
}
/**
* Set Namespace Metadata
*
* ```ts
* const result = await setNamespaceMetadata(sessionClient, {
* namespace: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* metadataUri: uri("lens://4f91..."),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setNamespaceMetadata(
client: SessionClient,
request: SetNamespaceMetadataRequest,
): ResultAsync<
SetNamespaceMetadataResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SetNamespaceMetadataMutation, { request });
}
/**
* Fetch a Namespace.
*
* ```ts
* const result = await fetchNamespace(anyClient, {
* namespace: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The UsernameNamespace or `null` if it does not exist.
*/
export function fetchNamespace(
client: AnyClient,
request: NamespaceRequest,
): ResultAsync {
return client.query(NamespaceQuery, { request });
}
/**
* Fetch Namespaces.
*
* ```ts
* const result = await fetchNamespaces(anyClient, {
* filter: {
* managedBy: {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')
* }
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of Namespaces or empty list if none exist.
*/
export function fetchNamespaces(
client: AnyClient,
request: NamespacesRequest,
): ResultAsync, UnexpectedError> {
return client.query(NamespacesQuery, { request });
}
/**
* Update namespace rules.
*
* ```ts
* const result = await updateNamespaceRules(sessionClient, {
* namespace: evmAddress('0x1234…'),
* toAdd: {
* required: [{
* tokenGatedRule: {
* standard: TokenStandard.Erc20,
* currency: evmAddress('0x5678…'),
* value: '1.5', // Token value in its main unit
* }
* }],
* anyOf: [],
* }
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateNamespaceRules(
client: SessionClient,
request: UpdateNamespaceRulesRequest,
): ResultAsync<
UpdateNamespaceRulesResult,
UnauthenticatedError | UnexpectedError
> {
return client.mutation(UpdateNamespaceRulesMutation, { request });
}
/**
* Update reserved usernames in a namespace.
*
* ```ts
* const result = await updateReservedUsernames(sessionClient, {
* namespace: evmAddress('0x1234…'),
* toRelease: ['alice', 'bob'],
* toReserve: ['charlie', 'dave'],
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateReservedUsernames(
client: SessionClient,
request: UpdateReservedUsernamesRequest,
): ResultAsync<
UpdateReservedUsernamesResult,
UnauthenticatedError | UnexpectedError
> {
return client.mutation(UpdateReservedUsernamesMutation, { request });
}
/**
* Fetch all reserved usernames in a namespace.
*
* ```ts
* const result = await fetchNamespaceReservedUsernames(anyClient, {
* namespace: evmAddress('0x1234…'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of reserved usernames or empty list if none exist.
*/
export function fetchNamespaceReservedUsernames(
client: AnyClient,
request: NamespaceReservedUsernamesRequest,
): ResultAsync, UnexpectedError> {
return client.query(NamespaceReservedUsernamesQuery, { request });
}
================================================
FILE: packages/client/src/actions/notifications.test.ts
================================================
import { assertOk } from '@lens-protocol/types';
import { describe, it } from 'vitest';
import { loginAsAccountOwner } from '../test-utils';
import { fetchNotifications } from './notifications';
describe(`Given the '${fetchNotifications.name}' action`, () => {
describe('When invoked', () => {
it('Then it should not fail w/ a GQL BadRequest error', async () => {
const result = await loginAsAccountOwner().andThen(fetchNotifications);
assertOk(result);
});
});
});
================================================
FILE: packages/client/src/actions/notifications.ts
================================================
import type {
Notification,
NotificationsRequest,
Paginated,
} from '@lens-protocol/graphql';
import { NotificationsQuery } from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import type { UnexpectedError } from '../errors';
/**
* Fetch notifications for the authenticated Account.
*
* ```ts
* const result = await fetchNotifications(sessionClient);
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The query request.
* @returns Paginated notifications.
*/
export function fetchNotifications(
client: SessionClient,
request: NotificationsRequest = {},
): ResultAsync, UnexpectedError> {
return client.query(NotificationsQuery, { request }) as ResultAsync<
Paginated,
UnexpectedError
>;
}
================================================
FILE: packages/client/src/actions/onboarding.e2e.ts
================================================
import { type Account, RulesSubject } from '@lens-protocol/graphql';
import { account } from '@lens-protocol/metadata';
import {
assertOk,
type EvmAddress,
never,
nonNullable,
uri,
} from '@lens-protocol/types';
import { beforeAll, describe, expect, it } from 'vitest';
import {
createPublicClient,
loginAsBuilder,
loginAsOnboardingUser,
TEST_SIGNER,
wallet,
} from '../test-utils';
import { delay } from '../utils';
import { handleOperationWith } from '../viem';
import {
createAccount,
createAccountWithUsername,
fetchAccount,
} from './account';
import { fetchMeDetails } from './authentication';
import { createUsernameNamespace, fetchNamespace } from './namespace';
import { createUsername, fetchUsername } from './username';
const metadata = account({
name: 'John Doe',
bio: 'A test account',
});
describe('Given a new user', { timeout: 10000 }, () => {
describe('When testing the onboarding flow using a custom Namespace with payment rule', () => {
let namespace: EvmAddress;
beforeAll(async () => {
const result = await loginAsBuilder().andThen((sessionClient) =>
createUsernameNamespace(sessionClient, {
namespace: 'test',
symbol: 'TST',
rules: {
required: [
{ usernameLengthRule: { minLength: 1, maxLength: 42 } },
{
usernamePricePerLengthRule: {
native: '0.01',
recipient: TEST_SIGNER,
costOverrides: [
{ amount: '10', length: 5 },
{ amount: '200', length: 2 },
{ amount: '300', length: 1 },
{ amount: '100', length: 3 },
{ amount: '25', length: 4 },
],
},
},
],
},
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchNamespace(sessionClient, { txHash }))
.map(nonNullable),
);
assertOk(result);
namespace = result.value.address;
}, 15000);
it('Then it should work as expected', async () => {
const localName = `t${Date.now()}`;
const publicClient = createPublicClient();
const account = await loginAsOnboardingUser().andThen((sessionClient) =>
createAccount(sessionClient, {
metadataUri: uri(`data:application/json,${JSON.stringify(metadata)}`),
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
// Fetch the account
.andThen((txHash) =>
fetchAccount(sessionClient, { txHash }).map(nonNullable),
)
// Switch to the newly created account
.andThrough((account) =>
sessionClient.switchAccount({
account: account.address,
}),
)
// Create a username
.andThrough(
() =>
createUsername(sessionClient, {
username: { localName, namespace },
rulesSubject: RulesSubject.Signer,
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.map(() => delay(1000)), // Patch on race condition we are investigating
),
);
const username = await fetchUsername(publicClient, {
username: { localName, namespace },
}).map(nonNullable);
assertOk(account);
assertOk(username);
expect(username.value).toMatchObject({
ownedBy: account.value.address,
linkedTo: account.value.address,
});
});
});
describe('When testing the onboarding flow for the global lens/ Namespace', () => {
it('Then it should work as expected', async () => {
let newAccount: Account | null = null;
// Login as onboarding user
const result = await loginAsOnboardingUser().andThen((sessionClient) =>
// Create an account with username
createAccountWithUsername(sessionClient, {
username: { localName: `testname${Date.now()}` },
metadataUri: uri(`data:application/json,${JSON.stringify(metadata)}`),
})
// Sign if necessary
.andThen(handleOperationWith(wallet))
// Wait for the transaction to be indexed
.andThen(sessionClient.waitForTransaction)
// Fetch the account
.andThen((txHash) => fetchAccount(sessionClient, { txHash }))
.andTee((account) => {
newAccount = account ?? never('Account not found');
})
// Switch to the newly created account
.andThen((account) =>
sessionClient.switchAccount({
account: account?.address ?? never('Account not found'),
}),
)
// Ensure the switched account is what we expect
.andThen(() => fetchMeDetails(sessionClient)),
);
assertOk(result);
expect(result.value).toMatchObject({
loggedInAs: {
__typename: 'AccountOwned',
account: {
address: newAccount!.address,
owner: TEST_SIGNER,
},
},
});
});
});
});
================================================
FILE: packages/client/src/actions/post.test.ts
================================================
import { justPost } from '@lens-protocol/graphql';
import { textOnly } from '@lens-protocol/metadata';
import { assertOk, nonNullable } from '@lens-protocol/types';
import { describe, expect, it } from 'vitest';
import { loginAsAccountOwner, wallet } from '../test-utils';
import { handleOperationWith } from '../viem';
import { post } from './post';
import { fetchPost } from './posts';
const content = `data:application/json,${JSON.stringify(textOnly({ content: 'Hello world!' }))}`;
describe(`Given the '${post.name}' action`, () => {
describe('When posting on Lens', { timeout: 20000 }, () => {
it('Then it should should be possible to create a Post', async () => {
const result = await loginAsAccountOwner().andThen((sessionClient) =>
post(sessionClient, {
contentUri: content,
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchPost(sessionClient, { txHash }))
.map(nonNullable)
.map(justPost),
);
assertOk(result);
expect(result.value).toMatchObject({
__typename: 'Post',
});
});
it('Then it should be possible to create a Comment', async () => {
const result = await loginAsAccountOwner().andThen((sessionClient) =>
post(sessionClient, {
contentUri: content,
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchPost(sessionClient, { txHash }))
.map(nonNullable)
.map(justPost)
.andThen((parent) =>
post(sessionClient, {
contentUri: content,
commentOn: {
post: parent.id,
},
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchPost(sessionClient, { txHash }))
.map(nonNullable),
),
);
assertOk(result);
expect(result.value).toMatchObject({
__typename: 'Post',
commentOn: {
__typename: 'Post',
},
});
});
});
});
================================================
FILE: packages/client/src/actions/post.ts
================================================
import type {
AddReactionRequest,
AddReactionResult,
BookmarkPostRequest,
CreatePostRequest,
CreateRepostRequest,
DeletePostRequest,
DeletePostResult,
EditPostRequest,
HideReplyRequest,
PostResult,
ReportPostRequest,
UndoBookmarkPostRequest,
UndoReactionRequest,
UndoReactionResult,
UnhideReplyRequest,
UpdatePostRulesRequest,
UpdatePostRulesResult,
} from '@lens-protocol/graphql';
import {
AddReactionMutation,
BookmarkPostMutation,
DeletePostMutation,
EditPostMutation,
HideReplyMutation,
PostMutation,
ReportPostMutation,
RepostMutation,
UndoBookmarkPostMutation,
UndoReactionMutation,
UnhideReplyMutation,
UpdatePostRulesMutation,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Create a new Post.
*
* ```ts
* const result = await post(sessionClient, {
* contentUri: uri('https://example.com'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function post(
client: SessionClient,
request: CreatePostRequest,
): ResultAsync {
return client.mutation(PostMutation, { request });
}
/**
* Repost a Post.
*
* ```ts
* const result = await repost(sessionClient, {
* post: postId('42'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function repost(
client: SessionClient,
request: CreateRepostRequest,
): ResultAsync {
return client.mutation(RepostMutation, { request });
}
/**
* Edit a Post.
*
* ```ts
* const result = await editPost(sessionClient, {
* post: postId('42'),
* contentUri: uri('https://example.com'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function editPost(
client: SessionClient,
request: EditPostRequest,
): ResultAsync {
return client.mutation(EditPostMutation, { request });
}
/**
* Delete a Post.
*
* ```ts
* const result = await deletePost(sessionClient, {
* post: postId('42'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function deletePost(
client: SessionClient,
request: DeletePostRequest,
): ResultAsync {
return client.mutation(DeletePostMutation, { request });
}
/**
* React to a post.
*
* ```ts
* const result = await addReaction(sessionClient, {
* post: postId('42'),
* reaction: "UPVOTE" | "DOWNVOTE",
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Success boolean if reaction was added or error with a reason.
*/
export function addReaction(
client: SessionClient,
request: AddReactionRequest,
): ResultAsync {
return client.mutation(AddReactionMutation, { request });
}
/**
* Undo reaction from a post.
*
* ```ts
* const result = await undoReaction(sessionClient, {
* post: postId('42'),
* reaction: "UPVOTE" | "DOWNVOTE",
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Success boolean if reaction was removed or error with a reason.
*/
export function undoReaction(
client: SessionClient,
request: UndoReactionRequest,
): ResultAsync {
return client.mutation(UndoReactionMutation, { request });
}
/**
* Bookmark a post.
*
* ```ts
* const result = await bookmarkPost(sessionClient, {
* post: postId('42'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns void
*/
export function bookmarkPost(
client: SessionClient,
request: BookmarkPostRequest,
): ResultAsync {
return client.mutation(BookmarkPostMutation, { request });
}
/**
* Undo bookmark from a post.
*
* ```ts
* const result = await undoBookmarkPost(sessionClient, {
* post: postId('42'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns void
*/
export function undoBookmarkPost(
client: SessionClient,
request: UndoBookmarkPostRequest,
): ResultAsync {
return client.mutation(UndoBookmarkPostMutation, { request });
}
/**
* Hide a reply.
*
* ```ts
* const result = await hideReply(sessionClient, {
* post: postId('42'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns void
*/
export function hideReply(
client: SessionClient,
request: HideReplyRequest,
): ResultAsync {
return client.mutation(HideReplyMutation, { request });
}
/**
* Unhide a reply.
*
* ```ts
* const result = await unhideReply(sessionClient, {
* post: postId('42'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns void
*/
export function unhideReply(
client: SessionClient,
request: UnhideReplyRequest,
): ResultAsync {
return client.mutation(UnhideReplyMutation, { request });
}
/**
* Report a post
*
* ```ts
* const result = await reportPost(sessionClient, {
* reason: "SCAM",
* post: postId('1234…'),
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns void
*/
export function reportPost(
client: SessionClient,
request: ReportPostRequest,
): ResultAsync {
return client.mutation(ReportPostMutation, { request });
}
/**
* Update post rules.
*
* ```ts
* const result = await updatePostRules(sessionClient, {
* post: postId('42…'),
* toAdd: {
* anyOf: [{
* followersOnlyRule: {
* graph: evmAddress('0x1234…'),
* }
* }]
* required: [],
* }
* });
* ```
*
* @param client - The session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updatePostRules(
client: SessionClient,
request: UpdatePostRulesRequest,
): ResultAsync {
return client.mutation(UpdatePostRulesMutation, { request });
}
================================================
FILE: packages/client/src/actions/posts.test.ts
================================================
import { assertOk, postId } from '@lens-protocol/types';
import { describe, it } from 'vitest';
import { createPublicClient } from '../test-utils';
import { fetchPost } from './posts';
describe('Given the Post query actions', () => {
const client = createPublicClient();
describe(`When invoking the '${fetchPost.name}' action`, () => {
it('Then it should not fail w/ a GQL BadRequest error', async () => {
const result = await fetchPost(client, {
post: postId('42'),
});
assertOk(result);
});
});
});
================================================
FILE: packages/client/src/actions/posts.ts
================================================
import type {
Account,
AccountExecutedActions,
AccountPostReaction,
AnyPost,
Paginated,
PostActionContract,
PostActionContractsRequest,
PostBookmarksRequest,
PostEdit,
PostEditsRequest,
PostExecutedActions,
PostReactionStatus,
PostReactionStatusRequest,
PostReactionsRequest,
PostReferencesRequest,
PostRequest,
PostsRequest,
PostTag,
PostTagsRequest,
WhoExecutedActionOnAccountRequest,
WhoExecutedActionOnPostRequest,
WhoReferencedPostRequest,
} from '@lens-protocol/graphql';
import {
PostActionContractsQuery,
PostBookmarksQuery,
PostEditsQuery,
PostQuery,
PostReactionStatusQuery,
PostReactionsQuery,
PostReferencesQuery,
PostsQuery,
PostTagsQuery,
WhoExecutedActionOnAccountQuery,
WhoExecutedActionOnPostQuery,
WhoReferencedPostQuery,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch a Post.
*
* Using a {@link SessionClient} will yield {@link Post#operations}
* and {@link Account#operations} specific to the authenticated Account.
*
* ```ts
* const result = await fetchPost(anyClient, {
* post: postId('42')
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The Post or `null` if it does not exist.
*/
export function fetchPost(
client: AnyClient,
request: PostRequest,
): ResultAsync {
return client.query(PostQuery, { request });
}
/**
* Fetch paginated Posts.
*
* Using a {@link SessionClient} will yield {@link Post#operations}
* and {@link Account#operations} specific to the authenticated Account.
*
* ```ts
* const result = await fetchPosts(anyClient, {
* filter: {
* authors: [evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* }
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The paginated list of Posts.
*/
export function fetchPosts(
client: AnyClient,
request: PostsRequest,
): ResultAsync, UnexpectedError> {
return client.query(PostsQuery, { request });
}
/**
* Fetch availale Post Action contracts.
*
* ```ts
* const result = await fetchPostActionContracts(anyClient);
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of actions available.
*/
export function fetchPostActionContracts(
client: AnyClient,
request: PostActionContractsRequest = {},
): ResultAsync, UnexpectedError> {
return client.query(PostActionContractsQuery, { request });
}
/**
* Fetch reactions for a post.
*
* ```ts
* const result = await fetchPostReactions(anyClient, {
* post: postId('42'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of reactions for the post.
*/
export function fetchPostReactions(
client: AnyClient,
request: PostReactionsRequest,
): ResultAsync, UnexpectedError> {
return client.query(PostReactionsQuery, { request });
}
/**
* Fetch bookmarked posts.
*
* ```ts
* const result = await fetchPostBookmarks(anyClient);
* ```
*
* @param client - Session Lens client.
* @param request - The query request.
* @returns The list of bookmarked posts.
*/
export function fetchPostBookmarks(
client: SessionClient,
request: PostBookmarksRequest = {},
): ResultAsync, UnexpectedError | UnauthenticatedError> {
return client.query(PostBookmarksQuery, { request });
}
/**
* Fetch references to a post.
*
* ```ts
* const result = await fetchPostReferences(anyClient, {
* referencedTypes: [PostReferenceType.CommentOn],
* referencedPost: postId('42'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of posts references.
*/
export function fetchPostReferences(
client: AnyClient,
request: PostReferencesRequest,
): ResultAsync, UnexpectedError | UnauthenticatedError> {
return client.query(PostReferencesQuery, { request });
}
/**
* Fetch post tags.
*
* ```ts
* const result = await fetchPostTags(anyClient, {
* filter: {
* feeds: { globalFeed: true },
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of post tags.
*/
export function fetchPostTags(
client: AnyClient,
request: PostTagsRequest,
): ResultAsync, UnexpectedError> {
return client.query(PostTagsQuery, { request });
}
/**
* Fetch post reaction status.
*
* ```ts
* const result = await fetchPostReactionStatus(anyClient, {
* pairs: [{
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5')],
* post: postId('42'),
* }],
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of post reaction status.
*/
export function fetchPostReactionStatus(
client: AnyClient,
request: PostReactionStatusRequest,
): ResultAsync {
return client.query(PostReactionStatusQuery, { request });
}
/**
* Fetch who referenced post.
*
* ```ts
* const result = await fetchWhoReferencedPost(anyClient, {
* referenceTypes: [PostReferenceType.CommentOn]
* post: postId('42'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of accounts who referenced the post.
*/
export function fetchWhoReferencedPost(
client: AnyClient,
request: WhoReferencedPostRequest,
): ResultAsync, UnexpectedError> {
return client.query(WhoReferencedPostQuery, { request });
}
/**
* Fetch who executed an action on a Post.
*
* ```ts
* const result = await fetchWhoExecutedActionOnPost(anyClient, {
* post: postId('42'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of accounts who acted on the post.
*/
export function fetchWhoExecutedActionOnPost(
client: AnyClient,
request: WhoExecutedActionOnPostRequest,
): ResultAsync, UnexpectedError> {
return client.query(WhoExecutedActionOnPostQuery, { request });
}
/**
* Fetch who executed an action on an Account.
*
* ```ts
* const result = await fetchWhoExecutedActionOnAccount(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of accounts who acted on the post.
*/
export function fetchWhoExecutedActionOnAccount(
client: AnyClient,
request: WhoExecutedActionOnAccountRequest,
): ResultAsync, UnexpectedError> {
return client.query(WhoExecutedActionOnAccountQuery, { request });
}
/**
* Fetch post edits.
*
* ```ts
* const result = await fetchPostEdits(anyClient, {
* post: postId('42'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of edits for the post.
*/
export function fetchPostEdits(
client: AnyClient,
request: PostEditsRequest,
): ResultAsync, UnexpectedError> {
return client.query(PostEditsQuery, { request });
}
================================================
FILE: packages/client/src/actions/sns.ts
================================================
import type {
CreateSnsSubscriptionRequest,
DeleteSnsSubscriptionRequest,
GetSnsSubscriptionsRequest,
SnsSubscription,
} from '@lens-protocol/graphql';
import {
CreateSnsSubscriptionsMutation,
DeleteSnsSubscriptionMutation,
GetSnsSubscriptionsQuery,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch a SNS subscription.
*
* ```ts
* const result = await fetchSnsSubscription(sessionClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged as a builder.
* @param request - The query request.
* @returns The details of the SNS subscription.
*/
export function fetchSnsSubscription(
client: SessionClient,
request: GetSnsSubscriptionsRequest,
): ResultAsync | [], UnexpectedError> {
return client.query(GetSnsSubscriptionsQuery, { request });
}
/**
* Create SNS subscriptions.
*
* ```ts
* const result = await createSnsSubscription(sessionClient, {
* topics: [
* { accountMentioned: evmAddress('0x1234…') },
* { accountFollowed: evmAddress('0x90ab…') },
* ],
* webhook: uri('https://example.com'),
* });
* ```
*
* @param client - The session client logged as a builder.
* @param request - The mutation request.
* @returns List of SNS subscriptions created.
*/
export function createSnsSubscriptions(
client: SessionClient,
request: CreateSnsSubscriptionRequest,
): ResultAsync, UnexpectedError | UnauthenticatedError> {
return client.mutation(CreateSnsSubscriptionsMutation, { request });
}
/**
* Delete a SNS subscription.
*
* ```ts
* const result = await deleteSnsSubscription(sessionClient, {
* id: "1234-dasdf-...",
* });
* ```
*
* @param client - The session client logged as a builder.
* @param request - The mutation request.
* @returns Void
*/
export function deleteSnsSubscription(
client: SessionClient,
request: DeleteSnsSubscriptionRequest,
): ResultAsync {
return client.mutation(DeleteSnsSubscriptionMutation, { request });
}
================================================
FILE: packages/client/src/actions/sponsorship.ts
================================================
import {
CreateSponsorshipMutation,
type CreateSponsorshipRequest,
type CreateSponsorshipResult,
type Paginated,
PauseSponsorshipMutation,
type PausingRequest,
type PausingResult,
SetSponsorshipMetadataMutation,
type SetSponsorshipMetadataRequest,
type SetSponsorshipMetadataResult,
type Sponsorship,
type SponsorshipGrant,
SponsorshipGrantsQuery,
type SponsorshipGrantsRequest,
SponsorshipLimitExclusionsQuery,
type SponsorshipLimitExclusionsRequest,
type SponsorshipLimitsExempt,
SponsorshipQuery,
type SponsorshipRequest,
type SponsorshipSigner,
SponsorshipSignerQuery,
type SponsorshipSignersRequest,
SponsorshipsQuery,
type SponsorshipsRequest,
UnpauseSponsorshipMutation,
UpdateSponsorshipExclusionListMutation,
type UpdateSponsorshipExclusionListRequest,
type UpdateSponsorshipExclusionListResult,
UpdateSponsorshipLimitsMutation,
type UpdateSponsorshipLimitsRequest,
type UpdateSponsorshipLimitsResult,
UpdateSponsorshipSignersMutation,
type UpdateSponsorshipSignersRequest,
type UpdateSponsorshipSignersResult,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch a Sponsorship.
*
* ```ts
* const result = await fetchSponsorship(anyClient, {
* address: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The details of Sponsorship or null if not found.
*/
export function fetchSponsorship(
client: AnyClient,
request: SponsorshipRequest,
): ResultAsync {
return client.query(SponsorshipQuery, { request });
}
/**
* Fetch paginated Sponsorships.
*
* ```ts
* const result = await fetchSponsorships(anyClient, {
* filter: {
* managedBy: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* }
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The paginated list of Sponsorships.
*/
export function fetchSponsorships(
client: AnyClient,
request: SponsorshipsRequest,
): ResultAsync, UnexpectedError> {
return client.query(SponsorshipsQuery, { request });
}
/**
* Fetch paginated Sponsorship Signers.
*
* ```ts
* const result = await fetchSponsorshipSigners(anyClient, {
* filter: {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* }
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The paginated list of Sponsorship Signers.
*/
export function fetchSponsorshipSigners(
client: AnyClient,
request: SponsorshipSignersRequest,
): ResultAsync, UnexpectedError> {
return client.query(SponsorshipSignerQuery, { request });
}
/**
* Fetch paginated Sponsorship Grants.
*
* ```ts
* const result = await fetchSponsorshipGrants(anyClient, {
* filter: {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* }
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The paginated list of Sponsorship Signers.
*/
export function fetchSponsorshipGrants(
client: AnyClient,
request: SponsorshipGrantsRequest,
): ResultAsync, UnexpectedError> {
return client.query(SponsorshipGrantsQuery, { request });
}
/**
* Fetch paginated exclusion list from rate limits of a given Sponsorship.
*
* ```ts
* const result = await fetchSponsorshipLimitExclusions(anyClient, {
* filter: {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* }
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The paginated list of excluded addresses.
*/
export function fetchSponsorshipLimitExclusions(
client: AnyClient,
request: SponsorshipLimitExclusionsRequest,
): ResultAsync, UnexpectedError> {
return client.query(SponsorshipLimitExclusionsQuery, { request });
}
/**
* Create a Sponsorship.
*
* ```ts
* const result = await createSponsorship(sessionClient, {
* allowLensAccess: true,
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createSponsorship(
client: SessionClient,
request: CreateSponsorshipRequest,
): ResultAsync<
CreateSponsorshipResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(CreateSponsorshipMutation, { request });
}
/**
* Set Sponsorship metadata.
*
* ```ts
* const result = await setSponsorshipMetadata(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* metadataUri: uri("lens://4f91..."),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function setSponsorshipMetadata(
client: SessionClient,
request: SetSponsorshipMetadataRequest,
): ResultAsync<
SetSponsorshipMetadataResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(SetSponsorshipMetadataMutation, { request });
}
/**
* Update Sponsorship rate limits.
*
* ```ts
* const result = await updateSponsorshipLimits(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* rateLimits: {
* user: {
* window: SponsorshipRateLimitWindow.Hour,
* limit: 100,
* },
* global: {
* window: SponsorshipRateLimitWindow.Day,
* limit: 1_000_000,
* },
* }
* });
* ```
*
* Remove one limit by setting it to null or not providing it.
* ```ts
* const result = await updateSponsorshipLimits(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* rateLimits: {
* user: null,
* global: {
* window: SponsorshipRateLimitWindow.Day,
* limit: 1_000_000,
* },
* },
* });
* ```
*
* Remove all rate limits by setting them to null.
* ```ts
* const result = await updateSponsorshipLimits(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* rateLimits: null,
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateSponsorshipLimits(
client: SessionClient,
request: UpdateSponsorshipLimitsRequest,
): ResultAsync<
UpdateSponsorshipLimitsResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(UpdateSponsorshipLimitsMutation, { request });
}
/**
* Update exclusion list from rate limits for a given Sponsorship.
*
* ```ts
* const result = await updateSponsorshipExclusionList(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* toAdd: [
* {
* address: evmAddress('0x1234…'),
* label: "Bob The Builder",
* },
* ],
* toRemove: [
* evmAddress('0x5678…'),
* ],
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateSponsorshipExclusionList(
client: SessionClient,
request: UpdateSponsorshipExclusionListRequest,
): ResultAsync<
UpdateSponsorshipExclusionListResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(UpdateSponsorshipExclusionListMutation, { request });
}
/**
* Update the list of signers for a given Sponsorship.
*
* ```ts
* const result = await updateSponsorshipSigners(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* toAdd: [
* {
* address: evmAddress('0x1234…'),
* label: "Server A",
* },
* ],
* toRemove: [
* evmAddress('0x5678…'),
* ],
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function updateSponsorshipSigners(
client: SessionClient,
request: UpdateSponsorshipSignersRequest,
): ResultAsync<
UpdateSponsorshipSignersResult,
UnexpectedError | UnauthenticatedError
> {
return client.mutation(UpdateSponsorshipSignersMutation, { request });
}
/**
* Pause sponsorship.
*
* ```ts
* const result = await pauseSponsorship(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function pauseSponsorship(
client: SessionClient,
request: PausingRequest,
): ResultAsync {
return client.mutation(PauseSponsorshipMutation, { request });
}
/**
* Unpause sponsorship.
*
* ```ts
* const result = await unpauseSponsorship(sessionClient, {
* sponsorship: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client logged in as a builder.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function unpauseSponsorship(
client: SessionClient,
request: PausingRequest,
): ResultAsync {
return client.mutation(UnpauseSponsorshipMutation, { request });
}
================================================
FILE: packages/client/src/actions/timeline.ts
================================================
import type {
AnyPost,
Paginated,
TimelineHighlightsRequest,
TimelineItem,
TimelineRequest,
} from '@lens-protocol/graphql';
import { TimelineHighlightsQuery, TimelineQuery } from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnexpectedError } from '../errors';
/**
* Fetch timeline from an account.
*
* ```ts
* const result = await fetchTimeline(sessionClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The query request.
* @returns The list of timeline items.
*/
export function fetchTimeline(
client: SessionClient,
request: TimelineRequest,
): ResultAsync, UnexpectedError> {
return client.query(TimelineQuery, { request });
}
/**
* Fetch Timeline Highlights for an account.
*
* ```ts
* const result = await fetchTimelineHighlights(anyClient, {
* account: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of highlights post for an account.
*/
export function fetchTimelineHighlights(
client: AnyClient,
request: TimelineHighlightsRequest,
): ResultAsync, UnexpectedError> {
return client.query(TimelineHighlightsQuery, { request });
}
================================================
FILE: packages/client/src/actions/tipping.e2e.ts
================================================
import { justPost, type Post } from '@lens-protocol/graphql';
import { textOnly } from '@lens-protocol/metadata';
import {
assertOk,
bigDecimal,
nonNullable,
Result,
} from '@lens-protocol/types';
import { beforeAll, describe, it } from 'vitest';
import type { SessionClient } from '../clients';
import {
loginAsAccountOwner,
TEST_ACCOUNT,
TEST_ERC20,
TEST_SIGNER,
wallet,
} from '../test-utils';
import { handleOperationWith } from '../viem';
import { executeAccountAction, executePostAction } from '.';
import { deposit, fetchBalancesBulk, wrapTokens } from './funds';
import { findErc20Amount, findNativeAmount } from './helpers';
import { post } from './post';
import { fetchPost } from './posts';
describe('Given a Lens Account with some WGHO (or any other ERC20)', () => {
let sessionClient: SessionClient;
beforeAll(async () => {
await loginAsAccountOwner().andTee((client) => {
sessionClient = client;
});
const balance = await fetchBalancesBulk(sessionClient, {
includeNative: true,
address: TEST_ACCOUNT,
tokens: [TEST_ERC20],
}).andThen((balances) =>
Result.combine([
findNativeAmount(balances),
findErc20Amount(TEST_ERC20, balances),
]),
);
assertOk(balance);
// Check native balance
if (balance.value[0].value < '1') {
const result = await deposit(sessionClient, {
native: bigDecimal(1),
}).andThen(handleOperationWith(wallet));
assertOk(result);
}
// Check ERC20 balance
if (balance.value[1].value < '1') {
const wrapped = await wrapTokens(sessionClient, {
amount: bigDecimal(1),
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction);
assertOk(wrapped);
}
}, 30_000);
describe('When executing the Tipping Account Action', () => {
it('Then it should work as expected', async () => {
const result = await executeAccountAction(sessionClient, {
account: TEST_ACCOUNT,
action: {
tipping: {
currency: TEST_ERC20,
value: bigDecimal(0.1),
referrals: [
{
address: TEST_SIGNER,
percent: 10,
},
{
address: TEST_SIGNER,
percent: 90,
},
],
},
},
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction);
assertOk(result);
});
});
describe('When executing the Tipping Post Action', () => {
const content = `data:application/json,${JSON.stringify(textOnly({ content: 'Hello world!' }))}`;
let anyPost: Post;
beforeAll(async () => {
const result = await post(sessionClient, {
contentUri: content,
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction)
.andThen((txHash) => fetchPost(sessionClient, { txHash }))
.map(nonNullable)
.map(justPost);
assertOk(result);
anyPost = result.value;
});
it('Then it should work as expected', async () => {
const result = await executePostAction(sessionClient, {
post: anyPost.id,
action: {
tipping: {
currency: TEST_ERC20,
value: bigDecimal(0.1),
referrals: [
{
address: TEST_SIGNER,
percent: 10,
},
{
address: TEST_SIGNER,
percent: 90,
},
],
},
},
})
.andThen(handleOperationWith(wallet))
.andThen(sessionClient.waitForTransaction);
assertOk(result);
});
});
});
================================================
FILE: packages/client/src/actions/transactions.ts
================================================
import type {
PrepareSignerErc20ApprovalRequest,
PrepareSignerErc20ApprovalResult,
TransactionStatusRequest,
TransactionStatusResult,
} from '@lens-protocol/graphql';
import {
PrepareSignerErc20ApprovalMutation,
TransactionStatusQuery,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Fetch the indexing status of a Transaction.
*
* ```ts
* const result = await transactionStatus(anyClient, {
* txHash: txHash('0x97589c9e3a3c5b007d…'),
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The indexing status of the Transaction.
*/
export function transactionStatus(
client: AnyClient,
request: TransactionStatusRequest,
): ResultAsync {
return client.query(TransactionStatusQuery, { request }, 'network-only');
}
/**
* Prepare a signer ERC20 approval transaction.
*
* ```ts
* const result = await prepareSignerErc20Approval(sessionClient, {
* approval: {
* infinite: evmAddress('0x1235678901234567890123456789012345678901'),
* },
* });
* ```
*
* @param client - Session Lens client.
* @param request - The mutation request.
* @returns The prepared transaction.
*/
export function prepareSignerErc20Approval(
client: SessionClient,
request: PrepareSignerErc20ApprovalRequest,
): ResultAsync<
PrepareSignerErc20ApprovalResult,
UnauthenticatedError | UnexpectedError
> {
return client.mutation(PrepareSignerErc20ApprovalMutation, { request });
}
================================================
FILE: packages/client/src/actions/transfer.ts
================================================
import type {
TransferPrimitiveOwnershipRequest,
TransferPrimitiveOwnershipResult,
} from '@lens-protocol/graphql';
import { TransferPrimitiveOwnershipMutation } from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Transfer primitive ownership.
*
* ```ts
* const result = await transferPrimitiveOwnership(sessionClient, {
* newOwner: evmAddress('0x1234…'),
* address: evmAddress('0x5678…'),
* });
* ```
*
* @param client - Session client.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function transferPrimitiveOwnership(
client: SessionClient,
request: TransferPrimitiveOwnershipRequest,
): ResultAsync<
TransferPrimitiveOwnershipResult,
UnauthenticatedError | UnexpectedError
> {
return client.mutation(TransferPrimitiveOwnershipMutation, { request });
}
================================================
FILE: packages/client/src/actions/username.ts
================================================
import type {
AssignUsernameToAccountRequest,
AssignUsernameToAccountResult,
CanCreateUsernameRequest,
CanCreateUsernameResult,
CreateUsernameRequest,
CreateUsernameResult,
Paginated,
UnassignUsernameFromAccountRequest,
UnassignUsernameToAccountResult,
Username,
UsernameRequest,
UsernamesRequest,
} from '@lens-protocol/graphql';
import {
AssignUsernameToAccountMutation,
CanCreateUsernameQuery,
CreateUsernameMutation,
UnassignUsernameFromAccountMutation,
UsernameQuery,
UsernamesQuery,
} from '@lens-protocol/graphql';
import type { ResultAsync } from '@lens-protocol/types';
import type { AnyClient, SessionClient } from '../clients';
import type { UnauthenticatedError, UnexpectedError } from '../errors';
/**
* Checks if the given username can be created by the logged in Account.
*
* ```ts
* const result = await canCreateUsername(sessionClient, {
* localName: 'wagmi',
* });
* ```
*/
export function canCreateUsername(
client: SessionClient,
request: CanCreateUsernameRequest,
): ResultAsync<
CanCreateUsernameResult,
UnexpectedError | UnauthenticatedError
> {
return client.query(CanCreateUsernameQuery, { request });
}
/**
* Create a username
*
* ```ts
* const result = await createUsername(sessionClient, {
* username: {
* localName: 'wagmi',
* },
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function createUsername(
client: SessionClient,
request: CreateUsernameRequest,
): ResultAsync {
return client.mutation(CreateUsernameMutation, { request });
}
/**
* Assign a username to the account associated with the authenticated session.
*
* ```ts
* const result = await assignUsernameToAccount(sessionClient, {
* username: {
* localName: 'wagmi',
* },
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function assignUsernameToAccount(
client: SessionClient,
request: AssignUsernameToAccountRequest,
): ResultAsync<
AssignUsernameToAccountResult,
UnauthenticatedError | UnexpectedError
> {
return client.mutation(AssignUsernameToAccountMutation, { request });
}
/**
* Unassign a username to the account associated with the authenticated session.
*
* ```ts
* const result = await unassignUsernameFromAccount(sessionClient, {
* username: {
* localName: 'wagmi',
* },
* });
* ```
*
* @param client - The session client for the authenticated Account.
* @param request - The mutation request.
* @returns Tiered transaction result.
*/
export function unassignUsernameFromAccount(
client: SessionClient,
request: UnassignUsernameFromAccountRequest = {},
): ResultAsync<
UnassignUsernameToAccountResult,
UnauthenticatedError | UnexpectedError
> {
return client.mutation(UnassignUsernameFromAccountMutation, { request });
}
/**
* Fetch username details.
*
* ```ts
* const result = await fetchUsername(anyClient, {
* username: {
* localName: 'wagmi',
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The username details.
*/
export function fetchUsername(
client: AnyClient,
request: UsernameRequest,
): ResultAsync {
return client.query(UsernameQuery, { request });
}
/**
* Fetch usernames.
* Example: fetch usernames owned by a specific address.
*
* ```ts
* const result = await fetchUsernames(anyClient, {
* filter: {
* owner: evmAddress('0xe2f2a5C287993345a840db3B0845fbc70f5935a5'),
* },
* });
* ```
*
* @param client - Any Lens client.
* @param request - The query request.
* @returns The list of usernames.
*/
export function fetchUsernames(
client: AnyClient,
request: UsernamesRequest,
): ResultAsync, UnexpectedError> {
return client.query(UsernamesQuery, { request });
}
================================================
FILE: packages/client/src/authorization.ts
================================================
/**
* Operations types.
*/
export enum OperationType {
Post = 'Post',
Repost = 'Repost',
EditPost = 'EditPost',
DeletePost = 'DeletePost',
Follow = 'Follow',
Unfollow = 'Unfollow',
CreateAccount = 'CreateAccount',
CreateUsername = 'CreateUsername',
CreateAndAssignUsername = 'CreateAndAssignUsername',
AssignUsername = 'AssignUsername',
UnassignUsername = 'UnassignUsername',
SetAccountMetadata = 'SetAccountMetadata',
JoinGroup = 'JoinGroup',
LeaveGroup = 'LeaveGroup',
AddGroupMember = 'AddGroupMember',
RemoveGroupMember = 'RemoveGroupMember',
}
/**
* An operation approval request.
*/
export type OperationApprovalRequest = {
nonce: string;
deadline: string;
operation: OperationType;
validator: string;
account: string;
};
================================================
FILE: packages/client/src/batch.ts
================================================
import type { AnyVariables, StandardData } from '@lens-protocol/graphql';
import { Deferred, invariant, never, ResultAsync } from '@lens-protocol/types';
import type { TypedDocumentNode } from '@urql/core';
import {
type DocumentNode,
type FieldNode,
type FragmentDefinitionNode,
Kind,
type OperationDefinitionNode,
OperationTypeNode,
type VariableDefinitionNode,
visit,
} from 'graphql';
import { UnexpectedError } from './errors';
interface StoredQuery {
alias: string;
document: TypedDocumentNode, TVariables>;
variables: AnyVariables;
deferred: Deferred;
}
export type BatchQueryData = Record;
export class BatchQueryBuilder {
// biome-ignore lint/suspicious/noExplicitAny: intentional due to the etherogenous nature of the queries
private queries: StoredQuery[] = [];
addQuery = (
document: TypedDocumentNode, TVariables>,
variables: TVariables,
): ResultAsync => {
invariant(
this.queries.length < 10,
'Batch queries supports a maximum of 10 queries',
);
const alias = `value_${this.queries.length}`;
const deferred = new Deferred();
this.queries.push({ alias, document, variables, deferred });
return ResultAsync.fromPromise(deferred.promise, (err) => {
if (UnexpectedError.is(err)) {
return err;
}
return UnexpectedError.from(err);
});
};
build(): [
TypedDocumentNode,
TVariables,
] {
const allFragments: Map = new Map();
const selections: FieldNode[] = [];
const variableDefinitions: VariableDefinitionNode[] = [];
const mergedVariables: AnyVariables = {};
let varId = 0;
for (const { alias, document, variables } of this.queries) {
const [operation, fragments] = extractQueryParts(document);
for (const fragment of fragments) {
const name = fragment.name.value;
if (!allFragments.has(name)) {
allFragments.set(name, fragment);
}
}
const varMapping = new Map();
const localDefs =
operation.variableDefinitions?.map((v): VariableDefinitionNode => {
const newVarName = `${v.variable.name.value}_${varId++}`;
varMapping.set(v.variable.name.value, newVarName);
mergedVariables[newVarName] =
variables[v.variable.name.value] ?? never();
return {
...v,
variable: {
...v.variable,
name: { kind: Kind.NAME, value: newVarName },
},
};
}) ?? [];
variableDefinitions.push(...localDefs);
const rewritten = visit(operation.selectionSet, {
Variable(node) {
const renamed = varMapping.get(node.name.value);
if (!renamed) return node;
return {
...node,
name: { kind: Kind.NAME, value: renamed },
};
},
Field(node) {
return {
...node,
alias: { kind: Kind.NAME, value: alias },
};
},
});
selections.push(...(rewritten.selections as FieldNode[]));
}
const mergedOperation: OperationDefinitionNode = {
kind: Kind.OPERATION_DEFINITION,
operation: OperationTypeNode.QUERY,
variableDefinitions,
selectionSet: {
kind: Kind.SELECTION_SET,
selections,
},
};
const mergedDocument: DocumentNode = {
kind: Kind.DOCUMENT,
definitions: [mergedOperation, ...allFragments.values()],
};
return [mergedDocument, mergedVariables as TVariables];
}
resolve(data: BatchQueryData) {
for (const { alias, deferred } of this.queries) {
if (Object.hasOwn(data, alias) && data[alias] !== undefined) {
deferred.resolve(data[alias]);
} else {
deferred.reject(
UnexpectedError.from(
`Missing response data for query alias "${alias}". Please report this issue to the Lens team.`,
),
);
}
}
}
}
function extractQueryParts(
document: TypedDocumentNode>,
): [OperationDefinitionNode, FragmentDefinitionNode[]] {
let operation: OperationDefinitionNode | undefined;
const fragments: FragmentDefinitionNode[] = [];
for (const definition of document.definitions) {
switch (definition.kind) {
case Kind.OPERATION_DEFINITION:
invariant(
definition.operation === OperationTypeNode.QUERY,
'Only query operations are supported',
);
invariant(
operation === undefined,
'Only one operation definition is supported',
);
operation = definition;
break;
case Kind.FRAGMENT_DEFINITION:
fragments.push(definition);
break;
default:
never(`Unexpected definition kind: ${definition.kind}`);
}
}
invariant(operation, 'No operation definition found in the document');
return [operation, fragments];
}
================================================
FILE: packages/client/src/cache.ts
================================================
import type {
Account,
App,
CreateFollowRequest,
CreateUnfollowRequest,
Feed,
FollowResult,
Graph,
Group,
SimpleCollectAction,
SimpleCollectActionContract,
Sponsorship,
TippingAccountAction,
TippingPostActionContract,
UnfollowResult,
UnknownAccountAction,
UnknownPostAction,
UnknownPostActionContract,
UsernameNamespace,
} from '@lens-protocol/graphql';
import introspectedSchema from '@lens-protocol/graphql/schema';
import { gql } from '@urql/core';
import { cacheExchange } from '@urql/exchange-graphcache';
export const cache = /*#__PURE__*/ cacheExchange({
schema: introspectedSchema,
keys: {
// Entities with address field as key
Account: (data: Account) => data.address,
App: (data: App) => data.address,
Feed: (data: Feed) => data.address,
Graph: (data: Graph) => data.address,
Group: (data: Group) => data.address,
UsernameNamespace: (data: UsernameNamespace) => data.address,
Sponsorship: (data: Sponsorship) => data.address,
SimpleCollectActionContract: (data: SimpleCollectActionContract) =>
data.address,
TippingPostActionContract: (data: TippingPostActionContract) =>
data.address,
UnknownPostActionContract: (data: UnknownPostActionContract) =>
data.address,
SimpleCollectAction: (data: SimpleCollectAction) => data.address,
TippingAccountAction: (data: TippingAccountAction) => data.address,
UnknownAccountAction: (data: UnknownAccountAction) => data.address,
UnknownPostAction: (data: UnknownPostAction) => data.address,
// Entities with other fields as key
AuthenticatedSession: (data: any) => data.authenticationId,
// Entities without keys will be embedded directly on the parent entity
PaginatedResultInfo: () => null,
PaginatedAccountsResult: () => null,
PaginatedAccountsAvailableResult: () => null,
PaginatedAccountsBlockedResult: () => null,
PaginatedAccountManagersResult: () => null,
PaginatedActiveAuthenticationsResult: () => null,
PaginatedAdminsResult: () => null,
PaginatedAnyPostsResult: () => null,
PaginatedAppFeedsResult: () => null,
PaginatedAppSignersResult: () => null,
PaginatedAppUsersResult: () => null,
PaginatedFeedsResult: () => null,
PaginatedFollowersResult: () => null,
PaginatedFollowingResult: () => null,
PaginatedGraphsResult: () => null,
PaginatedGroupBannedAccountsResult: () => null,
PaginatedGroupMembersResult: () => null,
PaginatedGroupMembershipRequestsResult: () => null,
PaginatedGroupsResult: () => null,
PaginatedNamespaceReservedUsernamesResult: () => null,
PaginatedNotificationResult: () => null,
PaginatedPostActionContracts: () => null,
PaginatedPostEditsResult: () => null,
PaginatedPostExecutedActionsResult: () => null,
PaginatedPostReactionsResult: () => null,
PaginatedPostTagsResult: () => null,
PaginatedPostsForYouResult: () => null,
PaginatedPostsResult: () => null,
PaginatedTimelineResult: () => null,
PaginatedUsernamesResult: () => null,
PaginatedAccountExecutedActionsResult: () => null,
AppsResult: () => null,
NamespacesResult: () => null,
SponsorshipLimitsExclusionsResult: () => null,
SponsorshipSignersResult: () => null,
SponsorshipsResult: () => null,
SponsorshipGrantsResult: () => null,
// Account related types
AccountFollowRules: () => null,
AccountFollowOperationValidationFailed: () => null,
AccountFollowOperationValidationPassed: () => null,
AccountFollowOperationValidationUnknown: () => null,
AccountFollowUnsatisfiedRules: () => null,
AccountFollowUnsatisfiedRule: () => null,
AccountMetadata: () => null,
AccountManager: () => null,
AccountManagerPermissions: () => null,
AccountAvailable: () => null,
AccountManaged: () => null,
AccountOwned: () => null,
AccountBlocked: () => null,
AccountStats: () => null,
AccountFeedsStats: () => null,
AccountGraphsFollowStats: () => null,
// Namespace related types
NamespaceOperationValidationFailed: () => null,
NamespaceOperationValidationPassed: () => null,
NamespaceOperationValidationUnknown: () => null,
NamespaceUnsatisfiedRules: () => null,
NamespaceUnsatisfiedRule: () => null,
NamespaceRules: () => null,
NamespaceRule: () => null,
LoggedInUsernameNamespaceOperations: () => null,
LoggedInUsernameOperations: () => null,
// Feed related types
FeedOperationValidationFailed: () => null,
FeedOperationValidationPassed: () => null,
FeedOperationValidationUnknown: () => null,
FeedUnsatisfiedRules: () => null,
FeedUnsatisfiedRule: () => null,
FeedRules: () => null,
FeedRule: () => null,
FeedMetadata: () => null,
LoggedInFeedPostOperations: () => null,
// Graph related types
GraphRules: () => null,
GraphRule: () => null,
GraphMetadata: () => null,
// Group related types
GroupOperationValidationFailed: () => null,
GroupOperationValidationPassed: () => null,
GroupOperationValidationUnknown: () => null,
GroupUnsatisfiedRules: () => null,
GroupUnsatisfiedRule: () => null,
GroupRules: () => null,
GroupRule: () => null,
GroupMetadata: () => null,
GroupMember: () => null,
GroupMembershipRequest: () => null,
GroupBannedAccount: () => null,
LoggedInGroupOperations: () => null,
// Post related types
PostOperationValidationFailed: () => null,
PostOperationValidationPassed: () => null,
PostOperationValidationUnknown: () => null,
PostUnsatisfiedRules: () => null,
PostUnsatisfiedRule: () => null,
PostRules: () => null,
PostRule: () => null,
PostStats: () => null,
PostReaction: () => null,
PostTip: () => null,
PostFeedInfo: () => null,
PostGroupInfo: () => null,
PostForYou: () => null,
PostTag: () => null,
PostEdit: () => null,
PostExecutedActions: () => null,
LoggedInPostOperations: () => null,
// Metadata types
MetadataAttribute: () => null,
ArticleMetadata: () => null,
AudioMetadata: () => null,
CheckingInMetadata: () => null,
EmbedMetadata: () => null,
EventMetadata: () => null,
ImageMetadata: () => null,
LinkMetadata: () => null,
LivestreamMetadata: () => null,
MintMetadata: () => null,
SpaceMetadata: () => null,
StoryMetadata: () => null,
TextOnlyMetadata: () => null,
ThreeDMetadata: () => null,
TransactionMetadata: () => null,
VideoMetadata: () => null,
UnknownPostMetadata: () => null,
AppMetadata: () => null,
UsernameNamespaceMetadata: () => null,
UsernameNamespaceMetadataStandard: () => null,
SponsorshipMetadata: () => null,
ActionMetadata: () => null,
// Media types
MediaAudio: () => null,
MediaImage: () => null,
MediaVideo: () => null,
ThreeDAsset: () => null,
// Transaction types
TransactionStatusResult: () => null,
FinishedTransactionStatus: () => null,
PendingTransactionStatus: () => null,
FailedTransactionStatus: () => null,
NotIndexedYetStatus: () => null,
SubOperationStatus: () => null,
// Response types
PostResponse: () => null,
FollowResponse: () => null,
UnfollowResponse: () => null,
CreateAccountResponse: () => null,
AssignUsernameResponse: () => null,
UnassignUsernameResponse: () => null,
CreateUsernameResponse: () => null,
CreateAppResponse: () => null,
CreateFeedResponse: () => null,
CreateGraphResponse: () => null,
CreateGroupResponse: () => null,
CreateNamespaceResponse: () => null,
CreateSponsorshipResponse: () => null,
AccountBlockedResponse: () => null,
AccountUnblockedResponse: () => null,
// Notification types
CommentNotification: () => null,
ReactionNotification: () => null,
RepostNotification: () => null,
QuoteNotification: () => null,
FollowNotification: () => null,
MentionNotification: () => null,
PostActionExecutedNotification: () => null,
AccountActionExecutedNotification: () => null,
GroupMembershipRequestApprovedNotification: () => null,
GroupMembershipRequestRejectedNotification: () => null,
// Other types
Follower: () => null,
Following: () => null,
MeResult: () => null,
Repost: () => null,
TimelineItem: () => null,
AppUser: () => null,
AppFeed: () => null,
AppSigner: () => null,
Admin: () => null,
UsernameReserved: () => null,
UsernameNamespaceStats: () => null,
SponsorshipLimits: () => null,
SponsorshipRateLimit: () => null,
SponsorshipAllowance: () => null,
SponsorshipSigner: () => null,
SponsorshipLimitsExempt: () => null,
SponsorshipGrant: () => null,
GroupStatsResponse: () => null,
FollowStatusResult: () => null,
PostReactionStatus: () => null,
NotificationAccountFollow: () => null,
NotificationAccountPostReaction: () => null,
NotificationAccountRepost: () => null,
AccountPostReaction: () => null,
AccountMention: () => null,
GroupMention: () => null,
MentionReplace: () => null,
// Validation types
SimpleCollectValidationPassed: () => null,
SimpleCollectValidationFailed: () => null,
UsernameTaken: () => null,
// Error types
WrongSignerError: () => null,
ExpiredChallengeError: () => null,
ForbiddenError: () => null,
InsufficientFunds: () => null,
SignerErc20ApprovalRequired: () => null,
TransactionWillFail: () => null,
// Token/Amount types
NativeAmount: () => null,
Erc20Amount: () => null,
NativeToken: () => null,
Erc20: () => null,
NativeBalanceError: () => null,
Erc20BalanceError: () => null,
NetworkAddress: () => null,
// Key-Value types
IntKeyValue: () => null,
IntNullableKeyValue: () => null,
AddressKeyValue: () => null,
StringKeyValue: () => null,
BooleanKeyValue: () => null,
RawKeyValue: () => null,
BigDecimalKeyValue: () => null,
DictionaryKeyValue: () => null,
ArrayKeyValue: () => null,
KeyValuePair: () => null,
// Other utility types
BooleanValue: () => null,
PhysicalAddress: () => null,
EventLocation: () => null,
EventSchedulingAdjustments: () => null,
PayToCollectConfig: () => null,
RecipientPercent: () => null,
NftMetadata: () => null,
MarketplaceMetadataAttribute: () => null,
PaymasterParams: () => null,
Eip712Meta: () => null,
// Authentication types
AuthenticationChallenge: () => null,
AuthenticationTokens: () => null,
// Transaction request types
SponsoredTransactionRequest: () => null,
SelfFundedTransactionRequest: () => null,
Eip1559TransactionRequest: () => null,
Eip712TransactionRequest: () => null,
// EIP-712 types
CreateFrameEIP712TypedData: () => null,
CreateFrameEIP712TypedDataTypes: () => null,
CreateFrameEIP712TypedDataValue: () => null,
Eip712TypedDataDomain: () => null,
Eip712TypedDataField: () => null,
FrameLensManagerSignatureResult: () => null,
// Action executed types
TippingAccountActionExecuted: () => null,
UnknownAccountActionExecuted: () => null,
TippingPostActionExecuted: () => null,
SimpleCollectPostActionExecuted: () => null,
UnknownPostActionExecuted: () => null,
// SNS types
SnsSubscription: () => null,
// Debug types
DebugPostMetadataResult: () => null,
RefreshMetadataResult: () => null,
RefreshMetadataStatusResult: () => null,
AccessControlResult: () => null,
},
updates: {
Mutation: {
follow: (
result: { value: FollowResult },
args: { request: CreateFollowRequest },
cache,
) => {
// Optimistically update the follow status if getting txHash
if (result.value.__typename === 'FollowResponse') {
cache.writeFragment(
gql`
fragment _ on LoggedInAccountOperations {
id
isFollowedByMe
}`,
{
id: args.request.account,
isFollowedByMe: true,
},
);
}
},
unfollow: (
result: { value: UnfollowResult },
args: { request: CreateUnfollowRequest },
cache,
) => {
// Optimistically update the unfollow status if getting txHash
if (result.value.__typename === 'UnfollowResponse') {
cache.writeFragment(
gql`
fragment _ on LoggedInAccountOperations {
id
isFollowedByMe
}`,
{
id: args.request.account,
isFollowedByMe: false,
},
);
}
},
},
},
});
================================================
FILE: packages/client/src/clients.test.ts
================================================
import type { Account } from '@lens-protocol/graphql';
import {
AuthenticateMutation,
CurrentSessionQuery,
graphql,
HealthQuery,
RefreshMutation,
Role,
UsernameFragment,
} from '@lens-protocol/graphql';
import {
assertErr,
assertOk,
expectTypename,
nonNullable,
url,
} from '@lens-protocol/types';
import * as msw from 'msw';
import { setupServer } from 'msw/node';
import {
afterAll,
beforeAll,
describe,
expect,
expectTypeOf,
it,
} from 'vitest';
import { currentSession, fetchAccount, fetchPost } from './actions';
import { PublicClient } from './clients';
import {
GraphQLErrorCode,
UnauthenticatedError,
UnexpectedError,
} from './errors';
import {
createGraphQLErrorObject,
createPublicClient,
mockAccessToken,
signer,
TEST_ACCOUNT,
TEST_APP,
TEST_SIGNER,
wallet,
} from './test-utils';
import { delay } from './utils';
import { signMessageWith } from './viem';
describe(`Given an instance of the '${PublicClient.name}'`, () => {
const client = createPublicClient();
describe('When authenticating via the low-level methods', () => {
it('Then it should authenticate and stay authenticated', async () => {
const challenge = await client.challenge({
accountOwner: {
account: TEST_ACCOUNT,
owner: TEST_SIGNER,
app: TEST_APP,
},
});
assertOk(challenge);
const authenticated = await client.authenticate({
id: challenge.value.id,
signature: await signer.signMessage({
message: challenge.value.text,
}),
});
assertOk(authenticated);
const user = authenticated.value.getAuthenticatedUser();
assertOk(user);
expect(user.value).toMatchObject({
role: Role.AccountOwner,
address: TEST_ACCOUNT.toLowerCase(),
signer: TEST_SIGNER.toLowerCase(),
});
});
});
describe(`When authenticating via the '${PublicClient.prototype.login.name}' convenience method`, () => {
it('Then it should return an Err with any error thrown by the provided `SignMessage` function', async () => {
const authenticated = await client.login({
accountOwner: {
account: TEST_ACCOUNT,
owner: TEST_SIGNER,
app: TEST_APP,
},
signMessage: async () => {
throw new Error('Test Error');
},
});
assertErr(authenticated);
});
});
describe('When resuming an authenticated session', () => {
it('Then it should return a SessionClient instance associated with the credentials in the storage', async () => {
const client = createPublicClient();
await client.login({
accountOwner: {
account: TEST_ACCOUNT,
owner: TEST_SIGNER,
app: TEST_APP,
},
signMessage: signMessageWith(wallet),
});
const authenticated = await client.resumeSession();
assertOk(authenticated);
const authentication = await currentSession(authenticated.value);
expect(authentication._unsafeUnwrap()).toMatchObject({
signer: TEST_SIGNER,
app: TEST_APP,
});
});
it(`Then it should return an 'Err' if the session is not found in the storage`, async () => {
const client = createPublicClient();
const result = await client.resumeSession();
assertErr(result);
expect(result.error).toBeInstanceOf(UnauthenticatedError);
});
});
describe('When receiving a Network error', () => {
const client = PublicClient.create({
environment: {
backend: url('http://127.0.0.1'),
name: 'broken',
indexingTimeout: 1000,
pollingInterval: 1000,
},
origin: 'http://example.com',
});
it(`Then it should return an ${UnexpectedError.name}`, async () => {
const result = await client.query(HealthQuery, {});
assertErr(result);
expect(result.error).toBeInstanceOf(UnexpectedError);
});
});
describe('And a SessionClient created from it', () => {
describe(`When invoking the 'logout' method`, () => {
it('Then it should revoke the current authenticated session and clear the credentials from the storage', async () => {
const authenticated = await client.login({
accountOwner: {
account: TEST_ACCOUNT,
owner: TEST_SIGNER,
app: TEST_APP,
},
signMessage: signMessageWith(wallet),
});
assertOk(authenticated);
const result = await authenticated.value.logout();
assertOk(result);
assertErr(await currentSession(authenticated.value));
assertErr(authenticated.value.getAuthenticatedUser());
});
});
describe('When the Access Token is about to expire (within 30 seconds)', () => {
const accessToken = mockAccessToken({
exp: Date.now() / 1000 + 10,
});
const server = setupServer(
msw.graphql.mutation(
AuthenticateMutation,
async ({ request }) => {
const response = await fetch(request);
// biome-ignore lint/suspicious/noExplicitAny: keep it simple
const result = (await response.json()) as any;
result.data.value.accessToken = accessToken;
return msw.HttpResponse.json(result);
},
{
once: true,
},
),
// Pass through all other operations
msw.graphql.operation(() => msw.passthrough()),
);
beforeAll(() => {
server.listen();
});
afterAll(() => {
server.close();
});
it('Then it should preemptively refresh the token', async () => {
const authenticated = await client.login({
accountOwner: {
account: TEST_ACCOUNT,
owner: TEST_SIGNER,
app: TEST_APP,
},
signMessage: signMessageWith(wallet),
});
assertOk(authenticated);
const result = await fetchAccount(authenticated.value, {
address: TEST_ACCOUNT,
});
assertOk(result);
expect(result.value?.operations).not.toBe(null);
});
});
describe('When a request fails with UNAUTHENTICATED extension code', () => {
const server = setupServer(
msw.graphql.query(
CurrentSessionQuery,
(_) =>
msw.HttpResponse.json({
errors: [
createGraphQLErrorObject(GraphQLErrorCode.UNAUTHENTICATED),
],
}),
{
once: true,
},
),
// Pass through all other operations
msw.graphql.operation(() => msw.passthrough()),
);
beforeAll(() => {
server.listen();
});
afterAll(() => {
server.close();
});
it('Then it should silently refresh credentials and retry the request', async () => {
const authenticated = await client.login({
accountOwner: {
account: TEST_ACCOUNT,
owner: TEST_SIGNER,
app: TEST_APP,
},
signMessage: signMessageWith(wallet),
});
assertOk(authenticated);
// wait 1 second to make sure the new tokens have 'expiry at' different from the previous ones
await delay(1000);
const result = await currentSession(authenticated.value);
assertOk(result);
});
});
describe('When a token refresh fails', () => {
const server = setupServer(
msw.graphql.query(CurrentSessionQuery, (_) =>
msw.HttpResponse.json({
errors: [
createGraphQLErrorObject(GraphQLErrorCode.UNAUTHENTICATED),
],
}),
),
msw.graphql.mutation(RefreshMutation, (_) =>
msw.HttpResponse.json({
errors: [createGraphQLErrorObject(GraphQLErrorCode.BAD_USER_INPUT)],
}),
),
// Pass through all other operations
msw.graphql.operation(() => msw.passthrough()),
);
beforeAll(() => {
server.listen();
});
afterAll(() => {
server.close();
});
it(`Then it should return a '${UnauthenticatedError.name}' to the original request caller`, async () => {
const authenticated = await client.login({
accountOwner: {
account: TEST_ACCOUNT,
owner: TEST_SIGNER,
app: TEST_APP,
},
signMessage: signMessageWith(wallet),
});
assertOk(authenticated);
const result = await currentSession(authenticated.value);
assertErr(result);
expect(result.error).toBeInstanceOf(UnauthenticatedError);
});
});
});
describe('When some fragments are provided', () => {
it('Then it should replace them in any relevant query', async () => {
const BaseAccountFragment = graphql(
`fragment BaseAccount on Account {
test: address
}`,
);
const AccountFragment = graphql(
`fragment Account on Account {
...BaseAccount
username {
...Username
}
}`,
[BaseAccountFragment, UsernameFragment],
);
const client = createPublicClient({
fragments: [AccountFragment],
});
const result = await fetchAccount(client, { address: TEST_ACCOUNT });
assertOk(result);
expect(result.value).toMatchObject({
test: TEST_ACCOUNT,
username: {
value: expect.any(String),
},
});
});
});
describe('When batching multiple queries', () => {
it('Then it should return the results of all queries in the same order', async () => {
const client = createPublicClient();
const result = await client.batch((c) => [
fetchAccount(c, { address: TEST_ACCOUNT }).map(nonNullable),
fetchPost(c, { post: '4evp0jgqap2awsxpvt' })
.map(nonNullable)
.map(expectTypename('Post')),
]);
assertOk(result);
expect(result.value[0]).toMatchObject({
__typename: 'Account',
address: TEST_ACCOUNT,
});
expect(result.value[1]).toMatchObject({
__typename: 'Post',
slug: '4evp0jgqap2awsxpvt',
});
});
it('Then it should be possible to batch dynamic queries up to the 10 maximum', async () => {
const client = createPublicClient();
const result = await client.batch((c) =>
[TEST_ACCOUNT, TEST_ACCOUNT].map((address) =>
fetchAccount(c, { address }).map(nonNullable),
),
);
assertOk(result);
expectTypeOf(result.value).toEqualTypeOf();
});
it('Then it should warn if the batch size exceeds the maximum', () => {
const client = createPublicClient();
expect(() =>
client.batch((c) =>
Array.from({ length: 11 }, () =>
fetchAccount(c, { address: TEST_ACCOUNT }).map(nonNullable),
),
),
).toThrowErrorMatchingInlineSnapshot(
'[InvariantError: Batch queries supports a maximum of 10 queries]',
);
});
});
});
================================================
FILE: packages/client/src/clients.ts
================================================
import type {
AuthenticationChallenge,
ChallengeRequest,
SignedAuthChallenge,
StandardData,
SwitchAccountRequest,
} from '@lens-protocol/graphql';
import {
AuthenticateMutation,
ChallengeMutation,
RefreshMutation,
} from '@lens-protocol/graphql';
import type { Credentials, IStorage } from '@lens-protocol/storage';
import { CredentialsStorage } from '@lens-protocol/storage';
import {
errAsync,
invariant,
never,
ok,
okAsync,
type Result,
ResultAsync,
signatureFrom,
type TxHash,
} from '@lens-protocol/types';
import {
type AnyVariables,
createClient,
type Exchange,
fetchExchange,
type OperationResult,
type OperationResultSource,
type RequestPolicy,
type TypedDocumentNode,
type Client as UrqlClient,
} from '@urql/core';
import { type AuthConfig, authExchange } from '@urql/exchange-auth';
import { type AuthenticatedUser, authenticatedUser } from './AuthenticatedUser';
import {
revokeAuthentication,
switchAccount,
transactionStatus,
} from './actions';
import { BatchQueryBuilder } from './batch';
import type { ClientConfig } from './config';
import { type Context, configureContext } from './context';
import {
AuthenticationError,
GraphQLErrorCode,
hasExtensionCode,
SigningError,
TransactionIndexingError,
UnauthenticatedError,
UnexpectedError,
} from './errors';
import { Logger, LogLevel } from './logger';
import { decodeAccessToken, decodeIdToken } from './tokens';
import { delay } from './utils';
function takeValue({
data,
error,
}: OperationResult | undefined, AnyVariables>): T {
invariant(data, `Expected a value, got: ${error?.message}`);
return data.value;
}
/**
* A message signer.
*/
export type SignMessage = (message: string) => Promise;
/**
* The challenge request and the signer to use to sign the SIWE message.
*
* This is used to obtain a SIWE message that needs to be signed
* as part of the login process.
*/
export type LoginParams = ChallengeRequest & {
/**
* The signer to use to sign the SIWE message.
*/
signMessage: SignMessage;
};
abstract class AbstractClient {
/**
* @internal
*/
public readonly urql: UrqlClient;
protected readonly logger: Logger;
protected constructor(
/**
* @internal
*/
public readonly context: TContext,
) {
this.logger = Logger.named(
this.constructor.name,
context.debug ? LogLevel.DEBUG : LogLevel.SILENT,
);
this.urql = createClient({
url: context.environment.backend,
fetchOptions: {
credentials: 'omit',
headers: {
...(this.context.origin ? { Origin: this.context.origin } : {}),
...(this.context.apiKey ? { 'x-lens-app': this.context.apiKey } : {}),
},
},
exchanges: this.exchanges(),
});
}
/**
* Asserts that the client is a {@link PublicClient}.
*/
public abstract isPublicClient(): this is PublicClient;
/**
* Asserts that the client is a {@link SessionClient}.
*/
public abstract isSessionClient(): this is SessionClient;
public abstract query(
document: TypedDocumentNode, TVariables>,
variables: TVariables,
requestPolicy: RequestPolicy,
): ResultAsync;
public mutation(
document: TypedDocumentNode, TVariables>,
variables: TVariables,
): ResultAsync {
const mutation = this.context.fragments.replaceFrom(document);
return this.resultFrom(this.urql.mutation(mutation, variables)).map(
takeValue,
);
}
/**
* Execute a batch of GraphQL query operations.
*
* @alpha This is an alpha API and may be subject to breaking changes.
*
* ```ts
* const result = await sessionClient.batch((c) => [
* fetchAccount(c, { address: evmAddress('0x1234…') }).map(nonNullable),
* fetchBalancesBulk(c, {
* includeNative: true,
* tokens: [
* evmAddress("0x5678…"),
* evmAddress("0x9012…"),
* ],
* }),
* ]);
*
* // const result: Result<
* // [
* // Account,
* // AnyAccountBalance[],
* // ],
* // UnauthenticatedError | UnexpectedError
* // >
* ```
*
* @param cb - The callback with the scoped client to execute the actions with.
* @returns The results of all queries in the same order as they were added.
*/
batch(
cb: (client: this) => [ResultAsync, ResultAsync],
): ResultAsync<[T1, T2], E1 | E2>;
batch(
cb: (
client: this,
) => [ResultAsync, ResultAsync, ResultAsync],
): ResultAsync<[T1, T2, T3], E1 | E2 | E3>;
batch<
T1,
T2,
T3,
T4,
E1 extends Error,
E2 extends Error,
E3 extends Error,
E4 extends Error,
>(
cb: (
client: this,
) => [
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
],
): ResultAsync<[T1, T2, T3, T4], E1 | E2 | E3 | E4>;
batch<
T1,
T2,
T3,
T4,
T5,
E1 extends Error,
E2 extends Error,
E3 extends Error,
E4 extends Error,
E5 extends Error,
>(
cb: (
client: this,
) => [
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
],
): ResultAsync<[T1, T2, T3, T4, T5], E1 | E2 | E3 | E4 | E5>;
batch<
T1,
T2,
T3,
T4,
T5,
T6,
E1 extends Error,
E2 extends Error,
E3 extends Error,
E4 extends Error,
E5 extends Error,
E6 extends Error,
>(
cb: (
client: this,
) => [
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
],
): ResultAsync<[T1, T2, T3, T4, T5, T6], E1 | E2 | E3 | E4 | E5 | E6>;
batch<
T1,
T2,
T3,
T4,
T5,
T6,
T7,
E1 extends Error,
E2 extends Error,
E3 extends Error,
E4 extends Error,
E5 extends Error,
E6 extends Error,
E7 extends Error,
>(
cb: (
client: this,
) => [
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
],
): ResultAsync<
[T1, T2, T3, T4, T5, T6, T7],
E1 | E2 | E3 | E4 | E5 | E6 | E7
>;
batch<
T1,
T2,
T3,
T4,
T5,
T6,
T7,
T8,
E1 extends Error,
E2 extends Error,
E3 extends Error,
E4 extends Error,
E5 extends Error,
E6 extends Error,
E7 extends Error,
E8 extends Error,
>(
cb: (
client: this,
) => [
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
],
): ResultAsync<
[T1, T2, T3, T4, T5, T6, T7, T8],
E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8
>;
batch<
T1,
T2,
T3,
T4,
T5,
T6,
T7,
T8,
T9,
E1 extends Error,
E2 extends Error,
E3 extends Error,
E4 extends Error,
E5 extends Error,
E6 extends Error,
E7 extends Error,
E8 extends Error,
E9 extends Error,
>(
cb: (
client: this,
) => [
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
],
): ResultAsync<
[T1, T2, T3, T4, T5, T6, T7, T8, T9],
E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8 | E9
>;
batch<
T1,
T2,
T3,
T4,
T5,
T6,
T7,
T8,
T9,
T10,
E1 extends Error,
E2 extends Error,
E3 extends Error,
E4 extends Error,
E5 extends Error,
E6 extends Error,
E7 extends Error,
E8 extends Error,
E9 extends Error,
E10 extends Error,
>(
cb: (
client: this,
) => [
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
ResultAsync,
],
): ResultAsync<
[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10],
E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8 | E9 | E10
>;
batch(
cb: (client: this) => ResultAsync[],
): ResultAsync;
batch(
cb: (client: this) => ResultAsync[],
): ResultAsync {
const builder = new BatchQueryBuilder();
const client: this = Object.create(this, {
query: {
value: builder.addQuery,
},
});
const combined = ResultAsync.combine(cb(client));
const [document, variables] = builder.build();
const query = this.context.fragments.replaceFrom(document);
return this.resultFrom(this.urql.query(query, variables))
.andTee(({ data, error }) => {
invariant(data, `Expected a value, got: ${error?.message}`);
builder.resolve(data);
})
.andThen(() => combined);
}
protected exchanges(): Exchange[] {
if (this.context.cache) {
return [this.context.cache, fetchExchange];
}
return [fetchExchange];
}
protected resultFrom(
source: OperationResultSource>,
): ResultAsync, TError | UnexpectedError> {
return ResultAsync.fromPromise(source.toPromise(), (err: unknown) => {
this.logger.error(err);
console.log(err);
return UnexpectedError.from(err);
}).andThen((result) => {
if (result.error?.networkError) {
return errAsync(UnexpectedError.from(result.error.networkError));
}
return okAsync(result);
});
}
}
/**
* A client to interact with the public access queries and mutations of the Lens GraphQL API.
*/
export class PublicClient<
TContext extends Context = Context,
> extends AbstractClient