From 6ba68992e8064c2e71eee184383772a439b650e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 21 Sep 2021 19:04:57 +0200 Subject: [PATCH 01/39] Update Safe Service Client README file --- packages/safe-service-client/README.md | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/safe-service-client/README.md b/packages/safe-service-client/README.md index 4712233bb..0c4fb9294 100644 --- a/packages/safe-service-client/README.md +++ b/packages/safe-service-client/README.md @@ -191,6 +191,15 @@ Returns the balances for Ether and ERC20 tokens of a Safe. const balances: SafeBalanceResponse[] = await safeService.getBalances(safeAddress) ``` +This method can optionally receive the `options` parameter: + +```js +const options: SafeBalancesOptions = { + excludeSpamTokens: false // Optional parameter. Default value is true. +} +const balances: SafeBalanceResponse[] = await safeService.getBalances(safeAddress, options) +``` + ### getUsdBalances Returns the balances for Ether and ERC20 tokens of a Safe with USD fiat conversion. @@ -199,6 +208,15 @@ Returns the balances for Ether and ERC20 tokens of a Safe with USD fiat conversi const usdBalances: SafeBalanceUsdResponse[] = await safeService.getUsdBalances(safeAddress) ``` +This method can optionally receive the `options` parameter: + +```js +const options: SafeBalancesUsdOptions = { + excludeSpamTokens: false // Optional parameter. Default value is true. +} +const usdBalances: SafeBalanceUsdResponse[] = await safeService.getUsdBalances(safeAddress, options) +``` + ### getCollectibles Returns the collectives (ERC721 tokens) owned by the given Safe and information about them. @@ -207,6 +225,15 @@ Returns the collectives (ERC721 tokens) owned by the given Safe and information const collectibles: SafeCollectibleResponse[] = await safeService.getCollectibles(safeAddress) ``` +This method can optionally receive the `options` parameter: + +```js +const options: SafeCollectiblesOptions = { + excludeSpamTokens: false // Optional parameter. Default value is true. +} +const collectibles: SafeCollectibleResponse[] = await safeService.getCollectibles(safeAddress, options) +``` + ### getTokenList Returns the list of all the ERC20 tokens handled by the Safe. From 61621a48bf7c7e18d8b10728c6e6fd3cb931adce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 21 Sep 2021 19:38:41 +0200 Subject: [PATCH 02/39] Update Safe Core SDK README file --- packages/safe-core-sdk/README.md | 65 +++++++++++++++++++++----- packages/safe-service-client/README.md | 4 +- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/safe-core-sdk/README.md b/packages/safe-core-sdk/README.md index d4d74da04..e2e569872 100644 --- a/packages/safe-core-sdk/README.md +++ b/packages/safe-core-sdk/README.md @@ -375,6 +375,13 @@ const txResponse = await safeSdk.executeTransaction(safeTransaction) await txResponse.transactionResponse?.wait() ``` +This method can optionally receive the `options` parameter: + +```js +const options: CallTransactionOptionalProps = { ... } +const safeTransaction = await safeSdk.getEnableModuleTx(moduleAddress, options) +``` + ### getDisableModuleTx Returns a Safe transaction ready to be signed that will disable a Safe module. @@ -385,36 +392,53 @@ const txResponse = await safeSdk.executeTransaction(safeTransaction) await txResponse.transactionResponse?.wait() ``` +This method can optionally receive the `options` parameter: + +```js +const options: CallTransactionOptionalProps = { ... } +const safeTransaction = await safeSdk.getDisableModuleTx(moduleAddress, options) +``` + ### getAddOwnerTx -Returns the Safe transaction to add an owner and update the threshold. +Returns the Safe transaction to add an owner and optionally change the threshold. ```js -const safeTransaction = await safeSdk.getAddOwnerTx(newOwnerAddress, newThreshold) +const params: AddOwnerTxParams = { + ownerAddress, + threshold // If `threshold` is not provided, the current threshold will not change. +} +const safeTransaction = await safeSdk.getAddOwnerTx(params) const txResponse = await safeSdk.executeTransaction(safeTransaction) await txResponse.transactionResponse?.wait() ``` -If `threshold` is not provided, the current threshold will not change. +This method can optionally receive the `options` parameter: ```js -const safeTransaction = await safeSdk.getAddOwnerTx(newOwnerAddress) +const options: CallTransactionOptionalProps = { ... } +const safeTransaction = await safeSdk.getAddOwnerTx(params, options) ``` ### getRemoveOwnerTx -Returns the Safe transaction to remove an owner and update the threshold. +Returns the Safe transaction to remove an owner and optionally change the threshold. ```js -const safeTransaction = await safeSdk.getRemoveOwnerTx(ownerAddress, newThreshold) +const params: RemoveOwnerTxParams = { + ownerAddress, + newThreshold // If `newThreshold` is not provided, the current threshold will be decreased by one. +} +const safeTransaction = await safeSdk.getRemoveOwnerTx(params) const txResponse = await safeSdk.executeTransaction(safeTransaction) await txResponse.transactionResponse?.wait() ``` -If `threshold` is not provided, the current threshold will be decreased by one. +This method can optionally receive the `options` parameter: ```js -const safeTransaction = await safeSdk.getRemoveOwnerTx(ownerAddress) +const options: CallTransactionOptionalProps = { ... } +const safeTransaction = await safeSdk.getRemoveOwnerTx(params, options) ``` ### getSwapOwnerTx @@ -422,11 +446,22 @@ const safeTransaction = await safeSdk.getRemoveOwnerTx(ownerAddress) Returns the Safe transaction to replace an owner of the Safe with a new one. ```js -const safeTransaction = await safeSdk.getSwapOwnerTx(oldOwnerAddress, newOwnerAddress) +const params: SwapOwnerTxParams = { + oldOwnerAddress, + newOwnerAddress +} +const safeTransaction = await safeSdk.getSwapOwnerTx(params) const txResponse = await safeSdk.executeTransaction(safeTransaction) await txResponse.transactionResponse?.wait() ``` +This method can optionally receive the `options` parameter: + +```js +const options: CallTransactionOptionalProps = { ... } +const safeTransaction = await safeSdk.getSwapOwnerTx(params, options) +``` + ### getChangeThresholdTx Returns the Safe transaction to change the threshold. @@ -437,6 +472,13 @@ const txResponse = await safeSdk.executeTransaction(safeTransaction) await txResponse.transactionResponse?.wait() ``` +This method can optionally receive the `options` parameter: + +```js +const options: CallTransactionOptionalProps = { ... } +const safeTransaction = await safeSdk.getChangeThresholdTx(newThreshold, options) +``` + ### executeTransaction Executes a Safe transaction. @@ -454,11 +496,10 @@ Optionally, `gasLimit` and `gasPrice` values can be passed as execution options, ```js const options: TransactionOptions = { - gasLimit: 123456, - gasPrice: 123 // Optional parameter. + gasLimit, + gasPrice // Optional parameter. } const txResponse = await safeSdk.executeTransaction(safeTransaction, options) -await txResponse.transactionResponse?.wait() ``` ## License diff --git a/packages/safe-service-client/README.md b/packages/safe-service-client/README.md index 0c4fb9294..0d3f6367e 100644 --- a/packages/safe-service-client/README.md +++ b/packages/safe-service-client/README.md @@ -212,7 +212,7 @@ This method can optionally receive the `options` parameter: ```js const options: SafeBalancesUsdOptions = { - excludeSpamTokens: false // Optional parameter. Default value is true. + excludeSpamTokens: false // Optional parameter. Default value is true. } const usdBalances: SafeBalanceUsdResponse[] = await safeService.getUsdBalances(safeAddress, options) ``` @@ -229,7 +229,7 @@ This method can optionally receive the `options` parameter: ```js const options: SafeCollectiblesOptions = { - excludeSpamTokens: false // Optional parameter. Default value is true. + excludeSpamTokens: false // Optional parameter. Default value is true. } const collectibles: SafeCollectibleResponse[] = await safeService.getCollectibles(safeAddress, options) ``` From 124f04f81477becd3b4ea8c448b53e07d0604c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 21 Sep 2021 19:55:56 +0200 Subject: [PATCH 03/39] Fixes in README --- packages/safe-core-sdk/README.md | 15 +++++++++++---- packages/safe-service-client/README.md | 10 +++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/safe-core-sdk/README.md b/packages/safe-core-sdk/README.md index e2e569872..89519cedc 100644 --- a/packages/safe-core-sdk/README.md +++ b/packages/safe-core-sdk/README.md @@ -378,7 +378,14 @@ await txResponse.transactionResponse?.wait() This method can optionally receive the `options` parameter: ```js -const options: CallTransactionOptionalProps = { ... } +const options: CallTransactionOptionalProps = { + safeTxGas, // Optional + baseGas, // Optional + gasPrice, // Optional + gasToken, // Optional + refundReceiver, // Optional + nonce // Optional +} const safeTransaction = await safeSdk.getEnableModuleTx(moduleAddress, options) ``` @@ -406,7 +413,7 @@ Returns the Safe transaction to add an owner and optionally change the threshold ```js const params: AddOwnerTxParams = { ownerAddress, - threshold // If `threshold` is not provided, the current threshold will not change. + threshold // Optional. If `threshold` is not provided the current threshold will not change. } const safeTransaction = await safeSdk.getAddOwnerTx(params) const txResponse = await safeSdk.executeTransaction(safeTransaction) @@ -427,7 +434,7 @@ Returns the Safe transaction to remove an owner and optionally change the thresh ```js const params: RemoveOwnerTxParams = { ownerAddress, - newThreshold // If `newThreshold` is not provided, the current threshold will be decreased by one. + newThreshold // Optional. If `newThreshold` is not provided, the current threshold will be decreased by one. } const safeTransaction = await safeSdk.getRemoveOwnerTx(params) const txResponse = await safeSdk.executeTransaction(safeTransaction) @@ -497,7 +504,7 @@ Optionally, `gasLimit` and `gasPrice` values can be passed as execution options, ```js const options: TransactionOptions = { gasLimit, - gasPrice // Optional parameter. + gasPrice // Optional } const txResponse = await safeSdk.executeTransaction(safeTransaction, options) ``` diff --git a/packages/safe-service-client/README.md b/packages/safe-service-client/README.md index 0d3f6367e..167b0f2f9 100644 --- a/packages/safe-service-client/README.md +++ b/packages/safe-service-client/README.md @@ -109,7 +109,7 @@ const delegates: SafeDelegateListResponse = await safeService.getSafeDelegates(s ### addSafeDelegate -Adds a new delegate for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). +Adds a new delegate for a given Safe address. The signature is calculated by signing this hash: `keccak(address + str(int(current_epoch / 3600)))`. ```js await safeService.addSafeDelegate(safeAddress, delegate) @@ -117,7 +117,7 @@ await safeService.addSafeDelegate(safeAddress, delegate) ### removeSafeDelegate -Removes a delegate for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). +Removes a delegate for a given Safe address. The signature is calculated by signing this hash: `keccak(address + str(int(current_epoch / 3600)))`. ```js await safeService.removeSafeDelegate(safeAddress, delegate) @@ -195,7 +195,7 @@ This method can optionally receive the `options` parameter: ```js const options: SafeBalancesOptions = { - excludeSpamTokens: false // Optional parameter. Default value is true. + excludeSpamTokens // Optional. Default value is `true`. } const balances: SafeBalanceResponse[] = await safeService.getBalances(safeAddress, options) ``` @@ -212,7 +212,7 @@ This method can optionally receive the `options` parameter: ```js const options: SafeBalancesUsdOptions = { - excludeSpamTokens: false // Optional parameter. Default value is true. + excludeSpamTokens // Optional. Default value is `true`. } const usdBalances: SafeBalanceUsdResponse[] = await safeService.getUsdBalances(safeAddress, options) ``` @@ -229,7 +229,7 @@ This method can optionally receive the `options` parameter: ```js const options: SafeCollectiblesOptions = { - excludeSpamTokens: false // Optional parameter. Default value is true. + excludeSpamTokens // Optional. Default value is `true`. } const collectibles: SafeCollectibleResponse[] = await safeService.getCollectibles(safeAddress, options) ``` From 5d2cb34948683a7c1dac1c4040d5a7358ce7d466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 23 Sep 2021 06:06:23 +0200 Subject: [PATCH 04/39] Remove axios --- packages/safe-service-client/package.json | 3 +- .../src/utils/httpRequests.ts | 53 +++++++++---------- yarn.lock | 8 +++ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/safe-service-client/package.json b/packages/safe-service-client/package.json index 05ebac705..5dfcf2dcb 100644 --- a/packages/safe-service-client/package.json +++ b/packages/safe-service-client/package.json @@ -66,7 +66,6 @@ } }, "dependencies": { - "@gnosis.pm/safe-core-sdk-types": "^0.1.0", - "axios": "^0.21.1" + "@gnosis.pm/safe-core-sdk-types": "^0.1.0" } } diff --git a/packages/safe-service-client/src/utils/httpRequests.ts b/packages/safe-service-client/src/utils/httpRequests.ts index 987bbfd25..d7f226cf3 100644 --- a/packages/safe-service-client/src/utils/httpRequests.ts +++ b/packages/safe-service-client/src/utils/httpRequests.ts @@ -1,4 +1,4 @@ -import axios, { AxiosResponse } from 'axios' +import fetch from 'node-fetch' export enum HttpMethod { Get = 'get', @@ -13,34 +13,31 @@ interface HttpRequest { } export async function sendRequest({ url, method, body }: HttpRequest): Promise { - let response: AxiosResponse + const response = await fetch(url, { + method, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }) + let jsonResponse try { - response = await axios[method](url, body) + jsonResponse = await response.json() } catch (error) { - if (error.response) { - // The request was made and the server responded with a status code - // that falls out of the range of 2xx - const data = error.response.data - if (data) { - if (data.data) { - throw new Error(data.data) - } - if (data.detail) { - throw new Error(data.detail) - } - if (data.message) { - throw new Error(data.message) - } - } - throw new Error(error.response.statusText) - } else if (error.request) { - // The request was made but no response was received - // `error.request` is an instance of XMLHttpRequest in the browser and an instance of - // http.ClientRequest in node.js - throw new Error('Connection error') - } - // Something happened in setting up the request that triggered an Error - throw new Error(error.message) + throw new Error(response.statusText) } - return response.data as T + if (response.ok) { + return jsonResponse as T + } + if (jsonResponse.data) { + throw new Error(jsonResponse.data) + } + if (jsonResponse.detail) { + throw new Error(jsonResponse.detail) + } + if (jsonResponse.message) { + throw new Error(jsonResponse.message) + } + throw new Error(response.statusText) } diff --git a/yarn.lock b/yarn.lock index 7982c585c..e2fa8e5ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1145,6 +1145,14 @@ dependencies: ethereumjs-util "^7.0.10" +"@gnosis.pm/safe-core-sdk@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-core-sdk/-/safe-core-sdk-0.3.1.tgz#15171657490a90ac3773b6965c2af4b873af136f" + integrity sha512-L0na+KSAnicO4B9MtzPu90kwThZ2PHd11CHSRzhJ7l4oIOGB4a/KyPohcOzXTO9Vz3yDIELPlbcW5IEasUk6ag== + dependencies: + "@gnosis.pm/safe-core-sdk-types" "^0.1.1" + ethereumjs-util "^7.0.10" + "@gnosis.pm/safe-deployments@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-deployments/-/safe-deployments-1.1.0.tgz#63133f4576c5d5f2779f61d322e1e449fb701b1c" From 54f5ecfed6abb78e84594878cfb38a48e9806704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 23 Sep 2021 06:06:35 +0200 Subject: [PATCH 05/39] Update endpoints test --- .../tests/endpoint.test.ts | 289 +++++++++--------- 1 file changed, 142 insertions(+), 147 deletions(-) diff --git a/packages/safe-service-client/tests/endpoint.test.ts b/packages/safe-service-client/tests/endpoint.test.ts index 7a349f273..c9ee72fdc 100644 --- a/packages/safe-service-client/tests/endpoint.test.ts +++ b/packages/safe-service-client/tests/endpoint.test.ts @@ -1,5 +1,4 @@ import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' -import axios from 'axios' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import sinon from 'sinon' @@ -9,9 +8,11 @@ import SafeServiceClient, { SafeBalancesUsdOptions, SafeCollectiblesOptions, SafeDelegate, + SafeDelegateDelete, SafeMultisigTransactionEstimate } from '../src' import { getTxServiceBaseUrl } from '../src/utils' +import * as httpRequests from '../src/utils/httpRequests' chai.use(chaiAsPromised) chai.use(sinonChai) @@ -22,68 +23,69 @@ describe('Endpoint tests', () => { const txServiceBaseUrl = 'https://safe-transaction.rinkeby.gnosis.io' const serviceSdk = new SafeServiceClient(txServiceBaseUrl) - const axiosGet = sinon.stub(axios, 'get').resolves({ data: { success: true } }) - const axiosPost = sinon.stub(axios, 'post').resolves({ data: { success: true } }) - const axiosDelete = sinon.stub(axios, 'delete').resolves({ data: { success: true } }) + const fetchData = sinon + .stub(httpRequests, 'sendRequest') + .returns(Promise.resolve({ data: { success: true } })) describe('', () => { it('getServiceInfo', async () => { chai.expect(serviceSdk.getServiceInfo()).to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith(`${getTxServiceBaseUrl(txServiceBaseUrl)}/about`) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about`, + method: 'get' + }) }) it('getServiceMasterCopiesInfo', async () => { chai .expect(serviceSdk.getServiceMasterCopiesInfo()) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith(`${getTxServiceBaseUrl(txServiceBaseUrl)}/about/master-copies`) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about/master-copies`, + method: 'get' + }) }) it('decodeData', async () => { const data = '0x610b592500000000000000000000000090F8bf6A479f320ead074411a4B0e7944Ea8c9C1' chai.expect(serviceSdk.decodeData(data)).to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosPost) - .to.have.been.calledWith(`${getTxServiceBaseUrl(txServiceBaseUrl)}/data-decoder/`) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/data-decoder/`, + method: 'post', + body: { data } + }) }) it('getSafesByOwner', async () => { chai .expect(serviceSdk.getSafesByOwner(ownerAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/owners/${ownerAddress}/safes/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/owners/${ownerAddress}/safes/`, + method: 'get' + }) }) it('getTransaction', async () => { chai .expect(serviceSdk.getTransaction(safeTxHash)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/multisig-transactions/${safeTxHash}/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/multisig-transactions/${safeTxHash}/`, + method: 'get' + }) }) it('getTransactionConfirmations', async () => { chai .expect(serviceSdk.getTransactionConfirmations(safeTxHash)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/multisig-transactions/${safeTxHash}/confirmations/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/multisig-transactions/${safeTxHash}/confirmations/`, + method: 'get' + }) }) it('confirmTransaction', async () => { @@ -91,33 +93,32 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.confirmTransaction(safeTxHash, signature)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosPost) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/multisig-transactions/${safeTxHash}/confirmations/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/multisig-transactions/${safeTxHash}/confirmations/`, + method: 'get' + }) }) it('getSafeInfo', async () => { chai .expect(serviceSdk.getSafeInfo(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith(`${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/`) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/`, + method: 'get' + }) }) it('getSafeDelegates', async () => { chai .expect(serviceSdk.getSafeDelegates(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, + method: 'get' + }) }) it('addSafeDelegate', async () => { @@ -130,41 +131,38 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.addSafeDelegate(safeAddress, delegate)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosPost) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, + method: 'get' + }) }) it('removeSafeDelegate', async () => { - const delegate: SafeDelegate = { + const delegate: SafeDelegateDelete = { safe: safeAddress, delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', - signature: '0x', - label: '' + signature: '0x' } chai .expect(serviceSdk.removeSafeDelegate(safeAddress, delegate)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosDelete) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/${ - delegate.delegate - }` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/${ + delegate.delegate + }`, + method: 'delete', + body: delegate + }) }) it('getSafeCreationInfo', async () => { chai .expect(serviceSdk.getSafeCreationInfo(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/creation/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/creation/`, + method: 'get' + }) }) it('estimateSafeTransaction', async () => { @@ -177,13 +175,13 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.estimateSafeTransaction(safeAddress, safeTransaction)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosPost) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/multisig-transactions/estimations/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/multisig-transactions/estimations/`, + method: 'post', + body: safeTransaction + }) }) it('proposeTransaction', async () => { @@ -208,44 +206,46 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.proposeTransaction(safeAddress, safeTxData, safeTxHash, signature)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosPost) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, + method: 'post', + body: { + ...safeTxData, + contractTransactionHash: safeTxHash, + sender: signature.signer, + signature: signature.data + } + }) }) it('getIncomingTransactions', async () => { chai .expect(serviceSdk.getIncomingTransactions(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/incoming-transfers/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/incoming-transfers/`, + method: 'get' + }) }) it('getModuleTransactions', async () => { chai .expect(serviceSdk.getModuleTransactions(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/module-transfers/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/module-transfers/`, + method: 'get' + }) }) it('getMultisigTransactions', async () => { chai .expect(serviceSdk.getMultisigTransactions(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, + method: 'get' + }) }) it('getPendingTransactions', async () => { @@ -253,26 +253,24 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.getPendingTransactions(safeAddress, currentNonce)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/multisig-transactions/?executed=false&nonce__gte=${currentNonce}` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/multisig-transactions/?executed=false&nonce__gte=${currentNonce}`, + method: 'get' + }) }) it('getBalances', async () => { chai .expect(serviceSdk.getBalances(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/balances/?exclude_spam=true` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/balances/?exclude_spam=true`, + method: 'get' + }) }) it('getBalances (with options)', async () => { @@ -282,26 +280,24 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.getBalances(safeAddress, options)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/balances/?exclude_spam=false` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/balances/?exclude_spam=false`, + method: 'get' + }) }) it('getUsdBalances', async () => { chai .expect(serviceSdk.getUsdBalances(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/balances/usd/?exclude_spam=true` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/balances/usd/?exclude_spam=true`, + method: 'get' + }) }) it('getUsdBalances (with options)', async () => { @@ -311,26 +307,24 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.getUsdBalances(safeAddress, options)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/balances/usd/?exclude_spam=false` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/balances/usd/?exclude_spam=false`, + method: 'get' + }) }) it('getCollectibles', async () => { chai .expect(serviceSdk.getCollectibles(safeAddress)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/collectibles/?exclude_spam=true` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/collectibles/?exclude_spam=true`, + method: 'get' + }) }) it('getCollectibles (with options)', async () => { @@ -340,28 +334,29 @@ describe('Endpoint tests', () => { chai .expect(serviceSdk.getCollectibles(safeAddress, options)) .to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith( - `${getTxServiceBaseUrl( - txServiceBaseUrl - )}/safes/${safeAddress}/collectibles/?exclude_spam=false` - ) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl( + txServiceBaseUrl + )}/safes/${safeAddress}/collectibles/?exclude_spam=false`, + method: 'get' + }) }) it('getTokens', async () => { chai.expect(serviceSdk.getTokenList()).to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith(`${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/`) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/`, + method: 'get' + }) }) it('getToken', async () => { const tokenAddress = '0x' chai.expect(serviceSdk.getToken(tokenAddress)).to.be.eventually.deep.equals({ success: true }) - chai - .expect(axiosGet) - .to.have.been.calledWith(`${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/${tokenAddress}/`) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/${tokenAddress}/`, + method: 'get' + }) }) }) }) From 9d38ba8e8e6b041e44a8ba31296955bb92654c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 23 Sep 2021 06:08:22 +0200 Subject: [PATCH 06/39] Update dependencies --- packages/safe-service-client/package.json | 30 ++-- yarn.lock | 175 ++++++++++++++++++++-- 2 files changed, 174 insertions(+), 31 deletions(-) diff --git a/packages/safe-service-client/package.json b/packages/safe-service-client/package.json index 5dfcf2dcb..35392ab1e 100644 --- a/packages/safe-service-client/package.json +++ b/packages/safe-service-client/package.json @@ -33,26 +33,26 @@ ], "homepage": "https://github.com/gnosis/safe-core-sdk#readme", "devDependencies": { - "@gnosis.pm/safe-core-sdk": "^0.1.2", - "@types/chai": "^4.2.19", + "@gnosis.pm/safe-core-sdk": "^0.3.1", + "@types/chai": "^4.2.22", "@types/chai-as-promised": "^7.1.4", - "@types/mocha": "^8.2.2", - "@types/node": "^15.12.4", - "@typescript-eslint/eslint-plugin": "^4.27.0", - "@typescript-eslint/parser": "^4.27.0", + "@types/mocha": "^9.0.0", + "@types/node": "^16.9.6", + "@typescript-eslint/eslint-plugin": "^4.31.2", + "@typescript-eslint/parser": "^4.31.2", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", - "eslint": "^7.29.0", + "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^3.4.0", - "husky": "^6.0.0", - "lint-staged": "^11.0.0", - "mocha": "^8.4.0", - "prettier": "^2.3.1", + "eslint-plugin-prettier": "^4.0.0", + "husky": "^7.0.2", + "lint-staged": "^11.1.2", + "mocha": "^9.1.1", + "prettier": "^2.4.1", "rimraf": "^3.0.2", "ts-generator": "^0.1.1", - "ts-node": "^9.1.1", - "typescript": "^4.3.4" + "ts-node": "^10.2.1", + "typescript": "^4.4.3" }, "lint-staged": { "src/**/!(*test).ts": [ @@ -66,6 +66,6 @@ } }, "dependencies": { - "@gnosis.pm/safe-core-sdk-types": "^0.1.0" + "@gnosis.pm/safe-core-sdk-types": "^0.1.1" } } diff --git a/yarn.lock b/yarn.lock index e2fa8e5ef..e227bd102 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1138,13 +1138,6 @@ solc "0.5.17" truffle "^5.1.21" -"@gnosis.pm/safe-core-sdk@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-core-sdk/-/safe-core-sdk-0.1.2.tgz#118037442b8cf766e5a2ddfbffeb90cd6dc87d91" - integrity sha512-tvInAfwENVG7QXWRMbXqCnZkTqPPIWzFnonU/fDRQmA5S5pKI9qIDYCjqOAwPzqRgvuItG3NxmSPynZD09771w== - dependencies: - ethereumjs-util "^7.0.10" - "@gnosis.pm/safe-core-sdk@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-core-sdk/-/safe-core-sdk-0.3.1.tgz#15171657490a90ac3773b6965c2af4b873af136f" @@ -3375,11 +3368,16 @@ dependencies: "@types/chai" "*" -"@types/chai@*", "@types/chai@^4.2.18", "@types/chai@^4.2.19": +"@types/chai@*", "@types/chai@^4.2.18": version "4.2.21" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650" integrity sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg== +"@types/chai@^4.2.22": + version "4.2.22" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.22.tgz#47020d7e4cf19194d43b5202f35f75bd2ad35ce7" + integrity sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ== + "@types/connect@*": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -3537,6 +3535,11 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323" integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== +"@types/mocha@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297" + integrity sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA== + "@types/node-fetch@^2.5.5": version "2.5.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" @@ -3575,10 +3578,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.7.tgz#29fea9a5b14e2b75c19028e1c7a32edd1e89fe92" integrity sha512-FA45p37/mLhpebgbPWWCKfOisTjxGK9lwcHlJ6XVLfu3NgfcazOJHdYUZCWPMK8QX4LhNZdmfo6iMz9FqpUbaw== -"@types/node@^15.12.4": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@^16.9.6": + version "16.9.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.6.tgz#040a64d7faf9e5d9e940357125f0963012e66f04" + integrity sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -3700,7 +3703,7 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== -"@typescript-eslint/eslint-plugin@^4.23.0", "@typescript-eslint/eslint-plugin@^4.26.1", "@typescript-eslint/eslint-plugin@^4.27.0": +"@typescript-eslint/eslint-plugin@^4.23.0", "@typescript-eslint/eslint-plugin@^4.26.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.1.tgz#808d206e2278e809292b5de752a91105da85860b" integrity sha512-AHqIU+SqZZgBEiWOrtN94ldR3ZUABV5dUG94j8Nms9rQnHFc8fvDOue/58K4CFz6r8OtDDc35Pw9NQPWo0Ayrw== @@ -3713,6 +3716,19 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/eslint-plugin@^4.31.2": + version "4.31.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.2.tgz#9f41efaee32cdab7ace94b15bd19b756dd099b0a" + integrity sha512-w63SCQ4bIwWN/+3FxzpnWrDjQRXVEGiTt9tJTRptRXeFvdZc/wLiz3FQUwNQ2CVoRGI6KUWMNUj/pk63noUfcA== + dependencies: + "@typescript-eslint/experimental-utils" "4.31.2" + "@typescript-eslint/scope-manager" "4.31.2" + debug "^4.3.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/experimental-utils@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.1.tgz#0af2b17b0296b60c6b207f11062119fa9c5a8994" @@ -3725,7 +3741,19 @@ eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@^4.23.0", "@typescript-eslint/parser@^4.26.1", "@typescript-eslint/parser@^4.27.0": +"@typescript-eslint/experimental-utils@4.31.2": + version "4.31.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.2.tgz#98727a9c1e977dd5d20c8705e69cd3c2a86553fa" + integrity sha512-3tm2T4nyA970yQ6R3JZV9l0yilE2FedYg8dcXrTar34zC9r6JB7WyBQbpIVongKPlhEMjhQ01qkwrzWy38Bk1Q== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.31.2" + "@typescript-eslint/types" "4.31.2" + "@typescript-eslint/typescript-estree" "4.31.2" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@^4.23.0", "@typescript-eslint/parser@^4.26.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.29.1.tgz#17dfbb45c9032ffa0fe15881d20fbc2a4bdeb02d" integrity sha512-3fL5iN20hzX3Q4OkG7QEPFjZV2qsVGiDhEwwh+EkmE/w7oteiOvUNzmpu5eSwGJX/anCryONltJ3WDmAzAoCMg== @@ -3735,6 +3763,16 @@ "@typescript-eslint/typescript-estree" "4.29.1" debug "^4.3.1" +"@typescript-eslint/parser@^4.31.2": + version "4.31.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.2.tgz#54aa75986e3302d91eff2bbbaa6ecfa8084e9c34" + integrity sha512-EcdO0E7M/sv23S/rLvenHkb58l3XhuSZzKf6DBvLgHqOYdL6YFMYVtreGFWirxaU2mS1GYDby3Lyxco7X5+Vjw== + dependencies: + "@typescript-eslint/scope-manager" "4.31.2" + "@typescript-eslint/types" "4.31.2" + "@typescript-eslint/typescript-estree" "4.31.2" + debug "^4.3.1" + "@typescript-eslint/scope-manager@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.1.tgz#f25da25bc6512812efa2ce5ebd36619d68e61358" @@ -3743,11 +3781,24 @@ "@typescript-eslint/types" "4.29.1" "@typescript-eslint/visitor-keys" "4.29.1" +"@typescript-eslint/scope-manager@4.31.2": + version "4.31.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.2.tgz#1d528cb3ed3bcd88019c20a57c18b897b073923a" + integrity sha512-2JGwudpFoR/3Czq6mPpE8zBPYdHWFGL6lUNIGolbKQeSNv4EAiHaR5GVDQaLA0FwgcdcMtRk+SBJbFGL7+La5w== + dependencies: + "@typescript-eslint/types" "4.31.2" + "@typescript-eslint/visitor-keys" "4.31.2" + "@typescript-eslint/types@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.1.tgz#94cce6cf7cc83451df03339cda99d326be2feaf5" integrity sha512-Jj2yu78IRfw4nlaLtKjVaGaxh/6FhofmQ/j8v3NXmAiKafbIqtAPnKYrf0sbGjKdj0hS316J8WhnGnErbJ4RCA== +"@typescript-eslint/types@4.31.2": + version "4.31.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.2.tgz#2aea7177d6d744521a168ed4668eddbd912dfadf" + integrity sha512-kWiTTBCTKEdBGrZKwFvOlGNcAsKGJSBc8xLvSjSppFO88AqGxGNYtF36EuEYG6XZ9vT0xX8RNiHbQUKglbSi1w== + "@typescript-eslint/typescript-estree@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.1.tgz#7b32a25ff8e51f2671ccc6b26cdbee3b1e6c5e7f" @@ -3761,6 +3812,19 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@4.31.2": + version "4.31.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.2.tgz#abfd50594d8056b37e7428df3b2d185ef2d0060c" + integrity sha512-ieBq8U9at6PvaC7/Z6oe8D3czeW5d//Fo1xkF/s9394VR0bg/UaMYPdARiWyKX+lLEjY3w/FNZJxitMsiWv+wA== + dependencies: + "@typescript-eslint/types" "4.31.2" + "@typescript-eslint/visitor-keys" "4.31.2" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/visitor-keys@4.29.1": version "4.29.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.1.tgz#0615be8b55721f5e854f3ee99f1a714f2d093e5d" @@ -3769,6 +3833,14 @@ "@typescript-eslint/types" "4.29.1" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@4.31.2": + version "4.31.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.2.tgz#7d5b4a4705db7fe59ecffb273c1d082760f635cc" + integrity sha512-PrBId7EQq2Nibns7dd/ch6S6/M4/iwLM9McbgeEbCXfxdwRUNxJ4UNreJ6Gh3fI2GNKNrWnQxKL7oCPmngKBug== + dependencies: + "@typescript-eslint/types" "4.31.2" + eslint-visitor-keys "^2.0.0" + "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" @@ -7526,6 +7598,13 @@ eslint-plugin-prettier@^3.4.0: dependencies: prettier-linter-helpers "^1.0.0" +eslint-plugin-prettier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" + integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== + dependencies: + prettier-linter-helpers "^1.0.0" + eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -7558,7 +7637,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@^7.26.0, eslint@^7.28.0, eslint@^7.29.0: +eslint@^7.26.0, eslint@^7.28.0, eslint@^7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -9819,6 +9898,11 @@ husky@^6.0.0: resolved "https://registry.yarnpkg.com/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e" integrity sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ== +husky@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.2.tgz#21900da0f30199acca43a46c043c4ad84ae88dff" + integrity sha512-8yKEWNX4z2YsofXAMT7KvA1g8p+GxtB1ffV8XtpAEGuXNAbCV5wdNKH+qTpw8SM9fh4aMPDR+yQuKfgnreyZlg== + ice-cap@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/ice-cap/-/ice-cap-0.0.4.tgz#8a6d31ab4cac8d4b56de4fa946df3352561b6e18" @@ -11603,7 +11687,7 @@ lint-staged@^10.5.4: string-argv "0.3.1" stringify-object "^3.3.0" -lint-staged@^11.0.0: +lint-staged@^11.0.0, lint-staged@^11.1.2: version "11.1.2" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.1.2.tgz#4dd78782ae43ee6ebf2969cad9af67a46b33cd90" integrity sha512-6lYpNoA9wGqkL6Hew/4n1H6lRqF3qCsujVT0Oq5Z4hiSAM7S6NksPJ3gnr7A7R52xCtiZMcEUNNQ6d6X5Bvh9w== @@ -12676,6 +12760,37 @@ mocha@^9.0.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" +mocha@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.1.tgz#33df2eb9c6262434630510c5f4283b36efda9b61" + integrity sha512-0wE74YMgOkCgBUj8VyIDwmLUjTsS13WV1Pg7l0SHea2qzZzlq7MDnfbPsHKcELBRk3+izEVkRofjmClpycudCA== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.2" + debug "4.3.1" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.1.7" + growl "1.10.5" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "3.0.4" + ms "2.1.3" + nanoid "3.1.23" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + which "2.0.2" + wide-align "1.1.3" + workerpool "6.1.5" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + mock-fs@^4.1.0: version "4.14.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" @@ -14510,6 +14625,11 @@ prettier@^2.1.2, prettier@^2.3.0, prettier@^2.3.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== +prettier@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" + integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== + printj@~1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" @@ -17062,6 +17182,24 @@ ts-node@^10.0.0: make-error "^1.1.1" yn "3.1.1" +ts-node@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5" + integrity sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw== + dependencies: + "@cspotcode/source-map-support" "0.6.1" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + yn "3.1.1" + ts-node@^9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" @@ -17285,6 +17423,11 @@ typescript@^4.2.4, typescript@^4.3.2, typescript@^4.3.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== +typescript@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324" + integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA== + typewise-core@^1.2, typewise-core@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" From d3f0e1f298fa9d1a1ef1b1e21cceffab1316cb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 27 Sep 2021 17:49:02 +0200 Subject: [PATCH 07/39] Refactor createTransaction to accept options in MultiSend --- packages/safe-core-sdk/src/Safe.ts | 32 ++++++++++++------- .../src/utils/transactions/utils.ts | 4 +-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/safe-core-sdk/src/Safe.ts b/packages/safe-core-sdk/src/Safe.ts index 20cd86440..2bdaa3580 100644 --- a/packages/safe-core-sdk/src/Safe.ts +++ b/packages/safe-core-sdk/src/Safe.ts @@ -1,5 +1,6 @@ import { BigNumber } from '@ethersproject/bignumber' import { + MetaTransactionData, OperationType, SafeSignature, SafeTransaction, @@ -234,28 +235,37 @@ class Safe { * @param safeTransactions - The list of transactions to process * @returns The Safe transaction */ + async createTransaction(safeTransactions: SafeTransactionDataPartial): Promise async createTransaction( - ...safeTransactions: SafeTransactionDataPartial[] + safeTransactions: MetaTransactionData[], + options?: CallTransactionOptionalProps + ): Promise + async createTransaction( + safeTransactions: SafeTransactionDataPartial | MetaTransactionData[], + options?: SafeTransactionDataPartial ): Promise { - if (safeTransactions.length === 1) { + if (safeTransactions instanceof Array) { + const multiSendData = encodeMultiSendData( + safeTransactions.map(standardizeMetaTransactionData) + ) + const multiSendTransaction = { + ...options, + to: this.#contractManager.multiSendContract.getAddress(), + value: '0', + data: this.#contractManager.multiSendContract.encode('multiSend', [multiSendData]), + operation: OperationType.DelegateCall + } const standardizedTransaction = await standardizeSafeTransactionData( this.#contractManager.safeContract, this.#ethAdapter, - safeTransactions[0] + multiSendTransaction ) return new EthSafeTransaction(standardizedTransaction) } - const multiSendData = encodeMultiSendData(safeTransactions.map(standardizeMetaTransactionData)) - const multiSendTransaction = { - to: this.#contractManager.multiSendContract.getAddress(), - value: '0', - data: this.#contractManager.multiSendContract.encode('multiSend', [multiSendData]), - operation: OperationType.DelegateCall - } const standardizedTransaction = await standardizeSafeTransactionData( this.#contractManager.safeContract, this.#ethAdapter, - multiSendTransaction + safeTransactions ) return new EthSafeTransaction(standardizedTransaction) } diff --git a/packages/safe-core-sdk/src/utils/transactions/utils.ts b/packages/safe-core-sdk/src/utils/transactions/utils.ts index 94d712ceb..055a9359d 100644 --- a/packages/safe-core-sdk/src/utils/transactions/utils.ts +++ b/packages/safe-core-sdk/src/utils/transactions/utils.ts @@ -15,9 +15,7 @@ export function standardizeMetaTransactionData( tx: SafeTransactionDataPartial ): MetaTransactionData { const standardizedTxs: MetaTransactionData = { - to: tx.to, - value: tx.value, - data: tx.data, + ...tx, operation: tx.operation ?? OperationType.Call } return standardizedTxs From 50d46fa12952ffcb022d01172759529671b580ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 27 Sep 2021 18:04:24 +0200 Subject: [PATCH 08/39] Rename CallTransactionOptionalProps --- packages/safe-core-sdk/README.md | 12 ++++++------ packages/safe-core-sdk/src/Safe.ts | 16 ++++++++-------- packages/safe-core-sdk/src/index.ts | 4 ++-- .../src/utils/transactions/types.ts | 6 +----- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/safe-core-sdk/README.md b/packages/safe-core-sdk/README.md index 89519cedc..816ba00e1 100644 --- a/packages/safe-core-sdk/README.md +++ b/packages/safe-core-sdk/README.md @@ -378,7 +378,7 @@ await txResponse.transactionResponse?.wait() This method can optionally receive the `options` parameter: ```js -const options: CallTransactionOptionalProps = { +const options: SafeTransactionOptionalProps = { safeTxGas, // Optional baseGas, // Optional gasPrice, // Optional @@ -402,7 +402,7 @@ await txResponse.transactionResponse?.wait() This method can optionally receive the `options` parameter: ```js -const options: CallTransactionOptionalProps = { ... } +const options: SafeTransactionOptionalProps = { ... } const safeTransaction = await safeSdk.getDisableModuleTx(moduleAddress, options) ``` @@ -423,7 +423,7 @@ await txResponse.transactionResponse?.wait() This method can optionally receive the `options` parameter: ```js -const options: CallTransactionOptionalProps = { ... } +const options: SafeTransactionOptionalProps = { ... } const safeTransaction = await safeSdk.getAddOwnerTx(params, options) ``` @@ -444,7 +444,7 @@ await txResponse.transactionResponse?.wait() This method can optionally receive the `options` parameter: ```js -const options: CallTransactionOptionalProps = { ... } +const options: SafeTransactionOptionalProps = { ... } const safeTransaction = await safeSdk.getRemoveOwnerTx(params, options) ``` @@ -465,7 +465,7 @@ await txResponse.transactionResponse?.wait() This method can optionally receive the `options` parameter: ```js -const options: CallTransactionOptionalProps = { ... } +const options: SafeTransactionOptionalProps = { ... } const safeTransaction = await safeSdk.getSwapOwnerTx(params, options) ``` @@ -482,7 +482,7 @@ await txResponse.transactionResponse?.wait() This method can optionally receive the `options` parameter: ```js -const options: CallTransactionOptionalProps = { ... } +const options: SafeTransactionOptionalProps = { ... } const safeTransaction = await safeSdk.getChangeThresholdTx(newThreshold, options) ``` diff --git a/packages/safe-core-sdk/src/Safe.ts b/packages/safe-core-sdk/src/Safe.ts index 2bdaa3580..99276beae 100644 --- a/packages/safe-core-sdk/src/Safe.ts +++ b/packages/safe-core-sdk/src/Safe.ts @@ -16,7 +16,7 @@ import { generatePreValidatedSignature, generateSignature } from './utils/signat import { estimateGasForTransactionExecution } from './utils/transactions/gas' import EthSafeTransaction from './utils/transactions/SafeTransaction' import { - CallTransactionOptionalProps, + SafeTransactionOptionalProps, TransactionOptions, TransactionResult } from './utils/transactions/types' @@ -238,7 +238,7 @@ class Safe { async createTransaction(safeTransactions: SafeTransactionDataPartial): Promise async createTransaction( safeTransactions: MetaTransactionData[], - options?: CallTransactionOptionalProps + options?: SafeTransactionOptionalProps ): Promise async createTransaction( safeTransactions: SafeTransactionDataPartial | MetaTransactionData[], @@ -378,7 +378,7 @@ class Safe { */ async getEnableModuleTx( moduleAddress: string, - options?: CallTransactionOptionalProps + options?: SafeTransactionOptionalProps ): Promise { const safeTransaction = await this.createTransaction({ to: this.getAddress(), @@ -400,7 +400,7 @@ class Safe { */ async getDisableModuleTx( moduleAddress: string, - options?: CallTransactionOptionalProps + options?: SafeTransactionOptionalProps ): Promise { const safeTransaction = await this.createTransaction({ to: this.getAddress(), @@ -424,7 +424,7 @@ class Safe { */ async getAddOwnerTx( { ownerAddress, threshold }: AddOwnerTxParams, - options?: CallTransactionOptionalProps + options?: SafeTransactionOptionalProps ): Promise { const safeTransaction = await this.createTransaction({ to: this.getAddress(), @@ -448,7 +448,7 @@ class Safe { */ async getRemoveOwnerTx( { ownerAddress, threshold }: RemoveOwnerTxParams, - options?: CallTransactionOptionalProps + options?: SafeTransactionOptionalProps ): Promise { const safeTransaction = await this.createTransaction({ to: this.getAddress(), @@ -472,7 +472,7 @@ class Safe { */ async getSwapOwnerTx( { oldOwnerAddress, newOwnerAddress }: SwapOwnerTxParams, - options?: CallTransactionOptionalProps + options?: SafeTransactionOptionalProps ): Promise { const safeTransaction = await this.createTransaction({ to: this.getAddress(), @@ -494,7 +494,7 @@ class Safe { */ async getChangeThresholdTx( threshold: number, - options?: CallTransactionOptionalProps + options?: SafeTransactionOptionalProps ): Promise { const safeTransaction = await this.createTransaction({ to: this.getAddress(), diff --git a/packages/safe-core-sdk/src/index.ts b/packages/safe-core-sdk/src/index.ts index 7b66acaee..65260ceba 100644 --- a/packages/safe-core-sdk/src/index.ts +++ b/packages/safe-core-sdk/src/index.ts @@ -15,7 +15,7 @@ import SafeFactory, { SafeFactoryConfig } from './safeFactory' import { - CallTransactionOptionalProps, + SafeTransactionOptionalProps, TransactionOptions, TransactionResult } from './utils/transactions/types' @@ -37,7 +37,7 @@ export { ContractNetworksConfig, TransactionOptions, TransactionResult, - CallTransactionOptionalProps, + SafeTransactionOptionalProps, AddOwnerTxParams, RemoveOwnerTxParams, SwapOwnerTxParams diff --git a/packages/safe-core-sdk/src/utils/transactions/types.ts b/packages/safe-core-sdk/src/utils/transactions/types.ts index a61e6716e..4a481284e 100644 --- a/packages/safe-core-sdk/src/utils/transactions/types.ts +++ b/packages/safe-core-sdk/src/utils/transactions/types.ts @@ -1,9 +1,7 @@ import { ContractTransaction } from '@ethersproject/contracts' -import { OperationType } from '@gnosis.pm/safe-core-sdk-types' import { PromiEvent, TransactionReceipt } from 'web3-core/types' -interface SafeTransactionOptionalProps { - operation?: OperationType +export interface SafeTransactionOptionalProps { safeTxGas?: number baseGas?: number gasPrice?: number @@ -12,8 +10,6 @@ interface SafeTransactionOptionalProps { nonce?: number } -export type CallTransactionOptionalProps = Omit - export interface TransactionOptions { from?: string gas?: number | string From 8f5c185579312c2c4b3d02f352af110586597ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 27 Sep 2021 18:37:09 +0200 Subject: [PATCH 09/39] Refactor tests --- packages/safe-core-sdk/tests/core.test.ts | 6 +- .../safe-core-sdk/tests/execution.test.ts | 55 +++++++++++-------- .../tests/module-manager.test.ts | 6 +- .../safe-core-sdk/tests/owner-manager.test.ts | 8 +-- .../safe-core-sdk/tests/threshold.test.ts | 4 +- 5 files changed, 45 insertions(+), 34 deletions(-) diff --git a/packages/safe-core-sdk/tests/core.test.ts b/packages/safe-core-sdk/tests/core.test.ts index 3e99d2d9b..880329b5e 100644 --- a/packages/safe-core-sdk/tests/core.test.ts +++ b/packages/safe-core-sdk/tests/core.test.ts @@ -1,4 +1,5 @@ import { BigNumber } from '@ethersproject/bignumber' +import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' @@ -123,11 +124,12 @@ describe('Safe Core SDK', () => { contractNetworks }) chai.expect(await safeSdk.getNonce()).to.be.eq(0) - const tx = await safeSdk.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '0', data: '0x' - }) + } + const tx = await safeSdk.createTransaction(txDataPartial) const txResponse = await safeSdk.executeTransaction(tx) await waitSafeTxReceipt(txResponse) chai.expect(await safeSdk.getNonce()).to.be.eq(1) diff --git a/packages/safe-core-sdk/tests/execution.test.ts b/packages/safe-core-sdk/tests/execution.test.ts index 9f60f39cb..dc6067980 100644 --- a/packages/safe-core-sdk/tests/execution.test.ts +++ b/packages/safe-core-sdk/tests/execution.test.ts @@ -1,5 +1,5 @@ import { BigNumber } from '@ethersproject/bignumber' -import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' +import { MetaTransactionData, SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' @@ -50,11 +50,12 @@ describe('Transactions execution', () => { }) const safeInitialBalance = await safeSdk1.getBalance() chai.expect(safeInitialBalance.toString()).to.be.eq('0') - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '500000000000000000', // 0.5 ETH data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) await chai .expect(safeSdk1.executeTransaction(tx)) .to.be.rejectedWith('Not enough Ether funds') @@ -72,11 +73,12 @@ describe('Transactions execution', () => { }) const ethAdapter2 = await getEthAdapter(account2.signer) const safeSdk2 = await safeSdk1.connect({ ethAdapter: ethAdapter2 }) - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: safe.address, value: '0', data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) await safeSdk1.signTransaction(tx) const txHash = await safeSdk2.getTransactionHash(tx) const txResponse = await safeSdk2.approveTransactionHash(txHash) @@ -96,11 +98,12 @@ describe('Transactions execution', () => { safeAddress: safe.address, contractNetworks }) - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: safe.address, value: '0', data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) await chai .expect(safeSdk1.executeTransaction(tx)) .to.be.rejectedWith('There are 2 signatures missing') @@ -125,11 +128,12 @@ describe('Transactions execution', () => { to: safe.address, value: BigNumber.from('1000000000000000000') // 1 ETH }) - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '500000000000000000', // 0.5 ETH data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) const rejectTx = await safeSdk1.createRejectionTransaction(tx.data.nonce) await safeSdk1.signTransaction(rejectTx) const txRejectResponse = await safeSdk2.executeTransaction(rejectTx) @@ -155,11 +159,12 @@ describe('Transactions execution', () => { value: BigNumber.from('1000000000000000000') // 1 ETH }) const safeInitialBalance = await safeSdk1.getBalance() - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '500000000000000000', // 0.5 ETH data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) const txResponse = await safeSdk1.executeTransaction(tx) await waitSafeTxReceipt(txResponse) const safeFinalBalance = await safeSdk1.getBalance() @@ -187,11 +192,12 @@ describe('Transactions execution', () => { value: BigNumber.from('1000000000000000000') // 1 ETH }) const safeInitialBalance = await safeSdk1.getBalance() - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '500000000000000000', // 0.5 ETH data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) await safeSdk1.signTransaction(tx) const txHash = await safeSdk2.getTransactionHash(tx) const txResponse1 = await safeSdk2.approveTransactionHash(txHash) @@ -222,11 +228,12 @@ describe('Transactions execution', () => { value: BigNumber.from('1000000000000000000') // 1 ETH }) const safeInitialBalance = await safeSdk1.getBalance() - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '500000000000000000', // 0.5 ETH data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) await safeSdk1.signTransaction(tx) const txHash = await safeSdk2.getTransactionHash(tx) const txResponse1 = await safeSdk2.approveTransactionHash(txHash) @@ -253,11 +260,12 @@ describe('Transactions execution', () => { to: safe.address, value: BigNumber.from('1000000000000000000') // 1 ETH }) - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '500000000000000000', // 0.5 ETH data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) const execOptions: TransactionOptions = { gasLimit: 123456 } const txResponse = await safeSdk1.executeTransaction(tx, execOptions) await waitSafeTxReceipt(txResponse) @@ -280,11 +288,12 @@ describe('Transactions execution', () => { to: safe.address, value: BigNumber.from('1000000000000000000') // 1 ETH }) - const tx = await safeSdk1.createTransaction({ + const txDataPartial: SafeTransactionDataPartial = { to: account2.address, value: '500000000000000000', // 0.5 ETH data: '0x' - }) + } + const tx = await safeSdk1.createTransaction(txDataPartial) const execOptions: TransactionOptions = { gasLimit: 123456, gasPrice: 170000000 @@ -318,7 +327,7 @@ describe('Transactions execution', () => { value: BigNumber.from('2000000000000000000') // 2 ETH }) const safeInitialBalance = await safeSdk1.getBalance() - const txs: SafeTransactionDataPartial[] = [ + const txs: MetaTransactionData[] = [ { to: account2.address, value: '1100000000000000000', // 1.1 ETH @@ -330,7 +339,7 @@ describe('Transactions execution', () => { data: '0x' } ] - const multiSendTx = await safeSdk1.createTransaction(...txs) + const multiSendTx = await safeSdk1.createTransaction(txs) await safeSdk1.signTransaction(multiSendTx) const txHash = await safeSdk2.getTransactionHash(multiSendTx) const txResponse1 = await safeSdk2.approveTransactionHash(txHash) @@ -369,7 +378,7 @@ describe('Transactions execution', () => { const accountInitialERC20Balance = await erc20Mintable.balanceOf(account2.address) chai.expect(accountInitialERC20Balance.toString()).to.be.eq('0') // 0 ERC20 - const txs: SafeTransactionDataPartial[] = [ + const txs: MetaTransactionData[] = [ { to: erc20Mintable.address, value: '0', @@ -387,7 +396,7 @@ describe('Transactions execution', () => { ]) } ] - const multiSendTx = await safeSdk1.createTransaction(...txs) + const multiSendTx = await safeSdk1.createTransaction(txs) await safeSdk1.signTransaction(multiSendTx) const txHash = await safeSdk2.getTransactionHash(multiSendTx) const txResponse1 = await safeSdk2.approveTransactionHash(txHash) diff --git a/packages/safe-core-sdk/tests/module-manager.test.ts b/packages/safe-core-sdk/tests/module-manager.test.ts index cab8bbd0e..092c0ff1f 100644 --- a/packages/safe-core-sdk/tests/module-manager.test.ts +++ b/packages/safe-core-sdk/tests/module-manager.test.ts @@ -1,7 +1,7 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' -import Safe, { CallTransactionOptionalProps, ContractNetworksConfig } from '../src' +import Safe, { ContractNetworksConfig, SafeTransactionOptionalProps } from '../src' import { SENTINEL_ADDRESS, ZERO_ADDRESS } from '../src/utils/constants' import { getDailyLimitModule, @@ -139,7 +139,7 @@ describe('Safe modules manager', () => { safeAddress: safe.address, contractNetworks }) - const options: CallTransactionOptionalProps = { + const options: SafeTransactionOptionalProps = { baseGas: 111, gasPrice: 222, gasToken: '0x333', @@ -245,7 +245,7 @@ describe('Safe modules manager', () => { chai.expect((await safeSdk.getModules()).length).to.be.eq(1) chai.expect(await safeSdk.isModuleEnabled(dailyLimitModule.address)).to.be.true - const options: CallTransactionOptionalProps = { + const options: SafeTransactionOptionalProps = { baseGas: 111, gasPrice: 222, gasToken: '0x333', diff --git a/packages/safe-core-sdk/tests/owner-manager.test.ts b/packages/safe-core-sdk/tests/owner-manager.test.ts index e2f25c5ba..481172a0a 100644 --- a/packages/safe-core-sdk/tests/owner-manager.test.ts +++ b/packages/safe-core-sdk/tests/owner-manager.test.ts @@ -1,7 +1,7 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' -import Safe, { CallTransactionOptionalProps, ContractNetworksConfig } from '../src' +import Safe, { ContractNetworksConfig, SafeTransactionOptionalProps } from '../src' import { SENTINEL_ADDRESS, ZERO_ADDRESS } from '../src/utils/constants' import { getFactory, @@ -184,7 +184,7 @@ describe('Safe owners manager', () => { safeAddress: safe.address, contractNetworks }) - const options: CallTransactionOptionalProps = { + const options: SafeTransactionOptionalProps = { baseGas: 111, gasPrice: 222, gasToken: '0x333', @@ -348,7 +348,7 @@ describe('Safe owners manager', () => { safeAddress: safe.address, contractNetworks }) - const options: CallTransactionOptionalProps = { + const options: SafeTransactionOptionalProps = { baseGas: 111, gasPrice: 222, gasToken: '0x333', @@ -614,7 +614,7 @@ describe('Safe owners manager', () => { safeAddress: safe.address, contractNetworks }) - const options: CallTransactionOptionalProps = { + const options: SafeTransactionOptionalProps = { baseGas: 111, gasPrice: 222, gasToken: '0x333', diff --git a/packages/safe-core-sdk/tests/threshold.test.ts b/packages/safe-core-sdk/tests/threshold.test.ts index 4f273d3ff..2f5f89749 100644 --- a/packages/safe-core-sdk/tests/threshold.test.ts +++ b/packages/safe-core-sdk/tests/threshold.test.ts @@ -1,7 +1,7 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { deployments, waffle } from 'hardhat' -import Safe, { CallTransactionOptionalProps, ContractNetworksConfig } from '../src' +import Safe, { ContractNetworksConfig, SafeTransactionOptionalProps } from '../src' import { getFactory, getMultiSend, @@ -92,7 +92,7 @@ describe('Safe Threshold', () => { }) const newThreshold = 2 chai.expect(await safeSdk.getThreshold()).to.be.not.eq(newThreshold) - const options: CallTransactionOptionalProps = { + const options: SafeTransactionOptionalProps = { baseGas: 111, gasPrice: 222, gasToken: '0x333', From aa925394cdf10571831ac3471f0f88152363e493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 27 Sep 2021 18:39:15 +0200 Subject: [PATCH 10/39] Add createTransaction with options tests --- .../tests/create-transaction.test.ts | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 packages/safe-core-sdk/tests/create-transaction.test.ts diff --git a/packages/safe-core-sdk/tests/create-transaction.test.ts b/packages/safe-core-sdk/tests/create-transaction.test.ts new file mode 100644 index 000000000..c23bc05f8 --- /dev/null +++ b/packages/safe-core-sdk/tests/create-transaction.test.ts @@ -0,0 +1,120 @@ +import { MetaTransactionData, SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import { deployments, waffle } from 'hardhat' +import Safe, { ContractNetworksConfig, SafeTransactionOptionalProps } from '../src' +import { + getERC20Mintable, + getFactory, + getMultiSend, + getSafeSingleton, + getSafeWithOwners +} from './utils/setupContracts' +import { getEthAdapter } from './utils/setupEthAdapter' +import { getAccounts } from './utils/setupTestNetwork' + +chai.use(chaiAsPromised) + +describe('Transactions creation', () => { + const setupTests = deployments.createFixture(async ({ deployments }) => { + await deployments.fixture() + const accounts = await getAccounts() + const chainId: number = (await waffle.provider.getNetwork()).chainId + const contractNetworks: ContractNetworksConfig = { + [chainId]: { + multiSendAddress: (await getMultiSend()).address, + safeMasterCopyAddress: (await getSafeSingleton()).address, + safeProxyFactoryAddress: (await getFactory()).address + } + } + return { + erc20Mintable: await getERC20Mintable(), + safe: await getSafeWithOwners([accounts[0].address, accounts[1].address]), + accounts, + chainId, + contractNetworks + } + }) + + describe('createTransaction', async () => { + it('should create a transaction', async () => { + const { accounts, contractNetworks } = await setupTests() + const [account1, account2] = accounts + const safe = await getSafeWithOwners([account1.address]) + const ethAdapter = await getEthAdapter(account1.signer) + const safeSdk = await Safe.create({ + ethAdapter, + safeAddress: safe.address, + contractNetworks + }) + const txDataPartial: SafeTransactionDataPartial = { + to: account2.address, + value: '500000000000000000', // 0.5 ETH + data: '0x', + baseGas: 111, + gasPrice: 222, + gasToken: '0x333', + refundReceiver: '0x444', + nonce: 555, + safeTxGas: 666 + } + const tx = await safeSdk.createTransaction(txDataPartial) + chai.expect(tx.data.to).to.be.eq(account2.address) + chai.expect(tx.data.value).to.be.eq('500000000000000000') + chai.expect(tx.data.data).to.be.eq('0x') + chai.expect(tx.data.baseGas).to.be.eq(111) + chai.expect(tx.data.gasPrice).to.be.eq(222) + chai.expect(tx.data.gasToken).to.be.eq('0x333') + chai.expect(tx.data.refundReceiver).to.be.eq('0x444') + chai.expect(tx.data.nonce).to.be.eq(555) + chai.expect(tx.data.safeTxGas).to.be.eq(666) + }) + + it('should create a MultiSend transaction with options', async () => { + const { accounts, contractNetworks, erc20Mintable, chainId } = await setupTests() + const [account1, account2] = accounts + const safe = await getSafeWithOwners([account1.address]) + const ethAdapter = await getEthAdapter(account1.signer) + const safeSdk = await Safe.create({ + ethAdapter, + safeAddress: safe.address, + contractNetworks + }) + const options: SafeTransactionOptionalProps = { + baseGas: 111, + gasPrice: 222, + gasToken: '0x333', + refundReceiver: '0x444', + nonce: 555, + safeTxGas: 666 + } + const txs: MetaTransactionData[] = [ + { + to: erc20Mintable.address, + value: '0', + data: erc20Mintable.interface.encodeFunctionData('transfer', [ + account2.address, + '1100000000000000000' // 1.1 ERC20 + ]) + }, + { + to: erc20Mintable.address, + value: '0', + data: erc20Mintable.interface.encodeFunctionData('transfer', [ + account2.address, + '100000000000000000' // 0.1 ERC20 + ]) + } + ] + const multiSendTx = await safeSdk.createTransaction(txs, options) + chai.expect(multiSendTx.data.to).to.be.eq(contractNetworks[chainId].multiSendAddress) + chai.expect(multiSendTx.data.value).to.be.eq('0') + chai.expect(multiSendTx.data.baseGas).to.be.eq(111) + chai.expect(multiSendTx.data.gasPrice).to.be.eq(222) + chai.expect(multiSendTx.data.gasToken).to.be.eq('0x333') + chai.expect(multiSendTx.data.refundReceiver).to.be.eq('0x444') + chai.expect(multiSendTx.data.nonce).to.be.eq(555) + chai.expect(multiSendTx.data.safeTxGas).to.be.eq(666) + }) + }) +}) From ec4232ca987e01f739b1e8068c436d4995f55df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 27 Sep 2021 19:00:36 +0200 Subject: [PATCH 11/39] Update readme with createTransaction changes --- packages/safe-core-sdk/README.md | 75 ++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/packages/safe-core-sdk/README.md b/packages/safe-core-sdk/README.md index 816ba00e1..bf7786ae5 100644 --- a/packages/safe-core-sdk/README.md +++ b/packages/safe-core-sdk/README.md @@ -260,35 +260,74 @@ const isOwner = await safeSdk.isOwner(address) ### createTransaction -Returns a Safe transaction ready to be signed by the owners and executed. Batched transactions are allowed if more than one transaction is added to the array of transactions. +Returns a Safe transaction ready to be signed by the owners and executed. -Each of the transactions provided as input to this function must be an object with the following properties: +```js +const transaction: SafeTransactionDataPartial = { + to, + data, + value, + operation, // Optional + safeTxGas, // Optional + baseGas, // Optional + gasPrice, // Optional + gasToken, // Optional + refundReceiver, // Optional + nonce // Optional +} +const safeTransaction = await safeSdk.createTransaction(transaction) +``` -* `to`: Required. -* `data`: Required. -* `value`: Required. -* `operation`: Optional. `OperationType.Call` (0) is the default value. -* `safeTxGas`: Optional. The right gas estimation is the default value. -* `baseGas`: Optional. 0 is the default value. -* `gasPrice`: Optional. 0 is the default value. -* `gasToken`: Optional. 0x address is the default value. -* `refundReceiver`: Optional. 0x address is the default value. -* `nonce`: Optional. The current Safe nonce is the default value. +Batched transactions are allowed if more than one transaction are passed as an array of transactions. -Read more about the [Safe transaction properties](https://docs.gnosis.io/safe/docs/contracts_tx_execution/). +```js +const transactions: MetaTransactionData[] = [ + { + to, + data, + value, + operation // Optional + }, + // ... +] +const safeTransaction = await safeSdk.createTransaction(transactions) +``` + +This method can also receive the `options` parameter to set the optional properties in the MultiSend transaction: ```js -const transactions: SafeTransactionDataPartial[] = [ +const transactions: MetaTransactionData[] = [ { - to: '0x
', - data: '0x', - value: '' + to, + data, + value, + operation }, // ... ] -const safeTransaction = await safeSdk.createTransaction(...transactions) +const options: SafeTransactionOptionalProps = { + safeTxGas, // Optional + baseGas, // Optional + gasPrice, // Optional + gasToken, // Optional + refundReceiver, // Optional + nonce // Optional +} +const safeTransaction = await safeSdk.createTransaction(transactions, options) ``` +If the optional properties are not manually set, the Safe transaction returned will have the default value for each one: + +* `operation`: `OperationType.Call` (0) is the default value. +* `safeTxGas`: The right gas estimation is the default value. +* `baseGas`: 0 is the default value. +* `gasPrice`: 0 is the default value. +* `gasToken`: 0x address is the default value. +* `refundReceiver`: 0x address is the default value. +* `nonce`: The current Safe nonce is the default value. + +Read more about the [Safe transaction properties](https://docs.gnosis.io/safe/docs/contracts_tx_execution/). + ### createRejectionTransaction Returns a Safe transaction ready to be signed by the owners that invalidates the pending Safe transaction/s with a specific nonce. From 2f103f0e8e997c38b6c4cc32c93f6a5488e4ca02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 28 Sep 2021 10:40:28 +0200 Subject: [PATCH 12/39] Rename test files --- .../tests/{contract-manager.test.ts => contractManager.test.ts} | 0 .../{create-transaction.test.ts => createTransaction.test.ts} | 0 .../tests/{module-manager.test.ts => moduleManager.test.ts} | 0 .../{off-chain-signatures.test.ts => offChainSignatures.test.ts} | 0 .../{on-chain-signatures.test.ts => onChainSignatures.test.ts} | 0 .../tests/{owner-manager.test.ts => ownerManager.test.ts} | 0 .../tests/{safe-factory.test.ts => safeFactory.test.ts} | 0 .../tests/{utils-signatures.test.ts => utilsSignatures.test.ts} | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename packages/safe-core-sdk/tests/{contract-manager.test.ts => contractManager.test.ts} (100%) rename packages/safe-core-sdk/tests/{create-transaction.test.ts => createTransaction.test.ts} (100%) rename packages/safe-core-sdk/tests/{module-manager.test.ts => moduleManager.test.ts} (100%) rename packages/safe-core-sdk/tests/{off-chain-signatures.test.ts => offChainSignatures.test.ts} (100%) rename packages/safe-core-sdk/tests/{on-chain-signatures.test.ts => onChainSignatures.test.ts} (100%) rename packages/safe-core-sdk/tests/{owner-manager.test.ts => ownerManager.test.ts} (100%) rename packages/safe-core-sdk/tests/{safe-factory.test.ts => safeFactory.test.ts} (100%) rename packages/safe-core-sdk/tests/{utils-signatures.test.ts => utilsSignatures.test.ts} (100%) diff --git a/packages/safe-core-sdk/tests/contract-manager.test.ts b/packages/safe-core-sdk/tests/contractManager.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/contract-manager.test.ts rename to packages/safe-core-sdk/tests/contractManager.test.ts diff --git a/packages/safe-core-sdk/tests/create-transaction.test.ts b/packages/safe-core-sdk/tests/createTransaction.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/create-transaction.test.ts rename to packages/safe-core-sdk/tests/createTransaction.test.ts diff --git a/packages/safe-core-sdk/tests/module-manager.test.ts b/packages/safe-core-sdk/tests/moduleManager.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/module-manager.test.ts rename to packages/safe-core-sdk/tests/moduleManager.test.ts diff --git a/packages/safe-core-sdk/tests/off-chain-signatures.test.ts b/packages/safe-core-sdk/tests/offChainSignatures.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/off-chain-signatures.test.ts rename to packages/safe-core-sdk/tests/offChainSignatures.test.ts diff --git a/packages/safe-core-sdk/tests/on-chain-signatures.test.ts b/packages/safe-core-sdk/tests/onChainSignatures.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/on-chain-signatures.test.ts rename to packages/safe-core-sdk/tests/onChainSignatures.test.ts diff --git a/packages/safe-core-sdk/tests/owner-manager.test.ts b/packages/safe-core-sdk/tests/ownerManager.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/owner-manager.test.ts rename to packages/safe-core-sdk/tests/ownerManager.test.ts diff --git a/packages/safe-core-sdk/tests/safe-factory.test.ts b/packages/safe-core-sdk/tests/safeFactory.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/safe-factory.test.ts rename to packages/safe-core-sdk/tests/safeFactory.test.ts diff --git a/packages/safe-core-sdk/tests/utils-signatures.test.ts b/packages/safe-core-sdk/tests/utilsSignatures.test.ts similarity index 100% rename from packages/safe-core-sdk/tests/utils-signatures.test.ts rename to packages/safe-core-sdk/tests/utilsSignatures.test.ts From 057ce0af42de2a90ff31e57abffb70e6cde8ce47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 28 Sep 2021 13:11:48 +0200 Subject: [PATCH 13/39] Update type SafeTransactionOptionalProps --- .../safe-core-sdk/src/utils/transactions/types.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/safe-core-sdk/src/utils/transactions/types.ts b/packages/safe-core-sdk/src/utils/transactions/types.ts index 4a481284e..df0057cca 100644 --- a/packages/safe-core-sdk/src/utils/transactions/types.ts +++ b/packages/safe-core-sdk/src/utils/transactions/types.ts @@ -1,14 +1,11 @@ import { ContractTransaction } from '@ethersproject/contracts' +import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types/dist/src' import { PromiEvent, TransactionReceipt } from 'web3-core/types' -export interface SafeTransactionOptionalProps { - safeTxGas?: number - baseGas?: number - gasPrice?: number - gasToken?: string - refundReceiver?: string - nonce?: number -} +export type SafeTransactionOptionalProps = Pick< + SafeTransactionDataPartial, + 'safeTxGas' | 'baseGas' | 'gasPrice' | 'gasToken' | 'refundReceiver' | 'nonce' +> export interface TransactionOptions { from?: string From badd61df123a439ff8ae88407cbc28e411e0fe7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 4 Oct 2021 17:54:39 +0200 Subject: [PATCH 14/39] Fix path in scripts --- packages/safe-ethers-adapters/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/safe-ethers-adapters/package.json b/packages/safe-ethers-adapters/package.json index f2223ac7d..c9519ebb0 100644 --- a/packages/safe-ethers-adapters/package.json +++ b/packages/safe-ethers-adapters/package.json @@ -14,8 +14,8 @@ "unbuild": "rimraf dist *.tsbuildinfo", "build": "tsc", "test": "nyc mocha -r ts-node/register tests/**/*.test.ts", - "example:creation": "ts-node example/creation.ts", - "example:interaction": "ts-node example/interaction.ts", + "example:creation": "ts-node examples/creation.ts", + "example:interaction": "ts-node examples/interaction.ts", "coverage": "nyc report --reporter=text-lcov | coveralls", "format": "prettier --write \"{src,tests}/**/*.ts\"", "lint": "tslint -p tsconfig.json", From 6fa3847c8cedd8ba4109846919a638df833d0abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 4 Oct 2021 23:56:46 +0200 Subject: [PATCH 15/39] Update e2e tests --- .../safe-service-client/e2e/getIncomingTransactions.test.ts | 4 ++-- .../safe-service-client/e2e/getMultisigTransactions.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts index a8ad76dd1..39e3d85db 100644 --- a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts @@ -27,8 +27,8 @@ describe('getIncomingTransactions', () => { const transferListResponse: TransferListResponse = await serviceSdk.getIncomingTransactions( safeAddress ) - chai.expect(transferListResponse.count).to.be.equal(5) - chai.expect(transferListResponse.results.length).to.be.equal(5) + chai.expect(transferListResponse.count).to.be.equal(6) + chai.expect(transferListResponse.results.length).to.be.equal(6) transferListResponse.results.map((transaction: TransferResponse) => { chai.expect(transaction.to).to.be.equal(safeAddress) }) diff --git a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts index 4d7bb0146..86d47ecf2 100644 --- a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts +++ b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts @@ -29,8 +29,8 @@ describe('getMultisigTransactions', () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' const safeMultisigTransactionListResponse: SafeMultisigTransactionListResponse = await serviceSdk.getMultisigTransactions(safeAddress) - chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(1) - chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(1) + chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(3) + chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(3) safeMultisigTransactionListResponse.results.map( (transaction: SafeMultisigTransactionResponse) => { chai.expect(transaction.safe).to.be.equal(safeAddress) From 2b85636eec9ad23cb76540e98c15cd972983dbc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 29 Sep 2021 01:59:44 +0200 Subject: [PATCH 16/39] Handle endpoint to remove all Safe delegates --- packages/safe-service-client/README.md | 8 ++++++++ .../src/SafeServiceClient.ts | 19 +++++++++++++++++++ .../tests/endpoint.test.ts | 10 ++++++++++ 3 files changed, 37 insertions(+) diff --git a/packages/safe-service-client/README.md b/packages/safe-service-client/README.md index 167b0f2f9..2c317ca51 100644 --- a/packages/safe-service-client/README.md +++ b/packages/safe-service-client/README.md @@ -115,6 +115,14 @@ Adds a new delegate for a given Safe address. The signature is calculated by sig await safeService.addSafeDelegate(safeAddress, delegate) ``` +### removeAllSafeDelegates + +Removes all delegates for a given Safe address. The signature is calculated by signing this hash: `keccak(address + str(int(current_epoch / 3600)))`. + +```js +await safeService.removeAllSafeDelegates(safeAddress) +``` + ### removeSafeDelegate Removes a delegate for a given Safe address. The signature is calculated by signing this hash: `keccak(address + str(int(current_epoch / 3600)))`. diff --git a/packages/safe-service-client/src/SafeServiceClient.ts b/packages/safe-service-client/src/SafeServiceClient.ts index 23dfb5682..71615ff76 100644 --- a/packages/safe-service-client/src/SafeServiceClient.ts +++ b/packages/safe-service-client/src/SafeServiceClient.ts @@ -220,6 +220,25 @@ class SafeServiceClient implements SafeTransactionService { }) } + /** + * Removes all delegates for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). + * + * @param safeAddress - The Safe address + * @returns + * @throws "Invalid Safe address" + * @throws "Malformed data" + * @throws "Invalid Ethereum address/Error processing data" + */ + async removeAllSafeDelegates(safeAddress: string): Promise { + if (safeAddress === '') { + throw new Error('Invalid Safe address') + } + return sendRequest({ + url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/`, + method: HttpMethod.Delete + }) + } + /** * Removes a delegate for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). * diff --git a/packages/safe-service-client/tests/endpoint.test.ts b/packages/safe-service-client/tests/endpoint.test.ts index c9ee72fdc..79033b50f 100644 --- a/packages/safe-service-client/tests/endpoint.test.ts +++ b/packages/safe-service-client/tests/endpoint.test.ts @@ -137,6 +137,16 @@ describe('Endpoint tests', () => { }) }) + it('removeAllSafeDelegates', async () => { + chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress)) + .to.be.eventually.deep.equals({ success: true }) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, + method: 'delete' + }) + }) + it('removeSafeDelegate', async () => { const delegate: SafeDelegateDelete = { safe: safeAddress, From e33d45bd015a9e49fdbb3856071ec27314b92e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 6 Oct 2021 13:22:02 +0200 Subject: [PATCH 17/39] Add hardhat --- .../safe-service-client/hardhat.config.ts | 52 +++++++++++++++++++ packages/safe-service-client/package.json | 6 ++- 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/safe-service-client/hardhat.config.ts diff --git a/packages/safe-service-client/hardhat.config.ts b/packages/safe-service-client/hardhat.config.ts new file mode 100644 index 000000000..dbdea6cf3 --- /dev/null +++ b/packages/safe-service-client/hardhat.config.ts @@ -0,0 +1,52 @@ +import '@nomiclabs/hardhat-ethers' +import '@nomiclabs/hardhat-waffle' +import dotenv from 'dotenv' +import { HardhatUserConfig, HttpNetworkUserConfig } from 'hardhat/types' +import yargs from 'yargs' + +const argv = yargs + .option('network', { + type: 'string', + default: 'hardhat', + }) + .help(false) + .version(false).argv + +dotenv.config() +const { INFURA_KEY, MNEMONIC, PK } = process.env +const DEFAULT_MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect' + +const sharedNetworkConfig: HttpNetworkUserConfig = {} +if (PK) { + sharedNetworkConfig.accounts = [PK]; +} else { + sharedNetworkConfig.accounts = { + mnemonic: MNEMONIC || DEFAULT_MNEMONIC, + } +} + +if (['rinkeby'].includes(argv.network) && INFURA_KEY === undefined) { + throw new Error( + `Could not find Infura key in env, unable to connect to network ${argv.network}`, + ) +} + +const config: HardhatUserConfig = { + defaultNetwork: "rinkeby", + paths: { + tests: 'e2e' + }, + networks: { + hardhat: { + allowUnlimitedContractSize: true, + blockGasLimit: 100000000, + gas: 100000000 + }, + rinkeby: { + ...sharedNetworkConfig, + url: `https://rinkeby.infura.io/v3/${INFURA_KEY}`, + } + } +} + +export default config diff --git a/packages/safe-service-client/package.json b/packages/safe-service-client/package.json index 35392ab1e..cbcb80504 100644 --- a/packages/safe-service-client/package.json +++ b/packages/safe-service-client/package.json @@ -14,7 +14,7 @@ "unbuild": "rimraf dist", "build": "yarn rimraf dist && tsc", "test": "mocha --require ts-node/register tests/**/*.test.ts", - "test:ci": "mocha --require ts-node/register e2e/**/*.test.ts", + "test:ci": "nyc hardhat test", "format": "prettier --write \"{src,tests,e2e}/**/*.ts\"", "lint": "tslint -p tsconfig.json" }, @@ -33,7 +33,8 @@ ], "homepage": "https://github.com/gnosis/safe-core-sdk#readme", "devDependencies": { - "@gnosis.pm/safe-core-sdk": "^0.3.1", + "@nomiclabs/hardhat-ethers": "^2.0.2", + "@nomiclabs/hardhat-waffle": "^2.0.1", "@types/chai": "^4.2.22", "@types/chai-as-promised": "^7.1.4", "@types/mocha": "^9.0.0", @@ -45,6 +46,7 @@ "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", + "hardhat": "^2.3.3", "husky": "^7.0.2", "lint-staged": "^11.1.2", "mocha": "^9.1.1", From 0c1240a4b2c5e4499c4c1b549d160c5058761e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 6 Oct 2021 13:26:28 +0200 Subject: [PATCH 18/39] Update e2e tests config --- packages/safe-service-client/e2e/config.ts | 3 ++- packages/safe-service-client/e2e/decodeData.test.ts | 2 +- packages/safe-service-client/e2e/getBalances.test.ts | 2 +- packages/safe-service-client/e2e/getCollectibles.test.ts | 2 +- .../safe-service-client/e2e/getIncomingTransactions.test.ts | 2 +- .../safe-service-client/e2e/getMultisigTransactions.test.ts | 2 +- .../safe-service-client/e2e/getPendingTransactions.test.ts | 2 +- packages/safe-service-client/e2e/getSafeInfo.test.ts | 2 +- packages/safe-service-client/e2e/getSafesByOwner.test.ts | 2 +- packages/safe-service-client/e2e/getServiceInfo.test.ts | 2 +- .../safe-service-client/e2e/getServiceMastercopiesInfo.test.ts | 2 +- packages/safe-service-client/e2e/getToken.test.ts | 2 +- packages/safe-service-client/e2e/getTokenList.test.ts | 2 +- packages/safe-service-client/e2e/getTransaction.test.ts | 2 +- .../e2e/getTransactionConfirmations.test.ts | 2 +- packages/safe-service-client/e2e/getUsdBalances.test.ts | 2 +- 16 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/safe-service-client/e2e/config.ts b/packages/safe-service-client/e2e/config.ts index 015a20506..bb3b8fe3a 100644 --- a/packages/safe-service-client/e2e/config.ts +++ b/packages/safe-service-client/e2e/config.ts @@ -1,5 +1,6 @@ const config = { - baseUrl: 'https://safe-transaction.rinkeby.staging.gnosisdev.com' + BASE_URL: 'https://safe-transaction.staging.gnosisdev.com', + JSON_RPC: `https://rinkeby.infura.io/v3/${process.env.INFURA_KEY}` } export default config diff --git a/packages/safe-service-client/e2e/decodeData.test.ts b/packages/safe-service-client/e2e/decodeData.test.ts index e8d35f2d5..e0c9eaa75 100644 --- a/packages/safe-service-client/e2e/decodeData.test.ts +++ b/packages/safe-service-client/e2e/decodeData.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('decodeData', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if data is empty', async () => { const data = '' diff --git a/packages/safe-service-client/e2e/getBalances.test.ts b/packages/safe-service-client/e2e/getBalances.test.ts index df73cab86..ebf59efd0 100644 --- a/packages/safe-service-client/e2e/getBalances.test.ts +++ b/packages/safe-service-client/e2e/getBalances.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getBalances', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' diff --git a/packages/safe-service-client/e2e/getCollectibles.test.ts b/packages/safe-service-client/e2e/getCollectibles.test.ts index 516171fa3..bc667224d 100644 --- a/packages/safe-service-client/e2e/getCollectibles.test.ts +++ b/packages/safe-service-client/e2e/getCollectibles.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getCollectibles', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' diff --git a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts index 39e3d85db..8d7e87b46 100644 --- a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getIncomingTransactions', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' diff --git a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts index 86d47ecf2..91509d32b 100644 --- a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts +++ b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts @@ -9,7 +9,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getMultisigTransactions', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' diff --git a/packages/safe-service-client/e2e/getPendingTransactions.test.ts b/packages/safe-service-client/e2e/getPendingTransactions.test.ts index 8871b915a..a92ae51de 100644 --- a/packages/safe-service-client/e2e/getPendingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getPendingTransactions.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getPendingTransactions', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if safeAddress is empty', async () => { const safeAddress = '' diff --git a/packages/safe-service-client/e2e/getSafeInfo.test.ts b/packages/safe-service-client/e2e/getSafeInfo.test.ts index 5843a3fc9..5e9124c03 100644 --- a/packages/safe-service-client/e2e/getSafeInfo.test.ts +++ b/packages/safe-service-client/e2e/getSafeInfo.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getSafeInfo', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' diff --git a/packages/safe-service-client/e2e/getSafesByOwner.test.ts b/packages/safe-service-client/e2e/getSafesByOwner.test.ts index f5098c555..f003fb751 100644 --- a/packages/safe-service-client/e2e/getSafesByOwner.test.ts +++ b/packages/safe-service-client/e2e/getSafesByOwner.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getSafesByOwner', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if owner address is empty', async () => { const ownerAddress = '' diff --git a/packages/safe-service-client/e2e/getServiceInfo.test.ts b/packages/safe-service-client/e2e/getServiceInfo.test.ts index 9a31bf8b6..432a4ecf2 100644 --- a/packages/safe-service-client/e2e/getServiceInfo.test.ts +++ b/packages/safe-service-client/e2e/getServiceInfo.test.ts @@ -3,7 +3,7 @@ import SafeServiceClient, { SafeServiceInfoResponse } from '../src' import config from './config' describe('getServiceInfo', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should return the Safe info', async () => { const safeInfo: SafeServiceInfoResponse = await serviceSdk.getServiceInfo() diff --git a/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts b/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts index f1cccb320..0d97fbeee 100644 --- a/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts +++ b/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts @@ -3,7 +3,7 @@ import SafeServiceClient, { MasterCopyResponse } from '../src' import config from './config' describe('getServiceMasterCopiesInfo', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should call getServiceMasterCopiesInfo', async () => { const masterCopiesResponse: MasterCopyResponse[] = await serviceSdk.getServiceMasterCopiesInfo() diff --git a/packages/safe-service-client/e2e/getToken.test.ts b/packages/safe-service-client/e2e/getToken.test.ts index 762b8e812..3c9eb7865 100644 --- a/packages/safe-service-client/e2e/getToken.test.ts +++ b/packages/safe-service-client/e2e/getToken.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getToken', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if token address is empty', async () => { const tokenAddress = '' diff --git a/packages/safe-service-client/e2e/getTokenList.test.ts b/packages/safe-service-client/e2e/getTokenList.test.ts index 32a91ed73..33630f990 100644 --- a/packages/safe-service-client/e2e/getTokenList.test.ts +++ b/packages/safe-service-client/e2e/getTokenList.test.ts @@ -3,7 +3,7 @@ import SafeServiceClient, { TokenInfoListResponse } from '../src' import config from './config' describe('getTokenList', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should return an array of tokens', async () => { const tokenInfoListResponse: TokenInfoListResponse = await serviceSdk.getTokenList() diff --git a/packages/safe-service-client/e2e/getTransaction.test.ts b/packages/safe-service-client/e2e/getTransaction.test.ts index a2d4db30a..b4346e3e3 100644 --- a/packages/safe-service-client/e2e/getTransaction.test.ts +++ b/packages/safe-service-client/e2e/getTransaction.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getTransaction', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if safeTxHash is empty', async () => { const safeTxHash = '' diff --git a/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts b/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts index 3d32184ad..806d0cc86 100644 --- a/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts +++ b/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getTransactionConfirmations', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if safeTxHash is empty', async () => { const safeTxHash = '' diff --git a/packages/safe-service-client/e2e/getUsdBalances.test.ts b/packages/safe-service-client/e2e/getUsdBalances.test.ts index 0f9fd92ea..a20c3b131 100644 --- a/packages/safe-service-client/e2e/getUsdBalances.test.ts +++ b/packages/safe-service-client/e2e/getUsdBalances.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('getUsdBalances', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' From c95b4ef74c603fe0ad551a864139603f47559c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 6 Oct 2021 13:27:34 +0200 Subject: [PATCH 19/39] Handle Safe delegates --- .../src/SafeServiceClient.ts | 83 +++++++++++++------ .../src/SafeTransactionService.ts | 9 +- .../src/types/safeTransactionServiceTypes.ts | 12 ++- 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/packages/safe-service-client/src/SafeServiceClient.ts b/packages/safe-service-client/src/SafeServiceClient.ts index 71615ff76..a55b892df 100644 --- a/packages/safe-service-client/src/SafeServiceClient.ts +++ b/packages/safe-service-client/src/SafeServiceClient.ts @@ -1,3 +1,4 @@ +import { Signer } from '@ethersproject/abstract-signer' import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import SafeTransactionService from './SafeTransactionService' import { @@ -11,7 +12,8 @@ import { SafeCollectiblesOptions, SafeCreationInfoResponse, SafeDelegate, - SafeDelegateDelete, + SafeDelegateConfig, + SafeDelegateDeleteConfig, SafeDelegateListResponse, SafeInfoResponse, SafeModuleTransactionListResponse, @@ -186,8 +188,7 @@ class SafeServiceClient implements SafeTransactionService { * @param safeAddress - The Safe address * @returns The list of delegates * @throws "Invalid Safe address" - * @throws "Invalid data" - * @throws "Invalid ethereum address" + * @throws "Checksum address validation failed" */ async getSafeDelegates(safeAddress: string): Promise { if (safeAddress === '') { @@ -200,63 +201,97 @@ class SafeServiceClient implements SafeTransactionService { } /** - * Adds a new delegate for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). + * Adds a new delegate for a given Safe address. * * @param safeAddress - The Safe address - * @param delegate - The new delegate + * @param delegateConfig - The configuration of the new delegate * @returns * @throws "Invalid Safe address" - * @throws "Malformed data" - * @throws "Invalid Ethereum address/Error processing data" + * @throws "Invalid Safe delegate address" + * @throws "Checksum address validation failed" + * @throws "Address is not checksumed" + * @throws "Safe= does not exist or it's still not indexed" + * @throws "Signing owner is not an owner of the Safe" */ - async addSafeDelegate(safeAddress: string, delegate: SafeDelegate): Promise { - if (safeAddress === '') { + async addSafeDelegate(delegateConfig: SafeDelegateConfig): Promise { + const { safe, delegate, label, signer } = delegateConfig + if (safe === '') { throw new Error('Invalid Safe address') } + if (delegate === '') { + throw new Error('Invalid Safe delegate address') + } + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = delegate + totp + const signature = await signer.signMessage(data) + const body: SafeDelegate = { + safe, + delegate, + label, + signature + } return sendRequest({ - url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/`, + url: `${this.#txServiceBaseUrl}/safes/${safe}/delegates/`, method: HttpMethod.Post, - body: delegate + body }) } /** - * Removes all delegates for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). + * Removes all delegates for a given Safe address. * * @param safeAddress - The Safe address * @returns * @throws "Invalid Safe address" - * @throws "Malformed data" - * @throws "Invalid Ethereum address/Error processing data" + * @throws "Checksum address validation failed" + * @throws "Safe= does not exist or it's still not indexed" + * @throws "Signing owner is not an owner of the Safe" */ - async removeAllSafeDelegates(safeAddress: string): Promise { + async removeAllSafeDelegates(safeAddress: string, signer: Signer): Promise { if (safeAddress === '') { throw new Error('Invalid Safe address') } + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = safeAddress + totp + const signature = await signer.signMessage(data) return sendRequest({ url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/`, - method: HttpMethod.Delete + method: HttpMethod.Delete, + body: { signature } }) } /** - * Removes a delegate for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). + * Removes a delegate for a given Safe address. * * @param safeAddress - The Safe address - * @param delegate - The delegate that will be removed + * @param delegateConfig - The configuration for the delegate that will be removed * @returns * @throws "Invalid Safe address" - * @throws "Malformed data" - * @throws "Invalid Ethereum address/Error processing data" + * @throws "Invalid Safe delegate address" + * @throws "Checksum address validation failed" + * @throws "Signing owner is not an owner of the Safe" + * @throws "Not found" */ - async removeSafeDelegate(safeAddress: string, delegate: SafeDelegateDelete): Promise { - if (safeAddress === '') { + async removeSafeDelegate(delegateConfig: SafeDelegateDeleteConfig): Promise { + const { safe, delegate, signer } = delegateConfig + if (safe === '') { throw new Error('Invalid Safe address') } + if (delegate === '') { + throw new Error('Invalid Safe delegate address') + } + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = delegate + totp + const signature = await signer.signMessage(data) return sendRequest({ - url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/${delegate.delegate}`, + url: `${this.#txServiceBaseUrl}/safes/${safe}/delegates/${delegate}`, method: HttpMethod.Delete, - body: delegate + body: { + safe, + delegate, + signature + } }) } diff --git a/packages/safe-service-client/src/SafeTransactionService.ts b/packages/safe-service-client/src/SafeTransactionService.ts index 81d117c15..2618c3fec 100644 --- a/packages/safe-service-client/src/SafeTransactionService.ts +++ b/packages/safe-service-client/src/SafeTransactionService.ts @@ -1,3 +1,4 @@ +import { Signer } from '@ethersproject/abstract-signer' import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import { MasterCopyResponse, @@ -10,7 +11,8 @@ import { SafeCollectiblesOptions, SafeCreationInfoResponse, SafeDelegate, - SafeDelegateDelete, + SafeDelegateConfig, + SafeDelegateDeleteConfig, SafeDelegateListResponse, SafeInfoResponse, SafeModuleTransactionListResponse, @@ -45,8 +47,9 @@ interface SafeTransactionService { // Safes getSafeInfo(safeAddress: string): Promise getSafeDelegates(safeAddress: string): Promise - addSafeDelegate(safeAddress: string, delegate: SafeDelegate): Promise - removeSafeDelegate(safeAddress: string, delegate: SafeDelegateDelete): Promise + addSafeDelegate(config: SafeDelegateConfig): Promise + removeSafeDelegate(config: SafeDelegateDeleteConfig): Promise + removeAllSafeDelegates(safeAddress: string, signer: Signer): Promise // Transactions getSafeCreationInfo(safeAddress: string): Promise diff --git a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts index 4c6d37eea..d1cd9a81c 100644 --- a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts +++ b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts @@ -1,3 +1,5 @@ +import { Signer } from '@ethersproject/abstract-signer' + export type SafeServiceInfoResponse = { readonly name: string readonly version: string @@ -49,17 +51,21 @@ export type SafeCreationInfoResponse = { readonly dataDecoded?: string } -export type SafeDelegate = { +export type SafeDelegateDeleteConfig = { readonly safe: string readonly delegate: string - readonly signature: string + readonly signer: Signer +} + +export type SafeDelegateConfig = SafeDelegateDeleteConfig & { readonly label: string } -export type SafeDelegateDelete = { +export type SafeDelegate = { readonly safe: string readonly delegate: string readonly signature: string + readonly label: string } export type SafeDelegateResponse = { From 38f15f7187dc508d07d76acbf6958e2d61e04ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 6 Oct 2021 13:28:02 +0200 Subject: [PATCH 20/39] Update sendRequest to handle API responses --- .../safe-service-client/src/utils/httpRequests.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/safe-service-client/src/utils/httpRequests.ts b/packages/safe-service-client/src/utils/httpRequests.ts index d7f226cf3..e6aee087a 100644 --- a/packages/safe-service-client/src/utils/httpRequests.ts +++ b/packages/safe-service-client/src/utils/httpRequests.ts @@ -21,12 +21,16 @@ export async function sendRequest({ url, method, body }: HttpRequest): Promis }, body: JSON.stringify(body) }) + let jsonResponse try { jsonResponse = await response.json() } catch (error) { - throw new Error(response.statusText) + if (!response.ok) { + throw new Error(response.statusText) + } } + if (response.ok) { return jsonResponse as T } @@ -39,5 +43,11 @@ export async function sendRequest({ url, method, body }: HttpRequest): Promis if (jsonResponse.message) { throw new Error(jsonResponse.message) } + if (jsonResponse.nonFieldErrors) { + throw new Error(jsonResponse.nonFieldErrors) + } + if (jsonResponse.delegate) { + throw new Error(jsonResponse.delegate) + } throw new Error(response.statusText) } From 098405b55aac573b84ff239300e221818817811c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 6 Oct 2021 13:28:38 +0200 Subject: [PATCH 21/39] Add e2e tests for handling delegates --- .../e2e/addSafeDelegate.test.ts | 154 +++++++++++++++++ .../e2e/getSafeDelegates.test.ts | 77 +++++++++ .../e2e/removeAllSafeDelegates.test.ts | 90 ++++++++++ .../e2e/removeSafeDelegate.test.ts | 158 ++++++++++++++++++ 4 files changed, 479 insertions(+) create mode 100644 packages/safe-service-client/e2e/addSafeDelegate.test.ts create mode 100644 packages/safe-service-client/e2e/getSafeDelegates.test.ts create mode 100644 packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts create mode 100644 packages/safe-service-client/e2e/removeSafeDelegate.test.ts diff --git a/packages/safe-service-client/e2e/addSafeDelegate.test.ts b/packages/safe-service-client/e2e/addSafeDelegate.test.ts new file mode 100644 index 000000000..241976f6d --- /dev/null +++ b/packages/safe-service-client/e2e/addSafeDelegate.test.ts @@ -0,0 +1,154 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { SafeDelegate, SafeDelegateConfig } from '../src' +import config from './config' +chai.use(chaiAsPromised) + +describe('addSafeDelegate', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe delegate address is empty', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe delegate address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe delegate address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith(`Address ${delegateAddress} is not checksumed`) + }) + + it('should fail if Safe does not exist', async () => { + const safeAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith(`Safe=${safeAddress} does not exist or it's still not indexed`) + }) + + it('should fail if the signer is not an owner of the Safe', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773', // Not a Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Signing owner is not an owner of the Safe') + }) + + it('should add a new delegate', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + + const { results: initialDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(initialDelegates.length).to.be.eq(0) + + const delegateResponse: SafeDelegate = await serviceSdk.addSafeDelegate(delegateConfig) + chai.expect(delegateResponse.safe).to.be.equal(delegateConfig.safe) + chai.expect(delegateResponse.delegate).to.be.equal(delegateConfig.delegate) + chai.expect(delegateResponse.signature).to.be.a('string') + chai.expect(delegateResponse.label).to.be.equal(delegateConfig.label) + + const { results: finalDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(finalDelegates.length).to.be.eq(1) + await serviceSdk.removeSafeDelegate(delegateConfig) + }) +}) diff --git a/packages/safe-service-client/e2e/getSafeDelegates.test.ts b/packages/safe-service-client/e2e/getSafeDelegates.test.ts new file mode 100644 index 000000000..3a691563a --- /dev/null +++ b/packages/safe-service-client/e2e/getSafeDelegates.test.ts @@ -0,0 +1,77 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { + SafeDelegateConfig, + SafeDelegateListResponse, + SafeDelegateResponse +} from '../src' +import config from './config' + +chai.use(chaiAsPromised) + +describe('getSafeDelegates', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + await chai + .expect(serviceSdk.getSafeDelegates(safeAddress)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + await chai + .expect(serviceSdk.getSafeDelegates(safeAddress)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should return an empty array if the Safe address is not found', async () => { + const safeAddress = '0x11dBF28A2B46bdD4E284e79e28B2E8b94Cfa39Bc' + const safeDelegateListResponse: SafeDelegateListResponse = await serviceSdk.getSafeDelegates( + safeAddress + ) + const results: SafeDelegateResponse[] = safeDelegateListResponse.results + chai.expect(results).to.be.empty + }) + + it('should return an array of delegates', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + + const delegateConfig1: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', + signer, + label: 'Label1' + } + const delegateConfig2: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', + signer, + label: 'Label2' + } + await serviceSdk.addSafeDelegate(delegateConfig1) + await serviceSdk.addSafeDelegate(delegateConfig2) + + const safeDelegateListResponse: SafeDelegateListResponse = await serviceSdk.getSafeDelegates( + safeAddress + ) + const { results } = safeDelegateListResponse + chai.expect(results.length).to.be.eq(2) + chai.expect(results[0].delegate).to.be.eq(delegateConfig1.delegate) + chai.expect(results[0].delegator).to.be.eq(await delegateConfig1.signer.getAddress()) + chai.expect(results[0].label).to.be.eq(delegateConfig1.label) + chai.expect(results[1].delegate).to.be.eq(delegateConfig2.delegate) + chai.expect(results[1].delegator).to.be.eq(await delegateConfig2.signer.getAddress()) + chai.expect(results[1].label).to.be.eq(delegateConfig2.label) + + await serviceSdk.removeAllSafeDelegates(safeAddress, signer) + }) +}) diff --git a/packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts b/packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts new file mode 100644 index 000000000..3a38ec6c9 --- /dev/null +++ b/packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts @@ -0,0 +1,90 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { SafeDelegateConfig } from '../src' +import config from './config' +chai.use(chaiAsPromised) + +describe('removeAllSafeDelegates', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe does not exist', async () => { + const safeAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith(`Safe=${safeAddress} does not exist or it's still not indexed`) + }) + + it('should fail if the signer is not an owner of the Safe', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773', // Not a Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith('Signing owner is not an owner of the Safe') + }) + + it('should remove all delegates', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + + const delegateConfig1: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', + signer, + label: 'Label1' + } + const delegateConfig2: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', + signer, + label: 'Label2' + } + await serviceSdk.addSafeDelegate(delegateConfig1) + await serviceSdk.addSafeDelegate(delegateConfig2) + const { results: initialDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(initialDelegates.length).to.be.eq(2) + + await serviceSdk.removeAllSafeDelegates(safeAddress, signer) + + const { results: finalDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(finalDelegates.length).to.be.eq(0) + }) +}) diff --git a/packages/safe-service-client/e2e/removeSafeDelegate.test.ts b/packages/safe-service-client/e2e/removeSafeDelegate.test.ts new file mode 100644 index 000000000..b215d63d4 --- /dev/null +++ b/packages/safe-service-client/e2e/removeSafeDelegate.test.ts @@ -0,0 +1,158 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { SafeDelegateDeleteConfig } from '../src' +import config from './config' +chai.use(chaiAsPromised) + +describe('removeSafeDelegate', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe delegate address is empty', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe delegate address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe delegate address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe does not exist', async () => { + const safeAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith(`Safe=${safeAddress} does not exist or it's still not indexed`) + }) + + it('should fail if the signer is not an owner of the Safe', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773', // Not a Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Signing owner is not an owner of the Safe') + }) + + it('should fail if the delegate to remove is not a delegate', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai.expect(serviceSdk.removeSafeDelegate(delegateConfig)).to.be.rejectedWith('Not found') + }) + + it('should remove a delegate', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + + await serviceSdk.addSafeDelegate({ ...delegateConfig, label: 'Label' }) + const { results: initialDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(initialDelegates.length).to.be.eq(1) + + await serviceSdk.removeSafeDelegate(delegateConfig) + + const { results: finalDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(finalDelegates.length).to.be.eq(0) + }) +}) From 33401e2447efe08c9ad9262072814f81e19f8270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 6 Oct 2021 13:28:52 +0200 Subject: [PATCH 22/39] Add endpoint test to handle delegates --- .../tests/endpoint.test.ts | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/packages/safe-service-client/tests/endpoint.test.ts b/packages/safe-service-client/tests/endpoint.test.ts index 79033b50f..b5b9e4183 100644 --- a/packages/safe-service-client/tests/endpoint.test.ts +++ b/packages/safe-service-client/tests/endpoint.test.ts @@ -1,14 +1,17 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import sinon from 'sinon' import sinonChai from 'sinon-chai' +import config from '../e2e/config' import SafeServiceClient, { SafeBalancesOptions, SafeBalancesUsdOptions, SafeCollectiblesOptions, - SafeDelegate, - SafeDelegateDelete, + SafeDelegateConfig, + SafeDelegateDeleteConfig, SafeMultisigTransactionEstimate } from '../src' import { getTxServiceBaseUrl } from '../src/utils' @@ -122,14 +125,19 @@ describe('Endpoint tests', () => { }) it('addSafeDelegate', async () => { - const delegate: SafeDelegate = { + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { safe: safeAddress, delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', - signature: '0x', + signer, label: '' } chai - .expect(serviceSdk.addSafeDelegate(safeAddress, delegate)) + .expect(serviceSdk.addSafeDelegate(delegateConfig)) .to.be.eventually.deep.equals({ success: true }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, @@ -138,30 +146,52 @@ describe('Endpoint tests', () => { }) it('removeAllSafeDelegates', async () => { + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = safeAddress + totp + const signature = await signer.signMessage(data) chai - .expect(serviceSdk.removeAllSafeDelegates(safeAddress)) + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) .to.be.eventually.deep.equals({ success: true }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, - method: 'delete' + method: 'delete', + body: { signature } }) }) it('removeSafeDelegate', async () => { - const delegate: SafeDelegateDelete = { + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegate = '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b' + const delegateConfig: SafeDelegateDeleteConfig = { safe: safeAddress, - delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', - signature: '0x' + delegate, + signer } + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = delegate + totp + const signature = await signer.signMessage(data) chai - .expect(serviceSdk.removeSafeDelegate(safeAddress, delegate)) + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) .to.be.eventually.deep.equals({ success: true }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/${ - delegate.delegate + delegateConfig.delegate }`, method: 'delete', - body: delegate + body: { + safe: delegateConfig.safe, + delegate: delegateConfig.delegate, + signature + } }) }) From a755d5c126c4ed0f597045f30cf0c22a3e11aad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 7 Oct 2021 14:15:30 +0200 Subject: [PATCH 23/39] Update e2e tests --- .../e2e/getIncomingTransactions.test.ts | 15 ++++++++++++--- .../e2e/getMultisigTransactions.test.ts | 14 +++++++++++--- .../e2e/getPendingTransactions.test.ts | 12 ++++++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts index 8d7e87b46..b76e4bc2b 100644 --- a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts @@ -22,13 +22,22 @@ describe('getIncomingTransactions', () => { .to.be.rejectedWith('Checksum address validation failed') }) + it('should return an empty list if there are no incoming transactions', async () => { + const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without incoming transactions + const transferListResponse: TransferListResponse = await serviceSdk.getIncomingTransactions( + safeAddress + ) + chai.expect(transferListResponse.count).to.be.equal(0) + chai.expect(transferListResponse.results.length).to.be.equal(0) + }) + it('should return the list of incoming transactions', async () => { - const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with incoming transactions const transferListResponse: TransferListResponse = await serviceSdk.getIncomingTransactions( safeAddress ) - chai.expect(transferListResponse.count).to.be.equal(6) - chai.expect(transferListResponse.results.length).to.be.equal(6) + chai.expect(transferListResponse.count).to.be.equal(10) + chai.expect(transferListResponse.results.length).to.be.equal(10) transferListResponse.results.map((transaction: TransferResponse) => { chai.expect(transaction.to).to.be.equal(safeAddress) }) diff --git a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts index 91509d32b..4ee249e1d 100644 --- a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts +++ b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts @@ -25,12 +25,20 @@ describe('getMultisigTransactions', () => { .to.be.rejectedWith('Checksum address validation failed') }) + it('should return an empty list if there are no multisig transactions', async () => { + const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without multisig transactions + const safeMultisigTransactionListResponse: SafeMultisigTransactionListResponse = + await serviceSdk.getMultisigTransactions(safeAddress) + chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(0) + chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(0) + }) + it('should return the list of multisig transactions', async () => { - const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with multisig transactions const safeMultisigTransactionListResponse: SafeMultisigTransactionListResponse = await serviceSdk.getMultisigTransactions(safeAddress) - chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(3) - chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(3) + chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(11) + chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(11) safeMultisigTransactionListResponse.results.map( (transaction: SafeMultisigTransactionResponse) => { chai.expect(transaction.safe).to.be.equal(safeAddress) diff --git a/packages/safe-service-client/e2e/getPendingTransactions.test.ts b/packages/safe-service-client/e2e/getPendingTransactions.test.ts index a92ae51de..037e2f12c 100644 --- a/packages/safe-service-client/e2e/getPendingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getPendingTransactions.test.ts @@ -22,11 +22,19 @@ describe('getPendingTransactions', () => { .to.be.rejectedWith('Checksum address validation failed') }) - it('should return the transaction with the given safeAddress', async () => { - const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + it('should return an empty list if there are no pending transactions', async () => { + const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without pending transaction const transactionList: SafeMultisigTransactionListResponse = await serviceSdk.getPendingTransactions(safeAddress) chai.expect(transactionList.count).to.be.equal(0) chai.expect(transactionList.results.length).to.be.equal(0) }) + + it('should return the the transaction list', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with pending transaction + const transactionList: SafeMultisigTransactionListResponse = + await serviceSdk.getPendingTransactions(safeAddress) + chai.expect(transactionList.count).to.be.equal(2) + chai.expect(transactionList.results.length).to.be.equal(2) + }) }) From 6ed8a5c9b985a1f15a93f4a44974e3aacb77c4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 7 Oct 2021 14:31:59 +0200 Subject: [PATCH 24/39] Fix endpoint tests --- .../tests/endpoint.test.ts | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/safe-service-client/tests/endpoint.test.ts b/packages/safe-service-client/tests/endpoint.test.ts index b5b9e4183..61913ec13 100644 --- a/packages/safe-service-client/tests/endpoint.test.ts +++ b/packages/safe-service-client/tests/endpoint.test.ts @@ -28,11 +28,11 @@ describe('Endpoint tests', () => { const fetchData = sinon .stub(httpRequests, 'sendRequest') - .returns(Promise.resolve({ data: { success: true } })) + .returns(Promise.resolve({ data: { success: true }})) describe('', () => { it('getServiceInfo', async () => { - chai.expect(serviceSdk.getServiceInfo()).to.be.eventually.deep.equals({ success: true }) + await chai.expect(serviceSdk.getServiceInfo()).to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about`, method: 'get' @@ -40,9 +40,9 @@ describe('Endpoint tests', () => { }) it('getServiceMasterCopiesInfo', async () => { - chai + await chai .expect(serviceSdk.getServiceMasterCopiesInfo()) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about/master-copies`, method: 'get' @@ -51,7 +51,7 @@ describe('Endpoint tests', () => { it('decodeData', async () => { const data = '0x610b592500000000000000000000000090F8bf6A479f320ead074411a4B0e7944Ea8c9C1' - chai.expect(serviceSdk.decodeData(data)).to.be.eventually.deep.equals({ success: true }) + await chai.expect(serviceSdk.decodeData(data)).to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/data-decoder/`, method: 'post', @@ -60,9 +60,9 @@ describe('Endpoint tests', () => { }) it('getSafesByOwner', async () => { - chai + await chai .expect(serviceSdk.getSafesByOwner(ownerAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/owners/${ownerAddress}/safes/`, method: 'get' @@ -70,9 +70,9 @@ describe('Endpoint tests', () => { }) it('getTransaction', async () => { - chai + await chai .expect(serviceSdk.getTransaction(safeTxHash)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/multisig-transactions/${safeTxHash}/`, method: 'get' @@ -80,9 +80,9 @@ describe('Endpoint tests', () => { }) it('getTransactionConfirmations', async () => { - chai + await chai .expect(serviceSdk.getTransactionConfirmations(safeTxHash)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -93,9 +93,9 @@ describe('Endpoint tests', () => { it('confirmTransaction', async () => { const signature = '0x' - chai + await chai .expect(serviceSdk.confirmTransaction(safeTxHash, signature)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -105,9 +105,9 @@ describe('Endpoint tests', () => { }) it('getSafeInfo', async () => { - chai + await chai .expect(serviceSdk.getSafeInfo(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/`, method: 'get' @@ -115,9 +115,9 @@ describe('Endpoint tests', () => { }) it('getSafeDelegates', async () => { - chai + await chai .expect(serviceSdk.getSafeDelegates(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'get' @@ -136,9 +136,9 @@ describe('Endpoint tests', () => { signer, label: '' } - chai + await chai .expect(serviceSdk.addSafeDelegate(delegateConfig)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'get' @@ -154,9 +154,9 @@ describe('Endpoint tests', () => { const totp = Math.floor(Date.now() / 1000 / 3600) const data = safeAddress + totp const signature = await signer.signMessage(data) - chai + await chai .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'delete', @@ -179,9 +179,9 @@ describe('Endpoint tests', () => { const totp = Math.floor(Date.now() / 1000 / 3600) const data = delegate + totp const signature = await signer.signMessage(data) - chai + await chai .expect(serviceSdk.removeSafeDelegate(delegateConfig)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/${ delegateConfig.delegate @@ -196,9 +196,9 @@ describe('Endpoint tests', () => { }) it('getSafeCreationInfo', async () => { - chai + await chai .expect(serviceSdk.getSafeCreationInfo(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/creation/`, method: 'get' @@ -212,9 +212,9 @@ describe('Endpoint tests', () => { data: '0x', operation: 0 } - chai + await chai .expect(serviceSdk.estimateSafeTransaction(safeAddress, safeTransaction)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -243,9 +243,9 @@ describe('Endpoint tests', () => { staticPart: () => '', dynamicPart: () => '' } - chai + await chai .expect(serviceSdk.proposeTransaction(safeAddress, safeTxData, safeTxHash, signature)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, method: 'post', @@ -259,9 +259,9 @@ describe('Endpoint tests', () => { }) it('getIncomingTransactions', async () => { - chai + await chai .expect(serviceSdk.getIncomingTransactions(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/incoming-transfers/`, method: 'get' @@ -269,9 +269,9 @@ describe('Endpoint tests', () => { }) it('getModuleTransactions', async () => { - chai + await chai .expect(serviceSdk.getModuleTransactions(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/module-transfers/`, method: 'get' @@ -279,9 +279,9 @@ describe('Endpoint tests', () => { }) it('getMultisigTransactions', async () => { - chai + await chai .expect(serviceSdk.getMultisigTransactions(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, method: 'get' @@ -290,9 +290,9 @@ describe('Endpoint tests', () => { it('getPendingTransactions', async () => { const currentNonce = 1 - chai + await chai .expect(serviceSdk.getPendingTransactions(safeAddress, currentNonce)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -302,9 +302,9 @@ describe('Endpoint tests', () => { }) it('getBalances', async () => { - chai + await chai .expect(serviceSdk.getBalances(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -317,9 +317,9 @@ describe('Endpoint tests', () => { const options: SafeBalancesOptions = { excludeSpamTokens: false } - chai + await chai .expect(serviceSdk.getBalances(safeAddress, options)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -329,9 +329,9 @@ describe('Endpoint tests', () => { }) it('getUsdBalances', async () => { - chai + await chai .expect(serviceSdk.getUsdBalances(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -344,9 +344,9 @@ describe('Endpoint tests', () => { const options: SafeBalancesUsdOptions = { excludeSpamTokens: false } - chai + await chai .expect(serviceSdk.getUsdBalances(safeAddress, options)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -356,9 +356,9 @@ describe('Endpoint tests', () => { }) it('getCollectibles', async () => { - chai + await chai .expect(serviceSdk.getCollectibles(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -371,9 +371,9 @@ describe('Endpoint tests', () => { const options: SafeCollectiblesOptions = { excludeSpamTokens: false } - chai + await chai .expect(serviceSdk.getCollectibles(safeAddress, options)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -383,7 +383,7 @@ describe('Endpoint tests', () => { }) it('getTokens', async () => { - chai.expect(serviceSdk.getTokenList()).to.be.eventually.deep.equals({ success: true }) + await chai.expect(serviceSdk.getTokenList()).to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/`, method: 'get' @@ -392,7 +392,7 @@ describe('Endpoint tests', () => { it('getToken', async () => { const tokenAddress = '0x' - chai.expect(serviceSdk.getToken(tokenAddress)).to.be.eventually.deep.equals({ success: true }) + await chai.expect(serviceSdk.getToken(tokenAddress)).to.be.eventually.deep.equals({ data: { success: true }}) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/${tokenAddress}/`, method: 'get' From 954fa9a266f3f79831f51aef9ed116268bf883ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 7 Oct 2021 16:56:04 +0200 Subject: [PATCH 25/39] Remove unneeded types --- .../e2e/addSafeDelegate.test.ts | 4 +- .../e2e/getBalances.test.ts | 4 +- .../e2e/getCollectibles.test.ts | 8 +-- .../e2e/getIncomingTransactions.test.ts | 12 ++-- .../e2e/getMultisigTransactions.test.ts | 23 +++---- .../e2e/getPendingTransactions.test.ts | 8 +-- .../e2e/getSafeDelegates.test.ts | 16 ++--- .../e2e/getSafeInfo.test.ts | 4 +- .../e2e/getSafesByOwner.test.ts | 6 +- .../e2e/getServiceInfo.test.ts | 4 +- .../e2e/getServiceMastercopiesInfo.test.ts | 6 +- .../safe-service-client/e2e/getToken.test.ts | 4 +- .../e2e/getTokenList.test.ts | 4 +- .../e2e/getTransaction.test.ts | 4 +- .../e2e/getTransactionConfirmations.test.ts | 8 +-- .../e2e/getUsdBalances.test.ts | 4 +- .../tests/endpoint.test.ts | 64 +++++++++++-------- 17 files changed, 85 insertions(+), 98 deletions(-) diff --git a/packages/safe-service-client/e2e/addSafeDelegate.test.ts b/packages/safe-service-client/e2e/addSafeDelegate.test.ts index 241976f6d..0ae05b07b 100644 --- a/packages/safe-service-client/e2e/addSafeDelegate.test.ts +++ b/packages/safe-service-client/e2e/addSafeDelegate.test.ts @@ -2,7 +2,7 @@ import { getDefaultProvider } from '@ethersproject/providers' import { Wallet } from '@ethersproject/wallet' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeDelegate, SafeDelegateConfig } from '../src' +import SafeServiceClient, { SafeDelegateConfig } from '../src' import config from './config' chai.use(chaiAsPromised) @@ -141,7 +141,7 @@ describe('addSafeDelegate', () => { const { results: initialDelegates } = await serviceSdk.getSafeDelegates(safeAddress) chai.expect(initialDelegates.length).to.be.eq(0) - const delegateResponse: SafeDelegate = await serviceSdk.addSafeDelegate(delegateConfig) + const delegateResponse = await serviceSdk.addSafeDelegate(delegateConfig) chai.expect(delegateResponse.safe).to.be.equal(delegateConfig.safe) chai.expect(delegateResponse.delegate).to.be.equal(delegateConfig.delegate) chai.expect(delegateResponse.signature).to.be.a('string') diff --git a/packages/safe-service-client/e2e/getBalances.test.ts b/packages/safe-service-client/e2e/getBalances.test.ts index ebf59efd0..9b40e67a2 100644 --- a/packages/safe-service-client/e2e/getBalances.test.ts +++ b/packages/safe-service-client/e2e/getBalances.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeBalanceResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -24,7 +24,7 @@ describe('getBalances', () => { it('should return the list of balances', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const balances: SafeBalanceResponse[] = await serviceSdk.getBalances(safeAddress) + const balances = await serviceSdk.getBalances(safeAddress) chai.expect(balances.length).to.be.equal(2) const ethBalance = balances.filter((safeBalance) => !safeBalance.tokenAddress)[0] chai.expect(ethBalance.token).to.be.equal(null) diff --git a/packages/safe-service-client/e2e/getCollectibles.test.ts b/packages/safe-service-client/e2e/getCollectibles.test.ts index bc667224d..7c9d86a3e 100644 --- a/packages/safe-service-client/e2e/getCollectibles.test.ts +++ b/packages/safe-service-client/e2e/getCollectibles.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeCollectibleResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -24,11 +24,9 @@ describe('getCollectibles', () => { it('should return the list of collectibles', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const safeCollectibleResponse: SafeCollectibleResponse[] = await serviceSdk.getCollectibles( - safeAddress - ) + const safeCollectibleResponse = await serviceSdk.getCollectibles(safeAddress) chai.expect(safeCollectibleResponse.length).to.be.equal(2) - safeCollectibleResponse.map((safeCollectible: SafeCollectibleResponse) => { + safeCollectibleResponse.map((safeCollectible) => { chai.expect(safeCollectible.address).to.be.equal('0x9cf1A34D70261f0594823EFCCeed53C8c639c464') chai.expect(safeCollectible.tokenName).to.be.equal('Safe NFTs') chai.expect(safeCollectible.metadata.type).to.be.equal('ERC721') diff --git a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts index b76e4bc2b..61dbb0a5e 100644 --- a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { TransferListResponse, TransferResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -24,21 +24,17 @@ describe('getIncomingTransactions', () => { it('should return an empty list if there are no incoming transactions', async () => { const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without incoming transactions - const transferListResponse: TransferListResponse = await serviceSdk.getIncomingTransactions( - safeAddress - ) + const transferListResponse = await serviceSdk.getIncomingTransactions(safeAddress) chai.expect(transferListResponse.count).to.be.equal(0) chai.expect(transferListResponse.results.length).to.be.equal(0) }) it('should return the list of incoming transactions', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with incoming transactions - const transferListResponse: TransferListResponse = await serviceSdk.getIncomingTransactions( - safeAddress - ) + const transferListResponse = await serviceSdk.getIncomingTransactions(safeAddress) chai.expect(transferListResponse.count).to.be.equal(10) chai.expect(transferListResponse.results.length).to.be.equal(10) - transferListResponse.results.map((transaction: TransferResponse) => { + transferListResponse.results.map((transaction) => { chai.expect(transaction.to).to.be.equal(safeAddress) }) }) diff --git a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts index 4ee249e1d..b08c094c8 100644 --- a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts +++ b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts @@ -1,9 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { - SafeMultisigTransactionListResponse, - SafeMultisigTransactionResponse -} from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -27,22 +24,22 @@ describe('getMultisigTransactions', () => { it('should return an empty list if there are no multisig transactions', async () => { const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without multisig transactions - const safeMultisigTransactionListResponse: SafeMultisigTransactionListResponse = - await serviceSdk.getMultisigTransactions(safeAddress) + const safeMultisigTransactionListResponse = await serviceSdk.getMultisigTransactions( + safeAddress + ) chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(0) chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(0) }) it('should return the list of multisig transactions', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with multisig transactions - const safeMultisigTransactionListResponse: SafeMultisigTransactionListResponse = - await serviceSdk.getMultisigTransactions(safeAddress) + const safeMultisigTransactionListResponse = await serviceSdk.getMultisigTransactions( + safeAddress + ) chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(11) chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(11) - safeMultisigTransactionListResponse.results.map( - (transaction: SafeMultisigTransactionResponse) => { - chai.expect(transaction.safe).to.be.equal(safeAddress) - } - ) + safeMultisigTransactionListResponse.results.map((transaction) => { + chai.expect(transaction.safe).to.be.equal(safeAddress) + }) }) }) diff --git a/packages/safe-service-client/e2e/getPendingTransactions.test.ts b/packages/safe-service-client/e2e/getPendingTransactions.test.ts index 037e2f12c..db235fdf1 100644 --- a/packages/safe-service-client/e2e/getPendingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getPendingTransactions.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeMultisigTransactionListResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -24,16 +24,14 @@ describe('getPendingTransactions', () => { it('should return an empty list if there are no pending transactions', async () => { const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without pending transaction - const transactionList: SafeMultisigTransactionListResponse = - await serviceSdk.getPendingTransactions(safeAddress) + const transactionList = await serviceSdk.getPendingTransactions(safeAddress) chai.expect(transactionList.count).to.be.equal(0) chai.expect(transactionList.results.length).to.be.equal(0) }) it('should return the the transaction list', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with pending transaction - const transactionList: SafeMultisigTransactionListResponse = - await serviceSdk.getPendingTransactions(safeAddress) + const transactionList = await serviceSdk.getPendingTransactions(safeAddress) chai.expect(transactionList.count).to.be.equal(2) chai.expect(transactionList.results.length).to.be.equal(2) }) diff --git a/packages/safe-service-client/e2e/getSafeDelegates.test.ts b/packages/safe-service-client/e2e/getSafeDelegates.test.ts index 3a691563a..100be2205 100644 --- a/packages/safe-service-client/e2e/getSafeDelegates.test.ts +++ b/packages/safe-service-client/e2e/getSafeDelegates.test.ts @@ -2,11 +2,7 @@ import { getDefaultProvider } from '@ethersproject/providers' import { Wallet } from '@ethersproject/wallet' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { - SafeDelegateConfig, - SafeDelegateListResponse, - SafeDelegateResponse -} from '../src' +import SafeServiceClient, { SafeDelegateConfig } from '../src' import config from './config' chai.use(chaiAsPromised) @@ -30,10 +26,8 @@ describe('getSafeDelegates', () => { it('should return an empty array if the Safe address is not found', async () => { const safeAddress = '0x11dBF28A2B46bdD4E284e79e28B2E8b94Cfa39Bc' - const safeDelegateListResponse: SafeDelegateListResponse = await serviceSdk.getSafeDelegates( - safeAddress - ) - const results: SafeDelegateResponse[] = safeDelegateListResponse.results + const safeDelegateListResponse = await serviceSdk.getSafeDelegates(safeAddress) + const results = safeDelegateListResponse.results chai.expect(results).to.be.empty }) @@ -60,9 +54,7 @@ describe('getSafeDelegates', () => { await serviceSdk.addSafeDelegate(delegateConfig1) await serviceSdk.addSafeDelegate(delegateConfig2) - const safeDelegateListResponse: SafeDelegateListResponse = await serviceSdk.getSafeDelegates( - safeAddress - ) + const safeDelegateListResponse = await serviceSdk.getSafeDelegates(safeAddress) const { results } = safeDelegateListResponse chai.expect(results.length).to.be.eq(2) chai.expect(results[0].delegate).to.be.eq(delegateConfig1.delegate) diff --git a/packages/safe-service-client/e2e/getSafeInfo.test.ts b/packages/safe-service-client/e2e/getSafeInfo.test.ts index 5e9124c03..1826e8f2c 100644 --- a/packages/safe-service-client/e2e/getSafeInfo.test.ts +++ b/packages/safe-service-client/e2e/getSafeInfo.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeInfoResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -24,7 +24,7 @@ describe('getSafeInfo', () => { it('should return an empty array if the safeTxHash is not found', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const safeInfoResponse: SafeInfoResponse = await serviceSdk.getSafeInfo(safeAddress) + const safeInfoResponse = await serviceSdk.getSafeInfo(safeAddress) chai.expect(safeInfoResponse.address).to.be.equal(safeAddress) }) }) diff --git a/packages/safe-service-client/e2e/getSafesByOwner.test.ts b/packages/safe-service-client/e2e/getSafesByOwner.test.ts index f003fb751..394c06795 100644 --- a/packages/safe-service-client/e2e/getSafesByOwner.test.ts +++ b/packages/safe-service-client/e2e/getSafesByOwner.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { OwnerResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -24,14 +24,14 @@ describe('getSafesByOwner', () => { it('should return an empty array if there are no owned Safes', async () => { const ownerAddress = '0x0000000000000000000000000000000000000001' - const ownerResponse: OwnerResponse = await serviceSdk.getSafesByOwner(ownerAddress) + const ownerResponse = await serviceSdk.getSafesByOwner(ownerAddress) const { safes } = ownerResponse chai.expect(safes.length).to.be.equal(0) }) it('should return the array of owned Safes', async () => { const ownerAddress = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1' - const ownerResponse: OwnerResponse = await serviceSdk.getSafesByOwner(ownerAddress) + const ownerResponse = await serviceSdk.getSafesByOwner(ownerAddress) const { safes } = ownerResponse chai.expect(safes.length).to.be.greaterThan(1) }) diff --git a/packages/safe-service-client/e2e/getServiceInfo.test.ts b/packages/safe-service-client/e2e/getServiceInfo.test.ts index 432a4ecf2..2c2bbdd92 100644 --- a/packages/safe-service-client/e2e/getServiceInfo.test.ts +++ b/packages/safe-service-client/e2e/getServiceInfo.test.ts @@ -1,12 +1,12 @@ import { expect } from 'chai' -import SafeServiceClient, { SafeServiceInfoResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' describe('getServiceInfo', () => { const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should return the Safe info', async () => { - const safeInfo: SafeServiceInfoResponse = await serviceSdk.getServiceInfo() + const safeInfo = await serviceSdk.getServiceInfo() expect(safeInfo.api_version).to.be.equal('v1') }) }) diff --git a/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts b/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts index 0d97fbeee..e0009a2f0 100644 --- a/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts +++ b/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts @@ -1,14 +1,14 @@ import chai from 'chai' -import SafeServiceClient, { MasterCopyResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' describe('getServiceMasterCopiesInfo', () => { const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should call getServiceMasterCopiesInfo', async () => { - const masterCopiesResponse: MasterCopyResponse[] = await serviceSdk.getServiceMasterCopiesInfo() + const masterCopiesResponse = await serviceSdk.getServiceMasterCopiesInfo() chai.expect(masterCopiesResponse.length).to.be.greaterThan(1) - masterCopiesResponse.map((masterCopy: MasterCopyResponse) => { + masterCopiesResponse.map((masterCopy) => { chai.expect(masterCopy.deployer).to.be.equal('Gnosis') }) }) diff --git a/packages/safe-service-client/e2e/getToken.test.ts b/packages/safe-service-client/e2e/getToken.test.ts index 3c9eb7865..109290eda 100644 --- a/packages/safe-service-client/e2e/getToken.test.ts +++ b/packages/safe-service-client/e2e/getToken.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { TokenInfoResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -22,7 +22,7 @@ describe('getToken', () => { it('should return the token info', async () => { const tokenAddress = '0xc778417E063141139Fce010982780140Aa0cD5Ab' - const tokenInfoResponse: TokenInfoResponse = await serviceSdk.getToken(tokenAddress) + const tokenInfoResponse = await serviceSdk.getToken(tokenAddress) chai.expect(tokenInfoResponse.address).to.be.equal('0xc778417E063141139Fce010982780140Aa0cD5Ab') }) }) diff --git a/packages/safe-service-client/e2e/getTokenList.test.ts b/packages/safe-service-client/e2e/getTokenList.test.ts index 33630f990..f111827c4 100644 --- a/packages/safe-service-client/e2e/getTokenList.test.ts +++ b/packages/safe-service-client/e2e/getTokenList.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' -import SafeServiceClient, { TokenInfoListResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' describe('getTokenList', () => { const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should return an array of tokens', async () => { - const tokenInfoListResponse: TokenInfoListResponse = await serviceSdk.getTokenList() + const tokenInfoListResponse = await serviceSdk.getTokenList() chai.expect(tokenInfoListResponse.count).to.be.greaterThan(1) chai.expect(tokenInfoListResponse.results.length).to.be.greaterThan(1) }) diff --git a/packages/safe-service-client/e2e/getTransaction.test.ts b/packages/safe-service-client/e2e/getTransaction.test.ts index b4346e3e3..e9784e15c 100644 --- a/packages/safe-service-client/e2e/getTransaction.test.ts +++ b/packages/safe-service-client/e2e/getTransaction.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeMultisigTransactionResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -22,7 +22,7 @@ describe('getTransaction', () => { it('should return the transaction with the given safeTxHash', async () => { const safeTxHash = '0xb22be4e57718560c89de96acd1acefe55c2673b31a7019a374ebb1d8a2842f5d' - const transaction: SafeMultisigTransactionResponse = await serviceSdk.getTransaction(safeTxHash) + const transaction = await serviceSdk.getTransaction(safeTxHash) chai.expect(transaction.safeTxHash).to.be.equal(safeTxHash) }) }) diff --git a/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts b/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts index 806d0cc86..773ffaf1b 100644 --- a/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts +++ b/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeMultisigConfirmationListResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -17,16 +17,14 @@ describe('getTransactionConfirmations', () => { it('should return an empty array if the safeTxHash is not found', async () => { const safeTxHash = '0x' - const transactionConfirmations: SafeMultisigConfirmationListResponse = - await serviceSdk.getTransactionConfirmations(safeTxHash) + const transactionConfirmations = await serviceSdk.getTransactionConfirmations(safeTxHash) chai.expect(transactionConfirmations.count).to.be.equal(0) chai.expect(transactionConfirmations.results.length).to.be.equal(0) }) it('should return the transaction with the given safeTxHash', async () => { const safeTxHash = '0xb22be4e57718560c89de96acd1acefe55c2673b31a7019a374ebb1d8a2842f5d' - const transactionConfirmations: SafeMultisigConfirmationListResponse = - await serviceSdk.getTransactionConfirmations(safeTxHash) + const transactionConfirmations = await serviceSdk.getTransactionConfirmations(safeTxHash) chai.expect(transactionConfirmations.count).to.be.equal(2) chai.expect(transactionConfirmations.results.length).to.be.equal(2) }) diff --git a/packages/safe-service-client/e2e/getUsdBalances.test.ts b/packages/safe-service-client/e2e/getUsdBalances.test.ts index a20c3b131..77f7ffdcb 100644 --- a/packages/safe-service-client/e2e/getUsdBalances.test.ts +++ b/packages/safe-service-client/e2e/getUsdBalances.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeBalanceUsdResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) @@ -24,7 +24,7 @@ describe('getUsdBalances', () => { it('should return the list of USD balances', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const balances: SafeBalanceUsdResponse[] = await serviceSdk.getUsdBalances(safeAddress) + const balances = await serviceSdk.getUsdBalances(safeAddress) chai.expect(balances.length).to.be.equal(2) const ethBalance = balances.filter((safeBalance) => !safeBalance.tokenAddress)[0] chai.expect(ethBalance.token).to.be.equal(null) diff --git a/packages/safe-service-client/tests/endpoint.test.ts b/packages/safe-service-client/tests/endpoint.test.ts index 61913ec13..94095b9d8 100644 --- a/packages/safe-service-client/tests/endpoint.test.ts +++ b/packages/safe-service-client/tests/endpoint.test.ts @@ -28,11 +28,13 @@ describe('Endpoint tests', () => { const fetchData = sinon .stub(httpRequests, 'sendRequest') - .returns(Promise.resolve({ data: { success: true }})) + .returns(Promise.resolve({ data: { success: true } })) describe('', () => { it('getServiceInfo', async () => { - await chai.expect(serviceSdk.getServiceInfo()).to.be.eventually.deep.equals({ data: { success: true }}) + await chai + .expect(serviceSdk.getServiceInfo()) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about`, method: 'get' @@ -42,7 +44,7 @@ describe('Endpoint tests', () => { it('getServiceMasterCopiesInfo', async () => { await chai .expect(serviceSdk.getServiceMasterCopiesInfo()) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about/master-copies`, method: 'get' @@ -51,7 +53,9 @@ describe('Endpoint tests', () => { it('decodeData', async () => { const data = '0x610b592500000000000000000000000090F8bf6A479f320ead074411a4B0e7944Ea8c9C1' - await chai.expect(serviceSdk.decodeData(data)).to.be.eventually.deep.equals({ data: { success: true }}) + await chai + .expect(serviceSdk.decodeData(data)) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/data-decoder/`, method: 'post', @@ -62,7 +66,7 @@ describe('Endpoint tests', () => { it('getSafesByOwner', async () => { await chai .expect(serviceSdk.getSafesByOwner(ownerAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/owners/${ownerAddress}/safes/`, method: 'get' @@ -72,7 +76,7 @@ describe('Endpoint tests', () => { it('getTransaction', async () => { await chai .expect(serviceSdk.getTransaction(safeTxHash)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/multisig-transactions/${safeTxHash}/`, method: 'get' @@ -82,7 +86,7 @@ describe('Endpoint tests', () => { it('getTransactionConfirmations', async () => { await chai .expect(serviceSdk.getTransactionConfirmations(safeTxHash)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -95,7 +99,7 @@ describe('Endpoint tests', () => { const signature = '0x' await chai .expect(serviceSdk.confirmTransaction(safeTxHash, signature)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -107,7 +111,7 @@ describe('Endpoint tests', () => { it('getSafeInfo', async () => { await chai .expect(serviceSdk.getSafeInfo(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/`, method: 'get' @@ -117,7 +121,7 @@ describe('Endpoint tests', () => { it('getSafeDelegates', async () => { await chai .expect(serviceSdk.getSafeDelegates(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'get' @@ -138,7 +142,7 @@ describe('Endpoint tests', () => { } await chai .expect(serviceSdk.addSafeDelegate(delegateConfig)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'get' @@ -156,7 +160,7 @@ describe('Endpoint tests', () => { const signature = await signer.signMessage(data) await chai .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'delete', @@ -181,7 +185,7 @@ describe('Endpoint tests', () => { const signature = await signer.signMessage(data) await chai .expect(serviceSdk.removeSafeDelegate(delegateConfig)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/${ delegateConfig.delegate @@ -198,7 +202,7 @@ describe('Endpoint tests', () => { it('getSafeCreationInfo', async () => { await chai .expect(serviceSdk.getSafeCreationInfo(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/creation/`, method: 'get' @@ -214,7 +218,7 @@ describe('Endpoint tests', () => { } await chai .expect(serviceSdk.estimateSafeTransaction(safeAddress, safeTransaction)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -245,7 +249,7 @@ describe('Endpoint tests', () => { } await chai .expect(serviceSdk.proposeTransaction(safeAddress, safeTxData, safeTxHash, signature)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, method: 'post', @@ -261,7 +265,7 @@ describe('Endpoint tests', () => { it('getIncomingTransactions', async () => { await chai .expect(serviceSdk.getIncomingTransactions(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/incoming-transfers/`, method: 'get' @@ -271,7 +275,7 @@ describe('Endpoint tests', () => { it('getModuleTransactions', async () => { await chai .expect(serviceSdk.getModuleTransactions(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/module-transfers/`, method: 'get' @@ -281,7 +285,7 @@ describe('Endpoint tests', () => { it('getMultisigTransactions', async () => { await chai .expect(serviceSdk.getMultisigTransactions(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, method: 'get' @@ -292,7 +296,7 @@ describe('Endpoint tests', () => { const currentNonce = 1 await chai .expect(serviceSdk.getPendingTransactions(safeAddress, currentNonce)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -304,7 +308,7 @@ describe('Endpoint tests', () => { it('getBalances', async () => { await chai .expect(serviceSdk.getBalances(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -319,7 +323,7 @@ describe('Endpoint tests', () => { } await chai .expect(serviceSdk.getBalances(safeAddress, options)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -331,7 +335,7 @@ describe('Endpoint tests', () => { it('getUsdBalances', async () => { await chai .expect(serviceSdk.getUsdBalances(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -346,7 +350,7 @@ describe('Endpoint tests', () => { } await chai .expect(serviceSdk.getUsdBalances(safeAddress, options)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -358,7 +362,7 @@ describe('Endpoint tests', () => { it('getCollectibles', async () => { await chai .expect(serviceSdk.getCollectibles(safeAddress)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -373,7 +377,7 @@ describe('Endpoint tests', () => { } await chai .expect(serviceSdk.getCollectibles(safeAddress, options)) - .to.be.eventually.deep.equals({ data: { success: true }}) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -383,7 +387,9 @@ describe('Endpoint tests', () => { }) it('getTokens', async () => { - await chai.expect(serviceSdk.getTokenList()).to.be.eventually.deep.equals({ data: { success: true }}) + await chai + .expect(serviceSdk.getTokenList()) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/`, method: 'get' @@ -392,7 +398,9 @@ describe('Endpoint tests', () => { it('getToken', async () => { const tokenAddress = '0x' - await chai.expect(serviceSdk.getToken(tokenAddress)).to.be.eventually.deep.equals({ data: { success: true }}) + await chai + .expect(serviceSdk.getToken(tokenAddress)) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/${tokenAddress}/`, method: 'get' From 3646e7eda838b1cdfe176cede5b3638ddc916b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 7 Oct 2021 17:22:59 +0200 Subject: [PATCH 26/39] Handle delegates (#85) * Handle endpoint to remove all Safe delegates * Add hardhat * Update e2e tests config * Handle Safe delegates * Update sendRequest to handle API responses * Add e2e tests for handling delegates * Add endpoint test to handle delegates * Update e2e tests * Fix endpoint tests * Remove unneeded types --- packages/safe-service-client/README.md | 8 + .../e2e/addSafeDelegate.test.ts | 154 ++++++++++++++++ packages/safe-service-client/e2e/config.ts | 3 +- .../e2e/decodeData.test.ts | 2 +- .../e2e/getBalances.test.ts | 6 +- .../e2e/getCollectibles.test.ts | 10 +- .../e2e/getIncomingTransactions.test.ts | 23 ++- .../e2e/getMultisigTransactions.test.ts | 33 ++-- .../e2e/getPendingTransactions.test.ts | 18 +- .../e2e/getSafeDelegates.test.ts | 69 ++++++++ .../e2e/getSafeInfo.test.ts | 6 +- .../e2e/getSafesByOwner.test.ts | 8 +- .../e2e/getServiceInfo.test.ts | 6 +- .../e2e/getServiceMastercopiesInfo.test.ts | 8 +- .../safe-service-client/e2e/getToken.test.ts | 6 +- .../e2e/getTokenList.test.ts | 6 +- .../e2e/getTransaction.test.ts | 6 +- .../e2e/getTransactionConfirmations.test.ts | 10 +- .../e2e/getUsdBalances.test.ts | 6 +- .../e2e/removeAllSafeDelegates.test.ts | 90 ++++++++++ .../e2e/removeSafeDelegate.test.ts | 158 +++++++++++++++++ .../safe-service-client/hardhat.config.ts | 52 ++++++ packages/safe-service-client/package.json | 6 +- .../src/SafeServiceClient.ts | 90 ++++++++-- .../src/SafeTransactionService.ts | 9 +- .../src/types/safeTransactionServiceTypes.ts | 12 +- .../src/utils/httpRequests.ts | 12 +- .../tests/endpoint.test.ts | 166 +++++++++++------- 28 files changed, 825 insertions(+), 158 deletions(-) create mode 100644 packages/safe-service-client/e2e/addSafeDelegate.test.ts create mode 100644 packages/safe-service-client/e2e/getSafeDelegates.test.ts create mode 100644 packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts create mode 100644 packages/safe-service-client/e2e/removeSafeDelegate.test.ts create mode 100644 packages/safe-service-client/hardhat.config.ts diff --git a/packages/safe-service-client/README.md b/packages/safe-service-client/README.md index 167b0f2f9..2c317ca51 100644 --- a/packages/safe-service-client/README.md +++ b/packages/safe-service-client/README.md @@ -115,6 +115,14 @@ Adds a new delegate for a given Safe address. The signature is calculated by sig await safeService.addSafeDelegate(safeAddress, delegate) ``` +### removeAllSafeDelegates + +Removes all delegates for a given Safe address. The signature is calculated by signing this hash: `keccak(address + str(int(current_epoch / 3600)))`. + +```js +await safeService.removeAllSafeDelegates(safeAddress) +``` + ### removeSafeDelegate Removes a delegate for a given Safe address. The signature is calculated by signing this hash: `keccak(address + str(int(current_epoch / 3600)))`. diff --git a/packages/safe-service-client/e2e/addSafeDelegate.test.ts b/packages/safe-service-client/e2e/addSafeDelegate.test.ts new file mode 100644 index 000000000..0ae05b07b --- /dev/null +++ b/packages/safe-service-client/e2e/addSafeDelegate.test.ts @@ -0,0 +1,154 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { SafeDelegateConfig } from '../src' +import config from './config' +chai.use(chaiAsPromised) + +describe('addSafeDelegate', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe delegate address is empty', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe delegate address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe delegate address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith(`Address ${delegateAddress} is not checksumed`) + }) + + it('should fail if Safe does not exist', async () => { + const safeAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith(`Safe=${safeAddress} does not exist or it's still not indexed`) + }) + + it('should fail if the signer is not an owner of the Safe', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773', // Not a Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Signing owner is not an owner of the Safe') + }) + + it('should add a new delegate', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer, + label: 'Label' + } + + const { results: initialDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(initialDelegates.length).to.be.eq(0) + + const delegateResponse = await serviceSdk.addSafeDelegate(delegateConfig) + chai.expect(delegateResponse.safe).to.be.equal(delegateConfig.safe) + chai.expect(delegateResponse.delegate).to.be.equal(delegateConfig.delegate) + chai.expect(delegateResponse.signature).to.be.a('string') + chai.expect(delegateResponse.label).to.be.equal(delegateConfig.label) + + const { results: finalDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(finalDelegates.length).to.be.eq(1) + await serviceSdk.removeSafeDelegate(delegateConfig) + }) +}) diff --git a/packages/safe-service-client/e2e/config.ts b/packages/safe-service-client/e2e/config.ts index 015a20506..bb3b8fe3a 100644 --- a/packages/safe-service-client/e2e/config.ts +++ b/packages/safe-service-client/e2e/config.ts @@ -1,5 +1,6 @@ const config = { - baseUrl: 'https://safe-transaction.rinkeby.staging.gnosisdev.com' + BASE_URL: 'https://safe-transaction.staging.gnosisdev.com', + JSON_RPC: `https://rinkeby.infura.io/v3/${process.env.INFURA_KEY}` } export default config diff --git a/packages/safe-service-client/e2e/decodeData.test.ts b/packages/safe-service-client/e2e/decodeData.test.ts index e8d35f2d5..e0c9eaa75 100644 --- a/packages/safe-service-client/e2e/decodeData.test.ts +++ b/packages/safe-service-client/e2e/decodeData.test.ts @@ -6,7 +6,7 @@ import config from './config' chai.use(chaiAsPromised) describe('decodeData', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if data is empty', async () => { const data = '' diff --git a/packages/safe-service-client/e2e/getBalances.test.ts b/packages/safe-service-client/e2e/getBalances.test.ts index df73cab86..9b40e67a2 100644 --- a/packages/safe-service-client/e2e/getBalances.test.ts +++ b/packages/safe-service-client/e2e/getBalances.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeBalanceResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getBalances', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' @@ -24,7 +24,7 @@ describe('getBalances', () => { it('should return the list of balances', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const balances: SafeBalanceResponse[] = await serviceSdk.getBalances(safeAddress) + const balances = await serviceSdk.getBalances(safeAddress) chai.expect(balances.length).to.be.equal(2) const ethBalance = balances.filter((safeBalance) => !safeBalance.tokenAddress)[0] chai.expect(ethBalance.token).to.be.equal(null) diff --git a/packages/safe-service-client/e2e/getCollectibles.test.ts b/packages/safe-service-client/e2e/getCollectibles.test.ts index 516171fa3..7c9d86a3e 100644 --- a/packages/safe-service-client/e2e/getCollectibles.test.ts +++ b/packages/safe-service-client/e2e/getCollectibles.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeCollectibleResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getCollectibles', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' @@ -24,11 +24,9 @@ describe('getCollectibles', () => { it('should return the list of collectibles', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const safeCollectibleResponse: SafeCollectibleResponse[] = await serviceSdk.getCollectibles( - safeAddress - ) + const safeCollectibleResponse = await serviceSdk.getCollectibles(safeAddress) chai.expect(safeCollectibleResponse.length).to.be.equal(2) - safeCollectibleResponse.map((safeCollectible: SafeCollectibleResponse) => { + safeCollectibleResponse.map((safeCollectible) => { chai.expect(safeCollectible.address).to.be.equal('0x9cf1A34D70261f0594823EFCCeed53C8c639c464') chai.expect(safeCollectible.tokenName).to.be.equal('Safe NFTs') chai.expect(safeCollectible.metadata.type).to.be.equal('ERC721') diff --git a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts index 39e3d85db..61dbb0a5e 100644 --- a/packages/safe-service-client/e2e/getIncomingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getIncomingTransactions.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { TransferListResponse, TransferResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getIncomingTransactions', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' @@ -22,14 +22,19 @@ describe('getIncomingTransactions', () => { .to.be.rejectedWith('Checksum address validation failed') }) + it('should return an empty list if there are no incoming transactions', async () => { + const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without incoming transactions + const transferListResponse = await serviceSdk.getIncomingTransactions(safeAddress) + chai.expect(transferListResponse.count).to.be.equal(0) + chai.expect(transferListResponse.results.length).to.be.equal(0) + }) + it('should return the list of incoming transactions', async () => { - const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const transferListResponse: TransferListResponse = await serviceSdk.getIncomingTransactions( - safeAddress - ) - chai.expect(transferListResponse.count).to.be.equal(6) - chai.expect(transferListResponse.results.length).to.be.equal(6) - transferListResponse.results.map((transaction: TransferResponse) => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with incoming transactions + const transferListResponse = await serviceSdk.getIncomingTransactions(safeAddress) + chai.expect(transferListResponse.count).to.be.equal(10) + chai.expect(transferListResponse.results.length).to.be.equal(10) + transferListResponse.results.map((transaction) => { chai.expect(transaction.to).to.be.equal(safeAddress) }) }) diff --git a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts index 86d47ecf2..b08c094c8 100644 --- a/packages/safe-service-client/e2e/getMultisigTransactions.test.ts +++ b/packages/safe-service-client/e2e/getMultisigTransactions.test.ts @@ -1,15 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { - SafeMultisigTransactionListResponse, - SafeMultisigTransactionResponse -} from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getMultisigTransactions', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' @@ -25,16 +22,24 @@ describe('getMultisigTransactions', () => { .to.be.rejectedWith('Checksum address validation failed') }) + it('should return an empty list if there are no multisig transactions', async () => { + const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without multisig transactions + const safeMultisigTransactionListResponse = await serviceSdk.getMultisigTransactions( + safeAddress + ) + chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(0) + chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(0) + }) + it('should return the list of multisig transactions', async () => { - const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const safeMultisigTransactionListResponse: SafeMultisigTransactionListResponse = - await serviceSdk.getMultisigTransactions(safeAddress) - chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(3) - chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(3) - safeMultisigTransactionListResponse.results.map( - (transaction: SafeMultisigTransactionResponse) => { - chai.expect(transaction.safe).to.be.equal(safeAddress) - } + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with multisig transactions + const safeMultisigTransactionListResponse = await serviceSdk.getMultisigTransactions( + safeAddress ) + chai.expect(safeMultisigTransactionListResponse.count).to.be.equal(11) + chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(11) + safeMultisigTransactionListResponse.results.map((transaction) => { + chai.expect(transaction.safe).to.be.equal(safeAddress) + }) }) }) diff --git a/packages/safe-service-client/e2e/getPendingTransactions.test.ts b/packages/safe-service-client/e2e/getPendingTransactions.test.ts index 8871b915a..db235fdf1 100644 --- a/packages/safe-service-client/e2e/getPendingTransactions.test.ts +++ b/packages/safe-service-client/e2e/getPendingTransactions.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeMultisigTransactionListResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getPendingTransactions', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if safeAddress is empty', async () => { const safeAddress = '' @@ -22,11 +22,17 @@ describe('getPendingTransactions', () => { .to.be.rejectedWith('Checksum address validation failed') }) - it('should return the transaction with the given safeAddress', async () => { - const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const transactionList: SafeMultisigTransactionListResponse = - await serviceSdk.getPendingTransactions(safeAddress) + it('should return an empty list if there are no pending transactions', async () => { + const safeAddress = '0x3e04a375aC5847C690A7f2fF54b45c59f7eeD6f0' // Safe without pending transaction + const transactionList = await serviceSdk.getPendingTransactions(safeAddress) chai.expect(transactionList.count).to.be.equal(0) chai.expect(transactionList.results.length).to.be.equal(0) }) + + it('should return the the transaction list', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' // Safe with pending transaction + const transactionList = await serviceSdk.getPendingTransactions(safeAddress) + chai.expect(transactionList.count).to.be.equal(2) + chai.expect(transactionList.results.length).to.be.equal(2) + }) }) diff --git a/packages/safe-service-client/e2e/getSafeDelegates.test.ts b/packages/safe-service-client/e2e/getSafeDelegates.test.ts new file mode 100644 index 000000000..100be2205 --- /dev/null +++ b/packages/safe-service-client/e2e/getSafeDelegates.test.ts @@ -0,0 +1,69 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { SafeDelegateConfig } from '../src' +import config from './config' + +chai.use(chaiAsPromised) + +describe('getSafeDelegates', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + await chai + .expect(serviceSdk.getSafeDelegates(safeAddress)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + await chai + .expect(serviceSdk.getSafeDelegates(safeAddress)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should return an empty array if the Safe address is not found', async () => { + const safeAddress = '0x11dBF28A2B46bdD4E284e79e28B2E8b94Cfa39Bc' + const safeDelegateListResponse = await serviceSdk.getSafeDelegates(safeAddress) + const results = safeDelegateListResponse.results + chai.expect(results).to.be.empty + }) + + it('should return an array of delegates', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + + const delegateConfig1: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', + signer, + label: 'Label1' + } + const delegateConfig2: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', + signer, + label: 'Label2' + } + await serviceSdk.addSafeDelegate(delegateConfig1) + await serviceSdk.addSafeDelegate(delegateConfig2) + + const safeDelegateListResponse = await serviceSdk.getSafeDelegates(safeAddress) + const { results } = safeDelegateListResponse + chai.expect(results.length).to.be.eq(2) + chai.expect(results[0].delegate).to.be.eq(delegateConfig1.delegate) + chai.expect(results[0].delegator).to.be.eq(await delegateConfig1.signer.getAddress()) + chai.expect(results[0].label).to.be.eq(delegateConfig1.label) + chai.expect(results[1].delegate).to.be.eq(delegateConfig2.delegate) + chai.expect(results[1].delegator).to.be.eq(await delegateConfig2.signer.getAddress()) + chai.expect(results[1].label).to.be.eq(delegateConfig2.label) + + await serviceSdk.removeAllSafeDelegates(safeAddress, signer) + }) +}) diff --git a/packages/safe-service-client/e2e/getSafeInfo.test.ts b/packages/safe-service-client/e2e/getSafeInfo.test.ts index 5843a3fc9..1826e8f2c 100644 --- a/packages/safe-service-client/e2e/getSafeInfo.test.ts +++ b/packages/safe-service-client/e2e/getSafeInfo.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeInfoResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getSafeInfo', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' @@ -24,7 +24,7 @@ describe('getSafeInfo', () => { it('should return an empty array if the safeTxHash is not found', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const safeInfoResponse: SafeInfoResponse = await serviceSdk.getSafeInfo(safeAddress) + const safeInfoResponse = await serviceSdk.getSafeInfo(safeAddress) chai.expect(safeInfoResponse.address).to.be.equal(safeAddress) }) }) diff --git a/packages/safe-service-client/e2e/getSafesByOwner.test.ts b/packages/safe-service-client/e2e/getSafesByOwner.test.ts index f5098c555..394c06795 100644 --- a/packages/safe-service-client/e2e/getSafesByOwner.test.ts +++ b/packages/safe-service-client/e2e/getSafesByOwner.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { OwnerResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getSafesByOwner', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if owner address is empty', async () => { const ownerAddress = '' @@ -24,14 +24,14 @@ describe('getSafesByOwner', () => { it('should return an empty array if there are no owned Safes', async () => { const ownerAddress = '0x0000000000000000000000000000000000000001' - const ownerResponse: OwnerResponse = await serviceSdk.getSafesByOwner(ownerAddress) + const ownerResponse = await serviceSdk.getSafesByOwner(ownerAddress) const { safes } = ownerResponse chai.expect(safes.length).to.be.equal(0) }) it('should return the array of owned Safes', async () => { const ownerAddress = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1' - const ownerResponse: OwnerResponse = await serviceSdk.getSafesByOwner(ownerAddress) + const ownerResponse = await serviceSdk.getSafesByOwner(ownerAddress) const { safes } = ownerResponse chai.expect(safes.length).to.be.greaterThan(1) }) diff --git a/packages/safe-service-client/e2e/getServiceInfo.test.ts b/packages/safe-service-client/e2e/getServiceInfo.test.ts index 9a31bf8b6..2c2bbdd92 100644 --- a/packages/safe-service-client/e2e/getServiceInfo.test.ts +++ b/packages/safe-service-client/e2e/getServiceInfo.test.ts @@ -1,12 +1,12 @@ import { expect } from 'chai' -import SafeServiceClient, { SafeServiceInfoResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' describe('getServiceInfo', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should return the Safe info', async () => { - const safeInfo: SafeServiceInfoResponse = await serviceSdk.getServiceInfo() + const safeInfo = await serviceSdk.getServiceInfo() expect(safeInfo.api_version).to.be.equal('v1') }) }) diff --git a/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts b/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts index f1cccb320..e0009a2f0 100644 --- a/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts +++ b/packages/safe-service-client/e2e/getServiceMastercopiesInfo.test.ts @@ -1,14 +1,14 @@ import chai from 'chai' -import SafeServiceClient, { MasterCopyResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' describe('getServiceMasterCopiesInfo', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should call getServiceMasterCopiesInfo', async () => { - const masterCopiesResponse: MasterCopyResponse[] = await serviceSdk.getServiceMasterCopiesInfo() + const masterCopiesResponse = await serviceSdk.getServiceMasterCopiesInfo() chai.expect(masterCopiesResponse.length).to.be.greaterThan(1) - masterCopiesResponse.map((masterCopy: MasterCopyResponse) => { + masterCopiesResponse.map((masterCopy) => { chai.expect(masterCopy.deployer).to.be.equal('Gnosis') }) }) diff --git a/packages/safe-service-client/e2e/getToken.test.ts b/packages/safe-service-client/e2e/getToken.test.ts index 762b8e812..109290eda 100644 --- a/packages/safe-service-client/e2e/getToken.test.ts +++ b/packages/safe-service-client/e2e/getToken.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { TokenInfoResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getToken', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if token address is empty', async () => { const tokenAddress = '' @@ -22,7 +22,7 @@ describe('getToken', () => { it('should return the token info', async () => { const tokenAddress = '0xc778417E063141139Fce010982780140Aa0cD5Ab' - const tokenInfoResponse: TokenInfoResponse = await serviceSdk.getToken(tokenAddress) + const tokenInfoResponse = await serviceSdk.getToken(tokenAddress) chai.expect(tokenInfoResponse.address).to.be.equal('0xc778417E063141139Fce010982780140Aa0cD5Ab') }) }) diff --git a/packages/safe-service-client/e2e/getTokenList.test.ts b/packages/safe-service-client/e2e/getTokenList.test.ts index 32a91ed73..f111827c4 100644 --- a/packages/safe-service-client/e2e/getTokenList.test.ts +++ b/packages/safe-service-client/e2e/getTokenList.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' -import SafeServiceClient, { TokenInfoListResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' describe('getTokenList', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should return an array of tokens', async () => { - const tokenInfoListResponse: TokenInfoListResponse = await serviceSdk.getTokenList() + const tokenInfoListResponse = await serviceSdk.getTokenList() chai.expect(tokenInfoListResponse.count).to.be.greaterThan(1) chai.expect(tokenInfoListResponse.results.length).to.be.greaterThan(1) }) diff --git a/packages/safe-service-client/e2e/getTransaction.test.ts b/packages/safe-service-client/e2e/getTransaction.test.ts index a2d4db30a..e9784e15c 100644 --- a/packages/safe-service-client/e2e/getTransaction.test.ts +++ b/packages/safe-service-client/e2e/getTransaction.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeMultisigTransactionResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getTransaction', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if safeTxHash is empty', async () => { const safeTxHash = '' @@ -22,7 +22,7 @@ describe('getTransaction', () => { it('should return the transaction with the given safeTxHash', async () => { const safeTxHash = '0xb22be4e57718560c89de96acd1acefe55c2673b31a7019a374ebb1d8a2842f5d' - const transaction: SafeMultisigTransactionResponse = await serviceSdk.getTransaction(safeTxHash) + const transaction = await serviceSdk.getTransaction(safeTxHash) chai.expect(transaction.safeTxHash).to.be.equal(safeTxHash) }) }) diff --git a/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts b/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts index 3d32184ad..773ffaf1b 100644 --- a/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts +++ b/packages/safe-service-client/e2e/getTransactionConfirmations.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeMultisigConfirmationListResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getTransactionConfirmations', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if safeTxHash is empty', async () => { const safeTxHash = '' @@ -17,16 +17,14 @@ describe('getTransactionConfirmations', () => { it('should return an empty array if the safeTxHash is not found', async () => { const safeTxHash = '0x' - const transactionConfirmations: SafeMultisigConfirmationListResponse = - await serviceSdk.getTransactionConfirmations(safeTxHash) + const transactionConfirmations = await serviceSdk.getTransactionConfirmations(safeTxHash) chai.expect(transactionConfirmations.count).to.be.equal(0) chai.expect(transactionConfirmations.results.length).to.be.equal(0) }) it('should return the transaction with the given safeTxHash', async () => { const safeTxHash = '0xb22be4e57718560c89de96acd1acefe55c2673b31a7019a374ebb1d8a2842f5d' - const transactionConfirmations: SafeMultisigConfirmationListResponse = - await serviceSdk.getTransactionConfirmations(safeTxHash) + const transactionConfirmations = await serviceSdk.getTransactionConfirmations(safeTxHash) chai.expect(transactionConfirmations.count).to.be.equal(2) chai.expect(transactionConfirmations.results.length).to.be.equal(2) }) diff --git a/packages/safe-service-client/e2e/getUsdBalances.test.ts b/packages/safe-service-client/e2e/getUsdBalances.test.ts index 0f9fd92ea..77f7ffdcb 100644 --- a/packages/safe-service-client/e2e/getUsdBalances.test.ts +++ b/packages/safe-service-client/e2e/getUsdBalances.test.ts @@ -1,12 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import SafeServiceClient, { SafeBalanceUsdResponse } from '../src' +import SafeServiceClient from '../src' import config from './config' chai.use(chaiAsPromised) describe('getUsdBalances', () => { - const serviceSdk = new SafeServiceClient(config.baseUrl) + const serviceSdk = new SafeServiceClient(config.BASE_URL) it('should fail if Safe address is empty', async () => { const safeAddress = '' @@ -24,7 +24,7 @@ describe('getUsdBalances', () => { it('should return the list of USD balances', async () => { const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' - const balances: SafeBalanceUsdResponse[] = await serviceSdk.getUsdBalances(safeAddress) + const balances = await serviceSdk.getUsdBalances(safeAddress) chai.expect(balances.length).to.be.equal(2) const ethBalance = balances.filter((safeBalance) => !safeBalance.tokenAddress)[0] chai.expect(ethBalance.token).to.be.equal(null) diff --git a/packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts b/packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts new file mode 100644 index 000000000..3a38ec6c9 --- /dev/null +++ b/packages/safe-service-client/e2e/removeAllSafeDelegates.test.ts @@ -0,0 +1,90 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { SafeDelegateConfig } from '../src' +import config from './config' +chai.use(chaiAsPromised) + +describe('removeAllSafeDelegates', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe does not exist', async () => { + const safeAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith(`Safe=${safeAddress} does not exist or it's still not indexed`) + }) + + it('should fail if the signer is not an owner of the Safe', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773', // Not a Safe owner + provider + ) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.rejectedWith('Signing owner is not an owner of the Safe') + }) + + it('should remove all delegates', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + + const delegateConfig1: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', + signer, + label: 'Label1' + } + const delegateConfig2: SafeDelegateConfig = { + safe: safeAddress, + delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', + signer, + label: 'Label2' + } + await serviceSdk.addSafeDelegate(delegateConfig1) + await serviceSdk.addSafeDelegate(delegateConfig2) + const { results: initialDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(initialDelegates.length).to.be.eq(2) + + await serviceSdk.removeAllSafeDelegates(safeAddress, signer) + + const { results: finalDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(finalDelegates.length).to.be.eq(0) + }) +}) diff --git a/packages/safe-service-client/e2e/removeSafeDelegate.test.ts b/packages/safe-service-client/e2e/removeSafeDelegate.test.ts new file mode 100644 index 000000000..b215d63d4 --- /dev/null +++ b/packages/safe-service-client/e2e/removeSafeDelegate.test.ts @@ -0,0 +1,158 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient, { SafeDelegateDeleteConfig } from '../src' +import config from './config' +chai.use(chaiAsPromised) + +describe('removeSafeDelegate', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should fail if Safe delegate address is empty', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Invalid Safe delegate address') + }) + + it('should fail if Safe address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe delegate address is not checksummed', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'.toLowerCase() + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Checksum address validation failed') + }) + + it('should fail if Safe does not exist', async () => { + const safeAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith(`Safe=${safeAddress} does not exist or it's still not indexed`) + }) + + it('should fail if the signer is not an owner of the Safe', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773', // Not a Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.rejectedWith('Signing owner is not an owner of the Safe') + }) + + it('should fail if the delegate to remove is not a delegate', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + await chai.expect(serviceSdk.removeSafeDelegate(delegateConfig)).to.be.rejectedWith('Not found') + }) + + it('should remove a delegate', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const delegateAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateDeleteConfig = { + safe: safeAddress, + delegate: delegateAddress, + signer + } + + await serviceSdk.addSafeDelegate({ ...delegateConfig, label: 'Label' }) + const { results: initialDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(initialDelegates.length).to.be.eq(1) + + await serviceSdk.removeSafeDelegate(delegateConfig) + + const { results: finalDelegates } = await serviceSdk.getSafeDelegates(safeAddress) + chai.expect(finalDelegates.length).to.be.eq(0) + }) +}) diff --git a/packages/safe-service-client/hardhat.config.ts b/packages/safe-service-client/hardhat.config.ts new file mode 100644 index 000000000..dbdea6cf3 --- /dev/null +++ b/packages/safe-service-client/hardhat.config.ts @@ -0,0 +1,52 @@ +import '@nomiclabs/hardhat-ethers' +import '@nomiclabs/hardhat-waffle' +import dotenv from 'dotenv' +import { HardhatUserConfig, HttpNetworkUserConfig } from 'hardhat/types' +import yargs from 'yargs' + +const argv = yargs + .option('network', { + type: 'string', + default: 'hardhat', + }) + .help(false) + .version(false).argv + +dotenv.config() +const { INFURA_KEY, MNEMONIC, PK } = process.env +const DEFAULT_MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect' + +const sharedNetworkConfig: HttpNetworkUserConfig = {} +if (PK) { + sharedNetworkConfig.accounts = [PK]; +} else { + sharedNetworkConfig.accounts = { + mnemonic: MNEMONIC || DEFAULT_MNEMONIC, + } +} + +if (['rinkeby'].includes(argv.network) && INFURA_KEY === undefined) { + throw new Error( + `Could not find Infura key in env, unable to connect to network ${argv.network}`, + ) +} + +const config: HardhatUserConfig = { + defaultNetwork: "rinkeby", + paths: { + tests: 'e2e' + }, + networks: { + hardhat: { + allowUnlimitedContractSize: true, + blockGasLimit: 100000000, + gas: 100000000 + }, + rinkeby: { + ...sharedNetworkConfig, + url: `https://rinkeby.infura.io/v3/${INFURA_KEY}`, + } + } +} + +export default config diff --git a/packages/safe-service-client/package.json b/packages/safe-service-client/package.json index 35392ab1e..cbcb80504 100644 --- a/packages/safe-service-client/package.json +++ b/packages/safe-service-client/package.json @@ -14,7 +14,7 @@ "unbuild": "rimraf dist", "build": "yarn rimraf dist && tsc", "test": "mocha --require ts-node/register tests/**/*.test.ts", - "test:ci": "mocha --require ts-node/register e2e/**/*.test.ts", + "test:ci": "nyc hardhat test", "format": "prettier --write \"{src,tests,e2e}/**/*.ts\"", "lint": "tslint -p tsconfig.json" }, @@ -33,7 +33,8 @@ ], "homepage": "https://github.com/gnosis/safe-core-sdk#readme", "devDependencies": { - "@gnosis.pm/safe-core-sdk": "^0.3.1", + "@nomiclabs/hardhat-ethers": "^2.0.2", + "@nomiclabs/hardhat-waffle": "^2.0.1", "@types/chai": "^4.2.22", "@types/chai-as-promised": "^7.1.4", "@types/mocha": "^9.0.0", @@ -45,6 +46,7 @@ "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", + "hardhat": "^2.3.3", "husky": "^7.0.2", "lint-staged": "^11.1.2", "mocha": "^9.1.1", diff --git a/packages/safe-service-client/src/SafeServiceClient.ts b/packages/safe-service-client/src/SafeServiceClient.ts index 23dfb5682..a55b892df 100644 --- a/packages/safe-service-client/src/SafeServiceClient.ts +++ b/packages/safe-service-client/src/SafeServiceClient.ts @@ -1,3 +1,4 @@ +import { Signer } from '@ethersproject/abstract-signer' import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import SafeTransactionService from './SafeTransactionService' import { @@ -11,7 +12,8 @@ import { SafeCollectiblesOptions, SafeCreationInfoResponse, SafeDelegate, - SafeDelegateDelete, + SafeDelegateConfig, + SafeDelegateDeleteConfig, SafeDelegateListResponse, SafeInfoResponse, SafeModuleTransactionListResponse, @@ -186,8 +188,7 @@ class SafeServiceClient implements SafeTransactionService { * @param safeAddress - The Safe address * @returns The list of delegates * @throws "Invalid Safe address" - * @throws "Invalid data" - * @throws "Invalid ethereum address" + * @throws "Checksum address validation failed" */ async getSafeDelegates(safeAddress: string): Promise { if (safeAddress === '') { @@ -200,44 +201,97 @@ class SafeServiceClient implements SafeTransactionService { } /** - * Adds a new delegate for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). + * Adds a new delegate for a given Safe address. * * @param safeAddress - The Safe address - * @param delegate - The new delegate + * @param delegateConfig - The configuration of the new delegate * @returns * @throws "Invalid Safe address" - * @throws "Malformed data" - * @throws "Invalid Ethereum address/Error processing data" + * @throws "Invalid Safe delegate address" + * @throws "Checksum address validation failed" + * @throws "Address is not checksumed" + * @throws "Safe= does not exist or it's still not indexed" + * @throws "Signing owner is not an owner of the Safe" */ - async addSafeDelegate(safeAddress: string, delegate: SafeDelegate): Promise { - if (safeAddress === '') { + async addSafeDelegate(delegateConfig: SafeDelegateConfig): Promise { + const { safe, delegate, label, signer } = delegateConfig + if (safe === '') { throw new Error('Invalid Safe address') } + if (delegate === '') { + throw new Error('Invalid Safe delegate address') + } + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = delegate + totp + const signature = await signer.signMessage(data) + const body: SafeDelegate = { + safe, + delegate, + label, + signature + } return sendRequest({ - url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/`, + url: `${this.#txServiceBaseUrl}/safes/${safe}/delegates/`, method: HttpMethod.Post, - body: delegate + body }) } /** - * Removes a delegate for a given Safe address. The signature is calculated by signing this hash: keccak(address + str(int(current_epoch / 3600))). + * Removes all delegates for a given Safe address. * * @param safeAddress - The Safe address - * @param delegate - The delegate that will be removed * @returns * @throws "Invalid Safe address" - * @throws "Malformed data" - * @throws "Invalid Ethereum address/Error processing data" + * @throws "Checksum address validation failed" + * @throws "Safe= does not exist or it's still not indexed" + * @throws "Signing owner is not an owner of the Safe" */ - async removeSafeDelegate(safeAddress: string, delegate: SafeDelegateDelete): Promise { + async removeAllSafeDelegates(safeAddress: string, signer: Signer): Promise { if (safeAddress === '') { throw new Error('Invalid Safe address') } + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = safeAddress + totp + const signature = await signer.signMessage(data) + return sendRequest({ + url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/`, + method: HttpMethod.Delete, + body: { signature } + }) + } + + /** + * Removes a delegate for a given Safe address. + * + * @param safeAddress - The Safe address + * @param delegateConfig - The configuration for the delegate that will be removed + * @returns + * @throws "Invalid Safe address" + * @throws "Invalid Safe delegate address" + * @throws "Checksum address validation failed" + * @throws "Signing owner is not an owner of the Safe" + * @throws "Not found" + */ + async removeSafeDelegate(delegateConfig: SafeDelegateDeleteConfig): Promise { + const { safe, delegate, signer } = delegateConfig + if (safe === '') { + throw new Error('Invalid Safe address') + } + if (delegate === '') { + throw new Error('Invalid Safe delegate address') + } + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = delegate + totp + const signature = await signer.signMessage(data) return sendRequest({ - url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/${delegate.delegate}`, + url: `${this.#txServiceBaseUrl}/safes/${safe}/delegates/${delegate}`, method: HttpMethod.Delete, - body: delegate + body: { + safe, + delegate, + signature + } }) } diff --git a/packages/safe-service-client/src/SafeTransactionService.ts b/packages/safe-service-client/src/SafeTransactionService.ts index 81d117c15..2618c3fec 100644 --- a/packages/safe-service-client/src/SafeTransactionService.ts +++ b/packages/safe-service-client/src/SafeTransactionService.ts @@ -1,3 +1,4 @@ +import { Signer } from '@ethersproject/abstract-signer' import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import { MasterCopyResponse, @@ -10,7 +11,8 @@ import { SafeCollectiblesOptions, SafeCreationInfoResponse, SafeDelegate, - SafeDelegateDelete, + SafeDelegateConfig, + SafeDelegateDeleteConfig, SafeDelegateListResponse, SafeInfoResponse, SafeModuleTransactionListResponse, @@ -45,8 +47,9 @@ interface SafeTransactionService { // Safes getSafeInfo(safeAddress: string): Promise getSafeDelegates(safeAddress: string): Promise - addSafeDelegate(safeAddress: string, delegate: SafeDelegate): Promise - removeSafeDelegate(safeAddress: string, delegate: SafeDelegateDelete): Promise + addSafeDelegate(config: SafeDelegateConfig): Promise + removeSafeDelegate(config: SafeDelegateDeleteConfig): Promise + removeAllSafeDelegates(safeAddress: string, signer: Signer): Promise // Transactions getSafeCreationInfo(safeAddress: string): Promise diff --git a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts index 4c6d37eea..d1cd9a81c 100644 --- a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts +++ b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts @@ -1,3 +1,5 @@ +import { Signer } from '@ethersproject/abstract-signer' + export type SafeServiceInfoResponse = { readonly name: string readonly version: string @@ -49,17 +51,21 @@ export type SafeCreationInfoResponse = { readonly dataDecoded?: string } -export type SafeDelegate = { +export type SafeDelegateDeleteConfig = { readonly safe: string readonly delegate: string - readonly signature: string + readonly signer: Signer +} + +export type SafeDelegateConfig = SafeDelegateDeleteConfig & { readonly label: string } -export type SafeDelegateDelete = { +export type SafeDelegate = { readonly safe: string readonly delegate: string readonly signature: string + readonly label: string } export type SafeDelegateResponse = { diff --git a/packages/safe-service-client/src/utils/httpRequests.ts b/packages/safe-service-client/src/utils/httpRequests.ts index d7f226cf3..e6aee087a 100644 --- a/packages/safe-service-client/src/utils/httpRequests.ts +++ b/packages/safe-service-client/src/utils/httpRequests.ts @@ -21,12 +21,16 @@ export async function sendRequest({ url, method, body }: HttpRequest): Promis }, body: JSON.stringify(body) }) + let jsonResponse try { jsonResponse = await response.json() } catch (error) { - throw new Error(response.statusText) + if (!response.ok) { + throw new Error(response.statusText) + } } + if (response.ok) { return jsonResponse as T } @@ -39,5 +43,11 @@ export async function sendRequest({ url, method, body }: HttpRequest): Promis if (jsonResponse.message) { throw new Error(jsonResponse.message) } + if (jsonResponse.nonFieldErrors) { + throw new Error(jsonResponse.nonFieldErrors) + } + if (jsonResponse.delegate) { + throw new Error(jsonResponse.delegate) + } throw new Error(response.statusText) } diff --git a/packages/safe-service-client/tests/endpoint.test.ts b/packages/safe-service-client/tests/endpoint.test.ts index c9ee72fdc..94095b9d8 100644 --- a/packages/safe-service-client/tests/endpoint.test.ts +++ b/packages/safe-service-client/tests/endpoint.test.ts @@ -1,14 +1,17 @@ +import { getDefaultProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import sinon from 'sinon' import sinonChai from 'sinon-chai' +import config from '../e2e/config' import SafeServiceClient, { SafeBalancesOptions, SafeBalancesUsdOptions, SafeCollectiblesOptions, - SafeDelegate, - SafeDelegateDelete, + SafeDelegateConfig, + SafeDelegateDeleteConfig, SafeMultisigTransactionEstimate } from '../src' import { getTxServiceBaseUrl } from '../src/utils' @@ -29,7 +32,9 @@ describe('Endpoint tests', () => { describe('', () => { it('getServiceInfo', async () => { - chai.expect(serviceSdk.getServiceInfo()).to.be.eventually.deep.equals({ success: true }) + await chai + .expect(serviceSdk.getServiceInfo()) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about`, method: 'get' @@ -37,9 +42,9 @@ describe('Endpoint tests', () => { }) it('getServiceMasterCopiesInfo', async () => { - chai + await chai .expect(serviceSdk.getServiceMasterCopiesInfo()) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/about/master-copies`, method: 'get' @@ -48,7 +53,9 @@ describe('Endpoint tests', () => { it('decodeData', async () => { const data = '0x610b592500000000000000000000000090F8bf6A479f320ead074411a4B0e7944Ea8c9C1' - chai.expect(serviceSdk.decodeData(data)).to.be.eventually.deep.equals({ success: true }) + await chai + .expect(serviceSdk.decodeData(data)) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/data-decoder/`, method: 'post', @@ -57,9 +64,9 @@ describe('Endpoint tests', () => { }) it('getSafesByOwner', async () => { - chai + await chai .expect(serviceSdk.getSafesByOwner(ownerAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/owners/${ownerAddress}/safes/`, method: 'get' @@ -67,9 +74,9 @@ describe('Endpoint tests', () => { }) it('getTransaction', async () => { - chai + await chai .expect(serviceSdk.getTransaction(safeTxHash)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/multisig-transactions/${safeTxHash}/`, method: 'get' @@ -77,9 +84,9 @@ describe('Endpoint tests', () => { }) it('getTransactionConfirmations', async () => { - chai + await chai .expect(serviceSdk.getTransactionConfirmations(safeTxHash)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -90,9 +97,9 @@ describe('Endpoint tests', () => { it('confirmTransaction', async () => { const signature = '0x' - chai + await chai .expect(serviceSdk.confirmTransaction(safeTxHash, signature)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -102,9 +109,9 @@ describe('Endpoint tests', () => { }) it('getSafeInfo', async () => { - chai + await chai .expect(serviceSdk.getSafeInfo(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/`, method: 'get' @@ -112,9 +119,9 @@ describe('Endpoint tests', () => { }) it('getSafeDelegates', async () => { - chai + await chai .expect(serviceSdk.getSafeDelegates(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'get' @@ -122,43 +129,80 @@ describe('Endpoint tests', () => { }) it('addSafeDelegate', async () => { - const delegate: SafeDelegate = { + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegateConfig: SafeDelegateConfig = { safe: safeAddress, delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', - signature: '0x', + signer, label: '' } - chai - .expect(serviceSdk.addSafeDelegate(safeAddress, delegate)) - .to.be.eventually.deep.equals({ success: true }) + await chai + .expect(serviceSdk.addSafeDelegate(delegateConfig)) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, method: 'get' }) }) + it('removeAllSafeDelegates', async () => { + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = safeAddress + totp + const signature = await signer.signMessage(data) + await chai + .expect(serviceSdk.removeAllSafeDelegates(safeAddress, signer)) + .to.be.eventually.deep.equals({ data: { success: true } }) + chai.expect(fetchData).to.have.been.calledWith({ + url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/`, + method: 'delete', + body: { signature } + }) + }) + it('removeSafeDelegate', async () => { - const delegate: SafeDelegateDelete = { + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const delegate = '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b' + const delegateConfig: SafeDelegateDeleteConfig = { safe: safeAddress, - delegate: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', - signature: '0x' + delegate, + signer } - chai - .expect(serviceSdk.removeSafeDelegate(safeAddress, delegate)) - .to.be.eventually.deep.equals({ success: true }) + const totp = Math.floor(Date.now() / 1000 / 3600) + const data = delegate + totp + const signature = await signer.signMessage(data) + await chai + .expect(serviceSdk.removeSafeDelegate(delegateConfig)) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/delegates/${ - delegate.delegate + delegateConfig.delegate }`, method: 'delete', - body: delegate + body: { + safe: delegateConfig.safe, + delegate: delegateConfig.delegate, + signature + } }) }) it('getSafeCreationInfo', async () => { - chai + await chai .expect(serviceSdk.getSafeCreationInfo(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/creation/`, method: 'get' @@ -172,9 +216,9 @@ describe('Endpoint tests', () => { data: '0x', operation: 0 } - chai + await chai .expect(serviceSdk.estimateSafeTransaction(safeAddress, safeTransaction)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -203,9 +247,9 @@ describe('Endpoint tests', () => { staticPart: () => '', dynamicPart: () => '' } - chai + await chai .expect(serviceSdk.proposeTransaction(safeAddress, safeTxData, safeTxHash, signature)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, method: 'post', @@ -219,9 +263,9 @@ describe('Endpoint tests', () => { }) it('getIncomingTransactions', async () => { - chai + await chai .expect(serviceSdk.getIncomingTransactions(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/incoming-transfers/`, method: 'get' @@ -229,9 +273,9 @@ describe('Endpoint tests', () => { }) it('getModuleTransactions', async () => { - chai + await chai .expect(serviceSdk.getModuleTransactions(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/module-transfers/`, method: 'get' @@ -239,9 +283,9 @@ describe('Endpoint tests', () => { }) it('getMultisigTransactions', async () => { - chai + await chai .expect(serviceSdk.getMultisigTransactions(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, method: 'get' @@ -250,9 +294,9 @@ describe('Endpoint tests', () => { it('getPendingTransactions', async () => { const currentNonce = 1 - chai + await chai .expect(serviceSdk.getPendingTransactions(safeAddress, currentNonce)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -262,9 +306,9 @@ describe('Endpoint tests', () => { }) it('getBalances', async () => { - chai + await chai .expect(serviceSdk.getBalances(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -277,9 +321,9 @@ describe('Endpoint tests', () => { const options: SafeBalancesOptions = { excludeSpamTokens: false } - chai + await chai .expect(serviceSdk.getBalances(safeAddress, options)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -289,9 +333,9 @@ describe('Endpoint tests', () => { }) it('getUsdBalances', async () => { - chai + await chai .expect(serviceSdk.getUsdBalances(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -304,9 +348,9 @@ describe('Endpoint tests', () => { const options: SafeBalancesUsdOptions = { excludeSpamTokens: false } - chai + await chai .expect(serviceSdk.getUsdBalances(safeAddress, options)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -316,9 +360,9 @@ describe('Endpoint tests', () => { }) it('getCollectibles', async () => { - chai + await chai .expect(serviceSdk.getCollectibles(safeAddress)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -331,9 +375,9 @@ describe('Endpoint tests', () => { const options: SafeCollectiblesOptions = { excludeSpamTokens: false } - chai + await chai .expect(serviceSdk.getCollectibles(safeAddress, options)) - .to.be.eventually.deep.equals({ success: true }) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl( txServiceBaseUrl @@ -343,7 +387,9 @@ describe('Endpoint tests', () => { }) it('getTokens', async () => { - chai.expect(serviceSdk.getTokenList()).to.be.eventually.deep.equals({ success: true }) + await chai + .expect(serviceSdk.getTokenList()) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/`, method: 'get' @@ -352,7 +398,9 @@ describe('Endpoint tests', () => { it('getToken', async () => { const tokenAddress = '0x' - chai.expect(serviceSdk.getToken(tokenAddress)).to.be.eventually.deep.equals({ success: true }) + await chai + .expect(serviceSdk.getToken(tokenAddress)) + .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/tokens/${tokenAddress}/`, method: 'get' From 8c8e475db69d19534c4f73fab31e36f48ebe1ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 11 Oct 2021 13:10:05 +0200 Subject: [PATCH 27/39] Update hardhat tests script and path --- packages/safe-service-client/hardhat.config.ts | 4 ++-- packages/safe-service-client/package.json | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/safe-service-client/hardhat.config.ts b/packages/safe-service-client/hardhat.config.ts index dbdea6cf3..fd1a38e59 100644 --- a/packages/safe-service-client/hardhat.config.ts +++ b/packages/safe-service-client/hardhat.config.ts @@ -13,7 +13,7 @@ const argv = yargs .version(false).argv dotenv.config() -const { INFURA_KEY, MNEMONIC, PK } = process.env +const { INFURA_KEY, MNEMONIC, PK, TESTS_PATH } = process.env const DEFAULT_MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect' const sharedNetworkConfig: HttpNetworkUserConfig = {} @@ -34,7 +34,7 @@ if (['rinkeby'].includes(argv.network) && INFURA_KEY === undefined) { const config: HardhatUserConfig = { defaultNetwork: "rinkeby", paths: { - tests: 'e2e' + tests: TESTS_PATH }, networks: { hardhat: { diff --git a/packages/safe-service-client/package.json b/packages/safe-service-client/package.json index cbcb80504..5bc250b4b 100644 --- a/packages/safe-service-client/package.json +++ b/packages/safe-service-client/package.json @@ -13,8 +13,8 @@ "scripts": { "unbuild": "rimraf dist", "build": "yarn rimraf dist && tsc", - "test": "mocha --require ts-node/register tests/**/*.test.ts", - "test:ci": "nyc hardhat test", + "test": "export TESTS_PATH=tests && nyc hardhat test", + "test:ci": "export TESTS_PATH=e2e && nyc hardhat test", "format": "prettier --write \"{src,tests,e2e}/**/*.ts\"", "lint": "tslint -p tsconfig.json" }, @@ -35,6 +35,7 @@ "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-waffle": "^2.0.1", + "@nomiclabs/hardhat-web3": "^2.0.0", "@types/chai": "^4.2.22", "@types/chai-as-promised": "^7.1.4", "@types/mocha": "^9.0.0", From c527c32df24242dee0e1252f7160e07c9c270743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 11 Oct 2021 13:53:59 +0200 Subject: [PATCH 28/39] Add getNextNonce to README file --- packages/safe-service-client/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/safe-service-client/README.md b/packages/safe-service-client/README.md index 2c317ca51..ff731a7d0 100644 --- a/packages/safe-service-client/README.md +++ b/packages/safe-service-client/README.md @@ -152,7 +152,7 @@ const estimateTx: SafeMultisigTransactionEstimateResponse = await safeService.es Creates a new multi-signature transaction and stores it in the Safe Transaction Service. ```js -await safeService.proposeTransaction(safeAddress, transaction, safeTxHash, signature) +await safeService.proposeTransaction({ safeAddress, safeTransaction, safeTxHash, senderAddress }) ``` ### getIncomingTransactions @@ -191,6 +191,14 @@ const pendingTxs: SafeMultisigTransactionListResponse = await safeService.getPen const pendingTxs: SafeMultisigTransactionListResponse = await safeService.getPendingTransactions(safeAddress, currentNonce) ``` +### getNextNonce + +Returns the right nonce to propose a new transaction right after the last pending transaction. + +```js +const nextNonce = await getNextNonce(safeAddress) +``` + ### getBalances Returns the balances for Ether and ERC20 tokens of a Safe. From eab5a1c5ab07eb30c350b325489641a710d77897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 11 Oct 2021 13:56:10 +0200 Subject: [PATCH 29/39] Update tests --- .../e2e/getNextNonce.test.ts | 28 ++++++++++++++ .../tests/endpoint.test.ts | 37 +++++++++++++------ 2 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 packages/safe-service-client/e2e/getNextNonce.test.ts diff --git a/packages/safe-service-client/e2e/getNextNonce.test.ts b/packages/safe-service-client/e2e/getNextNonce.test.ts new file mode 100644 index 000000000..8f2b3ee36 --- /dev/null +++ b/packages/safe-service-client/e2e/getNextNonce.test.ts @@ -0,0 +1,28 @@ +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import SafeServiceClient from '../src' +import config from './config' +chai.use(chaiAsPromised) + +describe('getNextNonce', () => { + const serviceSdk = new SafeServiceClient(config.BASE_URL) + + it('should fail if Safe address is empty', async () => { + const safeAddress = '' + await chai + .expect(serviceSdk.getNextNonce(safeAddress)) + .to.be.rejectedWith('Invalid Safe address') + }) + + it('should return the next Safe nonce when there are pending transactions', async () => { + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' + const nextNonce = await serviceSdk.getNextNonce(safeAddress) + chai.expect(nextNonce).to.be.equal(10) + }) + + it('should return the next Safe nonce when there are no pending transactions', async () => { + const safeAddress = '0x3e1ee196231490c8483df2D57403c7B814f91803' + const nextNonce = await serviceSdk.getNextNonce(safeAddress) + chai.expect(nextNonce).to.be.equal(0) + }) +}) diff --git a/packages/safe-service-client/tests/endpoint.test.ts b/packages/safe-service-client/tests/endpoint.test.ts index 94095b9d8..bf6757981 100644 --- a/packages/safe-service-client/tests/endpoint.test.ts +++ b/packages/safe-service-client/tests/endpoint.test.ts @@ -1,8 +1,10 @@ import { getDefaultProvider } from '@ethersproject/providers' import { Wallet } from '@ethersproject/wallet' -import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' +import Safe, { EthersAdapter } from '@gnosis.pm/safe-core-sdk' +import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' +import { ethers } from 'hardhat' import sinon from 'sinon' import sinonChai from 'sinon-chai' import config from '../e2e/config' @@ -20,7 +22,7 @@ chai.use(chaiAsPromised) chai.use(sinonChai) describe('Endpoint tests', () => { - const safeAddress = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1' + const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD' const ownerAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0' const safeTxHash = '0xede78ed72e9a8afd2b7a21f35c86f56cba5fffb2fff0838e253b7a41d19ceb48' const txServiceBaseUrl = 'https://safe-transaction.rinkeby.gnosis.io' @@ -229,7 +231,7 @@ describe('Endpoint tests', () => { }) it('proposeTransaction', async () => { - const safeTxData: SafeTransactionData = { + const safeTxData: SafeTransactionDataPartial = { to: '0xa33d2495760462018275994d85117600bd58221e', data: '0x', value: '123456789', @@ -241,14 +243,25 @@ describe('Endpoint tests', () => { refundReceiver: '0x0000000000000000000000000000000000000000', nonce: 1 } - const signature: SafeSignature = { - signer: 'signer', - data: 'signature', - staticPart: () => '', - dynamicPart: () => '' - } + const provider = getDefaultProvider(config.JSON_RPC) + const signer = new Wallet( + '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d', // A Safe owner + provider + ) + const signerAddress = await signer.getAddress() + const ethAdapter = new EthersAdapter({ ethers, signer }) + const safeSdk = await Safe.create({ ethAdapter, safeAddress }) + const safeTransaction = await safeSdk.createTransaction(safeTxData) + await safeSdk.signTransaction(safeTransaction) await chai - .expect(serviceSdk.proposeTransaction(safeAddress, safeTxData, safeTxHash, signature)) + .expect( + serviceSdk.proposeTransaction({ + safeAddress, + senderAddress: signerAddress, + safeTransaction, + safeTxHash + }) + ) .to.be.eventually.deep.equals({ data: { success: true } }) chai.expect(fetchData).to.have.been.calledWith({ url: `${getTxServiceBaseUrl(txServiceBaseUrl)}/safes/${safeAddress}/multisig-transactions/`, @@ -256,8 +269,8 @@ describe('Endpoint tests', () => { body: { ...safeTxData, contractTransactionHash: safeTxHash, - sender: signature.signer, - signature: signature.data + sender: safeTransaction.signatures.get(signerAddress.toLowerCase())?.signer, + signature: safeTransaction.signatures.get(signerAddress.toLowerCase())?.data } }) }) From 94e7b10e0cd01ee4ea5ff60acf0557bf98d95dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 11 Oct 2021 13:58:22 +0200 Subject: [PATCH 30/39] Refactor proposeTransaction --- .../src/SafeServiceClient.ts | 24 +++++++++---------- .../src/SafeTransactionService.ts | 14 +++++------ .../src/types/safeTransactionServiceTypes.ts | 8 +++++++ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/safe-service-client/src/SafeServiceClient.ts b/packages/safe-service-client/src/SafeServiceClient.ts index a55b892df..82c2d6787 100644 --- a/packages/safe-service-client/src/SafeServiceClient.ts +++ b/packages/safe-service-client/src/SafeServiceClient.ts @@ -1,9 +1,9 @@ import { Signer } from '@ethersproject/abstract-signer' -import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import SafeTransactionService from './SafeTransactionService' import { MasterCopyResponse, OwnerResponse, + ProposeTransactionProps, SafeBalanceResponse, SafeBalancesOptions, SafeBalancesUsdOptions, @@ -349,16 +349,16 @@ class SafeServiceClient implements SafeTransactionService { * @param signature - The signature of an owner or delegate of the specified Safe * @returns The hash of the Safe transaction proposed * @throws "Invalid Safe address" - * @throws "Invalid Safe safeTxHash" + * @throws "Invalid safeTxHash" * @throws "Invalid data" - * @throws "Invalid ethereum address/User is not an owner/Invalid safeTxHash/Invalid signature/Nonce already executed/Sender is not an owner" + * @throws "Invalid ethereum address/User is not an owner/Invalid signature/Nonce already executed/Sender is not an owner" */ - async proposeTransaction( - safeAddress: string, - transaction: SafeTransactionData, - safeTxHash: string, - signature: SafeSignature - ): Promise { + async proposeTransaction({ + safeAddress, + senderAddress, + safeTransaction, + safeTxHash + }: ProposeTransactionProps): Promise { if (safeAddress === '') { throw new Error('Invalid Safe address') } @@ -369,10 +369,10 @@ class SafeServiceClient implements SafeTransactionService { url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/multisig-transactions/`, method: HttpMethod.Post, body: { - ...transaction, + ...safeTransaction.data, contractTransactionHash: safeTxHash, - sender: signature.signer, - signature: signature.data + sender: senderAddress, + signature: safeTransaction.signatures.get(senderAddress.toLowerCase())?.data } }) } diff --git a/packages/safe-service-client/src/SafeTransactionService.ts b/packages/safe-service-client/src/SafeTransactionService.ts index 2618c3fec..35c9f611b 100644 --- a/packages/safe-service-client/src/SafeTransactionService.ts +++ b/packages/safe-service-client/src/SafeTransactionService.ts @@ -1,8 +1,8 @@ import { Signer } from '@ethersproject/abstract-signer' -import { SafeSignature, SafeTransactionData } from '@gnosis.pm/safe-core-sdk-types' import { MasterCopyResponse, OwnerResponse, + ProposeTransactionProps, SafeBalanceResponse, SafeBalancesOptions, SafeBalancesUsdOptions, @@ -57,12 +57,12 @@ interface SafeTransactionService { safeAddress: string, safeTransaction: SafeMultisigTransactionEstimate ): Promise - proposeTransaction( - safeAddress: string, - transaction: SafeTransactionData, - safeTxHash: string, - signature: SafeSignature - ): Promise + proposeTransaction({ + safeAddress, + senderAddress, + safeTransaction, + safeTxHash + }: ProposeTransactionProps): Promise getIncomingTransactions(safeAddress: string): Promise getModuleTransactions(safeAddress: string): Promise getMultisigTransactions(safeAddress: string): Promise diff --git a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts index d1cd9a81c..5be0e8ae2 100644 --- a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts +++ b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts @@ -1,4 +1,5 @@ import { Signer } from '@ethersproject/abstract-signer' +import { SafeTransaction } from '@gnosis.pm/safe-core-sdk-types' export type SafeServiceInfoResponse = { readonly name: string @@ -112,6 +113,13 @@ export type SafeMultisigConfirmationListResponse = { readonly results: SafeMultisigConfirmationResponse[] } +export type ProposeTransactionProps = { + safeAddress: string + senderAddress: string + safeTransaction: SafeTransaction + safeTxHash: string +} + export type SafeMultisigTransactionResponse = { readonly safe: string readonly to: string From 9b86db4e586c886adf6d54a84cf249c1ef00f1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Mon, 11 Oct 2021 13:58:56 +0200 Subject: [PATCH 31/39] Add getNextNonce --- .../src/SafeServiceClient.ts | 23 +++++++++++++++++++ .../src/SafeTransactionService.ts | 1 + 2 files changed, 24 insertions(+) diff --git a/packages/safe-service-client/src/SafeServiceClient.ts b/packages/safe-service-client/src/SafeServiceClient.ts index 82c2d6787..248ac8ce1 100644 --- a/packages/safe-service-client/src/SafeServiceClient.ts +++ b/packages/safe-service-client/src/SafeServiceClient.ts @@ -458,6 +458,29 @@ class SafeServiceClient implements SafeTransactionService { }) } + /** + * Returns the right nonce to propose a new transaction after the last pending transaction. + * + * @param safeAddress - The Safe address + * @returns The right nonce to propose a new transaction after the last pending transaction + * @throws "Invalid Safe address" + * @throws "Invalid data" + * @throws "Invalid ethereum address" + */ + async getNextNonce(safeAddress: string): Promise { + if (safeAddress === '') { + throw new Error('Invalid Safe address') + } + const pendingTransactions = await this.getPendingTransactions(safeAddress) + if (pendingTransactions.results.length > 0) { + const nonces = pendingTransactions.results.map((tx) => tx.nonce) + const lastNonce = Math.max(...nonces) + return lastNonce + 1 + } + const safeInfo = await this.getSafeInfo(safeAddress) + return safeInfo.nonce + } + /** * Returns the balances for Ether and ERC20 tokens of a Safe. * diff --git a/packages/safe-service-client/src/SafeTransactionService.ts b/packages/safe-service-client/src/SafeTransactionService.ts index 35c9f611b..f8904ef91 100644 --- a/packages/safe-service-client/src/SafeTransactionService.ts +++ b/packages/safe-service-client/src/SafeTransactionService.ts @@ -70,6 +70,7 @@ interface SafeTransactionService { safeAddress: string, currentNonce?: number ): Promise + getNextNonce(safeAddress: string): Promise // Balances getBalances(safeAddress: string, options?: SafeBalancesOptions): Promise From 8cee9b5238ad614309168d892b270da8ff9de839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 13 Oct 2021 14:26:24 +0200 Subject: [PATCH 32/39] Make signatures nullable in SafeMultisigTransactionResponse (#78) * Make signatures prop in SafeMultisigTransactionResponse nullable * Add script to generate tx-service types * Add openapi files to gitignore --- .gitignore | 2 ++ .../safe-service-client/scripts/generateTxServiceTypes.sh | 5 +++++ .../src/types/safeTransactionServiceTypes.ts | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 packages/safe-service-client/scripts/generateTxServiceTypes.sh diff --git a/.gitignore b/.gitignore index 44cf682e2..1d6f82d93 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ deployments # Typechain typechain + +openapi/ diff --git a/packages/safe-service-client/scripts/generateTxServiceTypes.sh b/packages/safe-service-client/scripts/generateTxServiceTypes.sh new file mode 100644 index 000000000..70a437cdf --- /dev/null +++ b/packages/safe-service-client/scripts/generateTxServiceTypes.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +mkdir -p openapi +curl 'https://safe-transaction.rinkeby.gnosis.io/?format=openapi' > openapi/tx-service.json +npx openapi-typescript openapi/tx-service.json --output openapi/tx-service.ts diff --git a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts index d1cd9a81c..d0b60b121 100644 --- a/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts +++ b/packages/safe-service-client/src/types/safeTransactionServiceTypes.ts @@ -140,7 +140,7 @@ export type SafeMultisigTransactionResponse = { readonly dataDecoded?: string readonly confirmationsRequired: number readonly confirmations?: SafeMultisigConfirmationResponse[] - readonly signatures: string + readonly signatures?: string } export type SafeMultisigTransactionListResponse = { From fe2bb974d814d2d7b3640efa91147ffe7fb0895d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Wed, 13 Oct 2021 16:44:26 +0200 Subject: [PATCH 33/39] Fix SafeTransactionDataPartial import (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix SafeTransactionDataPartial import * fix documentation: wrong import (#87) * fix import Co-authored-by: Germán Martínez Co-authored-by: Parv Garg --- packages/safe-core-sdk/README.md | 2 +- packages/safe-core-sdk/src/utils/transactions/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/safe-core-sdk/README.md b/packages/safe-core-sdk/README.md index bf7786ae5..8c04ddae5 100644 --- a/packages/safe-core-sdk/README.md +++ b/packages/safe-core-sdk/README.md @@ -92,7 +92,7 @@ const safeSdk: Safe = await Safe.create({ ethAdapter: ethAdapterOwner1, safeAddr ### 3. Create a Safe transaction ```js -import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk' +import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' const transactions: SafeTransactionDataPartial[] = [{ to: '0x
', diff --git a/packages/safe-core-sdk/src/utils/transactions/types.ts b/packages/safe-core-sdk/src/utils/transactions/types.ts index df0057cca..4fb2e7338 100644 --- a/packages/safe-core-sdk/src/utils/transactions/types.ts +++ b/packages/safe-core-sdk/src/utils/transactions/types.ts @@ -1,5 +1,5 @@ import { ContractTransaction } from '@ethersproject/contracts' -import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types/dist/src' +import { SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' import { PromiEvent, TransactionReceipt } from 'web3-core/types' export type SafeTransactionOptionalProps = Pick< From e48cd05d4bca2a908f8bdf005b368829e2849330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Sat, 23 Oct 2021 15:11:05 +0100 Subject: [PATCH 34/39] Export EthSignSignature --- packages/safe-core-sdk/src/index.ts | 4 +++- packages/safe-core-sdk/src/utils/signatures/SafeSignature.ts | 4 +++- packages/safe-core-sdk/src/utils/signatures/index.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/safe-core-sdk/src/index.ts b/packages/safe-core-sdk/src/index.ts index 65260ceba..4552edb1c 100644 --- a/packages/safe-core-sdk/src/index.ts +++ b/packages/safe-core-sdk/src/index.ts @@ -14,6 +14,7 @@ import SafeFactory, { SafeDeploymentConfig, SafeFactoryConfig } from './safeFactory' +import EthSignSignature from './utils/signatures/SafeSignature' import { SafeTransactionOptionalProps, TransactionOptions, @@ -40,5 +41,6 @@ export { SafeTransactionOptionalProps, AddOwnerTxParams, RemoveOwnerTxParams, - SwapOwnerTxParams + SwapOwnerTxParams, + EthSignSignature } diff --git a/packages/safe-core-sdk/src/utils/signatures/SafeSignature.ts b/packages/safe-core-sdk/src/utils/signatures/SafeSignature.ts index 2f7534c7f..1c2df2c19 100644 --- a/packages/safe-core-sdk/src/utils/signatures/SafeSignature.ts +++ b/packages/safe-core-sdk/src/utils/signatures/SafeSignature.ts @@ -1,6 +1,6 @@ import { SafeSignature } from '@gnosis.pm/safe-core-sdk-types' -export class EthSignSignature implements SafeSignature { +class EthSignSignature implements SafeSignature { signer: string data: string @@ -34,3 +34,5 @@ export class EthSignSignature implements SafeSignature { return '' } } + +export default EthSignSignature diff --git a/packages/safe-core-sdk/src/utils/signatures/index.ts b/packages/safe-core-sdk/src/utils/signatures/index.ts index 9008e90f6..533786737 100644 --- a/packages/safe-core-sdk/src/utils/signatures/index.ts +++ b/packages/safe-core-sdk/src/utils/signatures/index.ts @@ -2,7 +2,7 @@ import { SafeSignature } from '@gnosis.pm/safe-core-sdk-types' import { bufferToHex, ecrecover, pubToAddress } from 'ethereumjs-util' import EthAdapter from 'ethereumLibs/EthAdapter' import { sameString } from '../../utils' -import { EthSignSignature } from './SafeSignature' +import EthSignSignature from './SafeSignature' export function generatePreValidatedSignature(ownerAddress: string): SafeSignature { const signature = From 3a7c2560097f6f9f2f0e25fbd8980f90b095ac07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 26 Oct 2021 10:34:34 +0200 Subject: [PATCH 35/39] Fix validation of new threshold when adding an owner --- packages/safe-core-sdk/src/managers/ownerManager.ts | 2 +- packages/safe-core-sdk/tests/ownerManager.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/safe-core-sdk/src/managers/ownerManager.ts b/packages/safe-core-sdk/src/managers/ownerManager.ts index 7e409d02a..0da21a3c3 100644 --- a/packages/safe-core-sdk/src/managers/ownerManager.ts +++ b/packages/safe-core-sdk/src/managers/ownerManager.ts @@ -71,7 +71,7 @@ class OwnerManager { const owners = await this.getOwners() this.validateAddressIsNotOwner(ownerAddress, owners) const newThreshold = threshold ?? (await this.getThreshold()) - this.validateThreshold(newThreshold, owners.length) + this.validateThreshold(newThreshold, owners.length + 1) return this.#safeContract.encode('addOwnerWithThreshold', [ownerAddress, newThreshold]) } diff --git a/packages/safe-core-sdk/tests/ownerManager.test.ts b/packages/safe-core-sdk/tests/ownerManager.test.ts index 481172a0a..04bdb8c76 100644 --- a/packages/safe-core-sdk/tests/ownerManager.test.ts +++ b/packages/safe-core-sdk/tests/ownerManager.test.ts @@ -236,7 +236,7 @@ describe('Safe owners manager', () => { safeAddress: safe.address, contractNetworks }) - const newThreshold = 1 + const newThreshold = 2 const initialOwners = await safeSdk.getOwners() chai.expect(initialOwners.length).to.be.eq(1) chai.expect(initialOwners[0]).to.be.eq(account1.address) From 46f3dcb47939fa91b87453ef799de0210526edb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Tue, 26 Oct 2021 17:39:31 +0200 Subject: [PATCH 36/39] Add INFURA_KEY to Github workflow files --- .github/workflows/e2e-test.yml | 2 ++ .github/workflows/test.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 62da1ba54..cfa26a866 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -8,6 +8,8 @@ on: branches: - development - main +env: + INFURA_KEY: ${{ secrets.INFURA_KEY }} jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67df6a9f3..2f3d4b0b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,7 @@ name: Hardhat Test on: [push, pull_request] +env: + INFURA_KEY: ${{ secrets.INFURA_KEY }} jobs: test: runs-on: ubuntu-latest From f2f8dad9c2a5766d54cdb299315bd570d90feb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 28 Oct 2021 11:15:50 +0200 Subject: [PATCH 37/39] v1.0.0 safe-core-sdk --- packages/safe-core-sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-core-sdk/package.json b/packages/safe-core-sdk/package.json index 817e09050..e16a99a0b 100644 --- a/packages/safe-core-sdk/package.json +++ b/packages/safe-core-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@gnosis.pm/safe-core-sdk", - "version": "0.4.0", + "version": "1.0.0", "description": "Safe Core SDK", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", From 73b447a032c7c703bbc82676cab2ea5071681ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 28 Oct 2021 11:16:00 +0200 Subject: [PATCH 38/39] v1.0.0 safe-service-client --- packages/safe-service-client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/safe-service-client/package.json b/packages/safe-service-client/package.json index c79ce4df8..b0cad41b7 100644 --- a/packages/safe-service-client/package.json +++ b/packages/safe-service-client/package.json @@ -1,6 +1,6 @@ { "name": "@gnosis.pm/safe-service-client", - "version": "0.2.0", + "version": "1.0.0", "description": "Safe Service Client", "main": "dist/src/index.js", "typings": "dist/src/index.d.ts", From ee956bcfe2d7a7fb8566ee2ed6cf807a639489eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADnez?= Date: Thu, 28 Oct 2021 11:33:01 +0200 Subject: [PATCH 39/39] Fix CI tests --- .../e2e/getSafeDelegates.test.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/safe-service-client/e2e/getSafeDelegates.test.ts b/packages/safe-service-client/e2e/getSafeDelegates.test.ts index 100be2205..2a8db7fd9 100644 --- a/packages/safe-service-client/e2e/getSafeDelegates.test.ts +++ b/packages/safe-service-client/e2e/getSafeDelegates.test.ts @@ -56,13 +56,14 @@ describe('getSafeDelegates', () => { const safeDelegateListResponse = await serviceSdk.getSafeDelegates(safeAddress) const { results } = safeDelegateListResponse - chai.expect(results.length).to.be.eq(2) - chai.expect(results[0].delegate).to.be.eq(delegateConfig1.delegate) - chai.expect(results[0].delegator).to.be.eq(await delegateConfig1.signer.getAddress()) - chai.expect(results[0].label).to.be.eq(delegateConfig1.label) - chai.expect(results[1].delegate).to.be.eq(delegateConfig2.delegate) - chai.expect(results[1].delegator).to.be.eq(await delegateConfig2.signer.getAddress()) - chai.expect(results[1].label).to.be.eq(delegateConfig2.label) + const sortedResults = results.sort((a, b) => (a.delegate > b.delegate ? -1 : 1)) + chai.expect(sortedResults.length).to.be.eq(2) + chai.expect(sortedResults[0].delegate).to.be.eq(delegateConfig1.delegate) + chai.expect(sortedResults[0].delegator).to.be.eq(await delegateConfig1.signer.getAddress()) + chai.expect(sortedResults[0].label).to.be.eq(delegateConfig1.label) + chai.expect(sortedResults[1].delegate).to.be.eq(delegateConfig2.delegate) + chai.expect(sortedResults[1].delegator).to.be.eq(await delegateConfig2.signer.getAddress()) + chai.expect(sortedResults[1].label).to.be.eq(delegateConfig2.label) await serviceSdk.removeAllSafeDelegates(safeAddress, signer) })