Skip to content

Commit

Permalink
Merge pull request #1032 from lens-protocol/T-23349/ethers-adapter
Browse files Browse the repository at this point in the history
feat: ethers.js adapter
  • Loading branch information
cesarenaldi authored Dec 17, 2024
2 parents dd4b112 + 4512798 commit e22b57a
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 5 deletions.
18 changes: 17 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"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",
Expand All @@ -30,6 +35,7 @@
"typesVersions": {
"*": {
"actions": ["./dist/actions/index.d.ts"],
"ethers": ["./dist/ethers/index.d.ts"],
"viem": ["./dist/viem/index.d.ts"]
}
},
Expand All @@ -51,22 +57,32 @@
},
"peerDependencies": {
"@lens-network/sdk": "canary",
"viem": "^2.21.53"
"ethers": "^6.13.4",
"viem": "^2.21.53",
"zksync-ethers": "^6.15.3"
},
"peerDependenciesMeta": {
"@lens-network/sdk": {
"optional": true
},
"ethers": {
"optional": true
},
"viem": {
"optional": true
},
"zksync-ethers": {
"optional": true
}
},
"devDependencies": {
"@lens-network/sdk": "canary",
"@lens-protocol/metadata": "next",
"ethers": "^6.13.4",
"tsup": "^8.3.5",
"typescript": "^5.6.3",
"viem": "^2.21.53",
"zksync-ethers": "^6.15.3",
"zod": "^3.23.8"
},
"license": "MIT"
Expand Down
50 changes: 50 additions & 0 deletions packages/client/src/ethers/ethers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { testnet } from '@lens-protocol/env';
import { afterAll, beforeAll, describe, it } from 'vitest';

import { assertOk, evmAddress, uri } from '@lens-protocol/types';
import { handleWith } from '.';
import { post } from '../actions/post';
import { PublicClient } from '../clients';

import { Network, Wallet, getDefaultProvider } from '@lens-network/sdk/ethers';
import { TestLock } from '../../testing-utils';

// biome-ignore lint/suspicious/noExplicitAny: needs a fix in @lens-network/sdk
const wallet = new Wallet(import.meta.env.PRIVATE_KEY, getDefaultProvider(Network.Testnet) as any);

const owner = evmAddress(wallet.address);
const app = evmAddress(import.meta.env.TEST_APP);
const account = evmAddress(import.meta.env.TEST_ACCOUNT);

const publicClient = PublicClient.create({
environment: testnet,
origin: 'http://example.com',
});

describe('Given an integration with ethers.js', () => {
beforeAll(async () => {
await TestLock.acquire('post');
});

afterAll(() => {
TestLock.release('post');
});

describe('When handling transaction actions', () => {
it.sequential('Then it should be possible to chain them with other helpers', async () => {
const authenticated = await publicClient.login({
accountOwner: { account, app, owner },
signMessage: (message: string) => wallet.signMessage(message),
});
const sessionClient = authenticated._unsafeUnwrap();

const result = await post(sessionClient, {
contentUri: uri('https://devnet.irys.xyz/3n3Ujg3jPBHX58MPPqYXBSQtPhTgrcTk4RedJgV1Ejhb'),
})
.andThen(handleWith(wallet))
.andThen(sessionClient.waitForTransaction);

assertOk(result);
});
});
});
1 change: 1 addition & 0 deletions packages/client/src/ethers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './signer';
68 changes: 68 additions & 0 deletions packages/client/src/ethers/signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { ResultAsync, errAsync, okAsync, txHash } from '@lens-protocol/types';
import type { TxHash } from '@lens-protocol/types';
import type { Signer } from 'ethers';
import { types } from 'zksync-ethers';

import type {
SelfFundedTransactionRequest,
SponsoredTransactionRequest,
} from '@lens-protocol/graphql';
import { SigningError, ValidationError } from '../errors';
import { type OperationHandler, type OperationResult, isTransactionRequest } from '../types';

function nullableToOptional<T extends object>(
input: T,
): Partial<{ [K in keyof T]: Exclude<T[K], null> }> {
// biome-ignore lint/suspicious/noExplicitAny: simplicity
const result: any = {};

for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
const value = input[key];
if (value !== null) {
result[key] = value;
}
}
}

return result;
}

function signWith(
signer: Signer,
request: SponsoredTransactionRequest | SelfFundedTransactionRequest,
): ResultAsync<TxHash, SigningError> {
if (request.__typename === 'SponsoredTransactionRequest') {
const { __typename, from, customData, ...transactionLike } = request.raw;
const tx: types.TransactionLike = types.Transaction.from({
...transactionLike,
...nullableToOptional(customData),
});

return ResultAsync.fromPromise(
signer.sendTransaction(tx).then((tx) => txHash(tx.hash)),
(err) => SigningError.from(err),
);
}
const { __typename, from, ...transactionLike } = request.raw;
return ResultAsync.fromPromise(
signer.sendTransaction(transactionLike).then((tx) => txHash(tx.hash)),
(err) => SigningError.from(err),
);
}

export function handleWith(signer: Signer): OperationHandler {
return <T extends string, E extends string>(
result: OperationResult<T, E>,
): ResultAsync<TxHash, SigningError | ValidationError<E>> => {
if ('hash' in result) {
return okAsync(result.hash);
}

if (isTransactionRequest(result)) {
return signWith(signer, result);
}

return errAsync(ValidationError.fromErrorResponse(result));
};
}
11 changes: 10 additions & 1 deletion packages/client/src/viem/viem.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { testnet } from '@lens-protocol/env';
import { describe, expect, it } from 'vitest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { chains } from '@lens-network/sdk/viem';
import { evmAddress, uri } from '@lens-protocol/types';
import { http, createWalletClient } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { handleWith } from '.';
import { TestLock } from '../../testing-utils';
import { post } from '../actions/post';
import { PublicClient } from '../clients';

Expand All @@ -25,6 +26,14 @@ const publicClient = PublicClient.create({
});

describe('Given an integration with viem', () => {
beforeAll(async () => {
await TestLock.acquire('post');
});

afterAll(() => {
TestLock.release('post');
});

describe('When handling transaction actions', () => {
it('Then it should be possible to chain them with other helpers', async () => {
const authenticated = await publicClient.login({
Expand Down
17 changes: 17 additions & 0 deletions packages/client/testing-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,20 @@ export function signerWallet(): WalletClient<Transport, chains.LensNetworkChain,
transport: http(),
});
}

// biome-ignore lint/complexity/noStaticOnlyClass: simplicity
export class TestLock {
private static locks = new Map<string, boolean>();

static async acquire(identifier: string) {
while (TestLock.locks.get(identifier)) {
// Wait if lock is already held
await new Promise((resolve) => setTimeout(resolve, 100));
}
TestLock.locks.set(identifier, true); // Acquire lock
}

static release(identifier: string) {
TestLock.locks.delete(identifier); // Release lock
}
}
2 changes: 1 addition & 1 deletion packages/client/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { defineConfig } from 'tsup';

export default defineConfig(() => ({
entry: ['src/index.ts', 'src/actions/index.ts', 'src/viem/index.ts'],
entry: ['src/index.ts', 'src/actions/index.ts', 'src/ethers/index.ts', 'src/viem/index.ts'],
outDir: 'dist',
splitting: false,
sourcemap: true,
Expand Down
Loading

0 comments on commit e22b57a

Please sign in to comment.