Skip to content

Commit

Permalink
Decorate wallet transactions for subsigners
Browse files Browse the repository at this point in the history
  • Loading branch information
ScreamingHawk committed Jul 31, 2023
1 parent 7fa0984 commit a76acae
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 56 deletions.
6 changes: 6 additions & 0 deletions packages/guard/src/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export class GuardSigner implements signers.SapientSigner {
return this.address
}

async decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
return Promise.resolve(bundle)
}

async requestSignature(
id: string,
_message: BytesLike,
Expand Down
1 change: 1 addition & 0 deletions packages/signhub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"test:coverage": "nyc yarn test"
},
"dependencies": {
"@0xsequence/core": "workspace:*",
"ethers": "^5.5.2"
},
"peerDependencies": {},
Expand Down
15 changes: 13 additions & 2 deletions packages/signhub/src/orchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ethers } from "ethers"
import { commons } from "@0xsequence/core"
import { isSapientSigner, SapientSigner } from "./signers/signer"
import { SignerWrapper } from "./signers/wrapper"

Expand Down Expand Up @@ -39,10 +40,11 @@ export function isSignerStatusPending(status: SignerStatus): status is SignerSta
export const InitialSituation = "Initial"

/**
* It orchestrates the signing of a single digest by multiple signers.
* Orchestrates the signing of a single digests and transactions by multiple signers.
* It can provide internal visibility of the signing process, and it also
* provides the internal signers with additional information about the
* message being signed.
* message being signed. Transaction decoration can be used to ensure on-chain state
* is correctly managed during the signing process.
*/
export class Orchestrator {
private observers: ((status: Status, metadata: Object) => void)[] = []
Expand Down Expand Up @@ -82,6 +84,15 @@ export class Orchestrator {
])
}

async decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
for (const signer of this.signers) {
bundle = await signer.decorateTransactions(bundle)
}
return bundle
}

signMessage(args: {
candidates?: string[],
message: ethers.BytesLike,
Expand Down
14 changes: 14 additions & 0 deletions packages/signhub/src/signers/signer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { ethers } from "ethers"
import { commons } from "@0xsequence/core"
import { Status } from "../orchestrator"

export interface SapientSigner {
getAddress(): Promise<string>

/**
* Modify the transaction bundle before it is sent.
*/
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle>

/**
* Request a signature from the signer.
*/
requestSignature(
id: string,
message: ethers.BytesLike,
Expand All @@ -15,6 +26,9 @@ export interface SapientSigner {
}
): Promise<boolean>

/**
* Notify the signer of a status change.
*/
notifyStatusChange(
id: string,
status: Status,
Expand Down
7 changes: 7 additions & 0 deletions packages/signhub/src/signers/wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import { ethers } from 'ethers'
import { commons } from "@0xsequence/core"
import { Status } from '../orchestrator'
import { SapientSigner } from './signer'

Expand All @@ -10,6 +11,12 @@ export class SignerWrapper implements SapientSigner {
return this.signer.getAddress()
}

async decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle
): Promise<commons.transaction.IntendedTransactionBundle> {
return bundle
}

async requestSignature(
_id: string,
message: ethers.BytesLike,
Expand Down
36 changes: 36 additions & 0 deletions packages/signhub/tests/orchestrator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import * as chai from 'chai'
import { ethers } from 'ethers'
import { commons } from "@0xsequence/core"
import { isSignerStatusPending, isSignerStatusRejected, isSignerStatusSigned, Orchestrator, Status } from '../src'
import { SapientSigner } from '../src/signers'

Expand Down Expand Up @@ -109,6 +110,11 @@ describe('Orchestrator', () => {
getAddress: async function (): Promise<string> {
return brokenSignerEOA.address
},
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
throw new Error('This is a broken signer.')
},
requestSignature: async function (
id: string,
message: ethers.utils.BytesLike,
Expand Down Expand Up @@ -189,6 +195,11 @@ describe('Orchestrator', () => {
getAddress: async function (): Promise<string> {
return rejectSignerEOA.address
},
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
throw new Error('This is a rejected signer.')
},
requestSignature: async function (
id: string,
message: ethers.utils.BytesLike,
Expand Down Expand Up @@ -241,6 +252,11 @@ describe('Orchestrator', () => {
getAddress: async function (): Promise<string> {
return '0x1234'
},
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
return Promise.resolve(bundle)
},
requestSignature: async function (
id: string,
message: ethers.utils.BytesLike,
Expand Down Expand Up @@ -269,6 +285,11 @@ describe('Orchestrator', () => {
getAddress: async function (): Promise<string> {
return '0x1234'
},
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
return Promise.resolve(bundle)
},
requestSignature: async function (
id: string,
message: ethers.utils.BytesLike,
Expand Down Expand Up @@ -301,6 +322,11 @@ describe('Orchestrator', () => {
getAddress: async function (): Promise<string> {
return '0x1234'
},
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
return Promise.resolve(bundle)
},
requestSignature: async function (
id: string,
message: ethers.utils.BytesLike,
Expand Down Expand Up @@ -331,6 +357,11 @@ describe('Orchestrator', () => {
getAddress: async function (): Promise<string> {
return '0x5678'
},
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
return Promise.resolve(bundle)
},
requestSignature: async function (
id: string,
message: ethers.utils.BytesLike,
Expand Down Expand Up @@ -383,6 +414,11 @@ describe('Orchestrator', () => {
getAddress: async function (): Promise<string> {
return '0x1234'
},
decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle,
): Promise<commons.transaction.IntendedTransactionBundle> {
return Promise.resolve(bundle)
},
requestSignature: async function (
id: string,
message: ethers.utils.BytesLike,
Expand Down
6 changes: 6 additions & 0 deletions packages/wallet/src/orchestrator/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export class SequenceOrchestratorWrapper implements signers.SapientSigner {
return this.wallet.address
}

async decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle
): Promise<commons.transaction.IntendedTransactionBundle> {
return this.wallet.decorateTransactions(bundle)
}

async requestSignature(
_id: string,
message: ethers.utils.BytesLike,
Expand Down
13 changes: 9 additions & 4 deletions packages/wallet/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ export class Wallet<
async decorateTransactions(
bundle: commons.transaction.IntendedTransactionBundle
): Promise<commons.transaction.IntendedTransactionBundle> {
if (await this.reader().isDeployed(this.address)) return bundle
const decorated = await this.orchestrator.decorateTransactions(bundle)
const unchanged = decorated === bundle

if (unchanged && await this.reader().isDeployed(this.address)) {
return bundle
}

const deployTx = this.buildDeployTransaction()

Expand All @@ -151,12 +156,12 @@ export class Wallet<
return {
entrypoint: this.context.guestModule,
chainId: this.chainId,
intent: bundle.intent,
intent: decorated.intent,
transactions: [
...deployTx.transactions,
{
to: bundle.entrypoint,
data: commons.transaction.encodeBundleExecData(bundle),
to: decorated.entrypoint,
data: commons.transaction.encodeBundleExecData(decorated),
gasLimit: 0,
delegateCall: false,
revertOnError: true,
Expand Down
Loading

0 comments on commit a76acae

Please sign in to comment.