From df277db03f7569b0656f0f4d643f967f26e88337 Mon Sep 17 00:00:00 2001 From: aman035 Date: Mon, 18 Dec 2023 15:16:32 +0530 Subject: [PATCH] fix: fix viem support, add ethers support --- packages/restapi/package.json | 4 +- .../restapi/src/lib/channels/subscribe.ts | 61 ++--- .../restapi/src/lib/channels/subscribeV2.ts | 18 +- .../restapi/src/lib/channels/unsubscribe.ts | 63 +++--- .../restapi/src/lib/channels/unsubscribeV2.ts | 15 +- .../restapi/src/lib/chat/helpers/crypto.ts | 13 +- .../restapi/src/lib/chat/helpers/wallet.ts | 35 ++- packages/restapi/src/lib/helpers/crypto.ts | 6 +- packages/restapi/src/lib/helpers/signer.ts | 131 ++++++----- packages/restapi/src/lib/payloads/helpers.ts | 10 +- .../src/lib/pushNotification/channel.ts | 45 ---- .../src/lib/pushNotification/delegate.ts | 4 +- .../pushNotification/pushNotificationBase.ts | 210 +++++++++--------- packages/restapi/src/lib/pushapi/PushAPI.ts | 3 +- packages/restapi/src/lib/types/index.ts | 20 +- .../tests/lib/notification/channel.test.ts | 2 + packages/restapi/yarn.lock | 4 +- 17 files changed, 317 insertions(+), 327 deletions(-) diff --git a/packages/restapi/package.json b/packages/restapi/package.json index 5fdfc3ee0..280c0b6a4 100644 --- a/packages/restapi/package.json +++ b/packages/restapi/package.json @@ -5,10 +5,8 @@ "publishConfig": { "registry": "https://registry.npmjs.org/" }, - "peerDependencies": { - "ethers": "^5.6.8" - }, "dependencies": { + "ethers": "^5.6.8", "@ambire/signature-validator": "^1.3.1", "@metamask/eth-sig-util": "^5.0.2", "@pushprotocol/socket": "^0.5.2", diff --git a/packages/restapi/src/lib/channels/subscribe.ts b/packages/restapi/src/lib/channels/subscribe.ts index 6e84984b2..3b9c44145 100644 --- a/packages/restapi/src/lib/channels/subscribe.ts +++ b/packages/restapi/src/lib/channels/subscribe.ts @@ -1,30 +1,23 @@ -import axios from "axios"; -import { - getCAIPAddress, - getConfig, - getCAIPDetails, - signTypedData -} from '../helpers'; +import axios from 'axios'; +import { getCAIPAddress, getConfig, getCAIPDetails, Signer } from '../helpers'; import { getTypeInformation, getDomainInformation, - getSubscriptionMessage + getSubscriptionMessage, } from './signature.helpers'; -import Constants, {ENV} from '../constants'; -import { SignerType } from "../types"; +import Constants, { ENV } from '../constants'; +import { SignerType } from '../types'; export type SubscribeOptionsType = { signer: SignerType; channelAddress: string; userAddress: string; verifyingContractAddress?: string; env?: ENV; - onSuccess?: () => void - onError?: (err: Error) => void, -} + onSuccess?: () => void; + onError?: (err: Error) => void; +}; -export const subscribe = async ( - options: SubscribeOptionsType -) => { +export const subscribe = async (options: SubscribeOptionsType) => { const { signer, channelAddress, @@ -36,7 +29,11 @@ export const subscribe = async ( } = options || {}; try { - const _channelAddress = await getCAIPAddress(env, channelAddress, 'Channel'); + const _channelAddress = await getCAIPAddress( + env, + channelAddress, + 'Channel' + ); const channelCAIPDetails = getCAIPDetails(_channelAddress); if (!channelCAIPDetails) throw Error('Invalid Channel CAIP!'); @@ -44,11 +41,14 @@ export const subscribe = async ( const chainId = parseInt(channelCAIPDetails.networkId, 10); const _userAddress = await getCAIPAddress(env, userAddress, 'User'); - + const userCAIPDetails = getCAIPDetails(_userAddress); if (!userCAIPDetails) throw Error('Invalid User CAIP!'); - const { API_BASE_URL,EPNS_COMMUNICATOR_CONTRACT } = getConfig(env, channelCAIPDetails); + const { API_BASE_URL, EPNS_COMMUNICATOR_CONTRACT } = getConfig( + env, + channelCAIPDetails + ); const requestUrl = `${API_BASE_URL}/v1/channels/${_channelAddress}/subscribe`; @@ -59,17 +59,23 @@ export const subscribe = async ( ); // get type information - const typeInformation = getTypeInformation("Subscribe"); + const typeInformation = getTypeInformation('Subscribe'); // get message const messageInformation = getSubscriptionMessage( channelCAIPDetails.address, userCAIPDetails.address, - "Subscribe" + 'Subscribe' ); // sign a message using EIP712 - const signature = await signTypedData(signer, domainInformation, typeInformation, messageInformation, "Subscribe"); + const pushSigner = new Signer(signer); + const signature = await pushSigner.signTypedData( + domainInformation, + typeInformation as any, + messageInformation, + 'Subscribe' + ); const verificationProof = signature; // might change @@ -78,7 +84,7 @@ export const subscribe = async ( message: { ...messageInformation, channel: _channelAddress, - subscriber: _userAddress + subscriber: _userAddress, }, }; @@ -86,10 +92,13 @@ export const subscribe = async ( if (typeof onSuccess === 'function') onSuccess(); - return { status: "success", message: "successfully opted into channel" }; + return { status: 'success', message: 'successfully opted into channel' }; } catch (err) { if (typeof onError === 'function') onError(err as Error); - return { status: "error", message: err instanceof Error ? err.message : JSON.stringify(err) }; + return { + status: 'error', + message: err instanceof Error ? err.message : JSON.stringify(err), + }; } -} \ No newline at end of file +}; diff --git a/packages/restapi/src/lib/channels/subscribeV2.ts b/packages/restapi/src/lib/channels/subscribeV2.ts index b86b8e937..8d6fa2369 100644 --- a/packages/restapi/src/lib/channels/subscribeV2.ts +++ b/packages/restapi/src/lib/channels/subscribeV2.ts @@ -1,10 +1,5 @@ import axios from 'axios'; -import { - getCAIPAddress, - getConfig, - getCAIPDetails, - signTypedData, -} from '../helpers'; +import { getCAIPAddress, getConfig, getCAIPDetails, Signer } from '../helpers'; import { getDomainInformation, getTypeInformationV2, @@ -77,8 +72,8 @@ export const subscribeV2 = async (options: SubscribeOptionsV2Type) => { ), }; // sign a message using EIP712 - const signature = await signTypedData( - signer, + const pushSigner = new Signer(signer); + const signature = pushSigner.signTypedData( domainInformation, typeInformation, messageInformation, @@ -89,9 +84,7 @@ export const subscribeV2 = async (options: SubscribeOptionsV2Type) => { const body = { verificationProof: `eip712v2:${verificationProof}`, - message: - messageInformation.data, - + message: messageInformation.data, }; const res = await axios.post(requestUrl, body); @@ -100,11 +93,10 @@ export const subscribeV2 = async (options: SubscribeOptionsV2Type) => { return { status: res.status, message: 'successfully opted into channel' }; } catch (err: any) { - if (typeof onError === 'function') onError(err as Error); return { - status: err?.response?.status?? '' , + status: err?.response?.status ?? '', message: err instanceof Error ? err.message : JSON.stringify(err), }; } diff --git a/packages/restapi/src/lib/channels/unsubscribe.ts b/packages/restapi/src/lib/channels/unsubscribe.ts index 554fc6bf9..342f9d037 100644 --- a/packages/restapi/src/lib/channels/unsubscribe.ts +++ b/packages/restapi/src/lib/channels/unsubscribe.ts @@ -1,31 +1,24 @@ -import axios from "axios"; -import { - getCAIPAddress, - getConfig, - getCAIPDetails, - signTypedData -} from '../helpers'; +import axios from 'axios'; +import { getCAIPAddress, getConfig, getCAIPDetails, Signer } from '../helpers'; import { getTypeInformation, getDomainInformation, - getSubscriptionMessage + getSubscriptionMessage, } from './signature.helpers'; -import Constants, {ENV} from '../constants'; -import { SignerType } from "../types"; - +import Constants, { ENV } from '../constants'; +import { SignerType } from '../types'; + export type UnSubscribeOptionsType = { signer: SignerType; channelAddress: string; userAddress: string; verifyingContractAddress?: string; env?: ENV; - onSuccess?: () => void - onError?: (err: Error) => void, -} + onSuccess?: () => void; + onError?: (err: Error) => void; +}; -export const unsubscribe = async ( - options: UnSubscribeOptionsType -) => { +export const unsubscribe = async (options: UnSubscribeOptionsType) => { const { signer, channelAddress, @@ -37,7 +30,11 @@ export const unsubscribe = async ( } = options || {}; try { - const _channelAddress = await getCAIPAddress(env, channelAddress, 'Channel'); + const _channelAddress = await getCAIPAddress( + env, + channelAddress, + 'Channel' + ); const channelCAIPDetails = getCAIPDetails(_channelAddress); if (!channelCAIPDetails) throw Error('Invalid Channel CAIP!'); @@ -45,11 +42,14 @@ export const unsubscribe = async ( const chainId = parseInt(channelCAIPDetails.networkId, 10); const _userAddress = await getCAIPAddress(env, userAddress, 'User'); - + const userCAIPDetails = getCAIPDetails(_userAddress); if (!userCAIPDetails) throw Error('Invalid User CAIP!'); - const { API_BASE_URL,EPNS_COMMUNICATOR_CONTRACT } = getConfig(env, channelCAIPDetails); + const { API_BASE_URL, EPNS_COMMUNICATOR_CONTRACT } = getConfig( + env, + channelCAIPDetails + ); const requestUrl = `${API_BASE_URL}/v1/channels/${_channelAddress}/unsubscribe`; @@ -60,17 +60,23 @@ export const unsubscribe = async ( ); // get type information - const typeInformation = getTypeInformation("Unsubscribe"); + const typeInformation = getTypeInformation('Unsubscribe'); // get message const messageInformation = getSubscriptionMessage( channelCAIPDetails.address, userCAIPDetails.address, - "Unsubscribe" + 'Unsubscribe' ); // sign a message using EIP712 - const signature = await signTypedData(signer, domainInformation, typeInformation, messageInformation, "Unsubscribe"); + const pushSigner = new Signer(signer); + const signature = await pushSigner.signTypedData( + domainInformation, + typeInformation as any, + messageInformation, + 'Unsubscribe' + ); const verificationProof = signature; // might change @@ -79,7 +85,7 @@ export const unsubscribe = async ( message: { ...messageInformation, channel: _channelAddress, - unsubscriber: _userAddress + unsubscriber: _userAddress, }, }; @@ -87,10 +93,13 @@ export const unsubscribe = async ( if (typeof onSuccess === 'function') onSuccess(); - return { status: "success", message: "successfully opted out channel" }; + return { status: 'success', message: 'successfully opted out channel' }; } catch (err) { if (typeof onError === 'function') onError(err as Error); - return { status: "error", message: err instanceof Error ? err.message : JSON.stringify(err) }; + return { + status: 'error', + message: err instanceof Error ? err.message : JSON.stringify(err), + }; } -} \ No newline at end of file +}; diff --git a/packages/restapi/src/lib/channels/unsubscribeV2.ts b/packages/restapi/src/lib/channels/unsubscribeV2.ts index f792fb782..743d2420f 100644 --- a/packages/restapi/src/lib/channels/unsubscribeV2.ts +++ b/packages/restapi/src/lib/channels/unsubscribeV2.ts @@ -1,14 +1,7 @@ import axios from 'axios'; +import { getCAIPAddress, getConfig, getCAIPDetails, Signer } from '../helpers'; import { - getCAIPAddress, - getConfig, - getCAIPDetails, - signTypedData, -} from '../helpers'; -import { - getTypeInformation, getDomainInformation, - getSubscriptionMessage, getTypeInformationV2, getSubscriptionMessageV2, } from './signature.helpers'; @@ -79,8 +72,8 @@ export const unsubscribeV2 = async (options: UnSubscribeOptionsV2Type) => { }; // sign a message using EIP712 - const signature = await signTypedData( - signer, + const pushSigner = new Signer(signer); + const signature = await pushSigner.signTypedData( domainInformation, typeInformation, messageInformation, @@ -103,7 +96,7 @@ export const unsubscribeV2 = async (options: UnSubscribeOptionsV2Type) => { if (typeof onError === 'function') onError(err as Error); return { - status: err?.response?.status?? '' , + status: err?.response?.status ?? '', message: err instanceof Error ? err.message : JSON.stringify(err), }; } diff --git a/packages/restapi/src/lib/chat/helpers/crypto.ts b/packages/restapi/src/lib/chat/helpers/crypto.ts index d2787955e..411c3a450 100644 --- a/packages/restapi/src/lib/chat/helpers/crypto.ts +++ b/packages/restapi/src/lib/chat/helpers/crypto.ts @@ -13,12 +13,10 @@ import { } from '../../types'; import { get } from '../../user'; import { + Signer, decryptPGPKey, decryptWithWalletRPCMethod, isValidETHAddress, - walletToPCAIP10, - signTypedData, - signMessage, } from '../../helpers'; import { get as getUser } from '../../user'; import { createUserService } from './service'; @@ -363,7 +361,8 @@ export const getEip191Signature = async ( const _signer = wallet?.signer; // EIP191 signature - const signature = await signMessage(_signer, message); + const pushSigner = new Signer(_signer); + const signature = await pushSigner.signMessage(message); const sigType = version === 'v1' ? 'eip191' : 'eip191v2'; return { verificationProof: `${sigType}:${signature}` }; }; @@ -381,17 +380,17 @@ export const getEip712Signature = async ( const typeInformation = getTypeInformation(); const _signer = wallet?.signer; + const pushSigner = new Signer(_signer); let chainId: number; try { - chainId = await _signer.getChainId(); + chainId = await pushSigner.getChainId(); } catch (err) { chainId = 1; } const domain = getDomainInformation(chainId); // sign a message using EIP712 - const signedMessage = await signTypedData( - _signer, + const signedMessage = await pushSigner.signTypedData( isDomainEmpty ? {} : domain, typeInformation, { data: hash }, diff --git a/packages/restapi/src/lib/chat/helpers/wallet.ts b/packages/restapi/src/lib/chat/helpers/wallet.ts index 1481e62bf..1935f188b 100644 --- a/packages/restapi/src/lib/chat/helpers/wallet.ts +++ b/packages/restapi/src/lib/chat/helpers/wallet.ts @@ -1,26 +1,25 @@ -import { pCAIP10ToWallet, getAddress } from "../../helpers"; -import { SignerType, walletType } from "../../types"; +import { Signer, pCAIP10ToWallet } from '../../helpers'; +import { SignerType, walletType } from '../../types'; -export const getWallet = (options: walletType): { - account: string | null, - signer: SignerType | null +export const getWallet = ( + options: walletType +): { + account: string | null; + signer: SignerType | null; } => { - const { - account, - signer - } = options || {}; + const { account, signer } = options || {}; return { account: account ? pCAIP10ToWallet(account) : account, - signer + signer, }; -} +}; -export const getAccountAddress = async (options: walletType): Promise => { - const { - account, - signer - } = options || {}; +export const getAccountAddress = async ( + options: walletType +): Promise => { + const { account, signer } = options || {}; - return account || (await getAddress(signer as SignerType)) || '' -} \ No newline at end of file + const pushSigner = new Signer(signer as SignerType); + return account || (await pushSigner.getAddress()) || ''; +}; diff --git a/packages/restapi/src/lib/helpers/crypto.ts b/packages/restapi/src/lib/helpers/crypto.ts index cb2cb18fb..932c148f6 100644 --- a/packages/restapi/src/lib/helpers/crypto.ts +++ b/packages/restapi/src/lib/helpers/crypto.ts @@ -32,7 +32,7 @@ import { import { verifyProfileSignature } from '../chat/helpers/signature'; import { upgrade } from '../user/upgradeUser'; import PROGRESSHOOK from '../progressHook'; -import { getAddress } from './signer'; +import { Signer } from './signer'; const KDFSaltSize = 32; // bytes const AESGCMNonceSize = 12; // property iv @@ -52,8 +52,8 @@ if (typeof window !== 'undefined' && window.crypto) { /** DEPRECATED */ export const getPublicKey = async (options: walletType): Promise => { const { account, signer } = options || {}; - const address: string = - account || (await getAddress(signer as SignerType)) || ''; + const pushSigner = signer ? new Signer(signer) : undefined; + const address: string = account || (await pushSigner?.getAddress()) || ''; const metamaskProvider = new ethers.providers.Web3Provider( (window as any).ethereum ); diff --git a/packages/restapi/src/lib/helpers/signer.ts b/packages/restapi/src/lib/helpers/signer.ts index bb950e9fd..7255e0856 100644 --- a/packages/restapi/src/lib/helpers/signer.ts +++ b/packages/restapi/src/lib/helpers/signer.ts @@ -1,60 +1,89 @@ -import { SignerType } from '../types'; +import { SignerType, viemSignerType } from '../types'; +import { Bytes, TypedDataDomain, TypedDataField } from 'ethers'; -export const signMessage = async ( - signer: SignerType, - message: string -): Promise => { - // Check the signer type using type guards - if ('signMessage' in signer) { - // If the signer has a signMessage function with the ethersV5SignerType signature - if ('_signTypedData' in signer) { - // It's ethersV5SignerType, use its signMessage function - const signature = await signer.signMessage(message); - return signature; +export class Signer { + private signer: SignerType; + + constructor(signer: SignerType) { + this.signer = signer; + } + + /** + * Determine if the signer is a Viem signer + */ + isViemSigner(signer: SignerType): signer is viemSignerType { + return ( + typeof (signer as any).signTypedData === 'function' && + typeof (signer as any).getChainId === 'function' && + signer.signMessage.length === 1 && // Checking if the function takes one argument + (signer as any).signTypedData.length === 1 // Checking if the function takes one argument + ); + } + + async signMessage(message: string | Bytes): Promise { + if ( + 'signMessage' in this.signer && + typeof this.signer.signMessage === 'function' + ) { + if (this.isViemSigner(this.signer)) { + // Viem signer requires additional arguments + return this.signer.signMessage({ + message, + account: this.signer.account, + }); + } else { + // EthersV5 and EthersV6 + return this.signer.signMessage(message); + } } else { - // It's viemSignerType, use its signMessage function - const signature = await signer.signMessage({ - message, - account: signer.account, + throw new Error('Signer does not support signMessage'); + } + } + + async signTypedData( + domain: TypedDataDomain, + types: Record, + value: Record, + primaryType?: string + ): Promise { + if (this.isViemSigner(this.signer)) { + // Call Viem's signTypedData with its specific structure + return this.signer.signTypedData({ + domain: domain, + types: types, + primaryType: primaryType, + message: value, + account: this.signer.account, }); - return signature; + } else if ('_signTypedData' in this.signer) { + // ethersV5 signer uses _signTypedData + return this.signer._signTypedData(domain, types, value); + } else if ('signTypedData' in this.signer) { + // ethersV6 signer uses signTypedData + return this.signer.signTypedData(domain, types, value); + } else { + throw new Error('Signer does not support signTypedData'); } - } else { - throw new Error('Invalid signer type provided.'); } -}; -export const signTypedData = async ( - signer: SignerType, - domain: any, - types: any, - value: any, - primaryType: string -): Promise => { - // Check the signer type using type guards - if ('_signTypedData' in signer) { - // It's ethersV5SignerType, use its functions - const signature = await signer._signTypedData(domain, types, value); - return signature; - } else if ('signTypedData' in signer) { - // It's viemSignerType, use its functions - const signature = await signer.signTypedData({ - account: signer.account, - domain, - types, - primaryType: primaryType, - message: value, - }); - return signature; - } else { - throw new Error('Invalid signer type provided.'); + async getAddress(): Promise { + if (this.isViemSigner(this.signer)) { + return this.signer.account['address'] ?? ''; + } else { + return await this.signer.getAddress(); + } } -}; -export const getAddress = async (signer: SignerType): Promise => { - if ('getAddress' in signer) { - return await signer.getAddress(); - } else { - return signer.account['address'] ?? ''; + async getChainId(): Promise { + if (this.isViemSigner(this.signer)) { + // Viem signer has a direct method for getChainId + return this.signer.getChainId(); + } else if ('provider' in this.signer && this.signer.provider) { + // EthersV5 and EthersV6 + const network = await this.signer.provider.getNetwork(); + return network.chainId; + } else { + return 1; // Return default chainId + } } -}; +} diff --git a/packages/restapi/src/lib/payloads/helpers.ts b/packages/restapi/src/lib/payloads/helpers.ts index 5c58d09bb..63cc5e121 100644 --- a/packages/restapi/src/lib/payloads/helpers.ts +++ b/packages/restapi/src/lib/payloads/helpers.ts @@ -1,6 +1,6 @@ import { v4 as uuidv4 } from 'uuid'; import { ENV } from '../constants'; -import { getCAIPAddress, signTypedData } from '../helpers'; +import { Signer, getCAIPAddress } from '../helpers'; import * as CryptoJS from 'crypto-js'; import { @@ -255,8 +255,8 @@ export async function getVerificationProof({ chainId: chainId, verifyingContract: verifyingContract, }; - const signature = await signTypedData( - signer, + const pushSigner = new Signer(signer); + const signature = await pushSigner.signTypedData( domain, type, message, @@ -325,7 +325,9 @@ export function getSource( export function getCAIPFormat(chainId: number, address: string) { // EVM based chains if ( - [1, 11155111, 42, 137, 80001, 56, 97, 10, 420, 1442, 1101, 421613, 42161].includes(chainId) + [ + 1, 11155111, 42, 137, 80001, 56, 97, 10, 420, 1442, 1101, 421613, 42161, + ].includes(chainId) ) { return `eip155:${chainId}:${address}`; } diff --git a/packages/restapi/src/lib/pushNotification/channel.ts b/packages/restapi/src/lib/pushNotification/channel.ts index 824f5d52b..2a4b135e4 100644 --- a/packages/restapi/src/lib/pushNotification/channel.ts +++ b/packages/restapi/src/lib/pushNotification/channel.ts @@ -153,17 +153,6 @@ export class Channel extends PushNotificationBaseClass { progressHook, } = options || {}; try { - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); - } - } else if ('signTypedData' in this.signer!) { - if (!this.coreContract.write) { - throw new Error('viem signer is not provided'); - } - } else { - throw new Error('Unsupported Signer'); - } // create push token instance let aliasInfo; // validate all the parameters and length @@ -256,17 +245,6 @@ export class Channel extends PushNotificationBaseClass { try { // create push token instance let aliasInfo; - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); - } - } else if ('signTypedData' in this.signer!) { - if (!this.coreContract.write) { - throw new Error('viem signer is not provided'); - } - } else { - throw new Error('Unsupported Signer'); - } // validate all the parameters and length this.validateChannelParameters(options); // check for PUSH balance @@ -357,17 +335,6 @@ export class Channel extends PushNotificationBaseClass { verify = async (channelToBeVerified: string) => { try { this.checkSignerObjectExists(); - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); - } - } else if ('signTypedData' in this.signer!) { - if (!this.coreContract.write) { - throw new Error('viem signer is not provided'); - } - } else { - throw new Error('Unsupported Signer'); - } if (validateCAIP(channelToBeVerified)) { channelToBeVerified = channelToBeVerified.split(':')[2]; } @@ -396,18 +363,6 @@ export class Channel extends PushNotificationBaseClass { setting = async (configuration: NotificationSettings) => { try { this.checkSignerObjectExists(); - //TODO: create a separate function later - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); - } - } else if ('signTypedData' in this.signer!) { - if (!this.coreContract.write) { - throw new Error('viem signer is not provided'); - } - } else { - throw new Error('Unsupported Signer'); - } // check for PUSH balance const pushTokenContract = await this.createContractInstance( config.TOKEN[this.env!], diff --git a/packages/restapi/src/lib/pushNotification/delegate.ts b/packages/restapi/src/lib/pushNotification/delegate.ts index 90d7ad0e2..ba548328e 100644 --- a/packages/restapi/src/lib/pushNotification/delegate.ts +++ b/packages/restapi/src/lib/pushNotification/delegate.ts @@ -56,7 +56,7 @@ export class Delegate extends PushNotificationBaseClass { if (validateCAIP(delegate)) { delegate = this.getAddressFromCaip(delegate); } - const networkDetails = await this.getChianId(this.signer!); + const networkDetails = await this.getChainId(this.signer!); const caip = `eip155:${networkDetails}`; if (!CONFIG[this.env!][caip] || !config.VIEM_CONFIG[this.env!][caip]) { throw new Error('Unsupported Chainid'); @@ -88,7 +88,7 @@ export class Delegate extends PushNotificationBaseClass { if (validateCAIP(delegate)) { delegate = this.getAddressFromCaip(delegate); } - const networkDetails = await this.getChianId(this.signer!); + const networkDetails = await this.getChainId(this.signer!); const caip = `eip155:${networkDetails}`; if (!CONFIG[this.env!][caip] || !config.VIEM_CONFIG[this.env!][caip]) { throw new Error('Unsupported Chainid'); diff --git a/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts b/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts index b61435fa6..c4f79271c 100644 --- a/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts +++ b/packages/restapi/src/lib/pushNotification/pushNotificationBase.ts @@ -9,7 +9,7 @@ import { import * as config from '../config'; import { getAccountAddress } from '../chat/helpers'; import { IDENTITY_TYPE, NOTIFICATION_TYPE } from '../payloads/constants'; -import { ethers, Signer, BigNumber } from 'ethers'; +import { ethers, Signer as EthersSigner, BigNumber } from 'ethers'; import axios from 'axios'; import { createPublicClient, @@ -20,6 +20,7 @@ import { } from 'viem'; import * as PUSH_CHANNEL from '../channels'; import { + Signer, getAPIBaseUrls, getFallbackETHCAIPAddress, validateCAIP, @@ -70,25 +71,12 @@ export class PushNotificationBaseClass { let derivedAccount; let coreContract; if (signer) { - if (!('_signTypedData' in signer!) && !('signTypedData' in signer!)) { - throw new Error('Unsupported signer type'); - } else if ('_signTypedData' in signer) { - derivedAccount = await getAccountAddress({ - account: null, - signer: signer, - }); - if (signer?.provider) { - coreContract = new ethers.Contract( - config.CORE_CONFIG[env].EPNS_CORE_CONTRACT, - config.ABIS.CORE, - signer as unknown as Signer - ); - } - } else if ('signTypedData' in signer) { - derivedAccount = await getAccountAddress({ - account: null, - signer: signer, - }); + derivedAccount = await getAccountAddress({ + account: null, + signer: signer, + }); + const pushSigner = new Signer(signer); + if (pushSigner.isViemSigner(signer)) { const client = createPublicClient({ chain: config.TOKEN_VIEM_NETWORK_MAP[env], transport: http(), @@ -99,6 +87,12 @@ export class PushNotificationBaseClass { publicClient: client, walletClient: signer as unknown as WalletClient, }); + } else { + coreContract = new ethers.Contract( + config.CORE_CONFIG[env].EPNS_CORE_CONTRACT, + config.ABIS.CORE, + signer as unknown as EthersSigner + ); } } @@ -293,22 +287,12 @@ export class PushNotificationBaseClass { contractABI: any, network: Chain ) { + if (!this.signer) { + throw new Error('Signer is not provided'); + } let contract: any; - if ( - !('_signTypedData' in this.signer!) && - !('signTypedData' in this.signer!) - ) { - throw new Error('Unsupported signer type'); - } else if ('_signTypedData' in this.signer) { - if (!this.signer?.provider) { - throw new Error('Provider is required'); - } - contract = new ethers.Contract( - contractAddress, - contractABI, - this.signer as unknown as Signer - ); - } else if ('signTypedData' in this.signer) { + const pushSigner = this.signer ? new Signer(this.signer) : null; + if (pushSigner?.isViemSigner(this.signer)) { const client = createPublicClient({ chain: network, transport: http(), @@ -320,28 +304,33 @@ export class PushNotificationBaseClass { walletClient: this.signer as unknown as WalletClient, }); } else { - throw new Error('Unsupported signer type'); + contract = new ethers.Contract( + contractAddress, + contractABI, + this.signer as unknown as EthersSigner + ); } return contract; } protected async fetchBalance(contract: any, userAddress: string) { + if (!this.signer) { + throw new Error('Signer is not provided'); + } let balance: BigNumber; + const pushSigner = new Signer(this.signer); try { - if ('_signTypedData' in this.signer!) { - balance = await contract!['balanceOf'](userAddress); - } else if ('signTypedData' in this.signer!) { + if (pushSigner.isViemSigner(this.signer)) { const balanceInBigInt = await contract.read.balanceOf({ args: [userAddress], }); balance = ethers.BigNumber.from(balanceInBigInt); } else { - throw new Error('Unsupported signer'); + balance = await contract.balanceOf(userAddress); } return balance; - } catch (error) { - console.error(error); - throw new Error(JSON.stringify(error)); + } catch (err) { + throw new Error(JSON.stringify(err)); } } @@ -350,17 +339,20 @@ export class PushNotificationBaseClass { userAddress: string, spenderAddress: string ) { + if (!this.signer) { + throw new Error('Signer is not provided'); + } + + const pushSigner = new Signer(this.signer); let allowance: BigNumber; try { - if ('_signTypedData' in this.signer!) { + if (!pushSigner.isViemSigner(this.signer)) { allowance = await contract!['allowance'](userAddress, spenderAddress); - } else if ('signTypedData' in this.signer!) { + } else { const allowanceInBigInt = await contract.read.allowance({ args: [userAddress, spenderAddress], }); allowance = ethers.BigNumber.from(allowanceInBigInt); - } else { - throw new Error('Unsupported signer'); } return allowance; } catch (error) { @@ -369,17 +361,19 @@ export class PushNotificationBaseClass { } protected async fetchUpdateCounter(contract: any, userAddress: string) { + if (!this.signer) { + throw new Error('Signer is not provided'); + } let count: BigNumber; + const pushSigner = new Signer(this.signer); try { - if ('_signTypedData' in this.signer!) { + if (!pushSigner.isViemSigner(this.signer)) { count = await contract!['channelUpdateCounter'](userAddress); - } else if ('signTypedData' in this.signer!) { + } else { const countInBigInt = await contract.read.channelUpdateCounter({ args: [userAddress], }); count = ethers.BigNumber.from(countInBigInt); - } else { - throw new Error('Unsupported signer'); } // add one and return the count return count.add(ethers.BigNumber.from(1)); @@ -394,14 +388,19 @@ export class PushNotificationBaseClass { amount: string | BigNumber ) { try { - if ('_signTypedData' in this.signer!) { + if (!this.signer) { + throw new Error('Signer is not provided'); + } + const pushSigner = new Signer(this.signer); + + if (!pushSigner.isViemSigner(this.signer)) { if (!this.signer || !this.signer.provider) { throw new Error('ethers provider/signer is not provided'); } const approvalTrxPromise = contract!['approve'](spenderAddress, amount); const approvalTrx = await approvalTrxPromise; await this.signer?.provider?.waitForTransaction(approvalTrx.hash); - } else if ('signTypedData' in this.signer!) { + } else { if (!contract.write) { throw new Error('viem signer is not provided'); } @@ -409,8 +408,6 @@ export class PushNotificationBaseClass { args: [spenderAddress, amount], }); const approvalTrxRes = await approvalTrxPromise; - } else { - throw new Error('Unsupported signer'); } return true; } catch (error) { @@ -427,10 +424,11 @@ export class PushNotificationBaseClass { ) { let createChannelRes; try { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); + if (!this.signer) { + throw new Error('Signer is not provided'); } - if ('_signTypedData' in this.signer!) { + const pushSigner = new Signer(this.signer); + if (!pushSigner.isViemSigner(this.signer)) { const createChannelPromise = contract!['createChannelWithPUSH']( channelType, identityBytes, @@ -449,7 +447,7 @@ export class PushNotificationBaseClass { throw new Error('Something Went wrong while creating your channel'); } createChannelRes = createChannelTrx.hash; - } else if ('signTypedData' in this.signer!) { + } else { if (!contract.write) { throw new Error('viem signer is not provided'); } @@ -458,7 +456,6 @@ export class PushNotificationBaseClass { }); createChannelRes = await createChannelPromise; } - return createChannelRes; } catch (error: any) { throw new Error(error?.message); @@ -473,10 +470,11 @@ export class PushNotificationBaseClass { ) { let updateChannelRes; try { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); + if (!this.signer) { + throw new Error('Signer is not provided'); } - if ('_signTypedData' in this.signer!) { + const pushSigner = new Signer(this.signer); + if (!pushSigner.isViemSigner(this.signer)) { const updateChannelPromise = contract!['updateChannelMeta']( account, identityBytes, @@ -494,7 +492,7 @@ export class PushNotificationBaseClass { throw new Error('Something Went wrong while updating your channel'); } updateChannelRes = updateChannelTrx.hash; - } else if ('signTypedData' in this.signer!) { + } else { if (!contract.write) { throw new Error('viem signer is not provided'); } @@ -512,16 +510,20 @@ export class PushNotificationBaseClass { protected async verifyChannel(contract: any, channelToBeVerified: string) { try { + if (!this.signer) { + throw new Error('Signer is not provided'); + } + const pushSigner = new Signer(this.signer); let verifyTrxRes; - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); + if (!pushSigner.isViemSigner(this.signer)) { + if (!this.signer.provider) { + throw new Error('ethers provider is not provided'); } const verifyTrxPromise = contract!['verify'](channelToBeVerified); const verifyTrx = await verifyTrxPromise; await this.signer?.provider?.waitForTransaction(verifyTrx.hash); verifyTrxRes = verifyTrx.hash; - } else if ('signTypedData' in this.signer!) { + } else { if (!contract.write) { throw new Error('viem signer is not provided'); } @@ -529,8 +531,6 @@ export class PushNotificationBaseClass { args: [channelToBeVerified], }); verifyTrxRes = await verifyTrxPromise; - } else { - throw new Error('Unsupported signer'); } return verifyTrxRes; } catch (error: any) { @@ -546,10 +546,14 @@ export class PushNotificationBaseClass { fees: BigNumber ) { try { + if (!this.signer) { + throw new Error('Signer is not provided'); + } + const pushSigner = new Signer(this.signer); let createSettingsRes; - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); + if (!pushSigner.isViemSigner(this.signer)) { + if (!this.signer.provider) { + throw new Error('ethers provider is not provided'); } const createSettingsPromise = contract!['createChannelSettings']( numberOfSettings, @@ -560,7 +564,7 @@ export class PushNotificationBaseClass { const createSettings = await createSettingsPromise; await this.signer?.provider?.waitForTransaction(createSettings.hash); createSettingsRes = createSettings.hash; - } else if ('signTypedData' in this.signer!) { + } else { if (!contract.write) { throw new Error('viem signer is not provided'); } @@ -568,8 +572,6 @@ export class PushNotificationBaseClass { args: [numberOfSettings, settings, description, fees], }); createSettingsRes = await createSettingsTrxPromise; - } else { - throw new Error('Unsupported signer'); } return createSettingsRes; } catch (error: any) { @@ -579,16 +581,20 @@ export class PushNotificationBaseClass { protected async addDelegator(contract: any, delegatee: string) { try { + if (!this.signer) { + throw new Error('Signer is not provided'); + } + const pushSigner = new Signer(this.signer); let addDelegateRes; - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); + if (!pushSigner.isViemSigner(this.signer)) { + if (!this.signer.provider) { + throw new Error('ethers provider is not provided'); } const addDelegateTrxPromise = contract!['addDelegate'](delegatee); const addDelegateTrx = await addDelegateTrxPromise; await this.signer?.provider?.waitForTransaction(addDelegateTrx.hash); addDelegateRes = addDelegateTrx.hash; - } else if ('signTypedData' in this.signer!) { + } else { if (!contract.write) { throw new Error('viem signer is not provided'); } @@ -596,8 +602,6 @@ export class PushNotificationBaseClass { args: [delegatee], }); addDelegateRes = await addDelegateTrxPromise; - } else { - throw new Error('Unsupported signer'); } return addDelegateRes; } catch (error: any) { @@ -607,16 +611,20 @@ export class PushNotificationBaseClass { protected async removeDelegator(contract: any, delegatee: string) { try { + if (!this.signer) { + throw new Error('Signer is not provided'); + } + const pushSigner = new Signer(this.signer); let removeDelegateRes; - if ('_signTypedData' in this.signer!) { - if (!this.signer || !this.signer.provider) { - throw new Error('ethers provider/signer is not provided'); + if (!pushSigner.isViemSigner(this.signer)) { + if (!this.signer.provider) { + throw new Error('ethers provider is not provided'); } const removeDelegateTrxPromise = contract!['removeDelegate'](delegatee); const removeDelegateTrx = await removeDelegateTrxPromise; await this.signer?.provider?.waitForTransaction(removeDelegateTrx.hash); removeDelegateRes = removeDelegateTrx.hash; - } else if ('signTypedData' in this.signer!) { + } else { if (!contract.write) { throw new Error('viem signer is not provided'); } @@ -624,8 +632,6 @@ export class PushNotificationBaseClass { args: [delegatee], }); removeDelegateRes = await removeDelegateTrxPromise; - } else { - throw new Error('Unsupported signer'); } return removeDelegateRes; } catch (error: any) { @@ -633,30 +639,12 @@ export class PushNotificationBaseClass { } } - protected async getChianId(signer: SignerType) { - let chainId; - const isProviderExists = await this.checkProvider(signer); - if (!isProviderExists) { - throw new Error('Provider doesnt exists'); - } - if ('_signTypedData' in signer!) { - const chainDetails = await signer?.provider?.getNetwork(); - chainId = chainDetails?.chainId; - } else if ('signTypedData' in signer!) { - chainId = await signer.getChainId(); - } - return chainId; - } - - protected async checkProvider(signer: SignerType) { - let res = false; - if ('_signTypedData' in signer!) { - res = signer && signer?.provider ? true : false; - } else if ('signTypedData' in signer!) { - const chainId = await signer.getChainId(); - res = !!chainId; + protected async getChainId(signer: SignerType) { + if (!this.signer) { + throw new Error('Signer is not provided'); } - return res; + const pushSigner = new Signer(this.signer); + return pushSigner.getChainId(); } protected async uploadToIPFSViaPushNode(data: string): Promise { diff --git a/packages/restapi/src/lib/pushapi/PushAPI.ts b/packages/restapi/src/lib/pushapi/PushAPI.ts index 0549b5643..4b8de488d 100644 --- a/packages/restapi/src/lib/pushapi/PushAPI.ts +++ b/packages/restapi/src/lib/pushapi/PushAPI.ts @@ -97,7 +97,8 @@ export class PushAPI { if ( args.length === 1 && typeof args[0] === 'object' && - 'account' in args[0] + 'account' in args[0] && + typeof args[0].account === 'string' ) { // Single options object provided options = args[0]; diff --git a/packages/restapi/src/lib/types/index.ts b/packages/restapi/src/lib/types/index.ts index d091d909f..d33cca7b8 100644 --- a/packages/restapi/src/lib/types/index.ts +++ b/packages/restapi/src/lib/types/index.ts @@ -328,7 +328,6 @@ export enum GROUP_RULES_SUB_CATEGORY { GET = 'GET', } - export enum GROUP_RULES_PERMISSION { ENTRY = 'Entry', CHAT = 'Chat', @@ -649,12 +648,24 @@ export type ethersV5SignerType = { types: Record>, value: Record ) => Promise; - getChainId: () => Promise; getAddress: () => Promise; signMessage: (message: Bytes | string) => Promise; privateKey?: string; provider?: providers.Provider; }; + +export type ethersV6SignerType = { + signTypedData: ( + domain: TypedDataDomain, + types: Record>, + value: Record + ) => Promise; + getAddress: () => Promise; + signMessage: (message: Bytes | string) => Promise; + privateKey?: string; + provider?: providers.Provider; +}; + export type viemSignerType = { signTypedData: (args: { account: any; @@ -674,7 +685,10 @@ export type viemSignerType = { provider?: providers.Provider; }; -export type SignerType = ethersV5SignerType | viemSignerType; +export type SignerType = + | ethersV5SignerType + | ethersV6SignerType + | viemSignerType; export type EnvOptionsType = { env?: ENV; diff --git a/packages/restapi/tests/lib/notification/channel.test.ts b/packages/restapi/tests/lib/notification/channel.test.ts index ecd4c6bc5..57cc7f934 100644 --- a/packages/restapi/tests/lib/notification/channel.test.ts +++ b/packages/restapi/tests/lib/notification/channel.test.ts @@ -411,6 +411,8 @@ describe('PushAPI.channel functionality', () => { describe('channel :: create', () => { it('Should create channel', async () => { + const channelInfo = await userKate.channel.info(); + if (channelInfo) return; // skip if already exists const res = await userKate.channel.create({ name: 'SDK Test', description: 'Testing new description', diff --git a/packages/restapi/yarn.lock b/packages/restapi/yarn.lock index 1a2318580..6b2c64890 100644 --- a/packages/restapi/yarn.lock +++ b/packages/restapi/yarn.lock @@ -1213,9 +1213,9 @@ ethereum-cryptography@^1.1.2: "@scure/bip32" "1.1.5" "@scure/bip39" "1.1.1" -ethers@^5.6.5: +ethers@^5.6.5, ethers@^5.6.8: version "5.7.2" - resolved "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== dependencies: "@ethersproject/abi" "5.7.0"