Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(safe-service-client) Handle error messages and add e2e tests #64

Merged
merged 15 commits into from
Sep 14, 2021
Merged
26 changes: 26 additions & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: e2e Test
on:
push:
branches:
- development
- main
pull_request:
branches:
- development
- main
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: |
yarn install
yarn build
yarn test:ci
7 changes: 4 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn build
- run: yarn test
- run: |
yarn install
yarn build
yarn test
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@
"clean": "lerna clean",
"unbuild": "lerna run unbuild",
"build": "lerna run build --stream",
"test": "FORCE_COLOR=1 lerna run test --stream"
"test": "FORCE_COLOR=1 lerna run test --stream",
"test:ci": "FORCE_COLOR=1 lerna run test:ci --stream"
},
"workspaces": {
"packages": [
5 changes: 5 additions & 0 deletions packages/safe-service-client/e2e/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const config = {
baseUrl: 'https://safe-transaction.rinkeby.staging.gnosisdev.com'
}

export default config
44 changes: 44 additions & 0 deletions packages/safe-service-client/e2e/decodeData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('decodeData', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if data is empty', async () => {
const data = ''
await chai.expect(serviceSdk.decodeData(data)).to.be.rejectedWith('Invalid data')
})

it('should fail if data is invalid', async () => {
const data = '0x1'
await chai
.expect(serviceSdk.decodeData(data))
.to.be.rejectedWith('Ensure this field has at least 1 hexadecimal chars (not counting 0x).')
})

it('should fail if the function selector is not found', async () => {
const data = '0x123456789'
await chai.expect(serviceSdk.decodeData(data)).to.be.rejectedWith('Not Found')
})

it('should decode the data', async () => {
const data = '0x610b592500000000000000000000000090F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
const decodedData = await serviceSdk.decodeData(data)
chai.expect(JSON.stringify(decodedData)).to.be.equal(
JSON.stringify({
method: 'enableModule',
parameters: [
{
name: 'module',
type: 'address',
value: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
}
]
})
)
})
})
38 changes: 38 additions & 0 deletions packages/safe-service-client/e2e/getBalances.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { SafeBalanceResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getBalances', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if Safe address is empty', async () => {
const safeAddress = ''
await chai
.expect(serviceSdk.getBalances(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.getBalances(safeAddress))
.to.be.rejectedWith('Checksum address validation failed')
})

it('should return the list of balances', async () => {
const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'
const balances: SafeBalanceResponse[] = 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)
chai.expect(ethBalance.balance).to.be.equal('4000000000000000000')
const wethBalance = balances.filter(
(safeBalance) => safeBalance.tokenAddress === '0xc778417E063141139Fce010982780140Aa0cD5Ab'
)[0]
chai.expect(wethBalance.token.symbol).to.be.equal('WETH')
chai.expect(wethBalance.balance).to.be.equal('10000000000000000')
})
})
37 changes: 37 additions & 0 deletions packages/safe-service-client/e2e/getCollectibles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { SafeCollectibleResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getCollectibles', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if Safe address is empty', async () => {
const safeAddress = ''
await chai
.expect(serviceSdk.getCollectibles(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.getCollectibles(safeAddress))
.to.be.rejectedWith('Checksum address validation failed')
})

it('should return the list of collectibles', async () => {
const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'
const safeCollectibleResponse: SafeCollectibleResponse[] = await serviceSdk.getCollectibles(
safeAddress
)
chai.expect(safeCollectibleResponse.length).to.be.equal(2)
safeCollectibleResponse.map((safeCollectible: SafeCollectibleResponse) => {
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')
})
})
})
36 changes: 36 additions & 0 deletions packages/safe-service-client/e2e/getIncomingTransactions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { TransferListResponse, TransferResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getIncomingTransactions', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if Safe address is empty', async () => {
const safeAddress = ''
await chai
.expect(serviceSdk.getIncomingTransactions(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.getIncomingTransactions(safeAddress))
.to.be.rejectedWith('Checksum address validation failed')
})

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(5)
chai.expect(transferListResponse.results.length).to.be.equal(5)
transferListResponse.results.map((transaction: TransferResponse) => {
chai.expect(transaction.to).to.be.equal(safeAddress)
})
})
})
40 changes: 40 additions & 0 deletions packages/safe-service-client/e2e/getMultisigTransactions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, {
SafeMultisigTransactionListResponse,
SafeMultisigTransactionResponse
} from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getMultisigTransactions', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if Safe address is empty', async () => {
const safeAddress = ''
await chai
.expect(serviceSdk.getMultisigTransactions(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.getMultisigTransactions(safeAddress))
.to.be.rejectedWith('Checksum address validation failed')
})

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(1)
chai.expect(safeMultisigTransactionListResponse.results.length).to.be.equal(1)
safeMultisigTransactionListResponse.results.map(
(transaction: SafeMultisigTransactionResponse) => {
chai.expect(transaction.safe).to.be.equal(safeAddress)
}
)
})
})
32 changes: 32 additions & 0 deletions packages/safe-service-client/e2e/getPendingTransactions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { SafeMultisigTransactionListResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getPendingTransactions', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if safeAddress is empty', async () => {
const safeAddress = ''
await chai
.expect(serviceSdk.getPendingTransactions(safeAddress))
.to.be.rejectedWith('Invalid Safe address')
})

it('should fail if safeAddress is not checksummed', async () => {
const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'.toLowerCase()
await chai
.expect(serviceSdk.getPendingTransactions(safeAddress))
.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)
chai.expect(transactionList.count).to.be.equal(0)
chai.expect(transactionList.results.length).to.be.equal(0)
})
})
30 changes: 30 additions & 0 deletions packages/safe-service-client/e2e/getSafeInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { SafeInfoResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getSafeInfo', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if Safe address is empty', async () => {
const safeAddress = ''
await chai
.expect(serviceSdk.getSafeInfo(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.getSafeInfo(safeAddress))
.to.be.rejectedWith('Checksum address validation failed')
})

it('should return an empty array if the safeTxHash is not found', async () => {
const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'
const safeInfoResponse: SafeInfoResponse = await serviceSdk.getSafeInfo(safeAddress)
chai.expect(safeInfoResponse.address).to.be.equal(safeAddress)
})
})
38 changes: 38 additions & 0 deletions packages/safe-service-client/e2e/getSafesByOwner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { OwnerResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getSafesByOwner', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if owner address is empty', async () => {
const ownerAddress = ''
await chai
.expect(serviceSdk.getSafesByOwner(ownerAddress))
.to.be.rejectedWith('Invalid owner address')
})

it('should fail if owner address is not checksummed', async () => {
const ownerAddress = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'.toLowerCase()
await chai
.expect(serviceSdk.getSafesByOwner(ownerAddress))
.to.be.rejectedWith('Checksum address validation failed')
})

it('should return an empty array if there are no owned Safes', async () => {
const ownerAddress = '0x0000000000000000000000000000000000000001'
const ownerResponse: 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 { safes } = ownerResponse
chai.expect(safes.length).to.be.greaterThan(1)
})
})
12 changes: 12 additions & 0 deletions packages/safe-service-client/e2e/getServiceInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect } from 'chai'
import SafeServiceClient, { SafeServiceInfoResponse } from '../src'
import config from './config'

describe('getServiceInfo', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should return the Safe info', async () => {
const safeInfo: SafeServiceInfoResponse = await serviceSdk.getServiceInfo()
expect(safeInfo.api_version).to.be.equal('v1')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import chai from 'chai'
import SafeServiceClient, { MasterCopyResponse } from '../src'
import config from './config'

describe('getServiceMasterCopiesInfo', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should call getServiceMasterCopiesInfo', async () => {
const masterCopiesResponse: MasterCopyResponse[] = await serviceSdk.getServiceMasterCopiesInfo()
chai.expect(masterCopiesResponse.length).to.be.greaterThan(1)
masterCopiesResponse.map((masterCopy: MasterCopyResponse) => {
chai.expect(masterCopy.deployer).to.be.equal('Gnosis')
})
})
})
28 changes: 28 additions & 0 deletions packages/safe-service-client/e2e/getToken.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { TokenInfoResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getToken', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if token address is empty', async () => {
const tokenAddress = ''
await chai.expect(serviceSdk.getToken(tokenAddress)).to.be.rejectedWith('Invalid token address')
})

it('should fail if token address is not checksummed', async () => {
const tokenAddress = '0xc778417E063141139Fce010982780140Aa0cD5Ab'.toLowerCase()
await chai
.expect(serviceSdk.getToken(tokenAddress))
.to.be.rejectedWith('Invalid ethereum address')
})

it('should return the token info', async () => {
const tokenAddress = '0xc778417E063141139Fce010982780140Aa0cD5Ab'
const tokenInfoResponse: TokenInfoResponse = await serviceSdk.getToken(tokenAddress)
chai.expect(tokenInfoResponse.address).to.be.equal('0xc778417E063141139Fce010982780140Aa0cD5Ab')
})
})
13 changes: 13 additions & 0 deletions packages/safe-service-client/e2e/getTokenList.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import chai from 'chai'
import SafeServiceClient, { TokenInfoListResponse } from '../src'
import config from './config'

describe('getTokenList', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should return an array of tokens', async () => {
const tokenInfoListResponse: TokenInfoListResponse = await serviceSdk.getTokenList()
chai.expect(tokenInfoListResponse.count).to.be.greaterThan(1)
chai.expect(tokenInfoListResponse.results.length).to.be.greaterThan(1)
})
})
28 changes: 28 additions & 0 deletions packages/safe-service-client/e2e/getTransaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { SafeMultisigTransactionResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getTransaction', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if safeTxHash is empty', async () => {
const safeTxHash = ''
await chai
.expect(serviceSdk.getTransaction(safeTxHash))
.to.be.rejectedWith('Invalid safeTxHash')
})

it('should fail if safeTxHash is not found', async () => {
const safeTxHash = '0x'
await chai.expect(serviceSdk.getTransaction(safeTxHash)).to.be.rejectedWith('Not found.')
})

it('should return the transaction with the given safeTxHash', async () => {
const safeTxHash = '0xb22be4e57718560c89de96acd1acefe55c2673b31a7019a374ebb1d8a2842f5d'
const transaction: SafeMultisigTransactionResponse = await serviceSdk.getTransaction(safeTxHash)
chai.expect(transaction.safeTxHash).to.be.equal(safeTxHash)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { SafeMultisigConfirmationListResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getTransactionConfirmations', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if safeTxHash is empty', async () => {
const safeTxHash = ''
await chai
.expect(serviceSdk.getTransactionConfirmations(safeTxHash))
.to.be.rejectedWith('Invalid safeTxHash')
})

it('should return an empty array if the safeTxHash is not found', async () => {
const safeTxHash = '0x'
const transactionConfirmations: SafeMultisigConfirmationListResponse =
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)
chai.expect(transactionConfirmations.count).to.be.equal(2)
chai.expect(transactionConfirmations.results.length).to.be.equal(2)
})
})
44 changes: 44 additions & 0 deletions packages/safe-service-client/e2e/getUsdBalances.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import SafeServiceClient, { SafeBalanceUsdResponse } from '../src'
import config from './config'

chai.use(chaiAsPromised)

describe('getUsdBalances', () => {
const serviceSdk = new SafeServiceClient(config.baseUrl)

it('should fail if Safe address is empty', async () => {
const safeAddress = ''
await chai
.expect(serviceSdk.getUsdBalances(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.getUsdBalances(safeAddress))
.to.be.rejectedWith('Checksum address validation failed')
})

it('should return the list of USD balances', async () => {
const safeAddress = '0xf9A2FAa4E3b140ad42AAE8Cac4958cFf38Ab08fD'
const balances: SafeBalanceUsdResponse[] = 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)
chai.expect(ethBalance.balance).to.be.equal('4000000000000000000')
chai.expect(ethBalance.fiatCode).to.be.equal('USD')
chai.expect(ethBalance.fiatBalance).not.to.be.equal('')
chai.expect(ethBalance.fiatConversion).not.to.be.equal('')
const wethBalance = balances.filter(
(safeBalance) => safeBalance.tokenAddress === '0xc778417E063141139Fce010982780140Aa0cD5Ab'
)[0]
chai.expect(wethBalance.token.symbol).to.be.equal('WETH')
chai.expect(wethBalance.balance).to.be.equal('10000000000000000')
chai.expect(wethBalance.fiatCode).to.be.equal('USD')
chai.expect(wethBalance.fiatBalance).not.to.be.equal('')
chai.expect(wethBalance.fiatConversion).not.to.be.equal('')
})
})
3 changes: 2 additions & 1 deletion packages/safe-service-client/package.json
Original file line number Diff line number Diff line change
@@ -14,7 +14,8 @@
"unbuild": "rimraf dist",
"build": "yarn rimraf dist && tsc",
"test": "mocha --require ts-node/register tests/**/*.test.ts",
"format": "prettier --write \"{src,tests}/**/*.ts\"",
"test:ci": "mocha --require ts-node/register e2e/**/*.test.ts",
"format": "prettier --write \"{src,tests,e2e}/**/*.ts\"",
"lint": "tslint -p tsconfig.json"
},
"repository": {
156 changes: 120 additions & 36 deletions packages/safe-service-client/src/SafeServiceClient.ts
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ class SafeServiceClient implements SafeTransactionService {
*
* @returns The list of Safe master copies
*/
async getServiceMasterCopiesInfo(): Promise<MasterCopyResponse> {
async getServiceMasterCopiesInfo(): Promise<MasterCopyResponse[]> {
return sendRequest({
url: `${this.#txServiceBaseUrl}/about/master-copies`,
method: HttpMethod.Get
@@ -62,10 +62,14 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param data - The Safe transaction data
* @returns The transaction data decoded
* @throws "404 Cannot find function selector to decode data"
* @throws "422 Invalid data"
* @throws "Invalid data"
* @throws "Not Found"
* @throws "Ensure this field has at least 1 hexadecimal chars (not counting 0x)."
*/
async decodeData(data: string): Promise<any> {
if (data === '') {
throw new Error('Invalid data')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/data-decoder/`,
method: HttpMethod.Post,
@@ -78,9 +82,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param ownerAddress - The owner address
* @returns The list of Safes where the address provided is an owner
* @throws "422 Owner address checksum not valid"
* @throws "Invalid owner address"
* @throws "Checksum address validation failed"
*/
async getSafesByOwner(ownerAddress: string): Promise<OwnerResponse> {
if (ownerAddress === '') {
throw new Error('Invalid owner address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/owners/${ownerAddress}/safes/`,
method: HttpMethod.Get
@@ -92,8 +100,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeTxHash - Hash of the Safe transaction
* @returns The information of a Safe transaction
* @throws "Invalid safeTxHash"
* @throws "Not found."
*/
async getTransaction(safeTxHash: string): Promise<SafeMultisigTransactionResponse> {
if (safeTxHash === '') {
throw new Error('Invalid safeTxHash')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/multisig-transactions/${safeTxHash}/`,
method: HttpMethod.Get
@@ -105,11 +118,14 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeTxHash - The hash of the Safe transaction
* @returns The list of confirmations
* @throws "400 Invalid data"
* @throws "Invalid safeTxHash"
*/
async getTransactionConfirmations(
safeTxHash: string
): Promise<SafeMultisigConfirmationListResponse> {
if (safeTxHash === '') {
throw new Error('Invalid safeTxHash')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/multisig-transactions/${safeTxHash}/confirmations/`,
method: HttpMethod.Get
@@ -122,10 +138,18 @@ class SafeServiceClient implements SafeTransactionService {
* @param safeTxHash - Hash of the Safe transaction that will be confirmed
* @param signature - Signature of the transaction
* @returns
* @throws "400 Malformed data"
* @throws "422 Error processing data"
* @throws "Invalid safeTxHash"
* @throws "Invalid signature"
* @throws "Malformed data"
* @throws "Error processing data"
*/
async confirmTransaction(safeTxHash: string, signature: string): Promise<SignatureResponse> {
if (safeTxHash === '') {
throw new Error('Invalid safeTxHash')
}
if (signature === '') {
throw new Error('Invalid signature')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/multisig-transactions/${safeTxHash}/confirmations/`,
method: HttpMethod.Post,
@@ -140,10 +164,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The information and configuration of the provided Safe address
* @throws "404 Safe not found"
* @throws "422 Checksum address validation failed/Cannot get Safe info"
* @throws "Invalid Safe address"
* @throws "Checksum address validation failed"
*/
async getSafeInfo(safeAddress: string): Promise<SafeInfoResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/`,
method: HttpMethod.Get
@@ -155,10 +182,14 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The list of delegates
* @throws "400 Invalid data"
* @throws "422 Invalid ethereum address"
* @throws "Invalid Safe address"
* @throws "Invalid data"
* @throws "Invalid ethereum address"
*/
async getSafeDelegates(safeAddress: string): Promise<SafeDelegateListResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/`,
method: HttpMethod.Get
@@ -171,10 +202,14 @@ class SafeServiceClient implements SafeTransactionService {
* @param safeAddress - The Safe address
* @param delegate - The new delegate
* @returns
* @throws "400 Malformed data"
* @throws "422 Invalid Ethereum address/Error processing data"
* @throws "Invalid Safe address"
* @throws "Malformed data"
* @throws "Invalid Ethereum address/Error processing data"
*/
async addSafeDelegate(safeAddress: string, delegate: SafeDelegate): Promise<any> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/`,
method: HttpMethod.Post,
@@ -188,10 +223,14 @@ class SafeServiceClient implements SafeTransactionService {
* @param safeAddress - The Safe address
* @param delegate - The delegate that will be removed
* @returns
* @throws "400 Malformed data"
* @throws "422 Invalid Ethereum address/Error processing data"
* @throws "Invalid Safe address"
* @throws "Malformed data"
* @throws "Invalid Ethereum address/Error processing data"
*/
async removeSafeDelegate(safeAddress: string, delegate: SafeDelegateDelete): Promise<any> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/delegates/${delegate.delegate}`,
method: HttpMethod.Delete,
@@ -204,11 +243,15 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The creation information of a Safe
* @throws "404 Safe creation not found"
* @throws "422 Owner address checksum not valid"
* @throws "503 Problem connecting to Ethereum network"
* @throws "Invalid Safe address"
* @throws "Safe creation not found"
* @throws "Checksum address validation failed"
* @throws "Problem connecting to Ethereum network"
*/
async getSafeCreationInfo(safeAddress: string): Promise<SafeCreationInfoResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/creation/`,
method: HttpMethod.Get
@@ -221,14 +264,18 @@ class SafeServiceClient implements SafeTransactionService {
* @param safeAddress - The Safe address
* @param safeTransaction - The Safe transaction to estimate
* @returns The safeTxGas for the given Safe transaction
* @throws "400 Data not valid"
* @throws "404 Safe not found"
* @throws "422 Tx not valid"
* @throws "Invalid Safe address"
* @throws "Data not valid"
* @throws "Safe not found"
* @throws "Tx not valid"
*/
async estimateSafeTransaction(
safeAddress: string,
safeTransaction: SafeMultisigTransactionEstimate
): Promise<SafeMultisigTransactionEstimateResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/multisig-transactions/estimations/`,
method: HttpMethod.Post,
@@ -244,15 +291,23 @@ class SafeServiceClient implements SafeTransactionService {
* @param safeTxHash - The hash of the Safe transaction
* @param signature - The signature of an owner or delegate of the specified Safe
* @returns The hash of the Safe transaction proposed
* @throws "400 Invalid data"
* @throws "422 Invalid ethereum address/User is not an owner/Invalid safeTxHash/Invalid signature/Nonce already executed/Sender is not an owner"
* @throws "Invalid Safe address"
* @throws "Invalid Safe 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"
*/
async proposeTransaction(
safeAddress: string,
transaction: SafeTransactionData,
safeTxHash: string,
signature: SafeSignature
): Promise<void> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
if (safeTxHash === '') {
throw new Error('Invalid safeTxHash')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/multisig-transactions/`,
method: HttpMethod.Post,
@@ -270,9 +325,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The history of incoming transactions
* @throws "422 Safe address checksum not valid"
* @throws "Invalid Safe address"
* @throws "Checksum address validation failed"
*/
async getIncomingTransactions(safeAddress: string): Promise<TransferListResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/incoming-transfers/`,
method: HttpMethod.Get
@@ -284,10 +343,14 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The history of module transactions
* @throws "400 Invalid data"
* @throws "422 Invalid ethereum address"
* @throws "Invalid Safe address"
* @throws "Invalid data"
* @throws "Invalid ethereum address"
*/
async getModuleTransactions(safeAddress: string): Promise<SafeModuleTransactionListResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/module-transfers/`,
method: HttpMethod.Get
@@ -299,10 +362,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The history of multi-signature transactions
* @throws "400 Invalid data"
* @throws "422 Invalid ethereum address"
* @throws "Invalid Safe address"
* @throws "Checksum address validation failed"
*/
async getMultisigTransactions(safeAddress: string): Promise<SafeMultisigTransactionListResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/multisig-transactions/`,
method: HttpMethod.Get
@@ -315,13 +381,17 @@ class SafeServiceClient implements SafeTransactionService {
* @param safeAddress - The Safe address
* @param currentNonce - Current nonce of the Safe
* @returns The list of transactions waiting for the confirmation of the Safe owners
* @throws "400 Invalid data"
* @throws "422 Invalid ethereum address"
* @throws "Invalid Safe address"
* @throws "Invalid data"
* @throws "Invalid ethereum address"
*/
async getPendingTransactions(
safeAddress: string,
currentNonce?: number
): Promise<SafeMultisigTransactionListResponse> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
let nonce = currentNonce ? currentNonce : (await this.getSafeInfo(safeAddress)).nonce
return sendRequest({
url: `${
@@ -336,10 +406,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The balances for Ether and ERC20 tokens
* @throws "404 Safe not found"
* @throws "422 Safe address checksum not valid"
* @throws "Invalid Safe address"
* @throws "Checksum address validation failed"
*/
async getBalances(safeAddress: string): Promise<SafeBalanceResponse[]> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/balances/`,
method: HttpMethod.Get
@@ -351,10 +424,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The balances for Ether and ERC20 tokens with USD fiat conversion
* @throws "404 Safe not found"
* @throws "422 Safe address checksum not valid"
* @throws "Invalid Safe address"
* @throws "Checksum address validation failed"
*/
async getUsdBalances(safeAddress: string): Promise<SafeBalanceUsdResponse[]> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/balances/usd/`,
method: HttpMethod.Get
@@ -366,10 +442,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param safeAddress - The Safe address
* @returns The collectives owned by the given Safe
* @throws "404 Safe not found"
* @throws "422 Safe address checksum not valid"
* @throws "Invalid Safe address"
* @throws "Checksum address validation failed"
*/
async getCollectibles(safeAddress: string): Promise<SafeCollectibleResponse[]> {
if (safeAddress === '') {
throw new Error('Invalid Safe address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/safes/${safeAddress}/collectibles/`,
method: HttpMethod.Get
@@ -393,8 +472,13 @@ class SafeServiceClient implements SafeTransactionService {
*
* @param tokenAddress - The token address
* @returns The information of the given ERC20 token
* @throws "Invalid token address"
* @throws "Checksum address validation failed"
*/
async getToken(tokenAddress: string): Promise<TokenInfoResponse> {
if (tokenAddress === '') {
throw new Error('Invalid token address')
}
return sendRequest({
url: `${this.#txServiceBaseUrl}/tokens/${tokenAddress}/`,
method: HttpMethod.Get
2 changes: 1 addition & 1 deletion packages/safe-service-client/src/SafeTransactionService.ts
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ import {
interface SafeTransactionService {
// About
getServiceInfo(): Promise<SafeServiceInfoResponse>
getServiceMasterCopiesInfo(): Promise<MasterCopyResponse>
getServiceMasterCopiesInfo(): Promise<MasterCopyResponse[]>

// Data decoder
decodeData(data: string): Promise<any>
Original file line number Diff line number Diff line change
@@ -219,7 +219,7 @@ export type SafeCollectibleResponse = {
readonly name: string
readonly description: string
readonly imageUri: string
readonly medatada: any
readonly metadata: any
}

export type TokenInfoResponse = {
21 changes: 14 additions & 7 deletions packages/safe-service-client/src/utils/httpRequests.ts
Original file line number Diff line number Diff line change
@@ -20,20 +20,27 @@ export async function sendRequest<T>({ url, method, body }: HttpRequest): Promis
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response.data) {
throw new Error(error.response.data.message)
} else {
throw new Error(error.response.statusText)
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')
} else {
// Something happened in setting up the request that triggered an Error
throw new Error(error.message)
}
// Something happened in setting up the request that triggered an Error
throw new Error(error.message)
}
return response.data as T
}
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import { getTxServiceBaseUrl } from '../src/utils'
chai.use(chaiAsPromised)
chai.use(sinonChai)

describe('Safe Service Client', () => {
describe('Endpoint tests', () => {
const safeAddress = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
const ownerAddress = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'
const safeTxHash = '0xede78ed72e9a8afd2b7a21f35c86f56cba5fffb2fff0838e253b7a41d19ceb48'
@@ -38,7 +38,7 @@ describe('Safe Service Client', () => {
})

it('decodeData', async () => {
const data = '0x610b592500000000000000000000000033a458e072b182152bb30243f29585a82c45a22b'
const data = '0x610b592500000000000000000000000090F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
chai.expect(serviceSdk.decodeData(data)).to.be.eventually.deep.equals({ success: true })
chai
.expect(axiosPost)
@@ -51,7 +51,9 @@ describe('Safe Service Client', () => {
.to.be.eventually.deep.equals({ success: true })
chai
.expect(axiosGet)
.to.have.been.calledWith(`${getTxServiceBaseUrl(txServiceBaseUrl)}/owners/${ownerAddress}/safes/`)
.to.have.been.calledWith(
`${getTxServiceBaseUrl(txServiceBaseUrl)}/owners/${ownerAddress}/safes/`
)
})

it('getTransaction', async () => {
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
@@ -3562,11 +3562,16 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.19.tgz#538e61fc220f77ae4a4663c3d8c3cb391365c209"
integrity sha512-niAuZrwrjKck4+XhoCw6AAVQBENHftpXw9F4ryk66fTgYaKQ53R4FI7c9vUGGw5vQis1HKBHDR1gcYI/Bq1xvw==

"@types/node@^15.12.2", "@types/node@^15.12.4", "@types/node@^15.3.0":
"@types/node@^15.12.2", "@types/node@^15.3.0":
version "15.14.7"
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/normalize-package-data@^2.4.0":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"