diff --git a/packages/api-kit/src/SafeApiKit.ts b/packages/api-kit/src/SafeApiKit.ts index 5e6babc8a..013243765 100644 --- a/packages/api-kit/src/SafeApiKit.ts +++ b/packages/api-kit/src/SafeApiKit.ts @@ -415,6 +415,14 @@ class SafeApiKit { return sendRequest({ url: `${this.#txServiceBaseUrl}/v1/safes/${address}/creation/`, method: HttpMethod.Get + }).then((response: any) => { + // FIXME remove when the transaction service returns the singleton property instead of masterCopy + if (!response?.singleton) { + const { masterCopy, ...rest } = response + return { ...rest, singleton: masterCopy } as SafeCreationInfoResponse + } + + return response as SafeCreationInfoResponse }) } @@ -664,16 +672,19 @@ class SafeApiKit { * @throws "Invalid data" * @throws "Invalid ethereum address" */ - async getNextNonce(safeAddress: string): Promise { + async getNextNonce(safeAddress: string): Promise { if (safeAddress === '') { throw new Error('Invalid Safe address') } const { address } = this.#getEip3770Address(safeAddress) const pendingTransactions = await this.getPendingTransactions(address) if (pendingTransactions.results.length > 0) { - const nonces = pendingTransactions.results.map((tx) => tx.nonce) - const lastNonce = Math.max(...nonces) - return lastNonce + 1 + const maxNonce = pendingTransactions.results.reduce((acc, tx) => { + const curr = BigInt(tx.nonce) + return curr > acc ? curr : acc + }, 0n) + + return (maxNonce + 1n).toString() } const safeInfo = await this.getSafeInfo(address) return safeInfo.nonce @@ -911,7 +922,7 @@ class SafeApiKit { url: `${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/safe-operations/`, method: HttpMethod.Post, body: { - nonce: Number(userOperation.nonce), + nonce: userOperation.nonce, initCode: isEmptyData(userOperation.initCode) ? null : userOperation.initCode, callData: userOperation.callData, callGasLimit: userOperation.callGasLimit.toString(), diff --git a/packages/api-kit/src/types/safeTransactionServiceTypes.ts b/packages/api-kit/src/types/safeTransactionServiceTypes.ts index a1418aa74..72f92570e 100644 --- a/packages/api-kit/src/types/safeTransactionServiceTypes.ts +++ b/packages/api-kit/src/types/safeTransactionServiceTypes.ts @@ -39,11 +39,12 @@ export type SafeSingletonResponse = { deployer: string deployedBlockNumber: number lastIndexedBlockNumber: number + l2: boolean } export type SafeInfoResponse = { readonly address: string - readonly nonce: number + readonly nonce: string readonly threshold: number readonly owners: string[] readonly singleton: string @@ -93,6 +94,7 @@ export type SafeDelegateResponse = { readonly delegate: string readonly delegator: string readonly label: string + readonly expiryDate: string } export type SignedSafeDelegateResponse = SafeDelegateResponse & { @@ -237,6 +239,7 @@ export type SafeMessage = { readonly safeAppId: null | string readonly confirmations: Array readonly preparedSignature: string + readonly origin?: string } export type SafeMessageListResponse = ListResponse diff --git a/packages/api-kit/tests/e2e/getNextNonce.test.ts b/packages/api-kit/tests/e2e/getNextNonce.test.ts index 297e2dec1..96d5c2207 100644 --- a/packages/api-kit/tests/e2e/getNextNonce.test.ts +++ b/packages/api-kit/tests/e2e/getNextNonce.test.ts @@ -24,19 +24,19 @@ describe('getNextNonce', () => { it('should return the next Safe nonce when there are pending transactions', async () => { const safeAddress = API_TESTING_SAFE.address const nextNonce = await safeApiKit.getNextNonce(safeAddress) - chai.expect(nextNonce).to.be.equal(API_TESTING_SAFE.nonce + 2) + chai.expect(nextNonce).to.be.equal((BigInt(API_TESTING_SAFE.nonce) + 2n).toString()) }) it('should return the next Safe nonce when there are pending transactions EIP-3770', async () => { const safeAddress = API_TESTING_SAFE.address const eip3770SafeAddress = `${config.EIP_3770_PREFIX}:${safeAddress}` const nextNonce = await safeApiKit.getNextNonce(eip3770SafeAddress) - chai.expect(nextNonce).to.be.equal(API_TESTING_SAFE.nonce + 2) + chai.expect(nextNonce).to.be.equal((BigInt(API_TESTING_SAFE.nonce) + 2n).toString()) }) it('should return the next Safe nonce when there are no pending transactions', async () => { const safeAddress = '0xDa8dd250065F19f7A29564396D7F13230b9fC5A3' const nextNonce = await safeApiKit.getNextNonce(safeAddress) - chai.expect(nextNonce).to.be.equal(5) + chai.expect(nextNonce).to.be.equal('5') }) }) diff --git a/packages/api-kit/tests/e2e/getSafeInfo.test.ts b/packages/api-kit/tests/e2e/getSafeInfo.test.ts index 49069a5f7..c6503d592 100644 --- a/packages/api-kit/tests/e2e/getSafeInfo.test.ts +++ b/packages/api-kit/tests/e2e/getSafeInfo.test.ts @@ -32,7 +32,7 @@ describe('getSafeInfo', () => { const safeAddress = API_TESTING_SAFE.address const safeInfoResponse = await safeApiKit.getSafeInfo(safeAddress) chai.expect(safeInfoResponse.address).to.be.equal(safeAddress) - chai.expect(safeInfoResponse.nonce).to.be.a('number') + chai.expect(safeInfoResponse.nonce).to.be.a('string') chai.expect(safeInfoResponse.threshold).to.be.equal(API_TESTING_SAFE.threshold) chai.expect(safeInfoResponse).to.have.property('owners').to.be.an('array') chai.expect(safeInfoResponse).to.have.property('modules').to.be.an('array') @@ -51,7 +51,7 @@ describe('getSafeInfo', () => { const eip3770SafeAddress = `${config.EIP_3770_PREFIX}:${safeAddress}` const safeInfoResponse = await safeApiKit.getSafeInfo(eip3770SafeAddress) chai.expect(safeInfoResponse.address).to.be.equal(safeAddress) - chai.expect(safeInfoResponse.nonce).to.be.a('number') + chai.expect(safeInfoResponse.nonce).to.be.a('string') chai.expect(safeInfoResponse.threshold).to.be.equal(API_TESTING_SAFE.threshold) chai.expect(safeInfoResponse).to.have.property('owners').to.be.an('array') chai.expect(safeInfoResponse).to.have.property('modules').to.be.an('array') diff --git a/packages/api-kit/tests/e2e/getSafeOperationsByAddress.test.ts b/packages/api-kit/tests/e2e/getSafeOperationsByAddress.test.ts index 95a283d9b..bcc402536 100644 --- a/packages/api-kit/tests/e2e/getSafeOperationsByAddress.test.ts +++ b/packages/api-kit/tests/e2e/getSafeOperationsByAddress.test.ts @@ -56,14 +56,23 @@ describe('getSafeOperationsByAddress', () => { chai.expect(safeOperation.userOperation).to.have.property('ethereumTxHash') chai.expect(safeOperation.userOperation).to.have.property('sender').to.eq(SAFE_ADDRESS) chai.expect(safeOperation.userOperation).to.have.property('userOperationHash') - chai.expect(safeOperation.userOperation).to.have.property('nonce') + chai.expect(safeOperation.userOperation).to.have.property('nonce').to.be.a('string') chai.expect(safeOperation.userOperation).to.have.property('initCode') chai.expect(safeOperation.userOperation).to.have.property('callData') - chai.expect(safeOperation.userOperation).to.have.property('callGasLimit') - chai.expect(safeOperation.userOperation).to.have.property('verificationGasLimit') - chai.expect(safeOperation.userOperation).to.have.property('preVerificationGas') - chai.expect(safeOperation.userOperation).to.have.property('maxFeePerGas') - chai.expect(safeOperation.userOperation).to.have.property('maxPriorityFeePerGas') + chai.expect(safeOperation.userOperation).to.have.property('callGasLimit').to.be.a('string') + chai + .expect(safeOperation.userOperation) + .to.have.property('verificationGasLimit') + .to.be.a('string') + chai + .expect(safeOperation.userOperation) + .to.have.property('preVerificationGas') + .to.be.a('string') + chai.expect(safeOperation.userOperation).to.have.property('maxFeePerGas').to.be.a('string') + chai + .expect(safeOperation.userOperation) + .to.have.property('maxPriorityFeePerGas') + .to.be.a('string') chai.expect(safeOperation.userOperation).to.have.property('paymaster') chai.expect(safeOperation.userOperation).to.have.property('paymasterData') chai.expect(safeOperation.userOperation).to.have.property('signature') diff --git a/packages/api-kit/tests/endpoint/index.test.ts b/packages/api-kit/tests/endpoint/index.test.ts index 9e34c2feb..31fb97d9b 100644 --- a/packages/api-kit/tests/endpoint/index.test.ts +++ b/packages/api-kit/tests/endpoint/index.test.ts @@ -306,7 +306,9 @@ describe('Endpoint tests', () => { it('getSafeCreationInfo', async () => { await chai .expect(safeApiKit.getSafeCreationInfo(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true } }) + // FIXME the singleton hack makes that the property is always added by SafeApiKit. + // Remove when is correctly returned by the service. + .to.be.eventually.deep.equals({ data: { success: true }, singleton: undefined }) chai.expect(fetchData).to.have.been.calledWith({ url: `${txServiceBaseUrl}/v1/safes/${safeAddress}/creation/`, method: 'get' @@ -316,7 +318,9 @@ describe('Endpoint tests', () => { it('getSafeCreationInfo EIP-3770', async () => { await chai .expect(safeApiKit.getSafeCreationInfo(eip3770SafeAddress)) - .to.be.eventually.deep.equals({ data: { success: true } }) + // FIXME the singleton hack makes that the property is always added by SafeApiKit. + // Remove when is correctly returned by the service. + .to.be.eventually.deep.equals({ data: { success: true }, singleton: undefined }) chai.expect(fetchData).to.have.been.calledWith({ url: `${txServiceBaseUrl}/v1/safes/${safeAddress}/creation/`, method: 'get' @@ -717,7 +721,7 @@ describe('Endpoint tests', () => { url: `${txServiceBaseUrl}/v1/safes/${safeAddress}/safe-operations/`, method: 'post', body: { - nonce: Number(userOperation.nonce), + nonce: userOperation.nonce, initCode: userOperation.initCode, callData: userOperation.callData, callGasLimit: userOperation.callGasLimit.toString(), diff --git a/packages/api-kit/tests/helpers/safe.ts b/packages/api-kit/tests/helpers/safe.ts index 62a0b77de..facf1de40 100644 --- a/packages/api-kit/tests/helpers/safe.ts +++ b/packages/api-kit/tests/helpers/safe.ts @@ -1,6 +1,6 @@ export const API_TESTING_SAFE = { address: '0xF8ef84392f7542576F6b9d1b140334144930Ac78', - nonce: 11, + nonce: '11', threshold: 2, owners: [ '0x56e2C102c664De6DfD7315d12c0178b61D16F171', diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 117d32fe8..30a5318db 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -391,7 +391,7 @@ describe('Safe4337Pack', () => { OperationType.DelegateCall ] }), - nonce: 1n, + nonce: '1', callGasLimit: 150000n, validAfter: 0, validUntil: 0, @@ -423,7 +423,7 @@ describe('Safe4337Pack', () => { OperationType.Call ] }), - nonce: 1n, + nonce: '1', callGasLimit: 150000n, validAfter: 0, validUntil: 0, @@ -485,7 +485,7 @@ describe('Safe4337Pack', () => { OperationType.Call ] }), - nonce: 1n, + nonce: '1', callGasLimit: 150000n, validAfter: 0, validUntil: 0, @@ -567,7 +567,7 @@ describe('Safe4337Pack', () => { OperationType.DelegateCall ] }), - nonce: 1n, + nonce: '1', callGasLimit: 150000n, validAfter: 0, validUntil: 0, diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 5162c88f2..e7ec9f779 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -540,7 +540,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ const userOperation: UserOperation = { sender: safeAddress, - nonce: nonce, + nonce, initCode: '0x', callData, callGasLimit: 1n, @@ -590,7 +590,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ const safeOperation = new EthSafeOperation( { sender: userOperation?.sender || '0x', - nonce: userOperation?.nonce?.toString() || '0', + nonce: userOperation?.nonce || '0', initCode: userOperation?.initCode || '', callData: userOperation?.callData || '', callGasLimit: BigInt(userOperation?.callGasLimit || 0n), diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts index 9ea2645dd..b1dd2941d 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts @@ -13,7 +13,7 @@ describe('SafeOperation', () => { expect(safeOperation.data).toMatchObject({ safe: fixtures.USER_OPERATION.sender, - nonce: BigInt(fixtures.USER_OPERATION.nonce), + nonce: fixtures.USER_OPERATION.nonce, initCode: fixtures.USER_OPERATION.initCode, callData: fixtures.USER_OPERATION.callData, callGasLimit: fixtures.USER_OPERATION.callGasLimit, @@ -75,7 +75,7 @@ describe('SafeOperation', () => { expect(safeOperation.data).toMatchObject({ safe: fixtures.USER_OPERATION.sender, - nonce: BigInt(fixtures.USER_OPERATION.nonce), + nonce: fixtures.USER_OPERATION.nonce, initCode: fixtures.USER_OPERATION.initCode, callData: fixtures.USER_OPERATION.callData, callGasLimit: BigInt(fixtures.GAS_ESTIMATION.callGasLimit), diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 3bf833787..3a812e58f 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -1,4 +1,4 @@ -import { Hex, encodePacked, toHex } from 'viem' +import { Hex, encodePacked } from 'viem' import { EstimateGasData, SafeOperation, @@ -31,7 +31,7 @@ class EthSafeOperation implements SafeOperation { this.moduleAddress = moduleAddress this.data = { safe: userOperation.sender, - nonce: BigInt(userOperation.nonce), + nonce: userOperation.nonce, initCode: userOperation.initCode, callData: userOperation.callData, callGasLimit: userOperation.callGasLimit, @@ -75,7 +75,7 @@ class EthSafeOperation implements SafeOperation { toUserOperation(): UserOperation { return { sender: this.data.safe, - nonce: toHex(this.data.nonce), + nonce: this.data.nonce, initCode: this.data.initCode, callData: this.data.callData, callGasLimit: this.data.callGasLimit, diff --git a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts index b2304d729..09d8cafac 100644 --- a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts +++ b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts @@ -27,7 +27,7 @@ export const ENTRYPOINTS = [ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07] export const USER_OPERATION_RECEIPT = { userOpHash: '0x3cb881d1969036174f38d636d22108d1d032145518b53104fc0b1e1296d2cc9c', sender: '0x1405B3659a11a16459fc27Fa1925b60388C38Ce1', - nonce: '0x1', + nonce: '1', actualGasUsed: '0x27067', actualGasCost: '0x42f29418377167', success: true, @@ -52,7 +52,7 @@ export const USER_OPERATION_RECEIPT = { export const USER_OPERATION = { sender: '0x1405B3659a11a16459fc27Fa1925b60388C38Ce1', - nonce: '0x1', + nonce: '1', initCode: '0x', callData: '0x7bb3742800000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b52600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001848d80ff0a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000132001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000d725e11588f040d86c4c49d8236e32a5868549f000000000000000000000000000000000000000000000000000000000000186a0001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000d725e11588f040d86c4c49d8236e32a5868549f000000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000', @@ -119,15 +119,15 @@ export const SAFE_OPERATION_RESPONSE = { ethereumTxHash: null, sender: '0xE322e721bCe76cE7FCf3A475f139A9314571ad3D', userOperationHash: '0x5d23b7d96a718582601183b1849a4c76b2a13d3787f15074d62a0b6e4a3f76a1', - nonce: 3, + nonce: '3', initCode: '0x', callData: '0x7bb37428000000000000000000000000e322e721bce76ce7fcf3a475f139a9314571ad3d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - callGasLimit: 122497, - verificationGasLimit: 123498, - preVerificationGas: 50705, - maxFeePerGas: 105183831060, - maxPriorityFeePerGas: 1380000000, + callGasLimit: '122497', + verificationGasLimit: '123498', + preVerificationGas: '50705', + maxFeePerGas: '105183831060', + maxPriorityFeePerGas: '1380000000', paymaster: null, paymasterData: null, signature: diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index 7fcf3d076..3474b2ea9 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -131,7 +131,7 @@ export function calculateSafeUserOperationHash( export function userOperationToHexValues(userOperation: UserOperation) { const userOperationWithHexValues = { ...userOperation, - nonce: toHex(BigInt(userOperation.nonce)), + nonce: toHex(userOperation.nonce), callGasLimit: toHex(userOperation.callGasLimit), verificationGasLimit: toHex(userOperation.verificationGasLimit), preVerificationGas: toHex(userOperation.preVerificationGas), diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index 4d9050020..e269970ac 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -290,7 +290,7 @@ export type UserOperation = { export type SafeUserOperation = { safe: string - nonce: bigint + nonce: string initCode: string callData: string callGasLimit: bigint @@ -343,14 +343,14 @@ export type UserOperationResponse = { readonly ethereumTxHash: null | string readonly sender: string readonly userOperationHash: string - readonly nonce: number + readonly nonce: string readonly initCode: null | string readonly callData: null | string - readonly callGasLimit: number - readonly verificationGasLimit: number - readonly preVerificationGas: number - readonly maxFeePerGas: number - readonly maxPriorityFeePerGas: number + readonly callGasLimit: string + readonly verificationGasLimit: string + readonly preVerificationGas: string + readonly maxFeePerGas: string + readonly maxPriorityFeePerGas: string readonly paymaster: null | string readonly paymasterData: null | string readonly signature: string