Skip to content

Commit

Permalink
Merge 1695377 into b9c2a25
Browse files Browse the repository at this point in the history
  • Loading branch information
yagopv authored Jun 7, 2024
2 parents b9c2a25 + 1695377 commit 03a3c84
Show file tree
Hide file tree
Showing 22 changed files with 633 additions and 402 deletions.
29 changes: 20 additions & 9 deletions packages/api-kit/src/SafeApiKit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
SafeMultisigTransactionEstimate,
SafeMultisigTransactionEstimateResponse,
SafeMultisigTransactionListResponse,
SafeOperationResponse,
SafeServiceInfoResponse,
SignatureResponse,
TokenInfoListResponse,
Expand All @@ -36,10 +35,14 @@ import { validateEip3770Address, validateEthereumAddress } from '@safe-global/pr
import {
Eip3770Address,
SafeMultisigConfirmationListResponse,
SafeMultisigTransactionResponse
SafeMultisigTransactionResponse,
SafeOperationResponse,
SafeOperation,
isSafeOperation
} from '@safe-global/safe-core-sdk-types'
import { TRANSACTION_SERVICE_URLS } from './utils/config'
import { isEmptyData } from './utils'
import { getAddSafeOperationProps } from './utils/safeOperation'

export interface SafeApiKitConfig {
/** chainId - The chainId */
Expand Down Expand Up @@ -786,15 +789,23 @@ class SafeApiKit {
* @throws "Invalid module address {moduleAddress}"
* @throws "Signature must not be empty"
*/
async addSafeOperation({
entryPoint,
moduleAddress: moduleAddressProp,
options,
safeAddress: safeAddressProp,
userOperation
}: AddSafeOperationProps): Promise<void> {
async addSafeOperation(safeOperation: AddSafeOperationProps | SafeOperation): Promise<void> {
let safeAddress: string, moduleAddress: string
let addSafeOperationProps: AddSafeOperationProps

if (isSafeOperation(safeOperation)) {
addSafeOperationProps = await getAddSafeOperationProps(safeOperation)
} else {
addSafeOperationProps = safeOperation
}

const {
entryPoint,
moduleAddress: moduleAddressProp,
options,
safeAddress: safeAddressProp,
userOperation
} = addSafeOperationProps
if (!safeAddressProp) {
throw new Error('Safe address must not be empty')
}
Expand Down
41 changes: 2 additions & 39 deletions packages/api-kit/src/types/safeTransactionServiceTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Signer, TypedDataDomain, TypedDataField } from 'ethers'
import {
SafeMultisigTransactionResponse,
SafeTransactionData,
UserOperation
UserOperation,
SafeOperationResponse
} from '@safe-global/safe-core-sdk-types'

export type SafeServiceInfoResponse = {
Expand Down Expand Up @@ -289,44 +290,6 @@ export type EIP712TypedData = {
message: Record<string, unknown>
}

export type SafeOperationConfirmation = {
readonly created: string
readonly modified: string
readonly owner: string
readonly signature: string
readonly signatureType: string
}

export type UserOperationResponse = {
readonly ethereumTxHash: string
readonly sender: string
readonly userOperationHash: string
readonly nonce: number
readonly initCode: null | string
readonly callData: null | string
readonly callGasLimit: number
readonly verificationGasLimit: number
readonly preVerificationGas: number
readonly maxFeePerGas: number
readonly maxPriorityFeePerGas: number
readonly paymaster: null | string
readonly paymasterData: null | string
readonly signature: string
readonly entryPoint: string
}

export type SafeOperationResponse = {
readonly created: string
readonly modified: string
readonly safeOperationHash: string
readonly validAfter: string
readonly validUntil: string
readonly moduleAddress: string
readonly confirmations?: Array<SafeOperationConfirmation>
readonly preparedSignature?: string
readonly userOperation?: UserOperationResponse
}

export type GetSafeOperationListProps = {
/** Address of the Safe to get SafeOperations for */
safeAddress: string
Expand Down
17 changes: 17 additions & 0 deletions packages/api-kit/src/utils/safeOperation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SafeOperation } from '@safe-global/safe-core-sdk-types'

export const getAddSafeOperationProps = async (safeOperation: SafeOperation) => {
const userOperation = safeOperation.toUserOperation()
userOperation.signature = safeOperation.encodedSignatures() // Without validity dates

return {
entryPoint: safeOperation.data.entryPoint,
moduleAddress: safeOperation.moduleAddress,
safeAddress: safeOperation.data.safe,
userOperation,
options: {
validAfter: safeOperation.data.validAfter,
validUntil: safeOperation.data.validUntil
}
}
}
49 changes: 22 additions & 27 deletions packages/api-kit/tests/e2e/addSafeOperation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import chaiAsPromised from 'chai-as-promised'
import { ethers } from 'ethers'
import sinon from 'sinon'
import sinonChai from 'sinon-chai'
import { SafeOperation } from '@safe-global/safe-core-sdk-types'
import Safe from '@safe-global/protocol-kit'
import SafeApiKit from '@safe-global/api-kit'
import { Safe4337Pack } from '@safe-global/relay-kit'
import { generateTransferCallData } from '@safe-global/relay-kit/src/packs/safe-4337/testing-utils/helpers'
import { RPC_4337_CALLS } from '@safe-global/relay-kit/packs/safe-4337/constants'
import { getSafe4337ModuleDeployment } from '@safe-global/safe-modules-deployments'
import config from '../utils/config'
import { getKits } from '../utils/setupKits'
import { getAddSafeOperationProps } from '@safe-global/api-kit/utils/safeOperation'

chai.use(chaiAsPromised)
chai.use(sinonChai)
Expand All @@ -26,7 +25,6 @@ const TX_SERVICE_URL = 'https://safe-transaction-sepolia.staging.5afe.dev/api'
let safeApiKit: SafeApiKit
let protocolKit: Safe
let safe4337Pack: Safe4337Pack
let moduleAddress: string

describe('addSafeOperation', () => {
const transferUSDC = {
Expand Down Expand Up @@ -76,31 +74,8 @@ describe('addSafeOperation', () => {
paymasterAddress: PAYMASTER_ADDRESS
}
})

const chainId = (await protocolKit.getSafeProvider().getChainId()).toString()

moduleAddress = getSafe4337ModuleDeployment({
released: true,
version: '0.2.0',
network: chainId
})?.networkAddresses[chainId] as string
})

const getAddSafeOperationProps = async (safeOperation: SafeOperation) => {
const userOperation = safeOperation.toUserOperation()
userOperation.signature = safeOperation.encodedSignatures()
return {
entryPoint: safeOperation.data.entryPoint,
moduleAddress,
safeAddress: SAFE_ADDRESS,
userOperation,
options: {
validAfter: safeOperation.data.validAfter,
validUntil: safeOperation.data.validUntil
}
}
}

describe('should fail', () => {
it('if safeAddress is empty', async () => {
const safeOperation = await safe4337Pack.createTransaction({ transactions: [transferUSDC] })
Expand Down Expand Up @@ -172,7 +147,7 @@ describe('addSafeOperation', () => {
})
})

it('should add a new SafeOperation', async () => {
it('should add a new SafeOperation using an standard UserOperation and props', async () => {
const safeOperation = await safe4337Pack.createTransaction({ transactions: [transferUSDC] })
const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation)
const addSafeOperationProps = await getAddSafeOperationProps(signedSafeOperation)
Expand All @@ -190,4 +165,24 @@ describe('addSafeOperation', () => {
})
chai.expect(safeOperationsAfter.count).to.equal(initialNumSafeOperations + 1)
})

it('should add a new SafeOperation using a SafeOperation object from the relay-kit', async () => {
const safeOperation = await safe4337Pack.createTransaction({
transactions: [transferUSDC, transferUSDC]
})
const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation)

// Get the number of SafeOperations before adding a new one
const safeOperationsBefore = await safeApiKit.getSafeOperationsByAddress({
safeAddress: SAFE_ADDRESS
})
const initialNumSafeOperations = safeOperationsBefore.count

await chai.expect(safeApiKit.addSafeOperation(signedSafeOperation)).to.be.fulfilled

const safeOperationsAfter = await safeApiKit.getSafeOperationsByAddress({
safeAddress: SAFE_ADDRESS
})
chai.expect(safeOperationsAfter.count).to.equal(initialNumSafeOperations + 1)
})
})
61 changes: 59 additions & 2 deletions packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import EthSafeOperation from './SafeOperation'
import * as constants from './constants'
import * as fixtures from './testing-utils/fixtures'
import { createSafe4337Pack, generateTransferCallData } from './testing-utils/helpers'
import * as utils from './utils'

import dotenv from 'dotenv'
import * as utils from './utils'

dotenv.config()

Expand Down Expand Up @@ -497,7 +497,7 @@ describe('Safe4337Pack', () => {
})
})

it('should all to sign a SafeOperation', async () => {
it('should allow to sign a SafeOperation', async () => {
const transferUSDC = {
to: fixtures.PAYMASTER_TOKEN_ADDRESS,
data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1, 100_000n),
Expand Down Expand Up @@ -527,6 +527,34 @@ describe('Safe4337Pack', () => {
})
})

it('should allow to sign a SafeOperation using a SafeOperationResponse object from the api to add a signature', async () => {
const safe4337Pack = await createSafe4337Pack({
options: {
safeAddress: fixtures.SAFE_ADDRESS_v1_4_1
}
})

expect(await safe4337Pack.signSafeOperation(fixtures.SAFE_OPERATION_RESPONSE)).toMatchObject({
signatures: new Map()
.set(
fixtures.OWNER_1.toLowerCase(),
new protocolKit.EthSafeSignature(
fixtures.OWNER_1,
'0xcb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1c',
false
)
)
.set(
fixtures.OWNER_2.toLowerCase(),
new protocolKit.EthSafeSignature(
fixtures.OWNER_2,
'0xcb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1d',
false
)
)
})
})

it('should allow to send an UserOperation to a bundler', async () => {
const transferUSDC = {
to: fixtures.PAYMASTER_TOKEN_ADDRESS,
Expand Down Expand Up @@ -554,6 +582,35 @@ describe('Safe4337Pack', () => {
])
})

it('should allow to send a UserOperation to the bundler using a SafeOperationResponse object from the api', async () => {
const safe4337Pack = await createSafe4337Pack({
options: {
safeAddress: fixtures.SAFE_ADDRESS_v1_4_1
}
})

await safe4337Pack.executeTransaction({ executable: fixtures.SAFE_OPERATION_RESPONSE })

expect(sendMock).toHaveBeenCalledWith(constants.RPC_4337_CALLS.SEND_USER_OPERATION, [
utils.userOperationToHexValues({
sender: '0xE322e721bCe76cE7FCf3A475f139A9314571ad3D',
nonce: '3',
initCode: '0x',
callData:
'0x7bb37428000000000000000000000000e322e721bce76ce7fcf3a475f139a9314571ad3d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
callGasLimit: 122497n,
verificationGasLimit: 123498n,
preVerificationGas: 50705n,
maxFeePerGas: 105183831060n,
maxPriorityFeePerGas: 1380000000n,
paymasterAndData: '0x',
signature:
'0x000000000000000000000000cb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1d'
}),
fixtures.ENTRYPOINTS[0]
])
})

it('should return a UserOperation based on a userOpHash', async () => {
const safe4337Pack = await createSafe4337Pack({
options: {
Expand Down
Loading

0 comments on commit 03a3c84

Please sign in to comment.