Skip to content

Commit 9c4a8a4

Browse files
committed
Merge branch 'development' into prepare-release
2 parents dc5071d + c389c5f commit 9c4a8a4

File tree

109 files changed

+2848
-1915
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+2848
-1915
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
1111
coverage
1212
*.lcov
1313

14+
#esbuild report
15+
meta-*.json
16+
1417
# nyc test coverage
1518
.nyc_output
1619

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@types/node": "^22.10.9",
2424
"@typescript-eslint/eslint-plugin": "^7.18.0",
2525
"@typescript-eslint/parser": "^7.18.0",
26+
"esbuild": "^0.24.0",
2627
"eslint": "^8.56.0",
2728
"eslint-config-prettier": "^9.1.0",
2829
"eslint-plugin-prettier": "^5.2.3",
@@ -35,7 +36,7 @@
3536
"ts-jest": "^29.2.5",
3637
"ts-node": "^10.9.2",
3738
"tsc-alias": "^1.8.8",
38-
"typescript": "^5.3.3"
39+
"typescript": "^5.6.3"
3940
},
4041
"lint-staged": {
4142
"./packages/**/*.{js,jsx,ts,tsx}": [

packages/api-kit/package.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
"name": "@safe-global/api-kit",
33
"version": "2.5.9",
44
"description": "SDK that facilitates the interaction with the Safe Transaction Service API",
5-
"main": "dist/src/index.js",
6-
"typings": "dist/src/index.d.ts",
5+
"types": "dist/src/index.d.ts",
6+
"main": "dist/cjs/index.cjs",
7+
"module": "dist/esm/index.mjs",
8+
"exports": {
9+
"types": "./dist/src/index.d.ts",
10+
"require": "./dist/cjs/index.cjs",
11+
"import": "./dist/esm/index.mjs"
12+
},
713
"keywords": [
814
"Ethereum",
915
"Wallet",
@@ -23,7 +29,10 @@
2329
"format:check": "prettier --check \"*/**/*.{js,json,md,ts}\"",
2430
"format": "prettier --write \"*/**/*.{js,json,md,ts}\"",
2531
"unbuild": "rimraf dist .nyc_output cache",
26-
"build": "yarn unbuild && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json"
32+
"build": "yarn unbuild && yarn build:esm && yarn build:cjs && yarn build:types",
33+
"build:esm": "esbuild ./src/index --format=esm --bundle --packages=external --outdir=dist/esm --out-extension:.js=.mjs",
34+
"build:cjs": "esbuild ./src/index --format=cjs --bundle --packages=external --outdir=dist/cjs --out-extension:.js=.cjs",
35+
"build:types": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json"
2736
},
2837
"repository": {
2938
"type": "git",

packages/api-kit/src/SafeApiKit.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ import { signDelegate } from '@safe-global/api-kit/utils/signDelegate'
3636
import { validateEip3770Address, validateEthereumAddress } from '@safe-global/protocol-kit'
3737
import {
3838
Eip3770Address,
39-
isSafeOperation,
4039
SafeMultisigConfirmationListResponse,
4140
SafeMultisigTransactionResponse,
4241
SafeOperation,
4342
SafeOperationConfirmationListResponse,
44-
SafeOperationResponse
43+
SafeOperationResponse,
44+
UserOperationV06
4545
} from '@safe-global/types-kit'
4646
import { TRANSACTION_SERVICE_URLS } from './utils/config'
4747
import { isEmptyData } from './utils'
48-
import { getAddSafeOperationProps } from './utils/safeOperation'
48+
import { getAddSafeOperationProps, isSafeOperation } from './utils/safeOperation'
4949

5050
export interface SafeApiKitConfig {
5151
/** chainId - The chainId */
@@ -817,6 +817,8 @@ class SafeApiKit {
817817
*/
818818
async getSafeOperationsByAddress({
819819
safeAddress,
820+
executed,
821+
hasConfirmations,
820822
ordering,
821823
limit,
822824
offset
@@ -841,12 +843,36 @@ class SafeApiKit {
841843
url.searchParams.set('offset', offset.toString())
842844
}
843845

846+
if (hasConfirmations != null) {
847+
url.searchParams.set('has_confirmations', hasConfirmations.toString())
848+
}
849+
850+
if (executed != null) {
851+
url.searchParams.set('executed', executed.toString())
852+
}
853+
844854
return sendRequest({
845855
url: url.toString(),
846856
method: HttpMethod.Get
847857
})
848858
}
849859

860+
/**
861+
* Get the SafeOperations that are pending to send to the bundler
862+
* @param getSafeOperationsProps - The parameters to filter the list of SafeOperations
863+
* @throws "Safe address must not be empty"
864+
* @throws "Invalid Ethereum address {safeAddress}"
865+
* @returns The pending SafeOperations
866+
*/
867+
async getPendingSafeOperations(
868+
props: Omit<GetSafeOperationListProps, 'executed'>
869+
): Promise<GetSafeOperationListResponse> {
870+
return this.getSafeOperationsByAddress({
871+
...props,
872+
executed: false
873+
})
874+
}
875+
850876
/**
851877
* Get a SafeOperation by its hash.
852878
* @param safeOperationHash The SafeOperation hash
@@ -918,21 +944,23 @@ class SafeApiKit {
918944
const getISOString = (date: number | undefined) =>
919945
!date ? null : new Date(date * 1000).toISOString()
920946

947+
const userOperationV06 = userOperation as UserOperationV06
948+
921949
return sendRequest({
922950
url: `${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/safe-operations/`,
923951
method: HttpMethod.Post,
924952
body: {
953+
initCode: isEmptyData(userOperationV06.initCode) ? null : userOperationV06.initCode,
925954
nonce: userOperation.nonce,
926-
initCode: isEmptyData(userOperation.initCode) ? null : userOperation.initCode,
927955
callData: userOperation.callData,
928956
callGasLimit: userOperation.callGasLimit.toString(),
929957
verificationGasLimit: userOperation.verificationGasLimit.toString(),
930958
preVerificationGas: userOperation.preVerificationGas.toString(),
931959
maxFeePerGas: userOperation.maxFeePerGas.toString(),
932960
maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas.toString(),
933-
paymasterAndData: isEmptyData(userOperation.paymasterAndData)
961+
paymasterAndData: isEmptyData(userOperationV06.paymasterAndData)
934962
? null
935-
: userOperation.paymasterAndData,
963+
: userOperationV06.paymasterAndData,
936964
entryPoint,
937965
validAfter: getISOString(options?.validAfter),
938966
validUntil: getISOString(options?.validUntil),

packages/api-kit/src/types/safeTransactionServiceTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ export type GetSafeOperationListProps = {
266266
safeAddress: string
267267
/** Which field to use when ordering the results. It can be: `user_operation__nonce`, `created` (default: `-user_operation__nonce`) */
268268
ordering?: string
269+
executed?: boolean
270+
hasConfirmations?: boolean
269271
} & ListOptions
270272

271273
export type GetSafeOperationListResponse = ListResponse<SafeOperationResponse>
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import { SafeOperation } from '@safe-global/types-kit'
2+
import { AddSafeOperationProps } from '../types/safeTransactionServiceTypes'
23

34
export const getAddSafeOperationProps = async (safeOperation: SafeOperation) => {
4-
const userOperation = safeOperation.toUserOperation()
5+
const userOperation = safeOperation.getUserOperation()
56
userOperation.signature = safeOperation.encodedSignatures() // Without validity dates
67

78
return {
8-
entryPoint: safeOperation.data.entryPoint,
9-
moduleAddress: safeOperation.moduleAddress,
10-
safeAddress: safeOperation.data.safe,
9+
entryPoint: safeOperation.options.entryPoint,
10+
moduleAddress: safeOperation.options.moduleAddress,
11+
safeAddress: userOperation.sender,
1112
userOperation,
1213
options: {
13-
validAfter: safeOperation.data.validAfter,
14-
validUntil: safeOperation.data.validUntil
14+
validAfter: safeOperation.options.validAfter,
15+
validUntil: safeOperation.options.validUntil
1516
}
1617
}
1718
}
19+
20+
export const isSafeOperation = (
21+
obj: AddSafeOperationProps | SafeOperation
22+
): obj is SafeOperation => {
23+
return 'signatures' in obj && 'getUserOperation' in obj && 'getHash' in obj
24+
}

packages/api-kit/tests/e2e/addMessageSignature.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import Safe, {
22
EthSafeSignature,
33
buildSignatureBytes,
44
hashSafeMessage,
5-
SigningMethod,
65
buildContractSignature
76
} from '@safe-global/protocol-kit'
8-
import { SafeMessage } from '@safe-global/types-kit'
7+
import { SafeMessage, SigningMethod } from '@safe-global/types-kit'
98
import SafeApiKit from '@safe-global/api-kit/index'
109
import chai from 'chai'
1110
import chaiAsPromised from 'chai-as-promised'

packages/api-kit/tests/e2e/addSafeOperation.test.ts

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
import chai from 'chai'
22
import chaiAsPromised from 'chai-as-promised'
3-
import sinon from 'sinon'
43
import Safe from '@safe-global/protocol-kit'
54
import SafeApiKit from '@safe-global/api-kit/index'
65
import { getAddSafeOperationProps } from '@safe-global/api-kit/utils/safeOperation'
7-
import { BundlerClient, Safe4337Pack } from '@safe-global/relay-kit'
8-
import { generateTransferCallData } from '@safe-global/relay-kit/packs/safe-4337/testing-utils/helpers'
9-
import {
10-
ENTRYPOINT_ABI,
11-
ENTRYPOINT_ADDRESS_V06,
12-
RPC_4337_CALLS
13-
} from '@safe-global/relay-kit/packs/safe-4337/constants'
14-
// Needs to be imported from dist folder in order to mock the getEip4337BundlerProvider function
15-
import * as safe4337Utils from '@safe-global/relay-kit/dist/src/packs/safe-4337/utils'
6+
import { Safe4337Pack } from '@safe-global/relay-kit'
7+
import { generateTransferCallData } from '@safe-global/relay-kit/test-utils'
168
import { getKits } from '../utils/setupKits'
179

1810
chai.use(chaiAsPromised)
1911

2012
const SIGNER_PK = '0x83a415ca62e11f5fa5567e98450d0f82ae19ff36ef876c10a8d448c788a53676'
2113
const SAFE_ADDRESS = '0x60C4Ab82D06Fd7dFE9517e17736C2Dcc77443EF0' // 1/2 Safe (v1.4.1) with signer above being an owner + 4337 module enabled
2214
const PAYMASTER_TOKEN_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'
23-
const PAYMASTER_ADDRESS = '0x0000000000325602a77416A16136FDafd04b299f'
24-
const BUNDLER_URL = 'https://bundler.url'
15+
const BUNDLER_URL = 'https://api.pimlico.io/v2/sepolia/rpc?apikey=pim_Vjs7ohRqWdvsjUegngf9Bg'
2516
const TX_SERVICE_URL = 'https://safe-transaction-sepolia.staging.5afe.dev/api'
2617

2718
let safeApiKit: SafeApiKit
@@ -36,58 +27,22 @@ describe('addSafeOperation', () => {
3627
operation: 0
3728
}
3829

39-
const requestStub = sinon.stub()
40-
// Setup mocks for the bundler client
4130
before(async () => {
42-
sinon.stub(safe4337Utils, 'getEip4337BundlerProvider').returns({
43-
request: requestStub,
44-
readContract: sinon
45-
.stub()
46-
.withArgs({
47-
address: ENTRYPOINT_ADDRESS_V06,
48-
abi: ENTRYPOINT_ABI,
49-
functionName: 'getNonce',
50-
args: [SAFE_ADDRESS, BigInt(0)]
51-
})
52-
.resolves(123n)
53-
} as unknown as BundlerClient)
5431
;({ safeApiKit, protocolKit } = await getKits({
5532
safeAddress: SAFE_ADDRESS,
5633
signer: SIGNER_PK,
5734
txServiceUrl: TX_SERVICE_URL
5835
}))
5936

60-
requestStub.withArgs({ method: RPC_4337_CALLS.CHAIN_ID }).resolves('0xaa36a7')
61-
requestStub
62-
.withArgs({ method: RPC_4337_CALLS.SUPPORTED_ENTRY_POINTS })
63-
.resolves([ENTRYPOINT_ADDRESS_V06])
64-
requestStub
65-
.withArgs({ method: 'pimlico_getUserOperationGasPrice' })
66-
.resolves({ fast: { maxFeePerGas: '0x3b9aca00', maxPriorityFeePerGas: '0x3b9aca00' } })
67-
requestStub
68-
.withArgs({ method: RPC_4337_CALLS.ESTIMATE_USER_OPERATION_GAS, params: sinon.match.any })
69-
.resolves({
70-
preVerificationGas: BigInt(Date.now()),
71-
callGasLimit: BigInt(Date.now()),
72-
verificationGasLimit: BigInt(Date.now())
73-
})
74-
7537
safe4337Pack = await Safe4337Pack.init({
7638
provider: protocolKit.getSafeProvider().provider,
7739
signer: protocolKit.getSafeProvider().signer,
7840
options: { safeAddress: SAFE_ADDRESS },
7941
bundlerUrl: BUNDLER_URL,
80-
paymasterOptions: {
81-
paymasterTokenAddress: PAYMASTER_TOKEN_ADDRESS,
82-
paymasterAddress: PAYMASTER_ADDRESS
83-
}
42+
safeModulesVersion: '0.2.0'
8443
})
8544
})
8645

87-
after(() => {
88-
sinon.restore()
89-
})
90-
9146
describe('should fail', () => {
9247
it('if safeAddress is empty', async () => {
9348
const safeOperation = await safe4337Pack.createTransaction({ transactions: [transferUSDC] })
@@ -183,7 +138,6 @@ describe('addSafeOperation', () => {
183138
transactions: [transferUSDC, transferUSDC]
184139
})
185140
const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation)
186-
187141
// Get the number of SafeOperations before adding a new one
188142
const safeOperationsBefore = await safeApiKit.getSafeOperationsByAddress({
189143
safeAddress: SAFE_ADDRESS

packages/api-kit/tests/e2e/confirmSafeOperation.test.ts

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
11
import chai from 'chai'
22
import chaiAsPromised from 'chai-as-promised'
3-
import sinon from 'sinon'
4-
import { BundlerClient, Safe4337InitOptions, Safe4337Pack } from '@safe-global/relay-kit'
5-
import { generateTransferCallData } from '@safe-global/relay-kit/packs/safe-4337/testing-utils/helpers'
3+
import { Safe4337InitOptions, Safe4337Pack, SafeOperation } from '@safe-global/relay-kit'
64
import SafeApiKit from '@safe-global/api-kit/index'
75
import { getAddSafeOperationProps } from '@safe-global/api-kit/utils/safeOperation'
8-
import { SafeOperation } from '@safe-global/types-kit'
9-
// Needs to be imported from dist folder in order to mock the getEip4337BundlerProvider function
10-
import * as safe4337Utils from '@safe-global/relay-kit/dist/src/packs/safe-4337/utils'
6+
import { generateTransferCallData } from '@safe-global/relay-kit/test-utils'
117
import { getApiKit, getEip1193Provider } from '../utils/setupKits'
12-
import {
13-
ENTRYPOINT_ADDRESS_V06,
14-
RPC_4337_CALLS
15-
} from '@safe-global/relay-kit/packs/safe-4337/constants'
168

179
chai.use(chaiAsPromised)
1810

19-
const PRIVATE_KEY_1 = '0x83a415ca62e11f5fa5567e98450d0f82ae19ff36ef876c10a8d448c788a53676'
20-
const PRIVATE_KEY_2 = '0xb88ad5789871315d0dab6fc5961d6714f24f35a6393f13a6f426dfecfc00ab44'
11+
const PRIVATE_KEY_1 = '0x83a415ca62e11f5fa5567e98450d0f82ae19ff36ef876c10a8d448c788a53676' // 0x56e2C102c664De6DfD7315d12c0178b61D16F171
12+
const PRIVATE_KEY_2 = '0xb88ad5789871315d0dab6fc5961d6714f24f35a6393f13a6f426dfecfc00ab44' // 0x9cCBDE03eDd71074ea9c49e413FA9CDfF16D263B
2113
const SAFE_ADDRESS = '0x60C4Ab82D06Fd7dFE9517e17736C2Dcc77443EF0' // 4337 enabled 1/2 Safe (v1.4.1) owned by PRIVATE_KEY_1 + PRIVATE_KEY_2
2214
const TX_SERVICE_URL = 'https://safe-transaction-sepolia.staging.5afe.dev/api'
23-
const BUNDLER_URL = `https://bundler.url`
15+
const BUNDLER_URL = 'https://api.pimlico.io/v2/sepolia/rpc?apikey=pim_Vjs7ohRqWdvsjUegngf9Bg'
2416
const PAYMASTER_TOKEN_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'
2517

2618
let safeApiKit: SafeApiKit
@@ -31,8 +23,8 @@ let safeOpHash: string
3123
describe('confirmSafeOperation', () => {
3224
const transferUSDC = {
3325
to: PAYMASTER_TOKEN_ADDRESS,
34-
data: generateTransferCallData(SAFE_ADDRESS, 100_000n),
35-
value: Date.now().toString(), // Make sure that the transaction hash is unique
26+
data: generateTransferCallData(SAFE_ADDRESS, 100_000n) + Date.now().toString(), // Make sure that the transaction hash is unique
27+
value: '0',
3628
operation: 0
3729
}
3830

@@ -41,7 +33,8 @@ describe('confirmSafeOperation', () => {
4133
provider: options.provider || getEip1193Provider(),
4234
signer: options.signer || PRIVATE_KEY_1,
4335
options: { safeAddress: SAFE_ADDRESS },
44-
bundlerUrl: BUNDLER_URL
36+
bundlerUrl: BUNDLER_URL,
37+
safeModulesVersion: '0.2.0'
4538
})
4639

4740
const createSignature = async (safeOperation: SafeOperation, signer: string) => {
@@ -68,21 +61,7 @@ describe('confirmSafeOperation', () => {
6861
return signedSafeOperation
6962
}
7063

71-
const requestStub = sinon.stub()
72-
7364
before(async () => {
74-
sinon.stub(safe4337Utils, 'getEip4337BundlerProvider').returns({
75-
request: requestStub
76-
} as unknown as BundlerClient)
77-
78-
requestStub.withArgs({ method: RPC_4337_CALLS.CHAIN_ID }).resolves('0xaa36a7')
79-
requestStub
80-
.withArgs({ method: RPC_4337_CALLS.SUPPORTED_ENTRY_POINTS })
81-
.resolves([ENTRYPOINT_ADDRESS_V06])
82-
requestStub
83-
.withArgs({ method: 'pimlico_getUserOperationGasPrice' })
84-
.resolves({ fast: { maxFeePerGas: '0x3b9aca00', maxPriorityFeePerGas: '0x3b9aca00' } })
85-
8665
safe4337Pack = await getSafe4337Pack({ signer: PRIVATE_KEY_1 })
8766
safeApiKit = getApiKit(TX_SERVICE_URL)
8867

@@ -91,10 +70,6 @@ describe('confirmSafeOperation', () => {
9170
safeOpHash = safeOperation.getHash()
9271
})
9372

94-
after(() => {
95-
sinon.restore()
96-
})
97-
9873
describe('should fail', () => {
9974
it('if SafeOperation hash is empty', async () => {
10075
const signature = await createSignature(safeOperation, PRIVATE_KEY_2)

0 commit comments

Comments
 (0)