Skip to content

Commit

Permalink
Merge 5817de9 into 3532f54
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniSomoza committed May 10, 2024
2 parents 3532f54 + 5817de9 commit 3cb5c47
Show file tree
Hide file tree
Showing 27 changed files with 958 additions and 42 deletions.
5 changes: 4 additions & 1 deletion packages/account-abstraction-kit/src/AccountAbstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ class AccountAbstraction {
}

#initializeProtocolKit = async () => {
const safeProvider = new SafeProvider({ provider: this.#provider, signer: this.#signer })
const safeProvider = new SafeProvider({
provider: this.#provider,
signer: this.#signer as string
})
const signer = await safeProvider.getSignerAddress()

if (!signer) {
Expand Down
12 changes: 12 additions & 0 deletions packages/protocol-kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,18 @@ const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
```
This method can optionally receive a passkey parameter:
```js
const params: AddPasskeyOwnerTxParams = {
passkey,
threshold // Optional. If `threshold` is not provided the current threshold will not change.
}
const safeTransaction = await safeSdk.createAddOwnerTx(params)
const txResponse = await safeSdk.executeTransaction(safeTransaction)
await txResponse.transactionResponse?.wait()
```
This method can optionally receive the `options` parameter:
```js
Expand Down
157 changes: 148 additions & 9 deletions packages/protocol-kit/src/Safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import ModuleManager from './managers/moduleManager'
import OwnerManager from './managers/ownerManager'
import {
AddOwnerTxParams,
AddPasskeyOwnerTxParams,
ConnectSafeConfig,
CreateTransactionProps,
PredictedSafeProps,
Expand All @@ -38,6 +39,7 @@ import {
SigningMethod,
SigningMethodType,
SwapOwnerTxParams,
passkeyArgType,
SafeModulesPaginated
} from './types'
import {
Expand All @@ -54,24 +56,28 @@ import {
generatePreValidatedSignature,
generateSignature,
preimageSafeMessageHash,
preimageSafeTransactionHash
preimageSafeTransactionHash,
adjustVInSignature
} from './utils'
import EthSafeTransaction from './utils/transactions/SafeTransaction'
import { SafeTransactionOptionalProps } from './utils/transactions/types'
import {
encodeMultiSendData,
isAddPasskeyOwnerTxParams,
standardizeMetaTransactionData,
standardizeSafeTransactionData
} from './utils/transactions/utils'
import { isSafeConfigWithPredictedSafe } from './utils/types'
import {
getCompatibilityFallbackHandlerContract,
getMultiSendCallOnlyContract,
getProxyFactoryContract
getProxyFactoryContract,
getSafeWebAuthnSignerFactoryContract
} from './contracts/safeDeploymentContracts'
import SafeMessage from './utils/messages/SafeMessage'
import semverSatisfies from 'semver/functions/satisfies'
import SafeProvider from './SafeProvider'
import PasskeySigner from './utils/passkeys/PasskeySigner'

const EQ_OR_GT_1_4_1 = '>=1.4.1'
const EQ_OR_GT_1_3_0 = '>=1.3.0'
Expand Down Expand Up @@ -114,10 +120,38 @@ class Safe {
async #initializeProtocolKit(config: SafeConfig) {
const { provider, signer, isL1SafeSingleton, contractNetworks } = config

this.#safeProvider = new SafeProvider({
provider,
signer
})
const isPasskeySigner = signer && typeof signer !== 'string'

if (isPasskeySigner) {
const safeProvider = new SafeProvider({
provider
})
const chainId = await safeProvider.getChainId()
const customContracts = contractNetworks?.[chainId.toString()]

const safeWebAuthnSignerFactoryContract = await getSafeWebAuthnSignerFactoryContract({
safeProvider,
safeVersion: '1.4.1',
customContracts
})

const passkeySigner = await PasskeySigner.init(
signer,
safeWebAuthnSignerFactoryContract,
safeProvider.getExternalProvider()
)

this.#safeProvider = new SafeProvider({
provider,
signer: passkeySigner
})
} else {
this.#safeProvider = new SafeProvider({
provider,
signer
})
}

if (isSafeConfigWithPredictedSafe(config)) {
this.#predictedSafe = config.predictedSafe
this.#contractManager = await ContractManager.init(
Expand Down Expand Up @@ -421,13 +455,37 @@ class Safe {
return this.#moduleManager.isModuleEnabled(moduleAddress)
}

/**
* Checks if a specific address or passkey is an owner of the current Safe.
*
* @param owner - The owner address or a passkey object
* @returns TRUE if the account is an owner
*/
async isOwner(owner: string | passkeyArgType): Promise<boolean> {
const isOwnerAddress = typeof owner === 'string'

if (isOwnerAddress) {
return this.#isOwnerAddress(owner)
}

// passkey flow
const webAuthnSignerFactoryContract = this.#contractManager.safeWebAuthnSignerFactoryContract
const provider = this.#safeProvider.getExternalProvider()

const passkeySigner = await PasskeySigner.init(owner, webAuthnSignerFactoryContract, provider)

const ownerAddress = await passkeySigner.getAddress()

return this.#isOwnerAddress(ownerAddress)
}

/**
* Checks if a specific address is an owner of the current Safe.
*
* @param ownerAddress - The account address
* @returns TRUE if the account is an owner
*/
async isOwner(ownerAddress: string): Promise<boolean> {
async #isOwnerAddress(ownerAddress: string): Promise<boolean> {
if (this.#predictedSafe?.safeAccountConfig.owners) {
return Promise.resolve(
this.#predictedSafe?.safeAccountConfig.owners.some((owner: string) =>
Expand Down Expand Up @@ -727,6 +785,27 @@ class Safe {
throw new Error('Transactions can only be signed by Safe owners')
}

// passkey flow
const isPasskeySigner = await this.#safeProvider.isPasskeySigner()
if (isPasskeySigner) {
const txHash = await this.getTransactionHash(transaction)
const signedHash = await this.#safeProvider.signMessage(txHash)

const signatureAdjusted = adjustVInSignature(
SigningMethod.ETH_SIGN,
signedHash,
txHash,
signerAddress
)

const signature = new EthSafeSignature(signerAddress, signatureAdjusted, true)

const signedSafeTransaction = await this.copyTransaction(transaction)
signedSafeTransaction.addSignature(signature)

return signedSafeTransaction
}

const safeVersion = await this.getContractVersion()
if (
signingMethod === SigningMethod.SAFE_SIGNATURE &&
Expand Down Expand Up @@ -810,7 +889,6 @@ class Safe {
throw new Error('Transaction hashes can only be approved by Safe owners')
}

// TODO: fix this
return this.#contractManager.safeContract.approveHash(hash, {
from: signerAddress,
...options
Expand Down Expand Up @@ -999,9 +1077,17 @@ class Safe {
* @throws "Threshold cannot exceed owner count"
*/
async createAddOwnerTx(
{ ownerAddress, threshold }: AddOwnerTxParams,
params: AddOwnerTxParams | AddPasskeyOwnerTxParams,
options?: SafeTransactionOptionalProps
): Promise<SafeTransaction> {
const isPasskey = isAddPasskeyOwnerTxParams(params)

if (isPasskey) {
return this.#createAddPasskeyOwnerTx(params, options)
}

const { ownerAddress, threshold } = params

const safeTransactionData = {
to: await this.getAddress(),
value: '0',
Expand All @@ -1014,6 +1100,59 @@ class Safe {
return safeTransaction
}

/**
* Returns the Safe transaction to add a passkey as owner and optionally change the threshold.
*
* @param params - The transaction params
* @param options - The transaction optional properties
* @returns The Safe transaction ready to be signed
* @throws "Invalid owner address provided"
* @throws "Address provided is already an owner"
* @throws "Threshold needs to be greater than 0"
* @throws "Threshold cannot exceed owner count"
*/
async #createAddPasskeyOwnerTx(
{ passkey, threshold }: AddPasskeyOwnerTxParams,
options?: SafeTransactionOptionalProps
): Promise<SafeTransaction> {
const webAuthnSignerFactoryContract = this.#contractManager.safeWebAuthnSignerFactoryContract
const provider = this.#safeProvider.getExternalProvider()

const passkeySigner = await PasskeySigner.init(passkey, webAuthnSignerFactoryContract, provider)

const ownerAddress = await passkeySigner.getAddress()
const isPasskeySignerDeployed = await this.#safeProvider.isContractDeployed(ownerAddress)

// The passkey Signer is a contract compliant with EIP-1271 standards, we need to check if it has been deployed.
if (isPasskeySignerDeployed) {
return this.createAddOwnerTx({ ownerAddress, threshold }, options)
}

// If it has not been deployed, we need to create a batch that includes both the Signer contract deployment and the addOwner transaction

// First transaction of the batch: The Deployment of the Signer
const createSignerTransaction = {
to: await passkeySigner.safeWebAuthnSignerFactoryContract.getAddress(),
value: '0',
data: passkeySigner.encodeCreateSigner()
}

// Second transaction of the batch: The AddOwner transaction
const addOwnerTransaction = {
to: await this.getAddress(),
value: '0',
data: await this.#ownerManager.encodeAddOwnerWithThresholdData(ownerAddress, threshold)
}

// transactions for the batch
const transactions = [createSignerTransaction, addOwnerTransaction]

return await this.createTransaction({
transactions,
options
})
}

/**
* Returns the Safe transaction to remove an owner and optionally change the threshold.
*
Expand Down
34 changes: 33 additions & 1 deletion packages/protocol-kit/src/SafeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@safe-global/protocol-kit/types'
import { SafeVersion } from '@safe-global/safe-core-sdk-types'
import SafeProvider from '@safe-global/protocol-kit/SafeProvider'
import PasskeySigner from './utils/passkeys/PasskeySigner'

class SafeFactory {
#contractNetworks?: ContractNetworksConfig
Expand Down Expand Up @@ -62,7 +63,38 @@ class SafeFactory {
}: SafeFactoryInitConfig) {
this.#provider = provider
this.#signer = signer
this.#safeProvider = new SafeProvider({ provider, signer })
const isPasskeySigner = signer && typeof signer !== 'string'

if (isPasskeySigner) {
const safeProvider = new SafeProvider({
provider
})
const chainId = await safeProvider.getChainId()
const customContracts = contractNetworks?.[chainId.toString()]

const safeWebAuthnSignerFactoryContract =
await safeProvider.getSafeWebAuthnSignerFactoryContract({
safeVersion: '1.4.1',
customContractAddress: customContracts?.safeWebAuthnSignerFactoryAddress,
customContractAbi: customContracts?.safeWebAuthnSignerFactoryAbi
})

const passkeySigner = await PasskeySigner.init(
signer,
safeWebAuthnSignerFactoryContract,
safeProvider.getExternalProvider()
)

this.#safeProvider = new SafeProvider({
provider,
signer: passkeySigner
})
} else {
this.#safeProvider = new SafeProvider({
provider,
signer
})
}
this.#safeVersion = safeVersion
this.#isL1SafeSingleton = isL1SafeSingleton
this.#contractNetworks = contractNetworks
Expand Down
Loading

0 comments on commit 3cb5c47

Please sign in to comment.