Skip to content

Commit

Permalink
feat: adds way to handle optional signing via viem signer
Browse files Browse the repository at this point in the history
  • Loading branch information
cesarenaldi committed Dec 3, 2024
1 parent e8ea090 commit 4d4a0c0
Show file tree
Hide file tree
Showing 10 changed files with 836 additions and 20 deletions.
23 changes: 21 additions & 2 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@
"import": "./dist/actions/index.js",
"require": "./dist/actions/index.cjs",
"types": "./dist/actions/index.d.cts"
},
"./viem": {
"import": "./dist/viem/index.js",
"require": "./dist/viem/index.cjs",
"types": "./dist/viem/index.d.cts"
}
},
"typesVersions": {
"*": {
"actions": ["./dist/actions/index.d.ts"]
"actions": ["./dist/actions/index.d.ts"],
"viem": ["./dist/viem/index.d.ts"]
}
},
"files": ["dist"],
Expand All @@ -43,10 +49,23 @@
"jwt-decode": "^4.0.0",
"loglevel": "^1.9.2"
},
"peerDependencies": {
"@lens-network/sdk": "canary",
"viem": "^2.21.53"
},
"peerDependenciesMeta": {
"@lens-network/sdk": {
"optional": true
},
"viem": {
"optional": true
}
},
"devDependencies": {
"@lens-network/sdk": "0.0.0-canary-20241202173005",
"tsup": "^8.3.5",
"typescript": "^5.6.3",
"viem": "^2.21.33"
"viem": "^2.21.53"
},
"license": "MIT"
}
10 changes: 5 additions & 5 deletions packages/client/src/actions/post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ const owner = evmAddress(signer.address);
const app = evmAddress(import.meta.env.TEST_APP);
const account = evmAddress(import.meta.env.TEST_ACCOUNT);

describe(`Given the '${post.name}' action`, () => {
const client = PublicClient.create({
environment: testnet,
origin: 'http://example.com',
});
const client = PublicClient.create({
environment: testnet,
origin: 'http://example.com',
});

describe(`Given the '${post.name}' action`, () => {
describe('When creating a Post', () => {
it('Then it should return the expected TransactionRequest', async () => {
const authenticated = await client.login({
Expand Down
19 changes: 19 additions & 0 deletions packages/client/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export class UnexpectedError extends ResultAwareError {
name = 'UnexpectedError' as const;
}

/**
* Error indicating a user is not authorized.
*/
export class AuthenticationError extends ResultAwareError {
name = 'AuthenticationError' as const;
}
Expand All @@ -48,3 +51,19 @@ export class AuthenticationError extends ResultAwareError {
export class SigningError extends ResultAwareError {
name = 'SigningError' as const;
}

/**
* Error indicating an operation was not executed due to a validation error.
* See the `cause` property for more information.
*/
export class ValidationError<T extends string> extends ResultAwareError {
name = 'ValidationError' as const;

constructor(public readonly cause: ErrorResponse<T>) {
super(cause.reason);
}

static fromErrorResponse<T extends string>(error: ErrorResponse<T>): ValidationError<T> {
return new ValidationError(error);
}
}
58 changes: 58 additions & 0 deletions packages/client/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,63 @@
import type { PaginatedResultInfo } from '@lens-protocol/graphql';

import type {
SelfFundedTransactionRequest,
SponsoredTransactionRequest,
} from '@lens-protocol/graphql';
import type { ResultAsync, TxHash } from '@lens-protocol/types';
import type { SigningError } from '../dist';
import type { ValidationError } from './errors';

export function isTransactionRequest(request: { __typename: string }): request is
| SponsoredTransactionRequest
| SelfFundedTransactionRequest {
return (
request.__typename === 'SponsoredTransactionRequest' ||
request.__typename === 'SelfFundedTransactionRequest'
);
}

export type OperationResponse<T extends string> = {
__typename: T;
hash: TxHash;
};

export type ErrorResponse<T extends string> = {
__typename: T;
reason: string;
};

export type DelegableOperationResult<
O extends string,
E extends string,
OR extends OperationResponse<string> = OperationResponse<O>,
ER extends ErrorResponse<string> = ErrorResponse<E>,
> = OR | SponsoredTransactionRequest | SelfFundedTransactionRequest | ER;

export type RestrictedOperationResult<
E extends string,
ER extends ErrorResponse<string> = ErrorResponse<E>,
> = SponsoredTransactionRequest | SelfFundedTransactionRequest | ER;

export type OperationResult<
O extends string,
E extends string,
OR extends OperationResponse<string> = OperationResponse<O>,
ER extends ErrorResponse<string> = ErrorResponse<E>,
> = DelegableOperationResult<O, E, OR, ER> | RestrictedOperationResult<E, ER>;

export type RestrictedOperationHandler<E extends string> = (
result: RestrictedOperationResult<E>,
) => ResultAsync<TxHash, SigningError | ValidationError<E>>;

export type DelegableOperationHandler<T extends string, E extends string> = (
result: DelegableOperationResult<T, E>,
) => ResultAsync<TxHash, SigningError | ValidationError<E>>;

export type OperationHandler<T extends string, E extends string> =
| RestrictedOperationHandler<E>
| DelegableOperationHandler<T, E>;

/**
* A standardized data object.
*
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/viem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './signer';
97 changes: 97 additions & 0 deletions packages/client/src/viem/signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type { chains } from '@lens-network/sdk/viem';
import type {
SelfFundedTransactionRequest,
SponsoredTransactionRequest,
} from '@lens-protocol/graphql';
import {
ResultAsync,
type TxHash,
errAsync,
invariant,
okAsync,
txHash,
} from '@lens-protocol/types';
import type { Account, Hash, Transport, WalletClient } from 'viem';
import { sendTransaction as sendEip1559Transaction } from 'viem/actions';
import { sendEip712Transaction } from 'viem/zksync';
import { SigningError, ValidationError } from '../errors';
import {
type DelegableOperationHandler,
type OperationHandler,
type OperationResult,
type RestrictedOperationHandler,
isTransactionRequest,
} from '../types';

async function sendTransaction(
walletClient: WalletClient<Transport, chains.LensNetworkChain, Account>,
request: SponsoredTransactionRequest | SelfFundedTransactionRequest,
): Promise<Hash> {
invariant(
walletClient.account.address === request.raw.from,
`Account mismatch: ${walletClient.account} !== ${request.raw.from}`,
);

if (request.__typename === 'SponsoredTransactionRequest') {
return sendEip712Transaction(walletClient, {
account: walletClient.account,
data: request.raw.data,
gas: BigInt(request.raw.gasLimit),

// TODO Replace this workaround hack once the gas price estimation is clarified.
maxFeePerGas: BigInt(request.raw.gasPrice),
maxPriorityFeePerGas: BigInt(request.raw.gasPrice),
// gasPrice: BigInt(request.raw.gasPrice),
nonce: request.raw.nonce,
paymaster: request.raw.customData.paymasterParams?.paymaster,
paymasterInput: request.raw.customData.paymasterParams?.paymasterInput,
to: request.raw.to,
value: BigInt(request.raw.value),
});
}

return sendEip1559Transaction(walletClient, {
account: walletClient.account,
data: request.raw.data,
gas: BigInt(request.raw.gasLimit),
maxFeePerGas: BigInt(request.raw.maxFeePerGas),
maxPriorityFeePerGas: BigInt(request.raw.maxPriorityFeePerGas),
nonce: request.raw.nonce,
to: request.raw.to,
type: 'eip1559',
value: BigInt(request.raw.value),
});
}

function signWith(
walletClient: WalletClient<Transport, chains.LensNetworkChain, Account>,
request: SponsoredTransactionRequest | SelfFundedTransactionRequest,
): ResultAsync<TxHash, SigningError> {
return ResultAsync.fromPromise(sendTransaction(walletClient, request).then(txHash), (err) =>
SigningError.from(err),
);
}

export function handleWith<T extends string, E extends string>(
walletClient: WalletClient<Transport, chains.LensNetworkChain, Account>,
): DelegableOperationHandler<T, E>;
export function handleWith<E extends string>(
walletClient: WalletClient<Transport, chains.LensNetworkChain, Account>,
): RestrictedOperationHandler<E>;
export function handleWith<T extends string, E extends string>(
walletClient: WalletClient<Transport, chains.LensNetworkChain, Account>,
): OperationHandler<T, E> {
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(walletClient, result);
}

return errAsync(ValidationError.fromErrorResponse(result));
};
}
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'],
entry: ['src/index.ts', 'src/actions/index.ts', 'src/viem/index.ts'],
outDir: 'dist',
splitting: false,
sourcemap: true,
Expand Down
Loading

0 comments on commit 4d4a0c0

Please sign in to comment.