From 5476d476a8fe37072f27caccac4468263a4b52c7 Mon Sep 17 00:00:00 2001 From: William Hua Date: Wed, 18 Oct 2023 14:35:50 -0400 Subject: [PATCH] signhub: InteractiveSigner --- packages/signhub/src/signers/interactive.ts | 109 ++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 packages/signhub/src/signers/interactive.ts diff --git a/packages/signhub/src/signers/interactive.ts b/packages/signhub/src/signers/interactive.ts new file mode 100644 index 000000000..3900692cf --- /dev/null +++ b/packages/signhub/src/signers/interactive.ts @@ -0,0 +1,109 @@ +import { BytesLike } from 'ethers' +import { Status, isSignerStatusPending } from '../orchestrator' +import { SapientSigner } from './signer' + +export class InteractiveSigner implements SapientSigner { + private readonly requests: Map = new Map() + + constructor( + private readonly signer: SapientSigner, + private readonly dependencies: string[] + ) {} + + async prompt(id: string) { + const request = this.requests.get(id) + + if (request === undefined) { + return + } + + this.signer.requestSignature(id, request.message, request.metadata, { + onSignature(signature) { + request.callbacks.onSignature(signature) + request.resolve() + }, + onRejection(_error) {}, + onStatus(_situation) {} + }) + } + + getAddress(): Promise { + return this.signer.getAddress() + } + + requestSignature( + id: string, + message: BytesLike, + metadata: object, + callbacks: { + onSignature: (signature: BytesLike) => void + onRejection: (error: string) => void + onStatus: (situation: string) => void + } + ): Promise { + return new Promise(resolve => { + const request = { + message, + metadata, + callbacks, + autoPrompts: 0, + resolve: () => resolve(true) + } + + this.requests.set(id, request) + + // prompt immediately if we're not waiting for other signers + if (this.dependencies.length === 0) { + request.autoPrompts++ + this.prompt(id) + } + }) + } + + notifyStatusChange(id: string, status: Status, metadata: object): void { + const request = this.requests.get(id) + + if (request === undefined) { + return + } + + request.metadata = metadata + + if (status.ended) { + this.requests.delete(id) + return + } + + if (request.autoPrompts > 0) { + return + } + + const isPending = (dependency: string) => { + const signerStatus = status.signers[dependency] + return signerStatus !== undefined && isSignerStatusPending(signerStatus) + } + + if (this.dependencies.some(isPending)) { + return + } + + request.autoPrompts++ + this.prompt(id) + } + + suffix(): BytesLike { + return this.signer.suffix() + } +} + +type Request = { + message: BytesLike + metadata: object + callbacks: { + onSignature: (signature: BytesLike) => void + onRejection: (error: string) => void + onStatus: (situation: string) => void + } + autoPrompts: number + resolve: () => void +}