diff --git a/packages/ssi-express-support/__tests__/index.ts b/packages/ssi-express-support/__tests__/index.ts index a4674ff79..7e2f2e311 100644 --- a/packages/ssi-express-support/__tests__/index.ts +++ b/packages/ssi-express-support/__tests__/index.ts @@ -13,7 +13,7 @@ const STRATEGY = env('OIDC_STRATEGY_NAME', PREFIX) ?? 'oidc' console.log(`Starting OIDC RP API for env '${ENVIRONMENT}/${process.env.NODE_ENV}', issuer: ${issuerUrl} with strategy ${STRATEGY}`) async function init() { - const oidcIssuer = await oidcDiscoverIssuer({issuerUrl}) + const oidcIssuer = await oidcDiscoverIssuer({ issuerUrl }) const devMetadata: ClientMetadata = { client_id: env('OIDC_CLIENT_ID', PREFIX) ?? 'EnergySHRDev', client_secret: env('OIDC_CLIENT_SECRET', PREFIX) ?? 'iZDmseeTIpuVFcodqc3cQpJ6gak7xMfa', @@ -41,42 +41,42 @@ async function init() { hostname: env('HOSTNAME', PREFIX) ?? 'localhost', }) - .withMorganLogging() - .withCorsConfigurer( - new ExpressCorsConfigurer().allowOrigin(env('OIDC_FRONTEND_CORS_ORIGIN', PREFIX) ?? 'http://localhost:3001').allowCredentials(true) - ) - .withPassportAuth(true) - .withSessionOptions({ - secret: env('OIDC_SESSION_SECRET', PREFIX) ?? 'defaultSecretPleaseChange!', - // proxy: true, - resave: false, - saveUninitialized: true, - /*cookie: { + .withMorganLogging() + .withCorsConfigurer( + new ExpressCorsConfigurer().allowOrigin(env('OIDC_FRONTEND_CORS_ORIGIN', PREFIX) ?? 'http://localhost:3001').allowCredentials(true) + ) + .withPassportAuth(true) + .withSessionOptions({ + secret: env('OIDC_SESSION_SECRET', PREFIX) ?? 'defaultSecretPleaseChange!', + // proxy: true, + resave: false, + saveUninitialized: true, + /*cookie: { maxAge: (24 * 60 * 60), httpOnly: false }*/ - }) - .build({startListening: false}) + }) + .build({ startListening: false }) passport.use( - STRATEGY, - new Strategy( - { - client, - passReqToCallback: true, - params: { - scope: 'openid email', - }, - }, - (req: any, tokenSet: any, userinfo: UserinfoResponse, done: any) => { - req.session.tokens = tokenSet - const authInfo = { - ...userinfo, - ...tokenSet.claims(), - } - return done(null, authInfo) - } - ) + STRATEGY, + new Strategy( + { + client, + passReqToCallback: true, + params: { + scope: 'openid email', + }, + }, + (req: any, tokenSet: any, userinfo: UserinfoResponse, done: any) => { + req.session.tokens = tokenSet + const authInfo = { + ...userinfo, + ...tokenSet.claims(), + } + return done(null, authInfo) + } + ) ) passport.serializeUser(function (user, done) { done(null, user) diff --git a/packages/ssi-express-support/src/express-utils.ts b/packages/ssi-express-support/src/express-utils.ts index d38e783f4..5dfa8788c 100644 --- a/packages/ssi-express-support/src/express-utils.ts +++ b/packages/ssi-express-support/src/express-utils.ts @@ -27,7 +27,7 @@ export function sendErrorResponse(response: express.Response, statusCode: number export const jsonErrorHandler = (err: any, req: express.Request, res: express.Response, next: NextFunction) => { const statusCode: number = 'statusCode' in err ? err.statusCode : 500 - const errorMsg = typeof err === 'string' ? err : (err.message ?? err) + const errorMsg = typeof err === 'string' ? err : err.message ?? err if (res.headersSent) { console.log('Headers already sent, when calling error handler. Will defer to next error handler') console.log(`Error was: ${JSON.stringify(err)}`) diff --git a/packages/ssi-express-support/src/openid-connect-rp.ts b/packages/ssi-express-support/src/openid-connect-rp.ts index 22a1a092d..1c66e369a 100644 --- a/packages/ssi-express-support/src/openid-connect-rp.ts +++ b/packages/ssi-express-support/src/openid-connect-rp.ts @@ -114,11 +114,10 @@ export function getLogoutCallbackEndpoint(router: Router, opts?: ISingleEndpoint router.get(path, (req, res, next) => { try { req.logout((err) => { - if (err) { - console.log(`Error during calling logout-callback: ${JSON.stringify(err)}`) - } - } - ) + if (err) { + console.log(`Error during calling logout-callback: ${JSON.stringify(err)}`) + } + }) return res.redirect(env('OIDC_FRONTEND_LOGOUT_REDIRECT_URL', PREFIX) ?? '/') } catch (e) { return sendErrorResponse(res, 500, 'An unexpected error occurred during logout callback', e) diff --git a/packages/web3-provider-headless/__tests__/agent.ts b/packages/web3-provider-headless/__tests__/agent.ts index 7716efe46..babaef27e 100644 --- a/packages/web3-provider-headless/__tests__/agent.ts +++ b/packages/web3-provider-headless/__tests__/agent.ts @@ -1,68 +1,69 @@ -import {ExpressBuilder, ExpressCorsConfigurer} from "@sphereon/ssi-express-support"; -import {SphereonKeyManager} from "@sphereon/ssi-sdk-ext.key-manager"; -import {SphereonKeyManagementSystem} from "@sphereon/ssi-sdk-ext.kms-local"; -import {createAgent, IDIDManager, IKeyManager, IResolver, TAgent} from "@veramo/core"; -import {DIDManager, MemoryDIDStore} from "@veramo/did-manager"; -import {getDidKeyResolver, KeyDIDProvider} from "@veramo/did-provider-key"; -import {DIDResolverPlugin} from "@veramo/did-resolver"; -import {MemoryKeyStore, MemoryPrivateKeyStore} from "@veramo/key-manager"; -import {Resolver} from "did-resolver"; -import {Signer} from 'ethers' -import {IRequiredContext, IWeb3Provider} from '../src' -import {EthersHeadlessProvider} from "../src"; -import {EthersKMSSignerBuilder} from "../src"; -import {createRpcServer} from "../src"; -import {injectWeb3Provider} from './web3-helper' - +import { ExpressBuilder, ExpressCorsConfigurer } from '@sphereon/ssi-express-support' +import { SphereonKeyManager } from '@sphereon/ssi-sdk-ext.key-manager' +import { SphereonKeyManagementSystem } from '@sphereon/ssi-sdk-ext.kms-local' +import { createAgent, IDIDManager, IKeyManager, IResolver, TAgent } from '@veramo/core' +import { DIDManager, MemoryDIDStore } from '@veramo/did-manager' +import { getDidKeyResolver, KeyDIDProvider } from '@veramo/did-provider-key' +import { DIDResolverPlugin } from '@veramo/did-resolver' +import { MemoryKeyStore, MemoryPrivateKeyStore } from '@veramo/key-manager' +import { Resolver } from 'did-resolver' +import { Signer } from 'ethers' +import { IRequiredContext, IWeb3Provider } from '../src' +import { EthersHeadlessProvider } from '../src' +import { EthersKMSSignerBuilder } from '../src' +import { createRpcServer } from '../src' +import { injectWeb3Provider } from './web3-helper' const agent: TAgent = createAgent({ - plugins: [ - new SphereonKeyManager({ - store: new MemoryKeyStore(), - kms: { - local: new SphereonKeyManagementSystem(new MemoryPrivateKeyStore()), - }, - }), - new DIDManager({ - providers: { - 'did:key': new KeyDIDProvider({defaultKms: 'local'}), - }, - store: new MemoryDIDStore(), - defaultProvider: 'did:key', - }), - new DIDResolverPlugin({ - resolver: new Resolver({ - ...getDidKeyResolver() - }), - }), - ], + plugins: [ + new SphereonKeyManager({ + store: new MemoryKeyStore(), + kms: { + local: new SphereonKeyManagementSystem(new MemoryPrivateKeyStore()), + }, + }), + new DIDManager({ + providers: { + 'did:key': new KeyDIDProvider({ defaultKms: 'local' }), + }, + store: new MemoryDIDStore(), + defaultProvider: 'did:key', + }), + new DIDResolverPlugin({ + resolver: new Resolver({ + ...getDidKeyResolver(), + }), + }), + ], }) -const context: IRequiredContext = {agent} -agent.keyManagerImport({ +const context: IRequiredContext = { agent } +agent + .keyManagerImport({ // privateKeyHex: '8f2695a99c416ab9241fc75ae53f90b083aecff9e4463e046a1527f456b502c6', privateKeyHex: process.env.WEB3_IMPORT_PRIVATEKEY_HEX ?? 'f5c0438db93a60a191530c0dd61a118bfb26ce4afb1ee54ea67deb15ec92d164', kms: 'local', - type: 'Secp256k1' -}).then(key => { - console.log("=============KEY===========") + type: 'Secp256k1', + }) + .then((key) => { + console.log('=============KEY===========') console.log(JSON.stringify(key)) - console.log("=============KEY===========") + console.log('=============KEY===========') const kmsSigner = new EthersKMSSignerBuilder().withContext(context).withKeyRef(key).build() let signers: Signer[] let web3Provider: IWeb3Provider // Inject window.ethereum instance - [signers, web3Provider] = injectWeb3Provider({signers: [kmsSigner]}) + ;[signers, web3Provider] = injectWeb3Provider({ signers: [kmsSigner] }) const headlessProvider = web3Provider as EthersHeadlessProvider console.log(`Signers: ${signers}`) const expressSupport = ExpressBuilder.fromServerOpts({ - hostname: "0.0.0.0", - port: 3000, - basePath: "/web3/rpc" - }).withCorsConfigurer(new ExpressCorsConfigurer().allowOrigin("*")).build() + hostname: '0.0.0.0', + port: 3000, + basePath: '/web3/rpc', + }) + .withCorsConfigurer(new ExpressCorsConfigurer().allowOrigin('*')) + .build() createRpcServer(headlessProvider, expressSupport) expressSupport.start() -}) + }) console.log('DONE') - - diff --git a/packages/web3-provider-headless/__tests__/connect.spec.test.ts b/packages/web3-provider-headless/__tests__/connect.test.ts.disabled similarity index 100% rename from packages/web3-provider-headless/__tests__/connect.spec.test.ts rename to packages/web3-provider-headless/__tests__/connect.test.ts.disabled diff --git a/packages/web3-provider-headless/__tests__/web3-helper.ts b/packages/web3-provider-headless/__tests__/web3-helper.ts index 6c91024cb..73d8b149b 100644 --- a/packages/web3-provider-headless/__tests__/web3-helper.ts +++ b/packages/web3-provider-headless/__tests__/web3-helper.ts @@ -6,8 +6,8 @@ import { createWeb3Provider, IWeb3Provider } from '../src' * * @returns {Array} An array containing the wallets and the web3Provider instance */ -export function injectWeb3Provider(opts?: {signers?: Signer[]}): [Signer[], IWeb3Provider] { - /* const wallet: Signer = Wallet.fromEncryptedJsonSync( +export function injectWeb3Provider(opts?: { signers?: Signer[] }): [Signer[], IWeb3Provider] { + /* const wallet: Signer = Wallet.fromEncryptedJsonSync( '{"address":"0c45d104d250b72301a7158fb27a8a4d4567b9ce","crypto":{"cipher":"aes-128-ctr","ciphertext":"6d388a272b062d155b18100ca7c78ad2fcd49c21171677ec37a5c913e768bc9e","cipherparams":{"iv":"f73a50cf77dcafdd498a225b8f0da125"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"088ade08639879b3532b49266ba16078cfe44515707ba3b17c9a911d750b6e40"},"mac":"905c5baef19f3c545dd7708be4ee94a95231a1e1c24966e97ccb4f01e9721b9a"},"id":"85a4618e-1f41-4cfe-bc62-0120aa7d517c","version":3}', 'rvvb' )*/ @@ -24,7 +24,7 @@ export function injectWeb3Provider(opts?: {signers?: Signer[]}): [Signer[], IWeb wallets, [100], // Chain ID - 31337 or is a common testnet id // [1337], 'http://127.0.0.1:8545' // Ethereum client's JSON-RPC URL - 'https://rpc.genx.minimal-gaia-x.eu' + 'https://rpc.genx.minimal-gaia-x.eu' ) // Expose the web3Provider instance to the global window object diff --git a/packages/web3-provider-headless/src/errors.ts b/packages/web3-provider-headless/src/errors.ts index bcb51a1fe..737445598 100644 --- a/packages/web3-provider-headless/src/errors.ts +++ b/packages/web3-provider-headless/src/errors.ts @@ -5,29 +5,15 @@ export class ErrorWithCode extends Error { } } -export const Deny = (): ErrorWithCode => - new ErrorWithCode('The user rejected the request.', 4001) +export const Deny = (): ErrorWithCode => new ErrorWithCode('The user rejected the request.', 4001) -export const Unauthorized = (): ErrorWithCode => - new ErrorWithCode( - 'The requested method and/or account has not been authorized by the user.', - 4100 - ) +export const Unauthorized = (): ErrorWithCode => new ErrorWithCode('The requested method and/or account has not been authorized by the user.', 4100) -export const UnsupportedMethod = (): ErrorWithCode => - new ErrorWithCode('The Provider does not support the requested method.', 4200) +export const UnsupportedMethod = (): ErrorWithCode => new ErrorWithCode('The Provider does not support the requested method.', 4200) -export const Disconnected = (): ErrorWithCode => - new ErrorWithCode('The Provider is disconnected from all chains.', 4900) +export const Disconnected = (): ErrorWithCode => new ErrorWithCode('The Provider is disconnected from all chains.', 4900) -export const ChainDisconnected = (): ErrorWithCode => - new ErrorWithCode( - 'The Provider is not connected to the requested chain.', - 4901 - ) +export const ChainDisconnected = (): ErrorWithCode => new ErrorWithCode('The Provider is not connected to the requested chain.', 4901) export const UnrecognizedChainID = (): ErrorWithCode => - new ErrorWithCode( - 'Unrecognized chain ID. Try adding the chain using `wallet_addEthereumChain` first.', - 4902 - ) + new ErrorWithCode('Unrecognized chain ID. Try adding the chain using `wallet_addEthereumChain` first.', 4902) diff --git a/packages/web3-provider-headless/src/ethers-headless-provider.ts b/packages/web3-provider-headless/src/ethers-headless-provider.ts index 506eac917..3fd2c51ef 100644 --- a/packages/web3-provider-headless/src/ethers-headless-provider.ts +++ b/packages/web3-provider-headless/src/ethers-headless-provider.ts @@ -1,28 +1,23 @@ -import {TransactionRequest} from '@ethersproject/abstract-provider' -import {toUtf8String} from '@ethersproject/strings' -import {signTypedData, SignTypedDataVersion} from '@metamask/eth-sig-util' +import { TransactionRequest } from '@ethersproject/abstract-provider' +import { toUtf8String } from '@ethersproject/strings' +import { signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util' import assert from 'assert/strict' -import {ethers, Signer, Wallet} from 'ethers' +import { ethers, Signer, Wallet } from 'ethers' // import {hashMessage} from "ethers/lib/utils"; -import {BehaviorSubject, filter, first, firstValueFrom, from, switchMap, tap} from 'rxjs' +import { BehaviorSubject, filter, first, firstValueFrom, from, switchMap, tap } from 'rxjs' -import {Deny, Disconnected, ErrorWithCode, Unauthorized, UnrecognizedChainID,} from './errors' -import {EthersKMSSigner} from "./ethers-kms-signer"; -import {EventEmitter} from './event-emitter' -import {ChainConnection, IWeb3Provider, PendingRequest, Web3Method, Web3ProviderConfig, without} from './types' +import { Deny, Disconnected, ErrorWithCode, Unauthorized, UnrecognizedChainID } from './errors' +import { EthersKMSSigner } from './ethers-kms-signer' +import { EventEmitter } from './event-emitter' +import { ChainConnection, IWeb3Provider, PendingRequest, Web3Method, Web3ProviderConfig, without } from './types' -export class EthersHeadlessProvider - extends EventEmitter - implements IWeb3Provider -{ +export class EthersHeadlessProvider extends EventEmitter implements IWeb3Provider { private _pendingRequests = new BehaviorSubject([]) private _signers: Signer[] = [] private _activeChainId: number private _rpc: Record = {} private _config: { debug: boolean; logger: typeof console.log } - private _authorizedRequests: { [K in Web3Method | string]?: boolean } = - {} - + private _authorizedRequests: { [K in Web3Method | string]?: boolean } = {} constructor( signers: Signer[], @@ -39,36 +34,18 @@ export class EthersHeadlessProvider request(args: { method: 'eth_call'; params: any[] }): Promise request(args: { method: 'eth_getBalance'; params: string[] }): Promise request(args: { method: 'eth_accounts'; params: [] }): Promise - request(args: { - method: 'eth_requestAccounts' - params: string[] - }): Promise + request(args: { method: 'eth_requestAccounts'; params: string[] }): Promise request(args: { method: 'net_version'; params: [] }): Promise request(args: { method: 'eth_chainId'; params: [] }): Promise request(args: { method: 'personal_sign'; params: string[] }): Promise - request(args: { - method: 'eth_signTypedData' | 'eth_signTypedData_v1' - params: [object[], string] - }): Promise - - request(args: { - method: 'eth_signTypedData_v3' | 'eth_signTypedData_v4' - params: string[] - }): Promise - - request(args: { - method: 'eth_sendTransaction' - params: TransactionRequest[] - }): Promise - - async request({ - method, - params - }: { - method: string - params: any[] - }): Promise { + request(args: { method: 'eth_signTypedData' | 'eth_signTypedData_v1'; params: [object[], string] }): Promise + + request(args: { method: 'eth_signTypedData_v3' | 'eth_signTypedData_v4'; params: string[] }): Promise + + request(args: { method: 'eth_sendTransaction'; params: TransactionRequest[] }): Promise + + async request({ method, params }: { method: string; params: any[] }): Promise { if (this._config.debug) { this._config.logger(JSON.stringify({ method, params })) } @@ -91,9 +68,7 @@ export class EthersHeadlessProvider async () => { const { chainId } = this.getCurrentChain() this.emit('connect', { chainId }) - return Promise.all( - this._signers.map((wallet) => wallet.getAddress()) - ) + return Promise.all(this._signers.map((wallet) => wallet.getAddress())) }, true, 'eth_requestAccounts' @@ -106,7 +81,7 @@ export class EthersHeadlessProvider case 'net_version': { const { chainId } = this.getCurrentChain() - return ""+chainId + return '' + chainId } case 'eth_sendTransaction': { @@ -124,7 +99,7 @@ export class EthersHeadlessProvider const wallet = this.getCurrentWallet() const rpc = this.getRpc() const message = params[1] - return await (wallet.connect(rpc) as EthersKMSSigner).signMessage(message) + return await (wallet.connect(rpc) as EthersKMSSigner).signMessage(message) }) } @@ -159,7 +134,7 @@ export class EthersHeadlessProvider if (this._config.debug) { this._config.logger('personal_sign', { message, - signature + signature, }) } @@ -179,7 +154,7 @@ export class EthersHeadlessProvider return signTypedData({ privateKey: Buffer.from(wallet.privateKey.slice(2), 'hex'), data: msgParams, - version: SignTypedDataVersion.V1 + version: SignTypedDataVersion.V1, }) }) } @@ -196,17 +171,14 @@ export class EthersHeadlessProvider return signTypedData({ privateKey: Buffer.from(wallet.privateKey.slice(2), 'hex'), data: msgParams, - version: - method === 'eth_signTypedData_v4' - ? SignTypedDataVersion.V4 - : SignTypedDataVersion.V3 + version: method === 'eth_signTypedData_v4' ? SignTypedDataVersion.V4 : SignTypedDataVersion.V3, }) }) } default: return this.getRpc().send(method, params) - // throw UnsupportedMethod() + // throw UnsupportedMethod() } } @@ -220,12 +192,7 @@ export class EthersHeadlessProvider return wallet } - waitAuthorization( - requestInfo: PendingRequest['requestInfo'], - task: () => Promise, - permanentPermission = false, - methodOverride?: string - ) { + waitAuthorization(requestInfo: PendingRequest['requestInfo'], task: () => Promise, permanentPermission = false, methodOverride?: string) { const method = methodOverride ?? requestInfo.method if (this._authorizedRequests[method]) { @@ -244,12 +211,10 @@ export class EthersHeadlessProvider }, reject(err) { reject(err) - } + }, } - this._pendingRequests.next( - this._pendingRequests.getValue().concat(pendingRequest) - ) + this._pendingRequests.next(this._pendingRequests.getValue().concat(pendingRequest)) }) } @@ -262,9 +227,7 @@ export class EthersHeadlessProvider }), first(), tap((item) => { - this._pendingRequests.next( - without(this._pendingRequests.getValue(), item) - ) + this._pendingRequests.next(without(this._pendingRequests.getValue(), item)) }) ) ) @@ -277,9 +240,7 @@ export class EthersHeadlessProvider } getPendingRequests(): PendingRequest['requestInfo'][] { - return this._pendingRequests - .getValue() - .map((pendingRequest) => pendingRequest.requestInfo) + return this._pendingRequests.getValue().map((pendingRequest) => pendingRequest.requestInfo) } getPendingRequestCount(requestKind?: Web3Method): number { @@ -288,9 +249,7 @@ export class EthersHeadlessProvider return pendingRequests.length } - return pendingRequests.filter( - (pendingRequest) => pendingRequest.requestInfo.method === requestKind - ).length + return pendingRequests.filter((pendingRequest) => pendingRequest.requestInfo.method === requestKind).length } async authorize(requestKind: Web3Method): Promise { @@ -298,10 +257,7 @@ export class EthersHeadlessProvider return pendingRequest.authorize() } - async reject( - requestKind: Web3Method, - reason: ErrorWithCode = Deny() - ): Promise { + async reject(requestKind: Web3Method, reason: ErrorWithCode = Deny()): Promise { const pendingRequest = await this.consumeRequest(requestKind) return pendingRequest.reject(reason) } @@ -316,16 +272,11 @@ export class EthersHeadlessProvider async changeAccounts(signers: Signer[]): Promise { this._signers = signers - this.emit( - 'accountsChanged', - await Promise.all(this._signers.map((signer) => signer.getAddress())) - ) + this.emit('accountsChanged', await Promise.all(this._signers.map((signer) => signer.getAddress()))) } private getCurrentChain(): ChainConnection { - const chainConn = this.chains.find( - (chainConn) => chainConn.chainId === this._activeChainId - ) + const chainConn = this.chains.find((chainConn) => chainConn.chainId === this._activeChainId) if (!chainConn) { throw Disconnected() } @@ -337,10 +288,7 @@ export class EthersHeadlessProvider let rpc = this._rpc[chainConn.chainId] if (!rpc) { - rpc = new ethers.providers.JsonRpcProvider( - chainConn.rpcUrl, - chainConn.chainId - ) + rpc = new ethers.providers.JsonRpcProvider(chainConn.rpcUrl, chainConn.chainId) this._rpc[chainConn.chainId] = rpc } @@ -360,9 +308,7 @@ export class EthersHeadlessProvider } switchNetwork(chainId: number): void { - const idx = this.chains.findIndex( - (connection) => connection.chainId === chainId - ) + const idx = this.chains.findIndex((connection) => connection.chainId === chainId) if (idx < 0) { throw UnrecognizedChainID() } diff --git a/packages/web3-provider-headless/src/ethers-kms-signer.ts b/packages/web3-provider-headless/src/ethers-kms-signer.ts index 0fb520e7d..e60a1af44 100644 --- a/packages/web3-provider-headless/src/ethers-kms-signer.ts +++ b/packages/web3-provider-headless/src/ethers-kms-signer.ts @@ -1,15 +1,15 @@ -import {Provider, TransactionRequest} from '@ethersproject/abstract-provider' -import {Deferrable} from '@ethersproject/properties' -import {serialize} from "@ethersproject/transactions"; -import {IKey} from '@veramo/core' -import {Eip712Payload} from '@veramo/key-manager' -import {ethers, Signer, TypedDataDomain, TypedDataField} from 'ethers' +import { Provider, TransactionRequest } from '@ethersproject/abstract-provider' +import { Deferrable } from '@ethersproject/properties' +import { serialize } from '@ethersproject/transactions' +import { IKey } from '@veramo/core' +import { Eip712Payload } from '@veramo/key-manager' +import { ethers, Signer, TypedDataDomain, TypedDataField } from 'ethers' // import {arrayify, defineReadOnly, serializeTransaction} from 'ethers/lib/utils' -import {arrayify, defineReadOnly/*, joinSignature*/} from 'ethers/lib/utils' +import { arrayify, defineReadOnly /*, joinSignature*/ } from 'ethers/lib/utils' import * as u8a from 'uint8arrays' // import {ECDSASignature} from "web3-eth-accounts"; import { getAddressFromAgent } from './functions' -import {IRequiredContext, TypedDataSigner} from './types' +import { IRequiredContext, TypedDataSigner } from './types' export class EthersKMSSignerBuilder { private context?: IRequiredContext @@ -58,8 +58,7 @@ export class EthersKMSSignerBuilder { * Be aware that the provided KeyRef needs to belong to the respective KMS, as it will use a lookup for the key in the KMS to sign */ export class EthersKMSSigner extends Signer implements TypedDataSigner { - - private readonly context: IRequiredContext; + private readonly context: IRequiredContext private readonly keyRef: Pick constructor({ provider, context, keyRef }: { provider?: ethers.providers.Provider; context: IRequiredContext; keyRef: Pick }) { @@ -74,7 +73,7 @@ export class EthersKMSSigner extends Signer implements TypedDataSigner { return await getAddressFromAgent(this.context, this.keyRef) } async signTransaction(transaction: Deferrable): Promise { - const {from, ...tx} = await transaction + const { from, ...tx } = await transaction return this.context.agent.keyManagerSign({ algorithm: 'eth_signTransaction', @@ -85,9 +84,8 @@ export class EthersKMSSigner extends Signer implements TypedDataSigner { }) } - async signRaw(message: string | Uint8Array): Promise { - return await this.context.agent.keyManagerSign({ + return await this.context.agent.keyManagerSign({ algorithm: 'eth_rawSign', keyRef: this.keyRef.kid, encoding: 'base16', @@ -125,7 +123,6 @@ export class EthersKMSSigner extends Signer implements TypedDataSigner { } } - /* /!** * Convert signature format of the `eth_sign` RPC method to signature parameters diff --git a/packages/web3-provider-headless/src/event-emitter.ts b/packages/web3-provider-headless/src/event-emitter.ts index cc54f3b71..e58b9a1e6 100644 --- a/packages/web3-provider-headless/src/event-emitter.ts +++ b/packages/web3-provider-headless/src/event-emitter.ts @@ -1,6 +1,5 @@ export class EventEmitter { - private readonly listeners: Record void>> = - Object.create(null) + private readonly listeners: Record void>> = Object.create(null) emit(eventName: string, ...args: any[]): boolean { this.listeners[eventName]?.forEach((listener) => { diff --git a/packages/web3-provider-headless/src/factory.ts b/packages/web3-provider-headless/src/factory.ts index 609b91754..20cbdf94e 100644 --- a/packages/web3-provider-headless/src/factory.ts +++ b/packages/web3-provider-headless/src/factory.ts @@ -7,10 +7,7 @@ type Fn = (...args: any[]) => any function relayEvents( eventEmitter: EventEmitter, - execute: ( - method: T, - ...args: IWeb3Provider[T] extends Fn ? Parameters : [] - ) => Promise + execute: (method: T, ...args: IWeb3Provider[T] extends Fn ? Parameters : []) => Promise ): void { const emit_ = eventEmitter.emit eventEmitter.emit = (eventName, ...args) => { @@ -30,12 +27,10 @@ export function createWeb3Provider( config?: Web3ProviderConfig ): IWeb3Provider { const chainIds: number[] = Array.isArray(chainId) ? chainId : [chainId] - const chains = chainIds.map(chainId => {return {chainId, rpcUrl}}) - const web3Provider = new EthersHeadlessProvider( - signers, - chains, - config - ) + const chains = chainIds.map((chainId) => { + return { chainId, rpcUrl } + }) + const web3Provider = new EthersHeadlessProvider(signers, chains, config) relayEvents(web3Provider, evaluate) return web3Provider } diff --git a/packages/web3-provider-headless/src/functions.ts b/packages/web3-provider-headless/src/functions.ts index 351005b99..cc9998343 100644 --- a/packages/web3-provider-headless/src/functions.ts +++ b/packages/web3-provider-headless/src/functions.ts @@ -1,19 +1,19 @@ -import {IKey} from "@veramo/core"; -import {ethers} from "ethers"; -import {IRequiredContext} from "./types"; +import { IKey } from '@veramo/core' +import { ethers } from 'ethers' +import { IRequiredContext } from './types' export async function getAddressFromAgent(context: IRequiredContext, keyRef: Pick): Promise { - const publicKeyHex = await getKey(context, keyRef).then(key => key?.publicKeyHex) - if (!publicKeyHex) { - throw Error(`Could not retrieve public hex key for ${keyRef}`) - } - const address = ethers.utils.computeAddress(`${(publicKeyHex.startsWith('0x') ? '' : '0x')}${publicKeyHex}`) - if (!address || !address.startsWith('0x')) { - throw Error(`Invalid address ${address} public key for key ${publicKeyHex}`) - } - return address + const publicKeyHex = await getKey(context, keyRef).then((key) => key?.publicKeyHex) + if (!publicKeyHex) { + throw Error(`Could not retrieve public hex key for ${keyRef}`) + } + const address = ethers.utils.computeAddress(`${publicKeyHex.startsWith('0x') ? '' : '0x'}${publicKeyHex}`) + if (!address || !address.startsWith('0x')) { + throw Error(`Invalid address ${address} public key for key ${publicKeyHex}`) + } + return address } export async function getKey(context: IRequiredContext, keyRef: Pick): Promise { - return await context.agent.keyManagerGet({kid: keyRef.kid}) + return await context.agent.keyManagerGet({ kid: keyRef.kid }) } diff --git a/packages/web3-provider-headless/src/rpc-server.ts b/packages/web3-provider-headless/src/rpc-server.ts index 1c419bb31..daf813409 100644 --- a/packages/web3-provider-headless/src/rpc-server.ts +++ b/packages/web3-provider-headless/src/rpc-server.ts @@ -1,80 +1,83 @@ -import {ExpressSupport, ISingleEndpointOpts, sendErrorResponse} from "@sphereon/ssi-express-support"; -import {EthersHeadlessProvider} from "./ethers-headless-provider"; -import {Web3Method} from "./types"; - +import { ExpressSupport, ISingleEndpointOpts, sendErrorResponse } from '@sphereon/ssi-express-support' +import { EthersHeadlessProvider } from './ethers-headless-provider' +import { Web3Method } from './types' export function createRpcServer(provider: EthersHeadlessProvider, expressSupport: ExpressSupport, opts?: ISingleEndpointOpts) { - const app = expressSupport.express - // app.post(opts?.basePath ?? "/web3/rpc", (req, res, next) => {console.log(`${JSON.stringify(req.body, null,2)}`); next()} , rpcHandler(createService(provider))); - app.post(opts?.path ?? "/web3/rpc", (req, res, next) => { - console.log(`REQ ${req.body?.method}:\r\n${JSON.stringify(req.body, null, 2)}\r\n===`); - next() - }, async (req, res, next) => { - try { - const method = req.body.method - const params = req.body.params - const id = req.body.id + const app = expressSupport.express + // app.post(opts?.basePath ?? "/web3/rpc", (req, res, next) => {console.log(`${JSON.stringify(req.body, null,2)}`); next()} , rpcHandler(createService(provider))); + app.post( + opts?.path ?? '/web3/rpc', + (req, res, next) => { + console.log(`REQ ${req.body?.method}:\r\n${JSON.stringify(req.body, null, 2)}\r\n===`) + next() + }, + async (req, res, next) => { + try { + const method = req.body.method + const params = req.body.params + const id = req.body.id - // todo: A Notification is a Request object without an "id" member. - // A Request object that is a Notification signifies the Client's lack of interest in the corresponding Response object, - // and as such no Response object needs to be returned to the client. The Server MUST NOT reply to a Notification, including those that are within a batch request. - if (req.body.jsonrpc !== "2.0") { - console.log("No valid JSON RPC call received", JSON.stringify(req.body)) - return sendErrorResponse(res, 200, { - id: req.body.id, - jsonrpc: "2.0", - error: "No valid JSON RPC call received. No jsonrp version supplied", - code: -32600 - }) - } else if (!id || !method) { - console.log("No valid JSON RPC call received", JSON.stringify(req.body)) - return sendErrorResponse(res, 200, { - id: req.body.id, - jsonrpc: "2.0", - error: "No valid JSON RPC call received", - code: -32600 - }) - } - const result = provider.request({method, params}) - provider.authorizeAll() - const respBody = {id, jsonrpc: "2.0", result: await result} - res.json(respBody) - console.log(`RESPONSE for ${method}:\r\n${JSON.stringify(respBody, null, 2)}`) - } catch (error) { - console.log(error.message) - let msg = error.message - if (`body` in error) { - msg = error.body - return sendErrorResponse(res, 200, msg) - // res.json(error.body) - } else { - return sendErrorResponse(res, 200, { - id: req.body.id, - jsonrpc: "2.0", - error: msg, - code: error.code ?? -32000 - }) - } + // todo: A Notification is a Request object without an "id" member. + // A Request object that is a Notification signifies the Client's lack of interest in the corresponding Response object, + // and as such no Response object needs to be returned to the client. The Server MUST NOT reply to a Notification, including those that are within a batch request. + if (req.body.jsonrpc !== '2.0') { + console.log('No valid JSON RPC call received', JSON.stringify(req.body)) + return sendErrorResponse(res, 200, { + id: req.body.id, + jsonrpc: '2.0', + error: 'No valid JSON RPC call received. No jsonrp version supplied', + code: -32600, + }) + } else if (!id || !method) { + console.log('No valid JSON RPC call received', JSON.stringify(req.body)) + return sendErrorResponse(res, 200, { + id: req.body.id, + jsonrpc: '2.0', + error: 'No valid JSON RPC call received', + code: -32600, + }) + } + const result = provider.request({ method, params }) + provider.authorizeAll() + const respBody = { id, jsonrpc: '2.0', result: await result } + res.json(respBody) + console.log(`RESPONSE for ${method}:\r\n${JSON.stringify(respBody, null, 2)}`) + } catch (error) { + console.log(error.message) + let msg = error.message + if (`body` in error) { + msg = error.body + return sendErrorResponse(res, 200, msg) + // res.json(error.body) + } else { + return sendErrorResponse(res, 200, { + id: req.body.id, + jsonrpc: '2.0', + error: msg, + code: error.code ?? -32000, + }) } - return next() - }); + } + return next() + } + ) } export function createServiceMethod(method: string, service: Record, provider: EthersHeadlessProvider) { - service[method] = async (params: any) => { - // @ts-ignore - const result = provider.request({method, params}) + service[method] = async (params: any) => { + // @ts-ignore + const result = provider.request({ method, params }) - provider.authorizeAll() - return await result - } + provider.authorizeAll() + return await result + } } export function createService(provider: EthersHeadlessProvider) { - const service = {} - for (const method of Object.values(Web3Method)) { - createServiceMethod(method, service, provider) - } + const service = {} + for (const method of Object.values(Web3Method)) { + createServiceMethod(method, service, provider) + } - return service + return service } diff --git a/packages/web3-provider-headless/src/types.ts b/packages/web3-provider-headless/src/types.ts index ebc1ddfc1..eec6c48b1 100644 --- a/packages/web3-provider-headless/src/types.ts +++ b/packages/web3-provider-headless/src/types.ts @@ -1,8 +1,5 @@ import { TransactionRequest } from '@ethersproject/abstract-provider' -import { - TypedDataDomain, - TypedDataField -} from '@ethersproject/abstract-signer/src.ts' +import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer/src.ts' import { IAgentContext, IKeyManager } from '@veramo/core' export type rpcMethods = 'eth_call' | 'eth_getBalance' @@ -13,25 +10,13 @@ export interface IWeb3Provider { request(args: { method: 'eth_call'; params: any[] }): Promise request(args: { method: 'eth_getBalance'; params: string[] }): Promise request(args: { method: 'eth_accounts'; params: [] }): Promise - request(args: { - method: 'eth_requestAccounts' - params: [] - }): Promise + request(args: { method: 'eth_requestAccounts'; params: [] }): Promise request(args: { method: 'net_version'; params: [] }): Promise request(args: { method: 'eth_chainId'; params: [] }): Promise request(args: { method: 'personal_sign'; params: string[] }): Promise - request(args: { - method: 'eth_signTypedData' | 'eth_signTypedData_v1' - params: [object[], string] - }): Promise - request(args: { - method: 'eth_signTypedData_v3' | 'eth_signTypedData_v4' - params: string[] - }): Promise - request(args: { - method: 'eth_sendTransaction' - params: TransactionRequest[] - }): Promise + request(args: { method: 'eth_signTypedData' | 'eth_signTypedData_v1'; params: [object[], string] }): Promise + request(args: { method: 'eth_signTypedData_v3' | 'eth_signTypedData_v4'; params: string[] }): Promise + request(args: { method: 'eth_sendTransaction'; params: TransactionRequest[] }): Promise request(args: { method: rpcMethods | string; params?: any[] }): Promise emit(eventName: string, ...args: any[]): void @@ -56,11 +41,7 @@ export interface Web3ProviderConfig { logger: typeof console.log } export interface TypedDataSigner { - _signTypedData( - domain: TypedDataDomain, - types: Record>, - value: Record - ): Promise + _signTypedData(domain: TypedDataDomain, types: Record>, value: Record): Promise } export enum Web3Method { @@ -73,7 +54,7 @@ export enum Web3Method { SignTypedData = 'eth_signTypedData', SignTypedDataV1 = 'eth_signTypedData_v1', SignTypedDataV3 = 'eth_signTypedData_v3', - SignTypedDataV4 = 'eth_signTypedData_v4' + SignTypedDataV4 = 'eth_signTypedData_v4', } export function without(list: T[], item: T): T[] {