├── .eslintrc.js ├── .github └── workflows │ ├── lint.yml │ ├── release.yml │ ├── test.yml │ └── typedoc.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE.md ├── README.md ├── jest.config.js ├── package.json ├── scripts ├── make-cgw-types.sh ├── make-config-service-types.sh └── make-transaction-service-types.sh ├── src ├── config.ts ├── endpoint.ts ├── index.ts ├── types │ ├── accounts.ts │ ├── api.ts │ ├── auth.ts │ ├── chains.ts │ ├── common.ts │ ├── contracts.ts │ ├── decoded-data.ts │ ├── delegates.ts │ ├── emails.ts │ ├── human-description.ts │ ├── master-copies.ts │ ├── notifications.ts │ ├── recovery.ts │ ├── relay.ts │ ├── safe-apps.ts │ ├── safe-info.ts │ ├── safe-messages.ts │ └── transactions.ts └── utils.ts ├── tests ├── endpoint.test.ts ├── types.test.ts └── utils.test.ts ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', // Plugin to use typescript with eslint 5 | 'prettier', // Add prettier rules to eslint 6 | 'plugin:prettier/recommended', // Plugin to use prettier rules with eslint 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, 10 | sourceType: 'module', 11 | }, 12 | rules: { 13 | '@typescript-eslint/camelcase': 'off', 14 | '@typescript-eslint/no-var-requires': 'off', 15 | '@typescript-eslint/no-empty-function': 'off', 16 | '@typescript-eslint/no-explicit-any': 'off', 17 | '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: 'Lint' 2 | on: [pull_request] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.ref }} 6 | cancel-in-progress: true 7 | 8 | jobs: 9 | eslint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - run: yarn install 15 | 16 | - name: Run lint 17 | run: npm run lint:report 18 | continue-on-error: true 19 | 20 | - name: Annotate code with linting results 21 | uses: ataylorme/eslint-annotate-action@v3 22 | with: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | report-json: "eslint_report.json" 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish-github: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Extract version 18 | id: version 19 | run: | 20 | OLD_VERSION=$(git describe --tags --abbrev=0) 21 | NEW_VERSION_NUMBER_ONLY=$(node -p 'require("./package.json").version') 22 | NEW_VERSION=v${NEW_VERSION_NUMBER_ONLY} 23 | echo "OLD_VERSION=$OLD_VERSION" >> $GITHUB_ENV 24 | echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV 25 | if [ "$NEW_VERSION" != "$OLD_VERSION" ]; then 26 | echo "New version $NEW_VERSION detected" 27 | echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT 28 | git log "$OLD_VERSION"..HEAD --pretty=format:"* %s" --invert-grep --grep="$NEW_VERSION_NUMBER_ONLY" > CHANGELOG.md 29 | else 30 | echo "Version $OLD_VERSION hasn't changed, skipping the release" 31 | fi 32 | 33 | - name: Create a git tag 34 | if: env.NEW_VERSION != env.OLD_VERSION 35 | run: git tag $NEW_VERSION && git push --tags 36 | 37 | - name: GitHub release 38 | if: env.NEW_VERSION != env.OLD_VERSION 39 | uses: actions/create-release@v1 40 | id: create_release 41 | with: 42 | draft: false 43 | prerelease: false 44 | release_name: ${{ env.NEW_VERSION }} 45 | tag_name: ${{ env.NEW_VERSION }} 46 | body_path: CHANGELOG.md 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 'Unit tests' 2 | on: [pull_request] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.ref }} 6 | cancel-in-progress: true 7 | 8 | jobs: 9 | tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - run: npm install 14 | 15 | - name: Run tests 16 | uses: mattallty/jest-github-action@v1 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | with: 20 | test-command: 'npm run test:ci -- --outputFile=/home/runner/work/_actions/mattallty/jest-github-action/v1/dist/jest.results.json' 21 | coverage-comment: false 22 | -------------------------------------------------------------------------------- /.github/workflows/typedoc.yml: -------------------------------------------------------------------------------- 1 | name: typedoc 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | typedoc: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v2 16 | 17 | - run: npm install 18 | 19 | - run: npm install typescript typedoc 20 | 21 | - name: Create the docs directory locally in CI 22 | run: ./node_modules/.bin/typedoc --githubPages src 23 | 24 | - name: Deploy 🚀 25 | uses: JamesIves/github-pages-deploy-action@4.1.4 26 | with: 27 | branch: gh-pages 28 | folder: docs 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .DS_Store 4 | yarn-error.log 5 | .env* 6 | .eslintcache 7 | /.idea 8 | electron-builder.yml 9 | /.yalc 10 | yalc.lock 11 | # testing 12 | /coverage/ 13 | tsconfig.tsbuildinfo 14 | /openapi 15 | .#* 16 | jest.results.json 17 | /docs -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .github 4 | dist 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 120, 4 | "trailingComma": "all", 5 | "singleQuote": true, 6 | "semi": false 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2023 Safe Ecosystem Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Safe Gateway TypeScript SDK 2 | 3 | [![npm](https://img.shields.io/npm/v/@safe-global/safe-gateway-typescript-sdk?label=%40safe-global%2Fsafe-gateway-typescript-sdk)](https://www.npmjs.com/package/@safe-global/safe-gateway-typescript-sdk) 4 | 5 | A TypeScript SDK for the [Safe Client Gateway](https://github.com/safe-global/safe-client-gateway) 6 | 7 | 📖 [Type reference](https://safe-global.github.io/safe-gateway-typescript-sdk/modules.html)   |   Swagger [CGW Swagger](https://safe-client.safe.global) 8 | 9 | ## Usage policy 10 | 11 | NB: Safe Client Gateway isn't meant for public use. 12 | Please _do not_ use this SDK if you're building, e.g., a Safe App. 13 | 14 | ## Using the SDK 15 | 16 | Install: 17 | 18 | ```shell 19 | yarn add @safe-global/safe-gateway-typescript-sdk 20 | ``` 21 | 22 | Import: 23 | 24 | ```ts 25 | import { getChainsConfig, type ChainListResponse } from '@safe-global/safe-gateway-typescript-sdk' 26 | ``` 27 | 28 | Use: 29 | 30 | ```ts 31 | const chains = await getChainsConfig() 32 | ``` 33 | 34 | The SDK needs no initialization unless you want to override the base URL. You can set an alternative base URL like so: 35 | 36 | ```ts 37 | import { setBaseUrl } from '@safe-global/safe-gateway-typescript-sdk' 38 | 39 | // Switch the SDK to dev mode 40 | setBaseUrl('https://safe-client.staging.5afe.dev') 41 | ``` 42 | 43 | The full SDK reference can be found [here](https://safe-global.github.io/safe-gateway-typescript-sdk/modules.html). 44 | 45 | ## Adding an endpoint 46 | 47 | Endpoint types are defined in `src/types/gateway.ts`. 48 | 49 | Each endpoint consists of: 50 | 51 | - a function defined in `src/index.ts` (e.g. `getBalances`) 52 | - a path definition (e.g. `'/chains/{chainId}/safes/{address}/balances/{currency}/'`) 53 | - operation definition (e.g. `safes_balances_list`) 54 | - response definition 55 | 56 | To add a new endpoint, follow the pattern set by the existing endpoints. 57 | 58 | ## Eslint & prettier 59 | 60 | This command will run before every commit: 61 | 62 | ```shell 63 | yarn eslint:fix 64 | ``` 65 | 66 | ## Tests 67 | 68 | To run the unit and e2e tests locally: 69 | 70 | ```shell 71 | yarn test 72 | ``` 73 | 74 | N.B.: the e2e tests make actual API calls on staging. 75 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | clearMocks: true, 4 | verbose: true, 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@safe-global/safe-gateway-typescript-sdk", 3 | "version": "3.23.1", 4 | "main": "dist/index.js", 5 | "types": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "repository": "git@github.com:safe-global/safe-gateway-typescript-sdk.git", 10 | "author": "katspaugh", 11 | "license": "MIT", 12 | "engines": { 13 | "node": ">=16" 14 | }, 15 | "devDependencies": { 16 | "@types/jest": "^29.5.14", 17 | "@typescript-eslint/eslint-plugin": "^8.19.1", 18 | "@typescript-eslint/parser": "^8.19.1", 19 | "eslint": "^9.18.0", 20 | "eslint-config-prettier": "^9.1.0", 21 | "eslint-plugin-import": "^2.31.0", 22 | "eslint-plugin-prettier": "^5.2.1", 23 | "husky": "^9.1.7", 24 | "jest": "^29.7.0", 25 | "prettier": "^3.4.2", 26 | "ts-jest": "^29.2.5", 27 | "typescript": "5.7.3" 28 | }, 29 | "scripts": { 30 | "lint": "tsc && eslint \"./src/**/*\"", 31 | "lint:fix": "yarn lint --fix", 32 | "lint:report": "yarn lint --output-file eslint_report.json --format json", 33 | "build": "rm -rf dist && tsc", 34 | "prettier": "prettier -w './**/*.ts'", 35 | "test": "jest --watch --coverage .", 36 | "test:check": "jest --testPathPattern=tests", 37 | "test:ci": "jest --ci --coverage --json --watchAll=false --testLocationInResults --runInBand --testPathPattern=tests" 38 | }, 39 | "husky": { 40 | "hooks": { 41 | "pre-commit": "lint-staged --allow-empty" 42 | } 43 | }, 44 | "lint-staged": { 45 | "src/**/*.{js,jsx,ts,tsx}": [ 46 | "eslint --fix", 47 | "prettier --w ." 48 | ] 49 | }, 50 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 51 | } 52 | -------------------------------------------------------------------------------- /scripts/make-cgw-types.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p openapi 4 | curl 'https://safe-client.safe.global/api-json' > openapi/cgw.json 5 | npx openapi-typescript openapi/cgw.json --output openapi/cgw.ts 6 | -------------------------------------------------------------------------------- /scripts/make-config-service-types.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p openapi 4 | curl 'https://safe-config.gnosis.io/?format=openapi' > openapi/config-service.json 5 | npx openapi-typescript openapi/config-service.json --output openapi/config-service.ts 6 | -------------------------------------------------------------------------------- /scripts/make-transaction-service-types.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p openapi 4 | curl 'https://safe-transaction.rinkeby.gnosis.io/?format=openapi' > openapi/tx-service.json 5 | npx openapi-typescript openapi/tx-service.json --output openapi/tx-service.ts 6 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_BASE_URL = 'https://safe-client.safe.global' 2 | -------------------------------------------------------------------------------- /src/endpoint.ts: -------------------------------------------------------------------------------- 1 | import { fetchData, getData, insertParams, stringifyQuery } from './utils' 2 | import type { DeleteEndpoint, GetEndpoint, paths, PostEndpoint, Primitive, PutEndpoint } from './types/api' 3 | 4 | function makeUrl( 5 | baseUrl: string, 6 | path: string, 7 | pathParams?: Record, 8 | query?: Record, 9 | ): string { 10 | const pathname = insertParams(path, pathParams) 11 | const search = stringifyQuery(query) 12 | return `${baseUrl}${pathname}${search}` 13 | } 14 | 15 | export function postEndpoint( 16 | baseUrl: string, 17 | path: T, 18 | params?: paths[T] extends PostEndpoint ? paths[T]['post']['parameters'] : never, 19 | ): Promise { 20 | const url = makeUrl(baseUrl, path as string, params?.path, params?.query) 21 | return fetchData(url, 'POST', params?.body, params?.headers, params?.credentials) 22 | } 23 | 24 | export function putEndpoint( 25 | baseUrl: string, 26 | path: T, 27 | params?: paths[T] extends PutEndpoint ? paths[T]['put']['parameters'] : never, 28 | ): Promise { 29 | const url = makeUrl(baseUrl, path as string, params?.path, params?.query) 30 | return fetchData(url, 'PUT', params?.body, params?.headers, params?.credentials) 31 | } 32 | 33 | export function deleteEndpoint( 34 | baseUrl: string, 35 | path: T, 36 | params?: paths[T] extends DeleteEndpoint ? paths[T]['delete']['parameters'] : never, 37 | ): Promise { 38 | const url = makeUrl(baseUrl, path as string, params?.path, params?.query) 39 | return fetchData(url, 'DELETE', params?.body, params?.headers, params?.credentials) 40 | } 41 | 42 | export function getEndpoint( 43 | baseUrl: string, 44 | path: T, 45 | params?: paths[T] extends GetEndpoint ? paths[T]['get']['parameters'] : never, 46 | rawUrl?: string, 47 | ): Promise { 48 | if (rawUrl) { 49 | return getData(rawUrl, undefined, params?.credentials) 50 | } 51 | const url = makeUrl(baseUrl, path as string, params?.path, params?.query) 52 | return getData(url, params?.headers, params?.credentials) 53 | } 54 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { deleteEndpoint, getEndpoint, postEndpoint, putEndpoint } from './endpoint' 2 | import type { operations } from './types/api' 3 | import type { 4 | SafeTransactionEstimation, 5 | TransactionDetails, 6 | TransactionListPage, 7 | SafeIncomingTransfersResponse, 8 | SafeModuleTransactionsResponse, 9 | SafeMultisigTransactionsResponse, 10 | NoncesResponse, 11 | TransactionPreview, 12 | } from './types/transactions' 13 | import type { 14 | EthereumAddress, 15 | AllOwnedSafes, 16 | FiatCurrencies, 17 | OwnedSafes, 18 | SafeBalanceResponse, 19 | SafeCollectibleResponse, 20 | SafeCollectiblesPage, 21 | } from './types/common' 22 | import type { SafeInfo, SafeOverview } from './types/safe-info' 23 | import type { ChainListResponse, ChainInfo } from './types/chains' 24 | import type { SafeAppsResponse } from './types/safe-apps' 25 | import type { MasterCopyReponse } from './types/master-copies' 26 | import type { AnyConfirmationView, DecodedDataResponse } from './types/decoded-data' 27 | import type { SafeMessage, SafeMessageListPage } from './types/safe-messages' 28 | import { DEFAULT_BASE_URL } from './config' 29 | import type { DelegateResponse, DelegatesRequest } from './types/delegates' 30 | import type { GetEmailResponse } from './types/emails' 31 | import type { RelayCountResponse, RelayTransactionResponse } from './types/relay' 32 | import type { Contract } from './types/contracts' 33 | import type { AuthNonce } from './types/auth' 34 | 35 | export * from './types/safe-info' 36 | export * from './types/safe-apps' 37 | export * from './types/transactions' 38 | export * from './types/chains' 39 | export * from './types/common' 40 | export * from './types/master-copies' 41 | export * from './types/decoded-data' 42 | export * from './types/safe-messages' 43 | export * from './types/notifications' 44 | export * from './types/relay' 45 | 46 | // Can be set externally to a different CGW host 47 | let baseUrl: string = DEFAULT_BASE_URL 48 | 49 | /** 50 | * Set the base CGW URL 51 | */ 52 | export const setBaseUrl = (url: string): void => { 53 | baseUrl = url 54 | } 55 | 56 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 57 | 58 | /** 59 | * Relay a transaction from a Safe 60 | */ 61 | export function relayTransaction( 62 | chainId: string, 63 | body: operations['relay_transaction']['parameters']['body'], 64 | ): Promise { 65 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/relay', { path: { chainId }, body }) 66 | } 67 | 68 | /** 69 | * Get the relay limit and number of remaining relays remaining 70 | */ 71 | export function getRelayCount(chainId: string, address: string): Promise { 72 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/relay/{address}', { path: { chainId, address } }) 73 | } 74 | 75 | /** 76 | * Get basic information about a Safe. E.g. owners, modules, version etc 77 | */ 78 | export function getSafeInfo(chainId: string, address: string): Promise { 79 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{address}', { path: { chainId, address } }) 80 | } 81 | 82 | /** 83 | * Get filterable list of incoming transactions 84 | */ 85 | export function getIncomingTransfers( 86 | chainId: string, 87 | address: string, 88 | query?: operations['incoming_transfers']['parameters']['query'], 89 | pageUrl?: string, 90 | ): Promise { 91 | return getEndpoint( 92 | baseUrl, 93 | '/v1/chains/{chainId}/safes/{address}/incoming-transfers/', 94 | { 95 | path: { chainId, address }, 96 | query, 97 | }, 98 | pageUrl, 99 | ) 100 | } 101 | 102 | /** 103 | * Get filterable list of module transactions 104 | */ 105 | export function getModuleTransactions( 106 | chainId: string, 107 | address: string, 108 | query?: operations['module_transactions']['parameters']['query'], 109 | pageUrl?: string, 110 | ): Promise { 111 | return getEndpoint( 112 | baseUrl, 113 | '/v1/chains/{chainId}/safes/{address}/module-transactions/', 114 | { 115 | path: { chainId, address }, 116 | query, 117 | }, 118 | pageUrl, 119 | ) 120 | } 121 | 122 | /** 123 | * Get filterable list of multisig transactions 124 | */ 125 | export function getMultisigTransactions( 126 | chainId: string, 127 | address: string, 128 | query?: operations['multisig_transactions']['parameters']['query'], 129 | pageUrl?: string, 130 | ): Promise { 131 | return getEndpoint( 132 | baseUrl, 133 | '/v1/chains/{chainId}/safes/{address}/multisig-transactions/', 134 | { 135 | path: { chainId, address }, 136 | query, 137 | }, 138 | pageUrl, 139 | ) 140 | } 141 | 142 | /** 143 | * Get the total balance and all assets stored in a Safe 144 | */ 145 | export function getBalances( 146 | chainId: string, 147 | address: string, 148 | currency = 'usd', 149 | query: operations['safes_balances_list']['parameters']['query'] = {}, 150 | ): Promise { 151 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{address}/balances/{currency}', { 152 | path: { chainId, address, currency }, 153 | query, 154 | }) 155 | } 156 | 157 | /** 158 | * Get a list of supported fiat currencies (e.g. USD, EUR etc) 159 | */ 160 | export function getFiatCurrencies(): Promise { 161 | return getEndpoint(baseUrl, '/v1/balances/supported-fiat-codes') 162 | } 163 | 164 | /** 165 | * Get the addresses of all Safes belonging to an owner 166 | */ 167 | export function getOwnedSafes(chainId: string, address: string): Promise { 168 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/owners/{address}/safes', { path: { chainId, address } }) 169 | } 170 | 171 | /** 172 | * Get the addresses of all Safes belonging to an owner on all chains 173 | */ 174 | export function getAllOwnedSafes(address: string): Promise { 175 | return getEndpoint(baseUrl, '/v1/owners/{address}/safes', { path: { address } }) 176 | } 177 | 178 | /** 179 | * Get NFTs stored in a Safe 180 | */ 181 | export function getCollectibles( 182 | chainId: string, 183 | address: string, 184 | query: operations['safes_collectibles_list']['parameters']['query'] = {}, 185 | ): Promise { 186 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{address}/collectibles', { 187 | path: { chainId, address }, 188 | query, 189 | }) 190 | } 191 | 192 | /** 193 | * Get NFTs stored in a Safe 194 | */ 195 | export function getCollectiblesPage( 196 | chainId: string, 197 | address: string, 198 | query: operations['safes_collectibles_list_paginated']['parameters']['query'] = {}, 199 | pageUrl?: string, 200 | ): Promise { 201 | return getEndpoint( 202 | baseUrl, 203 | '/v2/chains/{chainId}/safes/{address}/collectibles', 204 | { path: { chainId, address }, query }, 205 | pageUrl, 206 | ) 207 | } 208 | 209 | /** 210 | * Get a list of past Safe transactions 211 | */ 212 | export function getTransactionHistory( 213 | chainId: string, 214 | address: string, 215 | query: operations['history_transactions']['parameters']['query'] = {}, 216 | pageUrl?: string, 217 | ): Promise { 218 | return getEndpoint( 219 | baseUrl, 220 | '/v1/chains/{chainId}/safes/{safe_address}/transactions/history', 221 | { path: { chainId, safe_address: address }, query }, 222 | pageUrl, 223 | ) 224 | } 225 | 226 | /** 227 | * Get the list of pending transactions 228 | */ 229 | export function getTransactionQueue( 230 | chainId: string, 231 | address: string, 232 | query: operations['queued_transactions']['parameters']['query'] = {}, 233 | pageUrl?: string, 234 | ): Promise { 235 | return getEndpoint( 236 | baseUrl, 237 | '/v1/chains/{chainId}/safes/{safe_address}/transactions/queued', 238 | { path: { chainId, safe_address: address }, query }, 239 | pageUrl, 240 | ) 241 | } 242 | 243 | /** 244 | * Get the details of an individual transaction by its id 245 | */ 246 | export function getTransactionDetails(chainId: string, transactionId: string): Promise { 247 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/transactions/{transactionId}', { 248 | path: { chainId, transactionId }, 249 | }) 250 | } 251 | 252 | /** 253 | * Delete a transaction by its safeTxHash 254 | */ 255 | export function deleteTransaction( 256 | chainId: string, 257 | safeTxHash: string, 258 | signature: operations['delete_transaction']['parameters']['body']['signature'], 259 | ): Promise { 260 | return deleteEndpoint(baseUrl, '/v1/chains/{chainId}/transactions/{safeTxHash}', { 261 | path: { chainId, safeTxHash }, 262 | body: { signature }, 263 | }) 264 | } 265 | 266 | /** 267 | * Request a gas estimate & recommmended tx nonce for a created transaction 268 | */ 269 | export function postSafeGasEstimation( 270 | chainId: string, 271 | address: string, 272 | body: operations['post_safe_gas_estimation']['parameters']['body'], 273 | ): Promise { 274 | return postEndpoint(baseUrl, '/v2/chains/{chainId}/safes/{safe_address}/multisig-transactions/estimations', { 275 | path: { chainId, safe_address: address }, 276 | body, 277 | }) 278 | } 279 | 280 | export function getNonces(chainId: string, address: string): Promise { 281 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/nonces', { 282 | path: { chainId, safe_address: address }, 283 | }) 284 | } 285 | 286 | /** 287 | * Propose a new transaction for other owners to sign/execute 288 | */ 289 | export function proposeTransaction( 290 | chainId: string, 291 | address: string, 292 | body: operations['propose_transaction']['parameters']['body'], 293 | ): Promise { 294 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/transactions/{safe_address}/propose', { 295 | path: { chainId, safe_address: address }, 296 | body, 297 | }) 298 | } 299 | 300 | /** 301 | * Returns decoded data 302 | */ 303 | export function getConfirmationView( 304 | chainId: string, 305 | safeAddress: string, 306 | operation: operations['data_decoder']['parameters']['body']['operation'], 307 | data: operations['data_decoder']['parameters']['body']['data'], 308 | to?: operations['data_decoder']['parameters']['body']['to'], 309 | value?: operations['data_decoder']['parameters']['body']['value'], 310 | ): Promise { 311 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/views/transaction-confirmation', { 312 | path: { chainId, safe_address: safeAddress }, 313 | body: { operation, data, to, value }, 314 | }) 315 | } 316 | 317 | /** 318 | * Get a tx preview 319 | */ 320 | export function getTxPreview( 321 | chainId: string, 322 | safeAddress: string, 323 | operation: operations['data_decoder']['parameters']['body']['operation'], 324 | data: operations['data_decoder']['parameters']['body']['data'], 325 | to?: operations['data_decoder']['parameters']['body']['to'], 326 | value?: operations['data_decoder']['parameters']['body']['value'], 327 | ): Promise { 328 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/transactions/{safe_address}/preview', { 329 | path: { chainId, safe_address: safeAddress }, 330 | body: { operation, data, to, value }, 331 | }) 332 | } 333 | 334 | /** 335 | * Returns all defined chain configs 336 | */ 337 | export function getChainsConfig(query?: operations['chains_list']['parameters']['query']): Promise { 338 | return getEndpoint(baseUrl, '/v1/chains', { 339 | query, 340 | }) 341 | } 342 | 343 | /** 344 | * Returns a chain config 345 | */ 346 | export function getChainConfig(chainId: string): Promise { 347 | return getEndpoint(baseUrl, '/v1/chains/{chainId}', { 348 | path: { chainId: chainId }, 349 | }) 350 | } 351 | 352 | /** 353 | * Returns Safe Apps List 354 | */ 355 | export function getSafeApps( 356 | chainId: string, 357 | query: operations['safe_apps_read']['parameters']['query'] = {}, 358 | ): Promise { 359 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/safe-apps', { 360 | path: { chainId: chainId }, 361 | query, 362 | }) 363 | } 364 | 365 | /** 366 | * Returns list of Master Copies 367 | */ 368 | export function getMasterCopies(chainId: string): Promise { 369 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/about/master-copies', { 370 | path: { chainId: chainId }, 371 | }) 372 | } 373 | 374 | /** 375 | * Returns decoded data 376 | */ 377 | export function getDecodedData( 378 | chainId: string, 379 | operation: operations['data_decoder']['parameters']['body']['operation'], 380 | encodedData: operations['data_decoder']['parameters']['body']['data'], 381 | to?: operations['data_decoder']['parameters']['body']['to'], 382 | ): Promise { 383 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/data-decoder', { 384 | path: { chainId: chainId }, 385 | body: { operation, data: encodedData, to }, 386 | }) 387 | } 388 | 389 | /** 390 | * Returns list of `SafeMessage`s 391 | */ 392 | export function getSafeMessages(chainId: string, address: string, pageUrl?: string): Promise { 393 | return getEndpoint( 394 | baseUrl, 395 | '/v1/chains/{chainId}/safes/{safe_address}/messages', 396 | { path: { chainId, safe_address: address }, query: {} }, 397 | pageUrl, 398 | ) 399 | } 400 | 401 | /** 402 | * Returns a `SafeMessage` 403 | */ 404 | export function getSafeMessage(chainId: string, messageHash: string): Promise> { 405 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/messages/{message_hash}', { 406 | path: { chainId, message_hash: messageHash }, 407 | }) 408 | } 409 | 410 | /** 411 | * Propose a new `SafeMessage` for other owners to sign 412 | */ 413 | export function proposeSafeMessage( 414 | chainId: string, 415 | address: string, 416 | body: operations['propose_safe_message']['parameters']['body'], 417 | ): Promise { 418 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/messages', { 419 | path: { chainId, safe_address: address }, 420 | body, 421 | }) 422 | } 423 | 424 | /** 425 | * Add a confirmation to a `SafeMessage` 426 | */ 427 | export function confirmSafeMessage( 428 | chainId: string, 429 | messageHash: string, 430 | body: operations['confirm_safe_message']['parameters']['body'], 431 | ): Promise { 432 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/messages/{message_hash}/signatures', { 433 | path: { chainId, message_hash: messageHash }, 434 | body, 435 | }) 436 | } 437 | 438 | /** 439 | * Returns a list of delegates 440 | */ 441 | export function getDelegates(chainId: string, query: DelegatesRequest = {}): Promise { 442 | return getEndpoint(baseUrl, '/v2/chains/{chainId}/delegates', { 443 | path: { chainId }, 444 | query, 445 | }) 446 | } 447 | 448 | /** 449 | * Registers a device/Safe for notifications 450 | */ 451 | export function registerDevice(body: operations['register_device']['parameters']['body']): Promise { 452 | return postEndpoint(baseUrl, '/v1/register/notifications', { 453 | body, 454 | }) 455 | } 456 | 457 | /** 458 | * Unregisters a Safe from notifications 459 | */ 460 | export function unregisterSafe(chainId: string, address: string, uuid: string): Promise { 461 | return deleteEndpoint(baseUrl, '/v1/chains/{chainId}/notifications/devices/{uuid}/safes/{safe_address}', { 462 | path: { chainId, safe_address: address, uuid }, 463 | }) 464 | } 465 | 466 | /** 467 | * Unregisters a device from notifications 468 | */ 469 | export function unregisterDevice(chainId: string, uuid: string): Promise { 470 | return deleteEndpoint(baseUrl, '/v1/chains/{chainId}/notifications/devices/{uuid}', { 471 | path: { chainId, uuid }, 472 | }) 473 | } 474 | 475 | /** 476 | * Registers a email address for a safe signer. 477 | * 478 | * The signer wallet has to sign a message of format: `email-register-{chainId}-{safeAddress}-{emailAddress}-{signer}-{timestamp}` 479 | * The signature is valid for 5 minutes. 480 | * 481 | * @param chainId 482 | * @param safeAddress 483 | * @param body Signer address and email address 484 | * @param headers Signature and Signature timestamp 485 | * @returns 200 if signature matches the data 486 | */ 487 | export function registerEmail( 488 | chainId: string, 489 | safeAddress: string, 490 | body: operations['register_email']['parameters']['body'], 491 | headers: operations['register_email']['parameters']['headers'], 492 | ): Promise { 493 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/emails', { 494 | path: { chainId, safe_address: safeAddress }, 495 | body, 496 | headers, 497 | }) 498 | } 499 | 500 | /** 501 | * Changes an already registered email address for a safe signer. The new email address still needs to be verified. 502 | * 503 | * The signer wallet has to sign a message of format: `email-edit-{chainId}-{safeAddress}-{emailAddress}-{signer}-{timestamp}` 504 | * The signature is valid for 5 minutes. 505 | * 506 | * @param chainId 507 | * @param safeAddress 508 | * @param signerAddress 509 | * @param body New email address 510 | * @param headers Signature and Signature timestamp 511 | * @returns 202 if signature matches the data 512 | */ 513 | export function changeEmail( 514 | chainId: string, 515 | safeAddress: string, 516 | signerAddress: string, 517 | body: operations['change_email']['parameters']['body'], 518 | headers: operations['change_email']['parameters']['headers'], 519 | ): Promise { 520 | return putEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}', { 521 | path: { chainId, safe_address: safeAddress, signer: signerAddress }, 522 | body, 523 | headers, 524 | }) 525 | } 526 | 527 | /** 528 | * Resends an email verification code. 529 | */ 530 | export function resendEmailVerificationCode( 531 | chainId: string, 532 | safeAddress: string, 533 | signerAddress: string, 534 | ): Promise { 535 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}/verify-resend', { 536 | path: { chainId, safe_address: safeAddress, signer: signerAddress }, 537 | body: '', 538 | }) 539 | } 540 | 541 | /** 542 | * Verifies a pending email address registration. 543 | * 544 | * @param chainId 545 | * @param safeAddress 546 | * @param signerAddress address who signed the email registration 547 | * @param body Verification code 548 | */ 549 | export function verifyEmail( 550 | chainId: string, 551 | safeAddress: string, 552 | signerAddress: string, 553 | body: operations['verify_email']['parameters']['body'], 554 | ): Promise { 555 | return putEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}/verify', { 556 | path: { chainId, safe_address: safeAddress, signer: signerAddress }, 557 | body, 558 | }) 559 | } 560 | 561 | /** 562 | * Gets the registered email address of the signer 563 | * 564 | * The signer wallet will have to sign a message of format: `email-retrieval-{chainId}-{safe}-{signer}-{timestamp}` 565 | * The signature is valid for 5 minutes. 566 | * 567 | * @param chainId 568 | * @param safeAddress 569 | * @param signerAddress address of the owner of the Safe 570 | * 571 | * @returns email address and verified flag 572 | */ 573 | export function getRegisteredEmail( 574 | chainId: string, 575 | safeAddress: string, 576 | signerAddress: string, 577 | headers: operations['get_email']['parameters']['headers'], 578 | ): Promise { 579 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}', { 580 | path: { chainId, safe_address: safeAddress, signer: signerAddress }, 581 | headers, 582 | }) 583 | } 584 | 585 | /** 586 | * Delete a registered email address for the signer 587 | * 588 | * The signer wallet will have to sign a message of format: `email-delete-{chainId}-{safe}-{signer}-{timestamp}` 589 | * The signature is valid for 5 minutes. 590 | * 591 | * @param chainId 592 | * @param safeAddress 593 | * @param signerAddress 594 | * @param headers 595 | */ 596 | export function deleteRegisteredEmail( 597 | chainId: string, 598 | safeAddress: string, 599 | signerAddress: string, 600 | headers: operations['delete_email']['parameters']['headers'], 601 | ): Promise { 602 | return deleteEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}', { 603 | path: { chainId, safe_address: safeAddress, signer: signerAddress }, 604 | headers, 605 | }) 606 | } 607 | 608 | /** 609 | * Register a recovery module for receiving alerts 610 | * @param chainId 611 | * @param safeAddress 612 | * @param body - { moduleAddress: string } 613 | */ 614 | export function registerRecoveryModule( 615 | chainId: string, 616 | safeAddress: string, 617 | body: operations['register_recovery_module']['parameters']['body'], 618 | ): Promise { 619 | return postEndpoint(baseUrl, '/v1/chains/{chainId}/safes/{safe_address}/recovery', { 620 | path: { chainId, safe_address: safeAddress }, 621 | body, 622 | }) 623 | } 624 | 625 | /** 626 | * Delete email subscription for a single category 627 | * @param query 628 | */ 629 | export function unsubscribeSingle(query: operations['unsubscribe_single']['parameters']['query']) { 630 | return deleteEndpoint(baseUrl, '/v1/subscriptions', { query }) 631 | } 632 | 633 | /** 634 | * Delete email subscription for all categories 635 | * @param query 636 | */ 637 | export function unsubscribeAll(query: operations['unsubscribe_all']['parameters']['query']) { 638 | return deleteEndpoint(baseUrl, '/v1/subscriptions/all', { query }) 639 | } 640 | 641 | /** 642 | * Get Safe overviews per address 643 | */ 644 | export function getSafeOverviews( 645 | safes: `${number}:${EthereumAddress}`[], 646 | query: Omit, 647 | ): Promise { 648 | return getEndpoint(baseUrl, '/v1/safes', { 649 | query: { 650 | ...query, 651 | safes: safes.join(','), 652 | }, 653 | }) 654 | } 655 | 656 | export function getContract(chainId: string, contractAddress: string): Promise { 657 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/contracts/{contractAddress}', { 658 | path: { 659 | chainId: chainId, 660 | contractAddress: contractAddress, 661 | }, 662 | }) 663 | } 664 | 665 | export function getAuthNonce(): Promise { 666 | return getEndpoint(baseUrl, '/v1/auth/nonce', { credentials: 'include' }) 667 | } 668 | 669 | export function verifyAuth(body: operations['verify_auth']['parameters']['body']) { 670 | return postEndpoint(baseUrl, '/v1/auth/verify', { 671 | body, 672 | credentials: 'include', 673 | }) 674 | } 675 | 676 | export function createAccount(body: operations['create_account']['parameters']['body']) { 677 | return postEndpoint(baseUrl, '/v1/accounts', { 678 | body, 679 | credentials: 'include', 680 | }) 681 | } 682 | 683 | export function getAccount(address: string) { 684 | return getEndpoint(baseUrl, '/v1/accounts/{address}', { 685 | path: { address }, 686 | credentials: 'include', 687 | }) 688 | } 689 | 690 | export function deleteAccount(address: string) { 691 | return deleteEndpoint(baseUrl, '/v1/accounts/{address}', { 692 | path: { address }, 693 | credentials: 'include', 694 | }) 695 | } 696 | 697 | export function getAccountDataTypes() { 698 | return getEndpoint(baseUrl, '/v1/accounts/data-types') 699 | } 700 | 701 | export function getAccountDataSettings(address: string) { 702 | return getEndpoint(baseUrl, '/v1/accounts/{address}/data-settings', { 703 | path: { address }, 704 | credentials: 'include', 705 | }) 706 | } 707 | 708 | export function putAccountDataSettings( 709 | address: string, 710 | body: operations['put_account_data_settings']['parameters']['body'], 711 | ) { 712 | return putEndpoint(baseUrl, '/v1/accounts/{address}/data-settings', { 713 | path: { address }, 714 | body, 715 | credentials: 'include', 716 | }) 717 | } 718 | 719 | export function getIndexingStatus(chainId: string) { 720 | return getEndpoint(baseUrl, '/v1/chains/{chainId}/about/indexing', { 721 | path: { chainId }, 722 | }) 723 | } 724 | 725 | /* eslint-enable @typescript-eslint/explicit-module-boundary-types */ 726 | -------------------------------------------------------------------------------- /src/types/accounts.ts: -------------------------------------------------------------------------------- 1 | export type Account = { 2 | id: string 3 | groupId: string | null 4 | address: `0x${string}` 5 | } 6 | 7 | export type AccountDataType = { 8 | id: string 9 | name: string 10 | description: string | null 11 | isActive: boolean 12 | } 13 | 14 | export type AccountDataSetting = { 15 | dataTypeId: string 16 | enabled: boolean 17 | } 18 | 19 | export type CreateAccountRequest = { 20 | address: `0x${string}` 21 | } 22 | 23 | export type UpsertAccountDataSettingsRequest = { 24 | accountDataSettings: AccountDataSetting[] 25 | } 26 | -------------------------------------------------------------------------------- /src/types/api.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AllOwnedSafes, 3 | FiatCurrencies, 4 | OwnedSafes, 5 | SafeBalanceResponse, 6 | SafeCollectibleResponse, 7 | SafeCollectiblesPage, 8 | } from './common' 9 | import type { 10 | MultisigTransactionRequest, 11 | TransactionDetails, 12 | SafeTransactionEstimation, 13 | SafeTransactionEstimationRequest, 14 | TransactionListPage, 15 | SafeIncomingTransfersResponse, 16 | SafeModuleTransactionsResponse, 17 | SafeMultisigTransactionsResponse, 18 | NoncesResponse, 19 | TransactionPreview, 20 | } from './transactions' 21 | import type { SafeInfo, SafeOverview } from './safe-info' 22 | import type { ChainListResponse, ChainInfo, ChainIndexingStatus } from './chains' 23 | import type { SafeAppsResponse } from './safe-apps' 24 | import type { AnyConfirmationView, DecodedDataRequest, DecodedDataResponse } from './decoded-data' 25 | import type { MasterCopyReponse } from './master-copies' 26 | import type { 27 | ConfirmSafeMessageRequest, 28 | ProposeSafeMessageRequest, 29 | SafeMessage, 30 | SafeMessageListPage, 31 | } from './safe-messages' 32 | import type { DelegateResponse, DelegatesRequest } from './delegates' 33 | import type { RegisterNotificationsRequest } from './notifications' 34 | import type { 35 | ChangeEmailRequestBody, 36 | GetEmailResponse, 37 | RegisterEmailRequestBody, 38 | AuthorizationEmailRequestHeaders, 39 | VerifyEmailRequestBody, 40 | } from './emails' 41 | import type { RelayCountResponse, RelayTransactionRequest, RelayTransactionResponse } from './relay' 42 | import type { RegisterRecoveryModuleRequestBody } from './recovery' 43 | import type { Contract } from './contracts' 44 | import type { AuthNonce } from './auth' 45 | import type { 46 | Account, 47 | AccountDataSetting, 48 | AccountDataType, 49 | CreateAccountRequest, 50 | UpsertAccountDataSettingsRequest, 51 | } from './accounts' 52 | 53 | export type Primitive = string | number | boolean | null 54 | 55 | interface Params { 56 | path?: { [key: string]: Primitive } 57 | headers?: Record 58 | query?: { [key: string]: Primitive } 59 | credentials?: RequestCredentials 60 | } 61 | 62 | interface BodyParams extends Params { 63 | body?: string | Record 64 | } 65 | 66 | interface Responses { 67 | 200: { schema: unknown } 68 | 69 | [key: number]: { schema: unknown } | unknown 70 | } 71 | 72 | interface Endpoint { 73 | parameters: { 74 | path: Record | null 75 | } | null 76 | } 77 | 78 | interface WriteMethod { 79 | parameters: BodyParams | null 80 | responses: Responses 81 | } 82 | 83 | export interface GetEndpoint extends Endpoint { 84 | get: { 85 | parameters: Params | null 86 | responses: Responses 87 | } 88 | } 89 | 90 | export interface PostEndpoint extends Endpoint { 91 | post: WriteMethod 92 | } 93 | 94 | export interface PutEndpoint extends Endpoint { 95 | put: WriteMethod 96 | } 97 | 98 | export interface DeleteEndpoint extends Endpoint { 99 | delete: WriteMethod 100 | } 101 | 102 | interface PathRegistry { 103 | [key: string]: GetEndpoint | PostEndpoint | PutEndpoint | DeleteEndpoint 104 | } 105 | 106 | export interface paths extends PathRegistry { 107 | '/v1/chains/{chainId}/relay': { 108 | post: operations['relay_transaction'] 109 | parameters: { 110 | path: { 111 | chainId: string 112 | } 113 | } 114 | } 115 | '/v1/chains/{chainId}/relay/{address}': { 116 | get: operations['relay_count'] 117 | parameters: { 118 | path: { 119 | chainId: string 120 | address: string 121 | } 122 | } 123 | } 124 | '/v1/chains/{chainId}/safes/{address}': { 125 | /** Get status of the safe */ 126 | get: operations['safes_read'] 127 | parameters: { 128 | path: { 129 | chainId: string 130 | address: string 131 | } 132 | } 133 | } 134 | '/v1/chains/{chainId}/safes/{address}/balances/{currency}': { 135 | get: operations['safes_balances_list'] 136 | parameters: { 137 | path: { 138 | chainId: string 139 | address: string 140 | currency: string 141 | } 142 | } 143 | } 144 | '/v1/chains/{chainId}/safes/{address}/incoming-transfers/': { 145 | get: operations['incoming_transfers'] 146 | parameters: { 147 | path: { 148 | chainId: string 149 | address: string 150 | currency: string 151 | } 152 | } 153 | } 154 | '/v1/chains/{chainId}/safes/{address}/module-transactions/': { 155 | get: operations['module_transactions'] 156 | parameters: { 157 | path: { 158 | chainId: string 159 | address: string 160 | currency: string 161 | } 162 | } 163 | } 164 | '/v1/chains/{chainId}/safes/{address}/multisig-transactions/': { 165 | get: operations['multisig_transactions'] 166 | parameters: { 167 | path: { 168 | chainId: string 169 | address: string 170 | currency: string 171 | } 172 | } 173 | } 174 | '/v1/balances/supported-fiat-codes': { 175 | get: operations['get_supported_fiat'] 176 | parameters: null 177 | } 178 | '/v1/chains/{chainId}/safes/{address}/collectibles': { 179 | /** Get collectibles (ERC721 tokens) and information about them */ 180 | get: operations['safes_collectibles_list'] 181 | parameters: { 182 | path: { 183 | chainId: string 184 | address: string 185 | } 186 | } 187 | } 188 | '/v2/chains/{chainId}/safes/{address}/collectibles': { 189 | /** Get collectibles (ERC721 tokens) and information about them */ 190 | get: operations['safes_collectibles_list_paginated'] 191 | parameters: { 192 | path: { 193 | chainId: string 194 | address: string 195 | } 196 | } 197 | } 198 | '/v1/chains/{chainId}/safes/{safe_address}/transactions/history': { 199 | get: operations['history_transactions'] 200 | parameters: { 201 | path: { 202 | chainId: string 203 | safe_address: string 204 | } 205 | } 206 | } 207 | '/v1/chains/{chainId}/safes/{safe_address}/transactions/queued': { 208 | get: operations['queued_transactions'] 209 | parameters: { 210 | path: { 211 | chainId: string 212 | safe_address: string 213 | } 214 | } 215 | } 216 | '/v1/chains/{chainId}/transactions/{transactionId}': { 217 | get: operations['get_transactions'] 218 | parameters: { 219 | path: { 220 | chainId: string 221 | transactionId: string 222 | } 223 | } 224 | } 225 | '/v1/chains/{chainId}/transactions/{safeTxHash}': { 226 | delete: operations['delete_transaction'] 227 | parameters: { 228 | path: { 229 | chainId: string 230 | safeTxHash: string 231 | } 232 | } 233 | } 234 | '/v2/chains/{chainId}/safes/{safe_address}/multisig-transactions/estimations': { 235 | post: operations['post_safe_gas_estimation'] 236 | parameters: { 237 | path: { 238 | chainId: string 239 | safe_address: string 240 | } 241 | } 242 | } 243 | '/v1/chains/{chainId}/transactions/{safe_address}/propose': { 244 | post: operations['propose_transaction'] 245 | parameters: { 246 | path: { 247 | chainId: string 248 | safe_address: string 249 | } 250 | } 251 | } 252 | '/v1/chains/{chainId}/transactions/{safe_address}/preview': { 253 | post: operations['preview_transaction'] 254 | parameters: { 255 | path: { 256 | chainId: string 257 | safe_address: string 258 | } 259 | } 260 | } 261 | '/v1/chains/{chainId}/safes/{safe_address}/views/transaction-confirmation': { 262 | post: operations['get_transaction_confirmation_view'] 263 | parameters: { 264 | path: { 265 | chainId: string 266 | safe_address: string 267 | } 268 | } 269 | } 270 | '/v1/chains/{chainId}/owners/{address}/safes': { 271 | get: operations['get_owned_safes'] 272 | parameters: { 273 | path: { 274 | chainId: string 275 | address: string 276 | } 277 | } 278 | } 279 | '/v1/owners/{address}/safes': { 280 | get: operations['get_all_owned_safes'] 281 | parameters: { 282 | path: { 283 | address: string 284 | } 285 | } 286 | } 287 | '/v1/chains': { 288 | get: operations['chains_list'] 289 | parameters: null 290 | } 291 | '/v1/chains/{chainId}': { 292 | get: operations['chains_read'] 293 | parameters: { 294 | path: { 295 | chainId: string 296 | } 297 | } 298 | } 299 | '/v1/chains/{chainId}/safe-apps': { 300 | get: operations['safe_apps_read'] 301 | parameters: { 302 | path: { 303 | chainId: string 304 | } 305 | } 306 | } 307 | '/v1/chains/{chainId}/about/master-copies': { 308 | get: operations['master_copies'] 309 | parameters: { 310 | path: { 311 | chainId: string 312 | } 313 | } 314 | } 315 | '/v1/chains/{chainId}/data-decoder': { 316 | post: operations['data_decoder'] 317 | parameters: { 318 | path: { 319 | chainId: string 320 | } 321 | } 322 | } 323 | '/v1/chains/{chainId}/safes/{safe_address}/messages': { 324 | get: operations['get_safe_messages'] 325 | post: operations['propose_safe_message'] 326 | parameters: { 327 | path: { 328 | chainId: string 329 | safe_address: string 330 | } 331 | } 332 | } 333 | '/v1/chains/{chainId}/messages/{message_hash}': { 334 | get: operations['get_safe_message'] 335 | parameters: { 336 | path: { 337 | chainId: string 338 | message_hash: string 339 | } 340 | } 341 | } 342 | '/v1/chains/{chainId}/messages/{message_hash}/signatures': { 343 | post: operations['confirm_safe_message'] 344 | parameters: { 345 | path: { 346 | chainId: string 347 | message_hash: string 348 | } 349 | } 350 | } 351 | '/v2/chains/{chainId}/delegates': { 352 | get: operations['get_delegates'] 353 | parameters: { 354 | path: { 355 | chainId: string 356 | } 357 | query: DelegatesRequest 358 | } 359 | } 360 | '/v1/register/notifications': { 361 | post: operations['register_device'] 362 | parameters: null 363 | } 364 | '/v1/chains/{chainId}/notifications/devices/{uuid}/safes/{safe_address}': { 365 | delete: operations['unregister_safe'] 366 | parameters: { 367 | path: { 368 | uuid: string 369 | chainId: string 370 | safe_address: string 371 | } 372 | } 373 | } 374 | '/v1/chains/{chainId}/notifications/devices/{uuid}': { 375 | delete: operations['unregister_device'] 376 | parameters: { 377 | path: { 378 | uuid: string 379 | chainId: string 380 | } 381 | } 382 | } 383 | '/v1/chains/{chainId}/safes/{safe_address}/nonces': { 384 | get: operations['get_nonces'] 385 | parameters: { 386 | path: { 387 | chainId: string 388 | safe_address: string 389 | } 390 | } 391 | } 392 | '/v1/chains/{chainId}/safes/{safe_address}/emails': { 393 | post: operations['register_email'] 394 | parameters: { 395 | path: { 396 | chainId: string 397 | safe_address: string 398 | } 399 | } 400 | } 401 | '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}': { 402 | put: operations['change_email'] 403 | get: operations['get_email'] 404 | delete: operations['delete_email'] 405 | parameters: { 406 | path: { 407 | chainId: string 408 | safe_address: string 409 | signer: string 410 | } 411 | } 412 | } 413 | '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}/verify-resend': { 414 | post: operations['verify_resend'] 415 | parameters: { 416 | path: { 417 | chainId: string 418 | safe_address: string 419 | signer: string 420 | } 421 | } 422 | } 423 | '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}/verify': { 424 | put: operations['verify_email'] 425 | parameters: { 426 | path: { 427 | chainId: string 428 | safe_address: string 429 | signer: string 430 | } 431 | } 432 | } 433 | '/v1/chains/{chainId}/safes/{safe_address}/recovery': { 434 | post: operations['register_recovery_module'] 435 | parameters: { 436 | path: { 437 | chainId: string 438 | safe_address: string 439 | } 440 | } 441 | } 442 | '/v1/chains/{chainId}/about/indexing': { 443 | get: operations['get_indexing'] 444 | parameters: { 445 | path: { 446 | chainId: string 447 | } 448 | } 449 | } 450 | '/v1/subscriptions': { 451 | delete: operations['unsubscribe_single'] 452 | parameters: { 453 | path: null 454 | } 455 | } 456 | '/v1/subscriptions/all': { 457 | delete: operations['unsubscribe_all'] 458 | parameters: { 459 | path: null 460 | } 461 | } 462 | '/v1/safes': { 463 | get: operations['SafesController_getSafeOverview'] 464 | parameters: { 465 | path: null 466 | } 467 | } 468 | '/v1/chains/{chainId}/contracts/{contractAddress}': { 469 | get: operations['get_contract'] 470 | parameters: { 471 | path: { 472 | chainId: string 473 | contractAddress: string 474 | } 475 | } 476 | } 477 | '/v1/auth/nonce': { 478 | get: operations['get_auth_nonce'] 479 | parameters: { 480 | path: null 481 | credentials: 'include' 482 | } 483 | } 484 | '/v1/auth/verify': { 485 | post: operations['verify_auth'] 486 | parameters: { 487 | path: null 488 | credentials: 'include' 489 | body: { 490 | message: string 491 | signature: string 492 | } 493 | } 494 | } 495 | '/v1/accounts': { 496 | post: operations['create_account'] 497 | parameters: { 498 | path: null 499 | credentials: 'include' 500 | } 501 | } 502 | '/v1/accounts/{address}': { 503 | get: operations['get_account'] 504 | delete: operations['delete_account'] 505 | parameters: { 506 | path: { 507 | address: string 508 | } 509 | credentials: 'include' 510 | } 511 | } 512 | '/v1/accounts/data-types': { 513 | get: operations['get_account_data_types'] 514 | parameters: { 515 | path: null 516 | } 517 | } 518 | '/v1/accounts/{address}/data-settings': { 519 | get: operations['get_account_data_settings'] 520 | put: operations['put_account_data_settings'] 521 | parameters: { 522 | path: { 523 | address: string 524 | } 525 | credentials: 'include' 526 | } 527 | } 528 | } 529 | 530 | export interface operations { 531 | /** Relay a transaction */ 532 | relay_transaction: { 533 | parameters: { 534 | path: { 535 | chainId: string 536 | } 537 | body: RelayTransactionRequest 538 | } 539 | responses: { 540 | 200: { 541 | schema: RelayTransactionResponse 542 | } 543 | } 544 | } 545 | /** Get the limit and current number of relays */ 546 | relay_count: { 547 | parameters: { 548 | path: { 549 | chainId: string 550 | address: string 551 | } 552 | } 553 | responses: { 554 | 200: { 555 | schema: RelayCountResponse 556 | } 557 | } 558 | } 559 | /** Get status of the safe */ 560 | safes_read: { 561 | parameters: { 562 | path: { 563 | chainId: string 564 | address: string 565 | } 566 | } 567 | responses: { 568 | 200: { 569 | schema: SafeInfo 570 | } 571 | /** Safe not found */ 572 | 404: unknown 573 | /** 574 | * code = 1: Checksum address validation failed 575 | * code = 50: Cannot get Safe info 576 | */ 577 | 422: unknown 578 | } 579 | } 580 | /** Get balance for Ether and ERC20 tokens with USD fiat conversion */ 581 | safes_balances_list: { 582 | parameters: { 583 | path: { 584 | chainId: string 585 | address: string 586 | currency: string 587 | } 588 | query: { 589 | /** If `True` just trusted tokens will be returned */ 590 | trusted?: boolean 591 | /** If `True` spam tokens will not be returned */ 592 | exclude_spam?: boolean 593 | } 594 | } 595 | responses: { 596 | 200: { 597 | schema: SafeBalanceResponse 598 | } 599 | /** Safe not found */ 600 | 404: unknown 601 | /** Safe address checksum not valid */ 602 | 422: unknown 603 | } 604 | } 605 | /** Get filterable incoming transfers */ 606 | incoming_transfers: { 607 | parameters: { 608 | path: { 609 | chainId: string 610 | address: string 611 | } 612 | query?: { 613 | /** If `True` just trusted tokens will be returned */ 614 | trusted?: boolean 615 | execution_date__gte?: string 616 | execution_date__lte?: string 617 | to?: string 618 | token_address?: string 619 | value?: string 620 | timezone?: string 621 | } 622 | } 623 | responses: { 624 | 200: { 625 | schema: SafeIncomingTransfersResponse 626 | } 627 | /** Safe not found */ 628 | 404: unknown 629 | /** Safe address checksum not valid */ 630 | 422: unknown 631 | } 632 | } 633 | /** Get filterable module transactions */ 634 | module_transactions: { 635 | parameters: { 636 | path: { 637 | chainId: string 638 | address: string 639 | } 640 | query?: { 641 | module?: string 642 | to?: string 643 | timezone?: string 644 | transaction_hash?: string 645 | } 646 | } 647 | responses: { 648 | 200: { 649 | schema: SafeModuleTransactionsResponse 650 | } 651 | /** Safe not found */ 652 | 404: unknown 653 | /** Safe address checksum not valid */ 654 | 422: unknown 655 | } 656 | } 657 | /** Get filterable multisig transactions */ 658 | multisig_transactions: { 659 | parameters: { 660 | path: { 661 | chainId: string 662 | address: string 663 | } 664 | query?: { 665 | execution_date__gte?: string 666 | execution_date__lte?: string 667 | to?: string 668 | value?: string 669 | nonce?: string 670 | executed?: string 671 | timezone?: string 672 | } 673 | } 674 | responses: { 675 | 200: { 676 | schema: SafeMultisigTransactionsResponse 677 | } 678 | /** Safe not found */ 679 | 404: unknown 680 | /** Safe address checksum not valid */ 681 | 422: unknown 682 | } 683 | } 684 | get_supported_fiat: { 685 | parameters: null 686 | responses: { 687 | 200: { 688 | schema: FiatCurrencies 689 | } 690 | } 691 | } 692 | /** Get collectibles (ERC721 tokens) and information about them */ 693 | safes_collectibles_list: { 694 | parameters: { 695 | path: { 696 | chainId: string 697 | address: string 698 | } 699 | query: { 700 | /** If `True` just trusted tokens will be returned */ 701 | trusted?: boolean 702 | /** If `True` spam tokens will not be returned */ 703 | exclude_spam?: boolean 704 | } 705 | } 706 | responses: { 707 | 200: { 708 | schema: SafeCollectibleResponse[] 709 | } 710 | /** Safe not found */ 711 | 404: unknown 712 | /** Safe address checksum not valid */ 713 | 422: unknown 714 | } 715 | } 716 | safes_collectibles_list_paginated: { 717 | parameters: { 718 | path: { 719 | chainId: string 720 | address: string 721 | } 722 | query: { 723 | /** If `True` just trusted tokens will be returned */ 724 | trusted?: boolean 725 | /** If `True` spam tokens will not be returned */ 726 | exclude_spam?: boolean 727 | } 728 | } 729 | responses: { 730 | 200: { 731 | schema: SafeCollectiblesPage 732 | } 733 | /** Safe not found */ 734 | 404: unknown 735 | /** Safe address checksum not valid */ 736 | 422: unknown 737 | } 738 | } 739 | history_transactions: { 740 | parameters: { 741 | path: { 742 | chainId: string 743 | safe_address: string 744 | } 745 | query: { 746 | /** Taken from the Page['next'] or Page['previous'] */ 747 | page_url?: string 748 | trusted?: boolean 749 | imitation?: boolean 750 | timezone?: string 751 | } 752 | } 753 | responses: { 754 | 200: { 755 | schema: TransactionListPage 756 | } 757 | } 758 | } 759 | queued_transactions: { 760 | parameters: { 761 | path: { 762 | chainId: string 763 | safe_address: string 764 | } 765 | query: { 766 | /** Taken from the Page['next'] or Page['previous'] */ 767 | page_url?: string 768 | trusted?: boolean 769 | timezone?: string 770 | } 771 | } 772 | responses: { 773 | 200: { 774 | schema: TransactionListPage 775 | } 776 | } 777 | } 778 | get_transactions: { 779 | parameters: { 780 | path: { 781 | chainId: string 782 | transactionId: string 783 | } 784 | } 785 | responses: { 786 | 200: { 787 | schema: TransactionDetails 788 | } 789 | } 790 | } 791 | delete_transaction: { 792 | parameters: { 793 | path: { 794 | chainId: string 795 | safeTxHash: string 796 | } 797 | body: { signature: string } 798 | } 799 | responses: { 800 | 200: { 801 | schema: void 802 | } 803 | } 804 | } 805 | post_safe_gas_estimation: { 806 | parameters: { 807 | path: { 808 | chainId: string 809 | safe_address: string 810 | } 811 | body: SafeTransactionEstimationRequest 812 | } 813 | responses: { 814 | 200: { 815 | schema: SafeTransactionEstimation 816 | } 817 | /** Safe not found */ 818 | 404: unknown 819 | /** Safe address checksum not valid */ 820 | 422: unknown 821 | } 822 | } 823 | propose_transaction: { 824 | parameters: { 825 | path: { 826 | chainId: string 827 | safe_address: string 828 | } 829 | body: MultisigTransactionRequest 830 | } 831 | responses: { 832 | 200: { 833 | schema: TransactionDetails 834 | } 835 | /** Safe not found */ 836 | 404: unknown 837 | /** Safe address checksum not valid */ 838 | 422: unknown 839 | } 840 | } 841 | preview_transaction: { 842 | parameters: { 843 | path: { 844 | chainId: string 845 | safe_address: string 846 | } 847 | body: DecodedDataRequest 848 | } 849 | responses: { 850 | 200: { 851 | schema: TransactionPreview 852 | } 853 | /** Safe not found */ 854 | 404: unknown 855 | /** Safe address checksum not valid */ 856 | 422: unknown 857 | } 858 | } 859 | get_transaction_confirmation_view: { 860 | parameters: { 861 | path: { 862 | /** A unique value identifying this chain. */ 863 | chainId: string 864 | safe_address: string 865 | } 866 | body: DecodedDataRequest 867 | } 868 | responses: { 869 | 200: { 870 | schema: AnyConfirmationView 871 | } 872 | } 873 | } 874 | get_owned_safes: { 875 | parameters: { 876 | path: { 877 | chainId: string 878 | address: string 879 | } 880 | } 881 | responses: { 882 | 200: { 883 | schema: OwnedSafes 884 | } 885 | } 886 | } 887 | get_all_owned_safes: { 888 | parameters: { 889 | path: { 890 | address: string 891 | } 892 | } 893 | responses: { 894 | 200: { 895 | schema: AllOwnedSafes 896 | } 897 | } 898 | } 899 | chains_list: { 900 | parameters: { 901 | query?: { 902 | /** Which field to use when ordering the results. */ 903 | ordering?: string 904 | /** Number of results to return per page. */ 905 | limit?: number 906 | /** The initial index from which to return the results. */ 907 | offset?: number 908 | } 909 | } 910 | responses: { 911 | 200: { 912 | schema: ChainListResponse 913 | } 914 | } 915 | } 916 | chains_read: { 917 | parameters: { 918 | path: { 919 | /** A unique value identifying this chain. */ 920 | chainId: string 921 | } 922 | } 923 | responses: { 924 | 200: { 925 | schema: ChainInfo 926 | } 927 | } 928 | } 929 | safe_apps_read: { 930 | parameters: { 931 | path: { 932 | /** A unique value identifying this chain. */ 933 | chainId: string 934 | } 935 | query?: { 936 | client_url?: string 937 | url?: string 938 | } 939 | } 940 | responses: { 941 | 200: { 942 | schema: SafeAppsResponse 943 | } 944 | } 945 | } 946 | master_copies: { 947 | parameters: { 948 | path: { 949 | /** A unique value identifying this chain. */ 950 | chainId: string 951 | } 952 | } 953 | responses: { 954 | 200: { 955 | schema: MasterCopyReponse 956 | } 957 | } 958 | } 959 | data_decoder: { 960 | parameters: { 961 | path: { 962 | /** A unique value identifying this chain. */ 963 | chainId: string 964 | } 965 | body: DecodedDataRequest 966 | } 967 | responses: { 968 | 200: { 969 | schema: DecodedDataResponse 970 | } 971 | } 972 | } 973 | get_safe_messages: { 974 | parameters: { 975 | path: { 976 | chainId: string 977 | safe_address: string 978 | } 979 | query: { 980 | /** Taken from the Page['next'] or Page['previous'] */ 981 | page_url?: string 982 | } 983 | } 984 | responses: { 985 | 200: { 986 | schema: SafeMessageListPage 987 | } 988 | } 989 | } 990 | get_safe_message: { 991 | parameters: { 992 | path: { 993 | chainId: string 994 | message_hash: string 995 | } 996 | } 997 | responses: { 998 | 200: { 999 | schema: SafeMessage 1000 | } 1001 | } 1002 | } 1003 | propose_safe_message: { 1004 | parameters: { 1005 | path: { 1006 | chainId: string 1007 | safe_address: string 1008 | } 1009 | body: ProposeSafeMessageRequest 1010 | } 1011 | responses: { 1012 | 200: { 1013 | schema: void 1014 | } 1015 | } 1016 | } 1017 | confirm_safe_message: { 1018 | parameters: { 1019 | path: { 1020 | chainId: string 1021 | message_hash: string 1022 | } 1023 | body: ConfirmSafeMessageRequest 1024 | } 1025 | responses: { 1026 | 200: { 1027 | schema: void 1028 | } 1029 | } 1030 | } 1031 | get_delegates: { 1032 | parameters: { 1033 | path: { 1034 | chainId: string 1035 | } 1036 | query: DelegatesRequest 1037 | } 1038 | responses: { 1039 | 200: { 1040 | schema: DelegateResponse 1041 | } 1042 | } 1043 | } 1044 | register_device: { 1045 | parameters: { 1046 | body: RegisterNotificationsRequest 1047 | } 1048 | responses: { 1049 | 200: { 1050 | schema: void 1051 | } 1052 | } 1053 | } 1054 | unregister_safe: { 1055 | parameters: { 1056 | path: { 1057 | uuid: string 1058 | chainId: string 1059 | safe_address: string 1060 | } 1061 | } 1062 | responses: { 1063 | 200: { 1064 | schema: void 1065 | } 1066 | } 1067 | } 1068 | unregister_device: { 1069 | parameters: { 1070 | path: { 1071 | uuid: string 1072 | chainId: string 1073 | } 1074 | } 1075 | responses: { 1076 | 200: { 1077 | schema: void 1078 | } 1079 | } 1080 | } 1081 | get_nonces: { 1082 | parameters: { 1083 | path: { 1084 | chainId: string 1085 | safe_address: string 1086 | } 1087 | } 1088 | responses: { 1089 | 200: { 1090 | schema: NoncesResponse 1091 | } 1092 | } 1093 | } 1094 | register_email: { 1095 | parameters: { 1096 | path: { 1097 | chainId: string 1098 | safe_address: string 1099 | } 1100 | body: RegisterEmailRequestBody 1101 | headers: AuthorizationEmailRequestHeaders 1102 | } 1103 | responses: { 1104 | 200: { 1105 | schema: void 1106 | } 1107 | 201: { 1108 | schema: void 1109 | } 1110 | } 1111 | } 1112 | change_email: { 1113 | parameters: { 1114 | path: { 1115 | chainId: string 1116 | safe_address: string 1117 | signer: string 1118 | } 1119 | body: ChangeEmailRequestBody 1120 | headers: AuthorizationEmailRequestHeaders 1121 | } 1122 | responses: { 1123 | 200: { 1124 | schema: void 1125 | } 1126 | 202: { 1127 | schema: void 1128 | } 1129 | } 1130 | } 1131 | get_email: { 1132 | parameters: { 1133 | path: { 1134 | chainId: string 1135 | safe_address: string 1136 | signer: string 1137 | } 1138 | headers: AuthorizationEmailRequestHeaders 1139 | } 1140 | responses: { 1141 | 200: { 1142 | schema: GetEmailResponse 1143 | } 1144 | } 1145 | } 1146 | verify_resend: { 1147 | parameters: { 1148 | path: { 1149 | chainId: string 1150 | safe_address: string 1151 | signer: string 1152 | } 1153 | body: '' 1154 | } 1155 | responses: { 1156 | 202: { 1157 | schema: void 1158 | } 1159 | 200: { 1160 | schema: void 1161 | } 1162 | } 1163 | } 1164 | verify_email: { 1165 | parameters: { 1166 | path: { 1167 | chainId: string 1168 | safe_address: string 1169 | signer: string 1170 | } 1171 | body: VerifyEmailRequestBody 1172 | } 1173 | 1174 | responses: { 1175 | 204: { 1176 | schema: void 1177 | } 1178 | 200: { 1179 | schema: void 1180 | } 1181 | 400: unknown 1182 | } 1183 | } 1184 | delete_email: { 1185 | parameters: { 1186 | path: { 1187 | chainId: string 1188 | safe_address: string 1189 | signer: string 1190 | } 1191 | headers: AuthorizationEmailRequestHeaders 1192 | } 1193 | 1194 | responses: { 1195 | 204: { 1196 | schema: void 1197 | } 1198 | 200: { 1199 | schema: void 1200 | } 1201 | 403: unknown 1202 | } 1203 | } 1204 | register_recovery_module: { 1205 | parameters: { 1206 | path: { 1207 | chainId: string 1208 | safe_address: string 1209 | } 1210 | body: RegisterRecoveryModuleRequestBody 1211 | } 1212 | 1213 | responses: { 1214 | 200: { 1215 | schema: void 1216 | } 1217 | } 1218 | } 1219 | get_indexing: { 1220 | parameters: { 1221 | path: { 1222 | chainId: string 1223 | } 1224 | } 1225 | 1226 | responses: { 1227 | 200: { 1228 | schema: ChainIndexingStatus 1229 | } 1230 | } 1231 | } 1232 | unsubscribe_single: { 1233 | parameters: { 1234 | query: { 1235 | category: string 1236 | token: string 1237 | } 1238 | } 1239 | 1240 | responses: { 1241 | 200: { 1242 | schema: void 1243 | } 1244 | } 1245 | } 1246 | unsubscribe_all: { 1247 | parameters: { 1248 | query: { 1249 | token: string 1250 | } 1251 | } 1252 | 1253 | responses: { 1254 | 200: { 1255 | schema: void 1256 | } 1257 | } 1258 | } 1259 | SafesController_getSafeOverview: { 1260 | parameters: { 1261 | query: { 1262 | currency: string 1263 | safes: string 1264 | trusted: boolean 1265 | exclude_spam: boolean 1266 | wallet_address?: string 1267 | } 1268 | } 1269 | responses: { 1270 | 200: { 1271 | schema: SafeOverview[] 1272 | } 1273 | } 1274 | } 1275 | get_contract: { 1276 | parameters: { 1277 | path: { 1278 | chainId: string 1279 | contractAddress: string 1280 | } 1281 | } 1282 | responses: { 1283 | 200: { 1284 | schema: Contract 1285 | } 1286 | } 1287 | } 1288 | get_auth_nonce: { 1289 | parameters: { 1290 | credentials: 'include' 1291 | } 1292 | responses: { 1293 | 200: { 1294 | schema: AuthNonce 1295 | } 1296 | } 1297 | } 1298 | verify_auth: { 1299 | parameters: { 1300 | body: { 1301 | message: string 1302 | signature: string 1303 | } 1304 | credentials: 'include' 1305 | } 1306 | responses: { 1307 | 200: { 1308 | schema: void 1309 | } 1310 | } 1311 | } 1312 | create_account: { 1313 | parameters: { 1314 | body: CreateAccountRequest 1315 | credentials: 'include' 1316 | } 1317 | responses: { 1318 | 200: { 1319 | schema: Account 1320 | } 1321 | 403: unknown 1322 | 422: unknown 1323 | } 1324 | } 1325 | get_account_data_types: { 1326 | parameters: null 1327 | responses: { 1328 | 200: { 1329 | schema: AccountDataType[] 1330 | } 1331 | } 1332 | } 1333 | get_account_data_settings: { 1334 | parameters: { 1335 | path: { 1336 | address: string 1337 | } 1338 | credentials: 'include' 1339 | } 1340 | responses: { 1341 | 200: { 1342 | schema: AccountDataSetting[] 1343 | } 1344 | 403: unknown 1345 | } 1346 | } 1347 | put_account_data_settings: { 1348 | parameters: { 1349 | path: { 1350 | address: string 1351 | } 1352 | credentials: 'include' 1353 | body: UpsertAccountDataSettingsRequest 1354 | } 1355 | responses: { 1356 | 200: { 1357 | schema: AccountDataSetting[] 1358 | } 1359 | 403: unknown 1360 | } 1361 | } 1362 | get_account: { 1363 | parameters: { 1364 | path: { 1365 | address: string 1366 | } 1367 | credentials: 'include' 1368 | } 1369 | responses: { 1370 | 200: { 1371 | schema: Account 1372 | } 1373 | 403: unknown 1374 | } 1375 | } 1376 | delete_account: { 1377 | parameters: { 1378 | path: { 1379 | address: string 1380 | } 1381 | credentials: 'include' 1382 | } 1383 | responses: { 1384 | 204: { 1385 | schema: void 1386 | } 1387 | 200: { 1388 | schema: void 1389 | } 1390 | 403: unknown 1391 | } 1392 | } 1393 | } 1394 | -------------------------------------------------------------------------------- /src/types/auth.ts: -------------------------------------------------------------------------------- 1 | export type AuthNonce = { 2 | nonce: string 3 | } 4 | -------------------------------------------------------------------------------- /src/types/chains.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from './common' 2 | 3 | export enum RPC_AUTHENTICATION { 4 | API_KEY_PATH = 'API_KEY_PATH', 5 | NO_AUTHENTICATION = 'NO_AUTHENTICATION', 6 | UNKNOWN = 'UNKNOWN', 7 | } 8 | 9 | export type RpcUri = { 10 | authentication: RPC_AUTHENTICATION 11 | value: string 12 | } 13 | 14 | export type BlockExplorerUriTemplate = { 15 | address: string 16 | txHash: string 17 | api: string 18 | } 19 | 20 | export type NativeCurrency = { 21 | name: string 22 | symbol: string 23 | decimals: number 24 | logoUri: string 25 | } 26 | 27 | export type Theme = { 28 | textColor: string 29 | backgroundColor: string 30 | } 31 | 32 | export enum GAS_PRICE_TYPE { 33 | ORACLE = 'ORACLE', 34 | FIXED = 'FIXED', 35 | FIXED_1559 = 'FIXED1559', 36 | UNKNOWN = 'UNKNOWN', 37 | } 38 | 39 | export type GasPriceOracle = { 40 | type: GAS_PRICE_TYPE.ORACLE 41 | uri: string 42 | gasParameter: string 43 | gweiFactor: string 44 | } 45 | 46 | export type GasPriceFixed = { 47 | type: GAS_PRICE_TYPE.FIXED 48 | weiValue: string 49 | } 50 | 51 | export type GasPriceFixedEIP1559 = { 52 | type: GAS_PRICE_TYPE.FIXED_1559 53 | maxFeePerGas: string 54 | maxPriorityFeePerGas: string 55 | } 56 | 57 | export type GasPriceUnknown = { 58 | type: GAS_PRICE_TYPE.UNKNOWN 59 | } 60 | 61 | export type GasPrice = (GasPriceOracle | GasPriceFixed | GasPriceFixedEIP1559 | GasPriceUnknown)[] 62 | 63 | export enum FEATURES { 64 | ERC721 = 'ERC721', 65 | SAFE_APPS = 'SAFE_APPS', 66 | CONTRACT_INTERACTION = 'CONTRACT_INTERACTION', 67 | DOMAIN_LOOKUP = 'DOMAIN_LOOKUP', 68 | SPENDING_LIMIT = 'SPENDING_LIMIT', 69 | EIP1559 = 'EIP1559', 70 | SAFE_TX_GAS_OPTIONAL = 'SAFE_TX_GAS_OPTIONAL', 71 | TX_SIMULATION = 'TX_SIMULATION', 72 | EIP1271 = 'EIP1271', 73 | } 74 | 75 | export type ChainInfo = { 76 | transactionService: string 77 | chainId: string // Restricted by what is returned by the CGW 78 | chainName: string 79 | shortName: string 80 | l2: boolean 81 | isTestnet: boolean 82 | description: string 83 | chainLogoUri: string | null 84 | rpcUri: RpcUri 85 | safeAppsRpcUri: RpcUri 86 | publicRpcUri: RpcUri 87 | blockExplorerUriTemplate: BlockExplorerUriTemplate 88 | nativeCurrency: NativeCurrency 89 | theme: Theme 90 | ensRegistryAddress?: string | null 91 | gasPrice: GasPrice 92 | disabledWallets: string[] 93 | features: FEATURES[] 94 | balancesProvider: { 95 | chainName: string | null 96 | enabled: boolean 97 | } 98 | contractAddresses: { 99 | safeSingletonAddress: `0x${string}` | null 100 | safeProxyFactoryAddress: `0x${string}` | null 101 | multiSendAddress: `0x${string}` | null 102 | multiSendCallOnlyAddress: `0x${string}` | null 103 | fallbackHandlerAddress: `0x${string}` | null 104 | signMessageLibAddress: `0x${string}` | null 105 | createCallAddress: `0x${string}` | null 106 | simulateTxAccessorAddress: `0x${string}` | null 107 | safeWebAuthnSignerFactoryAddress: `0x${string}` | null 108 | } 109 | recommendedMasterCopyVersion: string | null 110 | } 111 | 112 | export type ChainListResponse = Page 113 | 114 | export type ChainIndexingStatus = { 115 | lastSync: number 116 | synced: boolean 117 | } 118 | -------------------------------------------------------------------------------- /src/types/common.ts: -------------------------------------------------------------------------------- 1 | export type AddressEx = { 2 | value: string 3 | name?: string 4 | displayName?: string 5 | logoUri?: string 6 | } 7 | 8 | export type FiatCurrencies = string[] 9 | 10 | export type OwnedSafes = { safes: string[] } 11 | 12 | export type AllOwnedSafes = Record 13 | 14 | export enum TokenType { 15 | ERC20 = 'ERC20', 16 | ERC721 = 'ERC721', 17 | NATIVE_TOKEN = 'NATIVE_TOKEN', 18 | UNKNOWN = 'UNKNOWN', 19 | } 20 | 21 | /** 22 | * @see https://github.com/safe-global/safe-client-gateway/blob/main/src/common/models/backend/balances.rs 23 | */ 24 | export type TokenInfo = { 25 | type: TokenType | 'ERC20' | 'ERC721' | 'NATIVE_TOKEN' | 'UNKNOWN' 26 | address: string 27 | decimals?: number | null 28 | symbol: string 29 | name: string 30 | logoUri: string 31 | } 32 | 33 | export type SafeBalanceResponse = { 34 | fiatTotal: string 35 | items: Array<{ 36 | tokenInfo: TokenInfo 37 | balance: string 38 | fiatBalance: string 39 | fiatConversion: string 40 | }> 41 | } 42 | 43 | export type Page = { 44 | next?: string 45 | previous?: string 46 | results: Array 47 | } 48 | 49 | export type SafeCollectibleResponse = { 50 | address: string 51 | tokenName: string 52 | tokenSymbol: string 53 | logoUri: string 54 | id: string 55 | uri: string 56 | name: string 57 | description: string 58 | imageUri: string 59 | metadata: { [key: string]: string } 60 | } 61 | 62 | export type SafeCollectiblesPage = Page 63 | 64 | export type EthereumAddress = `0x${string}` 65 | -------------------------------------------------------------------------------- /src/types/contracts.ts: -------------------------------------------------------------------------------- 1 | export type Contract = { 2 | address: string 3 | name: string 4 | displayName: string 5 | logoUri: string 6 | contractAbi: object | null 7 | trustedForDelegateCall: boolean 8 | } 9 | -------------------------------------------------------------------------------- /src/types/decoded-data.ts: -------------------------------------------------------------------------------- 1 | import type { TokenInfo } from './common' 2 | import type { SwapOrder, TwapOrder } from './transactions' 3 | 4 | export enum ConfirmationViewTypes { 5 | GENERIC = 'GENERIC', 6 | COW_SWAP_ORDER = 'COW_SWAP_ORDER', 7 | COW_SWAP_TWAP_ORDER = 'COW_SWAP_TWAP_ORDER', 8 | KILN_NATIVE_STAKING_DEPOSIT = 'KILN_NATIVE_STAKING_DEPOSIT', 9 | KILN_NATIVE_STAKING_VALIDATORS_EXIT = 'KILN_NATIVE_STAKING_VALIDATORS_EXIT', 10 | KILN_NATIVE_STAKING_WITHDRAW = 'KILN_NATIVE_STAKING_WITHDRAW', 11 | } 12 | 13 | export type DecodedDataRequest = { 14 | operation: 0 | 1 15 | data: string 16 | to?: string 17 | value?: string 18 | } 19 | 20 | type ParamValue = string | ParamValue[] 21 | 22 | export type DecodedDataBasicParameter = { 23 | name: string 24 | type: string 25 | value: ParamValue 26 | } 27 | 28 | export type DecodedDataParameterValue = { 29 | operation: 0 | 1 30 | to: string 31 | value: string 32 | data: string | null 33 | dataDecoded?: { 34 | method: string 35 | parameters: DecodedDataBasicParameter[] 36 | } 37 | } 38 | 39 | export type DecodedDataParameter = { 40 | valueDecoded?: DecodedDataParameterValue[] 41 | } & DecodedDataBasicParameter 42 | 43 | export type DecodedDataResponse = { 44 | method: string 45 | parameters: DecodedDataParameter[] 46 | } 47 | 48 | export type BaselineConfirmationView = { 49 | type: ConfirmationViewTypes.GENERIC 50 | } & DecodedDataResponse 51 | 52 | /* Swaps */ 53 | export type SwapOrderConfirmationView = { 54 | type: ConfirmationViewTypes.COW_SWAP_ORDER 55 | } & DecodedDataResponse & 56 | Omit 57 | 58 | export type TwapOrderConfirmationView = { 59 | type: ConfirmationViewTypes.COW_SWAP_TWAP_ORDER 60 | } & DecodedDataResponse & 61 | Omit 62 | 63 | export type AnySwapOrderConfirmationView = SwapOrderConfirmationView | TwapOrderConfirmationView 64 | 65 | export enum NativeStakingStatus { 66 | NOT_STAKED = 'NOT_STAKED', 67 | ACTIVATING = 'ACTIVATING', 68 | DEPOSIT_IN_PROGRESS = 'DEPOSIT_IN_PROGRESS', 69 | ACTIVE = 'ACTIVE', 70 | EXIT_REQUESTED = 'EXIT_REQUESTED', 71 | EXITING = 'EXITING', 72 | EXITED = 'EXITED', 73 | SLASHED = 'SLASHED', 74 | } 75 | 76 | /* Staking */ 77 | export type NativeStakingDepositConfirmationView = { 78 | type: ConfirmationViewTypes.KILN_NATIVE_STAKING_DEPOSIT 79 | status: NativeStakingStatus 80 | estimatedEntryTime: number 81 | estimatedExitTime: number 82 | estimatedWithdrawalTime: number 83 | fee: number 84 | monthlyNrr: number 85 | annualNrr: number 86 | tokenInfo: TokenInfo 87 | value: string 88 | expectedAnnualReward: string 89 | expectedMonthlyReward: string 90 | expectedFiatAnnualReward: number 91 | expectedFiatMonthlyReward: number 92 | numValidators: number 93 | } & DecodedDataResponse 94 | 95 | export type NativeStakingValidatorsExitConfirmationView = { 96 | type: ConfirmationViewTypes.KILN_NATIVE_STAKING_VALIDATORS_EXIT 97 | status: NativeStakingStatus 98 | estimatedExitTime: number 99 | estimatedWithdrawalTime: number 100 | value: string 101 | numValidators: number 102 | tokenInfo: TokenInfo 103 | validators: string[] 104 | } & DecodedDataResponse 105 | 106 | export type NativeStakingWithdrawConfirmationView = { 107 | /** @enum {string} */ 108 | type: ConfirmationViewTypes.KILN_NATIVE_STAKING_WITHDRAW 109 | value: string 110 | tokenInfo: TokenInfo 111 | } & DecodedDataResponse 112 | 113 | export type AnyStakingConfirmationView = 114 | | NativeStakingDepositConfirmationView 115 | | NativeStakingValidatorsExitConfirmationView 116 | | NativeStakingWithdrawConfirmationView 117 | 118 | /* Union */ 119 | export type AnyConfirmationView = 120 | | BaselineConfirmationView 121 | | SwapOrderConfirmationView 122 | | TwapOrderConfirmationView 123 | | NativeStakingDepositConfirmationView 124 | | NativeStakingValidatorsExitConfirmationView 125 | | NativeStakingWithdrawConfirmationView 126 | -------------------------------------------------------------------------------- /src/types/delegates.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from './common' 2 | 3 | export type Delegate = { 4 | safe?: string 5 | delegate: string 6 | delegator: string 7 | label: string 8 | } 9 | 10 | export type DelegateResponse = Page 11 | 12 | export type DelegatesRequest = { 13 | safe?: string 14 | delegate?: string 15 | delegator?: string 16 | label?: string 17 | } 18 | 19 | export type AddDelegateRequest = { 20 | safe?: string 21 | delegate: string 22 | delegator: string 23 | signature: string 24 | label: string 25 | } 26 | 27 | export type DeleteDelegateRequest = { 28 | delegate: string 29 | delegator: string 30 | signature: string 31 | } 32 | 33 | export type DeleteSafeDelegateRequest = { 34 | safe: string 35 | delegate: string 36 | signature: string 37 | } 38 | -------------------------------------------------------------------------------- /src/types/emails.ts: -------------------------------------------------------------------------------- 1 | export type RegisterEmailRequestBody = { 2 | emailAddress: string 3 | signer: string 4 | } 5 | 6 | export type ChangeEmailRequestBody = { 7 | emailAddress: string 8 | } 9 | 10 | export type AuthorizationEmailRequestHeaders = { 11 | ['Safe-Wallet-Signature']: string 12 | ['Safe-Wallet-Signature-Timestamp']: string 13 | } 14 | 15 | export type VerifyEmailRequestBody = { 16 | code: string 17 | } 18 | 19 | export type GetEmailResponse = { 20 | email: string 21 | verified: boolean 22 | } 23 | -------------------------------------------------------------------------------- /src/types/human-description.ts: -------------------------------------------------------------------------------- 1 | export enum RichFragmentType { 2 | Text = 'text', 3 | TokenValue = 'tokenValue', 4 | Address = 'address', 5 | } 6 | 7 | export type RichTokenValueFragment = { 8 | type: RichFragmentType.TokenValue 9 | value: string 10 | symbol: string | null 11 | logoUri: string | null 12 | } 13 | 14 | export type RichTextFragment = { 15 | type: RichFragmentType.Text 16 | value: string 17 | } 18 | 19 | export type RichAddressFragment = { 20 | type: RichFragmentType.Address 21 | value: string 22 | } 23 | 24 | export type RichDecodedInfoFragment = RichTokenValueFragment | RichTextFragment | RichAddressFragment 25 | 26 | export type RichDecodedInfo = { 27 | fragments: Array 28 | } 29 | -------------------------------------------------------------------------------- /src/types/master-copies.ts: -------------------------------------------------------------------------------- 1 | export type MasterCopyReponse = { 2 | address: string 3 | version: string 4 | }[] 5 | -------------------------------------------------------------------------------- /src/types/notifications.ts: -------------------------------------------------------------------------------- 1 | export enum DeviceType { 2 | ANDROID = 'ANDROID', 3 | IOS = 'IOS', 4 | WEB = 'WEB', 5 | } 6 | 7 | type SafeRegistration = { 8 | chainId: string 9 | safes: Array 10 | signatures: Array 11 | } 12 | 13 | export type RegisterNotificationsRequest = { 14 | uuid?: string 15 | cloudMessagingToken: string 16 | buildNumber: string 17 | bundle: string 18 | deviceType: DeviceType 19 | version: string 20 | timestamp?: string 21 | safeRegistrations: Array 22 | } 23 | -------------------------------------------------------------------------------- /src/types/recovery.ts: -------------------------------------------------------------------------------- 1 | export type RegisterRecoveryModuleRequestBody = { 2 | moduleAddress: string 3 | } 4 | -------------------------------------------------------------------------------- /src/types/relay.ts: -------------------------------------------------------------------------------- 1 | export type RelayTransactionRequest = { 2 | version: string 3 | to: string 4 | data: string 5 | gasLimit?: string 6 | } 7 | 8 | export type RelayTransactionResponse = { 9 | taskId: string 10 | } 11 | 12 | export type RelayCountResponse = { 13 | remaining: number 14 | limit: number 15 | } 16 | -------------------------------------------------------------------------------- /src/types/safe-apps.ts: -------------------------------------------------------------------------------- 1 | export enum SafeAppAccessPolicyTypes { 2 | NoRestrictions = 'NO_RESTRICTIONS', 3 | DomainAllowlist = 'DOMAIN_ALLOWLIST', 4 | } 5 | 6 | export type SafeAppNoRestrictionsPolicy = { 7 | type: SafeAppAccessPolicyTypes.NoRestrictions 8 | } 9 | 10 | export type SafeAppDomainAllowlistPolicy = { 11 | type: SafeAppAccessPolicyTypes.DomainAllowlist 12 | value: string[] 13 | } 14 | 15 | export type SafeAppsAccessControlPolicies = SafeAppNoRestrictionsPolicy | SafeAppDomainAllowlistPolicy 16 | 17 | export type SafeAppProvider = { 18 | url: string 19 | name: string 20 | } 21 | 22 | export enum SafeAppFeatures { 23 | BATCHED_TRANSACTIONS = 'BATCHED_TRANSACTIONS', 24 | } 25 | 26 | export enum SafeAppSocialPlatforms { 27 | TWITTER = 'TWITTER', 28 | GITHUB = 'GITHUB', 29 | DISCORD = 'DISCORD', 30 | TELEGRAM = 'TELEGRAM', 31 | } 32 | 33 | export type SafeAppSocialProfile = { 34 | platform: SafeAppSocialPlatforms 35 | url: string 36 | } 37 | 38 | export type SafeAppData = { 39 | id: number 40 | url: string 41 | name: string 42 | iconUrl: string 43 | description: string 44 | chainIds: string[] 45 | provider?: SafeAppProvider 46 | accessControl: SafeAppsAccessControlPolicies 47 | tags: string[] 48 | features: SafeAppFeatures[] 49 | socialProfiles: SafeAppSocialProfile[] 50 | developerWebsite?: string 51 | } 52 | 53 | export type SafeAppsResponse = SafeAppData[] 54 | -------------------------------------------------------------------------------- /src/types/safe-info.ts: -------------------------------------------------------------------------------- 1 | import type { AddressEx } from './common' 2 | 3 | export enum ImplementationVersionState { 4 | UP_TO_DATE = 'UP_TO_DATE', 5 | OUTDATED = 'OUTDATED', 6 | UNKNOWN = 'UNKNOWN', 7 | } 8 | 9 | export type SafeInfo = { 10 | address: AddressEx 11 | chainId: string 12 | nonce: number 13 | threshold: number 14 | owners: AddressEx[] 15 | implementation: AddressEx 16 | implementationVersionState: ImplementationVersionState 17 | modules: AddressEx[] | null 18 | guard: AddressEx | null 19 | fallbackHandler: AddressEx | null 20 | version: string | null 21 | collectiblesTag: string | null 22 | txQueuedTag: string | null 23 | txHistoryTag: string | null 24 | messagesTag: string | null 25 | } 26 | 27 | export type SafeOverview = { 28 | address: AddressEx 29 | chainId: string 30 | threshold: number 31 | owners: AddressEx[] 32 | fiatTotal: string 33 | queued: number 34 | awaitingConfirmation: number | null 35 | } 36 | -------------------------------------------------------------------------------- /src/types/safe-messages.ts: -------------------------------------------------------------------------------- 1 | import type { AddressEx, Page } from './common' 2 | 3 | export enum SafeMessageListItemType { 4 | DATE_LABEL = 'DATE_LABEL', 5 | MESSAGE = 'MESSAGE', 6 | } 7 | 8 | export type SafeMessageDateLabel = { 9 | type: SafeMessageListItemType.DATE_LABEL 10 | timestamp: number 11 | } 12 | 13 | export enum SafeMessageStatus { 14 | NEEDS_CONFIRMATION = 'NEEDS_CONFIRMATION', 15 | CONFIRMED = 'CONFIRMED', 16 | } 17 | 18 | interface TypedDataDomain { 19 | name?: string 20 | version?: string 21 | chainId?: unknown // BigNumberish 22 | verifyingContract?: string 23 | salt?: ArrayLike | string // BytesLike 24 | } 25 | 26 | interface TypedDataTypes { 27 | name: string 28 | type: string 29 | } 30 | 31 | type TypedMessageTypes = { 32 | [key: string]: TypedDataTypes[] 33 | } 34 | 35 | export type EIP712TypedData = { 36 | domain: TypedDataDomain 37 | types: TypedMessageTypes 38 | message: Record 39 | } 40 | 41 | export type SafeMessage = { 42 | type: SafeMessageListItemType.MESSAGE 43 | messageHash: string 44 | status: SafeMessageStatus 45 | logoUri: string | null 46 | name: string | null 47 | message: string | EIP712TypedData 48 | creationTimestamp: number 49 | modifiedTimestamp: number 50 | confirmationsSubmitted: number 51 | confirmationsRequired: number 52 | proposedBy: AddressEx 53 | confirmations: { 54 | owner: AddressEx 55 | signature: string 56 | }[] 57 | preparedSignature?: string 58 | } 59 | 60 | export type SafeMessageListItem = SafeMessageDateLabel | SafeMessage 61 | 62 | export type SafeMessageListPage = Page 63 | 64 | export type ProposeSafeMessageRequest = { 65 | message: SafeMessage['message'] 66 | safeAppId?: number 67 | origin?: string 68 | signature: string 69 | } 70 | 71 | export type ConfirmSafeMessageRequest = { 72 | signature: string 73 | } 74 | -------------------------------------------------------------------------------- /src/types/transactions.ts: -------------------------------------------------------------------------------- 1 | import type { AddressEx, Page, TokenInfo } from './common' 2 | import type { 3 | NativeStakingDepositConfirmationView, 4 | NativeStakingValidatorsExitConfirmationView, 5 | NativeStakingWithdrawConfirmationView, 6 | } from './decoded-data' 7 | import type { RichDecodedInfo } from './human-description' 8 | 9 | export type ParamValue = string | ParamValue[] 10 | 11 | export enum Operation { 12 | CALL = 0, 13 | DELEGATE = 1, 14 | } 15 | 16 | export type InternalTransaction = { 17 | operation: Operation 18 | to: string 19 | value?: string 20 | data: string | null 21 | dataDecoded?: DataDecoded 22 | } 23 | 24 | export type ValueDecodedType = InternalTransaction[] 25 | 26 | export type Parameter = { 27 | name: string 28 | type: string 29 | value: ParamValue 30 | valueDecoded?: ValueDecodedType 31 | } 32 | 33 | export type DataDecoded = { 34 | method: string 35 | parameters?: Parameter[] 36 | } 37 | 38 | export enum TransactionStatus { 39 | AWAITING_CONFIRMATIONS = 'AWAITING_CONFIRMATIONS', 40 | AWAITING_EXECUTION = 'AWAITING_EXECUTION', 41 | CANCELLED = 'CANCELLED', 42 | FAILED = 'FAILED', 43 | SUCCESS = 'SUCCESS', 44 | } 45 | 46 | export enum TransferDirection { 47 | INCOMING = 'INCOMING', 48 | OUTGOING = 'OUTGOING', 49 | UNKNOWN = 'UNKNOWN', 50 | } 51 | 52 | export enum TransactionTokenType { 53 | ERC20 = 'ERC20', 54 | ERC721 = 'ERC721', 55 | NATIVE_COIN = 'NATIVE_COIN', 56 | } 57 | 58 | export enum SettingsInfoType { 59 | SET_FALLBACK_HANDLER = 'SET_FALLBACK_HANDLER', 60 | ADD_OWNER = 'ADD_OWNER', 61 | REMOVE_OWNER = 'REMOVE_OWNER', 62 | SWAP_OWNER = 'SWAP_OWNER', 63 | CHANGE_THRESHOLD = 'CHANGE_THRESHOLD', 64 | CHANGE_IMPLEMENTATION = 'CHANGE_IMPLEMENTATION', 65 | ENABLE_MODULE = 'ENABLE_MODULE', 66 | DISABLE_MODULE = 'DISABLE_MODULE', 67 | SET_GUARD = 'SET_GUARD', 68 | DELETE_GUARD = 'DELETE_GUARD', 69 | } 70 | 71 | export enum TransactionInfoType { 72 | TRANSFER = 'Transfer', 73 | SETTINGS_CHANGE = 'SettingsChange', 74 | CUSTOM = 'Custom', 75 | CREATION = 'Creation', 76 | SWAP_ORDER = 'SwapOrder', 77 | TWAP_ORDER = 'TwapOrder', 78 | SWAP_TRANSFER = 'SwapTransfer', 79 | NATIVE_STAKING_DEPOSIT = 'NativeStakingDeposit', 80 | NATIVE_STAKING_VALIDATORS_EXIT = 'NativeStakingValidatorsExit', 81 | NATIVE_STAKING_WITHDRAW = 'NativeStakingWithdraw', 82 | } 83 | 84 | export enum ConflictType { 85 | NONE = 'None', 86 | HAS_NEXT = 'HasNext', 87 | END = 'End', 88 | } 89 | 90 | export enum TransactionListItemType { 91 | TRANSACTION = 'TRANSACTION', 92 | LABEL = 'LABEL', 93 | CONFLICT_HEADER = 'CONFLICT_HEADER', 94 | DATE_LABEL = 'DATE_LABEL', 95 | } 96 | 97 | export enum DetailedExecutionInfoType { 98 | MULTISIG = 'MULTISIG', 99 | MODULE = 'MODULE', 100 | } 101 | 102 | export type Erc20Transfer = { 103 | type: TransactionTokenType.ERC20 104 | tokenAddress: string 105 | tokenName?: string 106 | tokenSymbol?: string 107 | logoUri?: string 108 | decimals?: number 109 | value: string 110 | trusted: boolean | null 111 | imitation: boolean 112 | } 113 | 114 | export type Erc721Transfer = { 115 | type: TransactionTokenType.ERC721 116 | tokenAddress: string 117 | tokenId: string 118 | tokenName?: string 119 | tokenSymbol?: string 120 | logoUri?: string 121 | } 122 | 123 | export type NativeCoinTransfer = { 124 | type: TransactionTokenType.NATIVE_COIN 125 | value: string 126 | } 127 | 128 | export type TransferInfo = Erc20Transfer | Erc721Transfer | NativeCoinTransfer 129 | 130 | export type Transfer = { 131 | type: TransactionInfoType.TRANSFER 132 | sender: AddressEx 133 | recipient: AddressEx 134 | direction: TransferDirection 135 | transferInfo: TransferInfo 136 | humanDescription?: string 137 | richDecodedInfo?: RichDecodedInfo 138 | } 139 | 140 | export type SetFallbackHandler = { 141 | type: SettingsInfoType.SET_FALLBACK_HANDLER 142 | handler: AddressEx 143 | } 144 | 145 | export type AddOwner = { 146 | type: SettingsInfoType.ADD_OWNER 147 | owner: AddressEx 148 | threshold: number 149 | } 150 | 151 | export type RemoveOwner = { 152 | type: SettingsInfoType.REMOVE_OWNER 153 | owner: AddressEx 154 | threshold: number 155 | } 156 | 157 | export type SwapOwner = { 158 | type: SettingsInfoType.SWAP_OWNER 159 | oldOwner: AddressEx 160 | newOwner: AddressEx 161 | } 162 | 163 | export type ChangeThreshold = { 164 | type: SettingsInfoType.CHANGE_THRESHOLD 165 | threshold: number 166 | } 167 | 168 | export type ChangeImplementation = { 169 | type: SettingsInfoType.CHANGE_IMPLEMENTATION 170 | implementation: AddressEx 171 | } 172 | 173 | export type EnableModule = { 174 | type: SettingsInfoType.ENABLE_MODULE 175 | module: AddressEx 176 | } 177 | 178 | export type DisableModule = { 179 | type: SettingsInfoType.DISABLE_MODULE 180 | module: AddressEx 181 | } 182 | 183 | export type SetGuard = { 184 | type: SettingsInfoType.SET_GUARD 185 | guard: AddressEx 186 | } 187 | 188 | export type DeleteGuard = { 189 | type: SettingsInfoType.DELETE_GUARD 190 | } 191 | 192 | export type SettingsInfo = 193 | | SetFallbackHandler 194 | | AddOwner 195 | | RemoveOwner 196 | | SwapOwner 197 | | ChangeThreshold 198 | | ChangeImplementation 199 | | EnableModule 200 | | DisableModule 201 | | SetGuard 202 | | DeleteGuard 203 | 204 | export type SettingsChange = { 205 | type: TransactionInfoType.SETTINGS_CHANGE 206 | dataDecoded: DataDecoded 207 | settingsInfo?: SettingsInfo 208 | humanDescription?: string 209 | richDecodedInfo?: RichDecodedInfo 210 | } 211 | 212 | export type Custom = { 213 | type: TransactionInfoType.CUSTOM 214 | to: AddressEx 215 | dataSize: string 216 | value: string 217 | methodName?: string 218 | actionCount?: number 219 | isCancellation: boolean 220 | humanDescription?: string 221 | richDecodedInfo?: RichDecodedInfo 222 | } 223 | 224 | export type MultiSend = { 225 | type: TransactionInfoType.CUSTOM 226 | to: AddressEx 227 | dataSize: string 228 | value: string 229 | methodName: 'multiSend' 230 | actionCount: number 231 | isCancellation: boolean 232 | humanDescription?: string 233 | richDecodedInfo?: RichDecodedInfo 234 | } 235 | 236 | export type Cancellation = Custom & { 237 | isCancellation: true 238 | } 239 | 240 | export type Creation = { 241 | type: TransactionInfoType.CREATION 242 | creator: AddressEx 243 | transactionHash: string 244 | implementation?: AddressEx 245 | factory?: AddressEx 246 | humanDescription?: string 247 | richDecodedInfo?: RichDecodedInfo 248 | } 249 | 250 | export type OrderStatuses = 'presignaturePending' | 'open' | 'fulfilled' | 'cancelled' | 'expired' 251 | export type OrderKind = 'sell' | 'buy' 252 | export type OrderToken = { 253 | /** @description The token address */ 254 | address: string 255 | /** @description The token decimals */ 256 | decimals: number 257 | /** @description The logo URI for the token */ 258 | logoUri?: string | null 259 | /** @description The token name */ 260 | name: string 261 | /** @description The token symbol */ 262 | symbol: string 263 | /** @description The token trusted status */ 264 | trusted: boolean 265 | } 266 | export type OrderClass = 'limit' | 'market' | 'liquidity' 267 | 268 | // Base type for common fields 269 | export type BaseOrder = { 270 | humanDescription?: string | null 271 | richDecodedInfo?: null | RichDecodedInfo 272 | status: OrderStatuses 273 | kind: OrderKind 274 | orderClass: OrderClass 275 | /** @description The timestamp when the order expires */ 276 | validUntil: number 277 | /** @description The sell token raw amount (no decimals) */ 278 | sellAmount: string 279 | /** @description The buy token raw amount (no decimals) */ 280 | buyAmount: string 281 | /** @description The executed sell token raw amount (no decimals) */ 282 | executedSellAmount: string 283 | /** @description The executed buy token raw amount (no decimals) */ 284 | executedBuyAmount: string 285 | sellToken: OrderToken 286 | buyToken: OrderToken 287 | /** @description The URL to the explorer page of the order */ 288 | explorerUrl: string 289 | /** 290 | * @deprecated Use `executedFee` instead 291 | * @description The amount of fees paid for this order. 292 | */ 293 | executedSurplusFee: string | null 294 | /** @description The amount of fees paid for this order */ 295 | executedFee: string | null 296 | /** @description The token in which the fee was paid, expressed by SURPLUS tokens (BUY tokens for SELL orders and SELL tokens for BUY orders). */ 297 | executedFeeToken: OrderToken 298 | /** @description The (optional) address to receive the proceeds of the trade */ 299 | receiver?: string | null 300 | owner: string 301 | /** @description The App Data for this order */ 302 | fullAppData?: Record | null 303 | } 304 | 305 | export enum DurationType { 306 | AUTO = 'AUTO', 307 | LIMIT_DURATION = 'LIMIT_DURATION', 308 | } 309 | 310 | export enum StartTimeValue { 311 | AT_MINING_TIME = 'AT_MINING_TIME', 312 | AT_EPOCH = 'AT_EPOCH', 313 | } 314 | 315 | type DurationOfPart = 316 | | { durationType: DurationType.AUTO } 317 | | { durationType: DurationType.LIMIT_DURATION; duration: number } 318 | 319 | type StartTime = { startType: StartTimeValue.AT_MINING_TIME } | { startType: StartTimeValue.AT_EPOCH; epoch: number } 320 | 321 | // Specific type for SwapOrder 322 | export type SwapOrder = BaseOrder & { 323 | type: TransactionInfoType.SWAP_ORDER 324 | uid: string 325 | } 326 | export type SwapTransferOrder = Omit & 327 | BaseOrder & { 328 | type: TransactionInfoType.SWAP_TRANSFER 329 | uid: string 330 | } 331 | 332 | // Specific type for TwapOrder 333 | export type TwapOrder = Omit & { 334 | type: TransactionInfoType.TWAP_ORDER 335 | /** @description The executed sell token raw amount (no decimals) */ 336 | executedSellAmount: null | string 337 | /** @description The executed buy token raw amount (no decimals) */ 338 | executedBuyAmount: null | string 339 | /** @description The number of parts the order is split into */ 340 | numberOfParts: string 341 | /** @description The amount of sellToken to sell in each part */ 342 | partSellAmount: string 343 | /** @description The amount of buyToken that must be bought in each part */ 344 | minPartLimit: string 345 | /** @description The duration of the TWAP interval */ 346 | timeBetweenParts: number 347 | /** @description Whether the TWAP is valid for the entire interval or not */ 348 | durationOfPart: DurationOfPart 349 | /** @description The start time of the TWAP */ 350 | startTime: StartTime 351 | } 352 | 353 | // Discriminated union type 354 | export type Order = SwapOrder | SwapTransferOrder | TwapOrder 355 | 356 | export type StakingTxDepositInfo = { 357 | type: TransactionInfoType.NATIVE_STAKING_DEPOSIT 358 | humanDescription?: string 359 | } & Omit 360 | 361 | export type StakingTxExitInfo = { 362 | type: TransactionInfoType.NATIVE_STAKING_VALIDATORS_EXIT 363 | humanDescription?: string 364 | } & Omit 365 | 366 | export type StakingTxWithdrawInfo = { 367 | type: TransactionInfoType.NATIVE_STAKING_WITHDRAW 368 | humanDescription?: string 369 | } & Omit 370 | 371 | export type StakingTxInfo = StakingTxDepositInfo | StakingTxExitInfo | StakingTxWithdrawInfo 372 | 373 | export type TransactionInfo = 374 | | Transfer 375 | | SettingsChange 376 | | Custom 377 | | MultiSend 378 | | Cancellation 379 | | Creation 380 | | Order 381 | | StakingTxInfo 382 | 383 | export type ModuleExecutionInfo = { 384 | type: DetailedExecutionInfoType.MODULE 385 | address: AddressEx 386 | } 387 | 388 | export type MultisigExecutionInfo = { 389 | type: DetailedExecutionInfoType.MULTISIG 390 | nonce: number 391 | confirmationsRequired: number 392 | confirmationsSubmitted: number 393 | missingSigners?: AddressEx[] 394 | } 395 | 396 | export type ExecutionInfo = ModuleExecutionInfo | MultisigExecutionInfo 397 | 398 | export type TransactionSummary = { 399 | id: string 400 | timestamp: number 401 | txStatus: TransactionStatus 402 | txInfo: TransactionInfo 403 | txHash: string | null 404 | executionInfo?: ExecutionInfo 405 | safeAppInfo?: SafeAppInfo 406 | } 407 | 408 | export type Transaction = { 409 | transaction: TransactionSummary 410 | conflictType: ConflictType 411 | type: TransactionListItemType.TRANSACTION 412 | } 413 | 414 | export type IncomingTransfer = Omit & { 415 | transaction: Omit & { 416 | txInfo: Omit & { direction: TransferDirection.INCOMING } 417 | } 418 | } 419 | 420 | export type ModuleTransaction = Omit & { 421 | transaction: Omit & { 422 | txInfo: Transfer 423 | executionInfo?: ModuleExecutionInfo 424 | } 425 | } 426 | 427 | export type MultisigTransaction = Omit & { 428 | transaction: Omit & { 429 | txInfo: Omit & { direction: TransferDirection.OUTGOING } 430 | executionInfo?: MultisigExecutionInfo 431 | } 432 | } 433 | 434 | export type DateLabel = { 435 | timestamp: number 436 | type: TransactionListItemType.DATE_LABEL 437 | } 438 | 439 | export enum LabelValue { 440 | Queued = 'Queued', 441 | Next = 'Next', 442 | } 443 | 444 | export type Label = { 445 | label: LabelValue 446 | type: TransactionListItemType.LABEL 447 | } 448 | 449 | export type ConflictHeader = { 450 | nonce: number 451 | type: TransactionListItemType.CONFLICT_HEADER 452 | } 453 | 454 | export type TransactionListItem = Transaction | DateLabel | Label | ConflictHeader 455 | 456 | export type TransactionListPage = Page 457 | 458 | export type MultisigTransactionRequest = { 459 | to: string 460 | value: string 461 | data?: string 462 | nonce: string 463 | operation: Operation 464 | safeTxGas: string 465 | baseGas: string 466 | gasPrice: string 467 | gasToken: string 468 | refundReceiver?: string 469 | safeTxHash: string 470 | sender: string 471 | signature?: string 472 | origin?: string 473 | } 474 | 475 | /* Transaction details types */ 476 | export type SafeAppInfo = { 477 | name: string 478 | url: string 479 | logoUri: string 480 | } 481 | 482 | export type TransactionData = { 483 | hexData?: string 484 | dataDecoded?: DataDecoded 485 | to: AddressEx 486 | value?: string 487 | operation: Operation 488 | addressInfoIndex?: { [key: string]: AddressEx } 489 | trustedDelegateCallTarget: boolean 490 | } 491 | 492 | export type ModuleExecutionDetails = { 493 | type: DetailedExecutionInfoType.MODULE 494 | address: AddressEx 495 | } 496 | 497 | export type MultisigConfirmation = { 498 | signer: AddressEx 499 | signature?: string 500 | submittedAt: number 501 | } 502 | 503 | export type MultisigExecutionDetails = { 504 | type: DetailedExecutionInfoType.MULTISIG 505 | submittedAt: number 506 | nonce: number 507 | safeTxGas: string 508 | baseGas: string 509 | gasPrice: string 510 | gasToken: string 511 | refundReceiver: AddressEx 512 | safeTxHash: string 513 | executor?: AddressEx 514 | signers: AddressEx[] 515 | confirmationsRequired: number 516 | confirmations: MultisigConfirmation[] 517 | rejectors?: AddressEx[] 518 | gasTokenInfo?: TokenInfo 519 | trusted: boolean 520 | proposer: AddressEx | null 521 | proposedByDelegate: AddressEx | null 522 | } 523 | 524 | export type DetailedExecutionInfo = ModuleExecutionDetails | MultisigExecutionDetails 525 | 526 | export type TransactionDetails = { 527 | safeAddress: string 528 | txId: string 529 | executedAt?: number 530 | txStatus: TransactionStatus 531 | txInfo: TransactionInfo 532 | txData?: TransactionData 533 | detailedExecutionInfo?: DetailedExecutionInfo 534 | txHash?: string 535 | safeAppInfo?: SafeAppInfo 536 | note?: string | null 537 | } 538 | 539 | /* Transaction details types end */ 540 | 541 | export type TransactionPreview = { 542 | txInfo: TransactionInfo 543 | txData: TransactionData 544 | } 545 | 546 | /* Transaction estimation types */ 547 | 548 | export type SafeTransactionEstimationRequest = { 549 | to: string 550 | value: string 551 | data: string 552 | operation: Operation 553 | } 554 | 555 | // CGW v2 response 556 | export type SafeTransactionEstimation = { 557 | currentNonce: number 558 | recommendedNonce: number 559 | safeTxGas: string 560 | } 561 | 562 | export type NoncesResponse = { 563 | currentNonce: number 564 | recommendedNonce: number 565 | } 566 | 567 | /* Transaction estimation types end */ 568 | 569 | export type SafeIncomingTransfersResponse = Page 570 | 571 | export type SafeModuleTransactionsResponse = Page 572 | 573 | export type SafeMultisigTransactionsResponse = Page 574 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export type Params = Record 2 | 3 | export type ErrorResponse = 4 | | { 5 | code: number 6 | statusCode?: never 7 | message: string 8 | } 9 | | { 10 | code?: never 11 | statusCode: number 12 | message: string 13 | } 14 | 15 | const isErrorResponse = (data: unknown): data is ErrorResponse => { 16 | const isObject = typeof data === 'object' && data !== null 17 | return isObject && ('code' in data || 'statusCode' in data) && 'message' in data 18 | } 19 | 20 | function replaceParam(str: string, key: string, value: string): string { 21 | return str.replace(new RegExp(`\\{${key}\\}`, 'g'), value) 22 | } 23 | 24 | export function insertParams(template: string, params?: Params): string { 25 | return params 26 | ? Object.keys(params).reduce((result: string, key) => { 27 | return replaceParam(result, key, String(params[key])) 28 | }, template) 29 | : template 30 | } 31 | 32 | export function stringifyQuery(query?: Params): string { 33 | if (!query) { 34 | return '' 35 | } 36 | 37 | const searchParams = new URLSearchParams() 38 | Object.keys(query).forEach((key) => { 39 | if (query[key] != null) { 40 | searchParams.append(key, String(query[key])) 41 | } 42 | }) 43 | const searchString = searchParams.toString() 44 | return searchString ? `?${searchString}` : '' 45 | } 46 | 47 | async function parseResponse(resp: Response): Promise { 48 | let json 49 | 50 | try { 51 | json = await resp.json() 52 | } catch { 53 | json = {} 54 | } 55 | 56 | if (!resp.ok) { 57 | const errTxt = isErrorResponse(json) 58 | ? `CGW error - ${json.code ?? json.statusCode}: ${json.message}` 59 | : `CGW error - status ${resp.statusText}` 60 | throw new Error(errTxt) 61 | } 62 | 63 | return json 64 | } 65 | 66 | export async function fetchData( 67 | url: string, 68 | method: 'POST' | 'PUT' | 'DELETE', 69 | body?: unknown, 70 | headers?: Record, 71 | credentials?: RequestCredentials, 72 | ): Promise { 73 | const requestHeaders: Record = { 74 | 'Content-Type': 'application/json', 75 | ...headers, 76 | } 77 | 78 | const options: RequestInit = { 79 | method: method ?? 'POST', 80 | headers: requestHeaders, 81 | } 82 | 83 | if (credentials) { 84 | options['credentials'] = credentials 85 | } 86 | 87 | if (body != null) { 88 | options.body = typeof body === 'string' ? body : JSON.stringify(body) 89 | } 90 | 91 | const resp = await fetch(url, options) 92 | 93 | return parseResponse(resp) 94 | } 95 | 96 | export async function getData( 97 | url: string, 98 | headers?: Record, 99 | credentials?: RequestCredentials, 100 | ): Promise { 101 | const options: RequestInit = { 102 | method: 'GET', 103 | } 104 | 105 | if (headers) { 106 | options['headers'] = { 107 | ...headers, 108 | 'Content-Type': 'application/json', 109 | } 110 | } 111 | 112 | if (credentials) { 113 | options['credentials'] = credentials 114 | } 115 | 116 | const resp = await fetch(url, options) 117 | 118 | return parseResponse(resp) 119 | } 120 | -------------------------------------------------------------------------------- /tests/endpoint.test.ts: -------------------------------------------------------------------------------- 1 | import { getData, fetchData } from '../src/utils' 2 | import { deleteEndpoint, getEndpoint, postEndpoint, putEndpoint } from '../src/endpoint' 3 | 4 | jest.mock('../src/utils', () => { 5 | const originalModule = jest.requireActual('../src/utils') 6 | 7 | return { 8 | __esModule: true, 9 | ...originalModule, 10 | fetchData: jest.fn(() => Promise.resolve({ success: true })), 11 | getData: jest.fn(() => Promise.resolve({ success: true })), 12 | } 13 | }) 14 | 15 | describe('getEndpoint', () => { 16 | it('should accept just a path', async () => { 17 | await expect(getEndpoint('https://test.test', '/v1/balances/supported-fiat-codes')).resolves.toEqual({ 18 | success: true, 19 | }) 20 | 21 | expect(getData).toHaveBeenCalledWith('https://test.test/v1/balances/supported-fiat-codes', undefined, undefined) 22 | }) 23 | 24 | it('should accept a path param', async () => { 25 | await expect( 26 | getEndpoint('https://test.test', '/v1/chains/{chainId}/safes/{address}', { 27 | path: { chainId: '4', address: '0x123' }, 28 | }), 29 | ).resolves.toEqual({ success: true }) 30 | 31 | expect(getData).toHaveBeenCalledWith('https://test.test/v1/chains/4/safes/0x123', undefined, undefined) 32 | }) 33 | 34 | it('should accept several path params', async () => { 35 | await expect( 36 | getEndpoint('https://test.test', '/v1/chains/{chainId}/safes/{address}/balances/{currency}', { 37 | path: { chainId: '4', address: '0x123', currency: 'usd' }, 38 | query: {}, 39 | }), 40 | ).resolves.toEqual({ success: true }) 41 | 42 | expect(getData).toHaveBeenCalledWith('https://test.test/v1/chains/4/safes/0x123/balances/usd', undefined, undefined) 43 | }) 44 | 45 | it('should accept query params', async () => { 46 | await expect( 47 | getEndpoint('https://test.test', '/v1/chains/{chainId}/safes/{address}/balances/{currency}', { 48 | path: { chainId: '4', address: '0x123', currency: 'usd' }, 49 | query: { exclude_spam: true }, 50 | }), 51 | ).resolves.toEqual({ success: true }) 52 | 53 | expect(getData).toHaveBeenCalledWith( 54 | 'https://test.test/v1/chains/4/safes/0x123/balances/usd?exclude_spam=true', 55 | undefined, 56 | undefined, 57 | ) 58 | }) 59 | 60 | it('should accept POST request with body', async () => { 61 | const body = { 62 | to: '0x123', 63 | value: 'test', 64 | data: '0x', 65 | nonce: '1', 66 | operation: 1, 67 | safeTxGas: '', 68 | baseGas: '100', 69 | gasPrice: '1', 70 | gasToken: '', 71 | refundReceiver: '', 72 | safeTxHash: '0x123', 73 | sender: '0x123', 74 | signature: '', 75 | origin: '', 76 | } 77 | 78 | await expect( 79 | postEndpoint('https://test.test', '/v1/chains/{chainId}/transactions/{safe_address}/propose', { 80 | path: { chainId: '4', safe_address: '0x123' }, 81 | body, 82 | }), 83 | ).resolves.toEqual({ success: true }) 84 | 85 | expect(fetchData).toHaveBeenCalledWith( 86 | 'https://test.test/v1/chains/4/transactions/0x123/propose', 87 | 'POST', 88 | body, 89 | undefined, 90 | undefined, 91 | ) 92 | }) 93 | 94 | it('should accept a raw URL', async () => { 95 | await expect( 96 | getEndpoint( 97 | 'https://test.test', 98 | '/v1/chains/{chainId}/safes/{address}/balances/{currency}', 99 | { path: { chainId: '4', address: '0x123', currency: 'usd' }, query: { exclude_spam: true } }, 100 | '/test-url?raw=true', 101 | ), 102 | ).resolves.toEqual({ success: true }) 103 | 104 | expect(getData).toHaveBeenCalledWith('/test-url?raw=true', undefined, undefined) 105 | }) 106 | 107 | it('should call a tx preview POST endpoint', async () => { 108 | await expect( 109 | postEndpoint('https://test.test', '/v1/chains/{chainId}/transactions/{safe_address}/preview', { 110 | path: { chainId: '4', safe_address: '0x123' }, 111 | body: { data: '0x456', operation: 0 }, 112 | }), 113 | ).resolves.toEqual({ success: true }) 114 | 115 | expect(fetchData).toHaveBeenCalledWith( 116 | 'https://test.test/v1/chains/4/transactions/0x123/preview', 117 | 'POST', 118 | { data: '0x456', operation: 0 }, 119 | undefined, 120 | undefined, 121 | ) 122 | }) 123 | 124 | it('should accept PUT request with body', async () => { 125 | const body = { 126 | emailAddress: 'test@test.com', 127 | } 128 | 129 | const headers = { 130 | 'Safe-Wallet-Signature': '0x234', 131 | 'Safe-Wallet-Signature-Timestamp': jest.now().toString(), 132 | } 133 | 134 | await expect( 135 | putEndpoint('https://test.test', '/v1/chains/{chainId}/safes/{safe_address}/emails/{signer}', { 136 | path: { chainId: '4', safe_address: '0x123', signer: '0x456' }, 137 | body, 138 | headers, 139 | }), 140 | ).resolves.toEqual({ success: true }) 141 | 142 | expect(fetchData).toHaveBeenCalledWith( 143 | 'https://test.test/v1/chains/4/safes/0x123/emails/0x456', 144 | 'PUT', 145 | body, 146 | headers, 147 | undefined, 148 | ) 149 | }) 150 | 151 | it('should send a DELETE request with body', async () => { 152 | const body = { 153 | signature: '0x123', 154 | } 155 | 156 | await expect( 157 | deleteEndpoint('https://test.test', '/v1/chains/{chainId}/transactions/{safeTxHash}', { 158 | path: { chainId: '4', safeTxHash: '0x456' }, 159 | body, 160 | }), 161 | ).resolves.toEqual({ success: true }) 162 | 163 | expect(fetchData).toHaveBeenCalledWith( 164 | 'https://test.test/v1/chains/4/transactions/0x456', 165 | 'DELETE', 166 | body, 167 | undefined, 168 | undefined, 169 | ) 170 | }) 171 | }) 172 | -------------------------------------------------------------------------------- /tests/types.test.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SafeAppData, 3 | MultisigTransactionRequest, 4 | BlockExplorerUriTemplate, 5 | FiatCurrencies, 6 | MasterCopyReponse, 7 | DecodedDataParameterValue, 8 | } from '../src' 9 | 10 | describe('Types are exported from index correctly', () => { 11 | it('Random type check', () => { 12 | const val = {} as unknown as 13 | | SafeAppData 14 | | MultisigTransactionRequest 15 | | BlockExplorerUriTemplate 16 | | FiatCurrencies 17 | | MasterCopyReponse 18 | | DecodedDataParameterValue 19 | expect(val).toBe(val) 20 | }) 21 | 22 | it('Dynamic enum export check', () => { 23 | /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ 24 | const { default: _ = null, ...rest } = require('../src/index') 25 | const exportedNames = Object.keys(rest) 26 | 27 | const fs = require('fs') 28 | const path = require('path') 29 | 30 | const typesDir = path.join(__dirname, '..', 'src', 'types') 31 | const files: string[] = fs.readdirSync(typesDir) 32 | 33 | const exportedTypeNames = files 34 | .filter((file) => file !== 'api.ts') 35 | .flatMap((file) => { 36 | /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ 37 | const { default: _ = null, ...rest } = require(`../src/types/${file}`) 38 | return Object.keys(rest) 39 | }) 40 | 41 | exportedTypeNames.every((type) => expect(exportedNames.includes(type)).toBe(true)) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { fetchData, getData, insertParams, stringifyQuery } from '../src/utils' 3 | 4 | const fetchMock = jest.spyOn(global, 'fetch') as typeof fetch & jest.Mock 5 | 6 | describe('utils', () => { 7 | describe('insertParams', () => { 8 | it('should insert a param into a string', () => { 9 | expect(insertParams('/{network}/safe/{address}', { address: '0x0' })).toBe('/{network}/safe/0x0') 10 | }) 11 | 12 | it('should insert several params into a string', () => { 13 | expect(insertParams('/{network}/safe/{address}', { address: '0x0', network: 'rinkeby' })).toBe( 14 | '/rinkeby/safe/0x0', 15 | ) 16 | }) 17 | }) 18 | 19 | describe('stringifyQuery', () => { 20 | it('should stringify query params', () => { 21 | expect(stringifyQuery({ spam: true, page: 11, name: 'token', exclude: null })).toBe( 22 | '?spam=true&page=11&name=token', 23 | ) 24 | }) 25 | 26 | it('should return an empty string for empty query', () => { 27 | expect(stringifyQuery()).toBe('') 28 | expect(stringifyQuery({})).toBe('') 29 | }) 30 | }) 31 | 32 | describe('getData', () => { 33 | it('should fetch a simple url', async () => { 34 | fetchMock.mockImplementation(() => { 35 | return Promise.resolve({ 36 | ok: true, 37 | text: () => Promise.resolve('{"success": "true"}'), 38 | json: () => Promise.resolve({ success: true }), 39 | }) 40 | }) 41 | 42 | await expect(getData('/test/safe?q=123')).resolves.toEqual({ success: true }) 43 | expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', { method: 'GET' }) 44 | }) 45 | 46 | it('should forward headers with a GET request', async () => { 47 | fetchMock.mockImplementation(() => { 48 | return Promise.resolve({ 49 | ok: true, 50 | text: () => Promise.resolve('{"success": "true"}'), 51 | json: () => Promise.resolve({ success: true }), 52 | }) 53 | }) 54 | 55 | await expect(getData('/test/safe', { TestHeader: '123456' })).resolves.toEqual({ 56 | success: true, 57 | }) 58 | 59 | expect(fetch).toHaveBeenCalledWith('/test/safe', { 60 | method: 'GET', 61 | headers: { 62 | TestHeader: '123456', 63 | 'Content-Type': 'application/json', 64 | }, 65 | }) 66 | }) 67 | 68 | it('should throw if response is not OK', async () => { 69 | fetchMock.mockImplementation(() => { 70 | return Promise.resolve({ 71 | ok: false, 72 | statusText: 'Failed', 73 | json: () => ({ code: 1337, message: 'something went wrong' }), 74 | }) 75 | }) 76 | 77 | await expect(getData('/test/safe?q=123')).rejects.toThrow('1337: something went wrong') 78 | expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', { method: 'GET' }) 79 | }) 80 | 81 | it('should throw the response text for 50x errors', async () => { 82 | fetchMock.mockImplementation(() => { 83 | return Promise.resolve({ 84 | ok: false, 85 | statusText: 'Failed', 86 | json: () => null, 87 | }) 88 | }) 89 | 90 | await expect(getData('/test/safe?q=123')).rejects.toThrow('Failed') 91 | expect(fetch).toHaveBeenCalledWith('/test/safe?q=123', { method: 'GET' }) 92 | }) 93 | }) 94 | 95 | describe('fetchData', () => { 96 | it('should make a post request', async () => { 97 | fetchMock.mockImplementation(() => { 98 | return Promise.resolve({ 99 | ok: true, 100 | text: () => Promise.resolve('{"success": "true"}'), 101 | json: () => Promise.resolve({ success: true }), 102 | }) 103 | }) 104 | 105 | await expect(fetchData('/test/safe', 'POST', '123')).resolves.toEqual({ success: true }) 106 | 107 | expect(fetch).toHaveBeenCalledWith('/test/safe', { 108 | method: 'POST', 109 | body: '123', 110 | headers: { 111 | 'Content-Type': 'application/json', 112 | }, 113 | }) 114 | }) 115 | 116 | it('should forward headers with a POST request', async () => { 117 | fetchMock.mockImplementation(() => { 118 | return Promise.resolve({ 119 | ok: true, 120 | text: () => Promise.resolve('{"success": "true"}'), 121 | json: () => Promise.resolve({ success: true }), 122 | }) 123 | }) 124 | 125 | await expect(fetchData('/test/safe', 'POST', '123', { TestHeader: '123456' })).resolves.toEqual({ success: true }) 126 | 127 | expect(fetch).toHaveBeenCalledWith('/test/safe', { 128 | method: 'POST', 129 | body: '123', 130 | headers: { 131 | TestHeader: '123456', 132 | 'Content-Type': 'application/json', 133 | }, 134 | }) 135 | }) 136 | 137 | it('should use PUT if specified', async () => { 138 | fetchMock.mockImplementation(() => { 139 | return Promise.resolve({ 140 | ok: true, 141 | text: () => Promise.resolve('{"success": "true"}'), 142 | json: () => Promise.resolve({ success: true }), 143 | }) 144 | }) 145 | 146 | await expect(fetchData('/test/safe', 'PUT', '123', { TestHeader: '123456' })).resolves.toEqual({ success: true }) 147 | 148 | expect(fetch).toHaveBeenCalledWith('/test/safe', { 149 | method: 'PUT', 150 | body: '123', 151 | headers: { 152 | TestHeader: '123456', 153 | 'Content-Type': 'application/json', 154 | }, 155 | }) 156 | }) 157 | }) 158 | 159 | describe('fetchData DELETE', () => { 160 | it('should make a DELETE request', async () => { 161 | fetchMock.mockImplementation(() => { 162 | return Promise.resolve({ 163 | ok: true, 164 | text: () => Promise.resolve('{"success": "true"}'), 165 | json: () => Promise.resolve({ success: true }), 166 | }) 167 | }) 168 | 169 | await expect(fetchData('/test/safe', 'DELETE')).resolves.toEqual({ success: true }) 170 | 171 | expect(fetch).toHaveBeenCalledWith('/test/safe', { 172 | method: 'DELETE', 173 | headers: { 174 | 'Content-Type': 'application/json', 175 | }, 176 | }) 177 | }) 178 | 179 | it('should make a DELETE request and pass headers', async () => { 180 | fetchMock.mockImplementation(() => { 181 | return Promise.resolve({ 182 | ok: true, 183 | text: () => Promise.resolve('{"success": "true"}'), 184 | json: () => Promise.resolve({ success: true }), 185 | }) 186 | }) 187 | 188 | await expect(fetchData('/test/safe', 'DELETE', undefined, { TestHeader: '123456' })).resolves.toEqual({ 189 | success: true, 190 | }) 191 | 192 | expect(fetch).toHaveBeenCalledWith('/test/safe', { 193 | method: 'DELETE', 194 | headers: { 195 | TestHeader: '123456', 196 | 'Content-Type': 'application/json', 197 | }, 198 | }) 199 | }) 200 | 201 | it('should not throw for an non-JSON response', async () => { 202 | const jsonMock = jest.fn().mockRejectedValue('error') 203 | 204 | fetchMock.mockImplementation(() => { 205 | return Promise.resolve({ 206 | ok: true, 207 | status: 204, 208 | statusText: 'No Content', 209 | json: jsonMock, 210 | }) 211 | }) 212 | 213 | await expect(fetchData('/test/safe', 'DELETE')).resolves.toEqual({}) 214 | 215 | expect(fetch).toHaveBeenCalledWith('/test/safe', { 216 | method: 'DELETE', 217 | headers: { 218 | 'Content-Type': 'application/json', 219 | }, 220 | }) 221 | }) 222 | }) 223 | }) 224 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "module": "commonjs", 5 | "target": "es6", 6 | "lib": ["es2019", "dom"], 7 | "sourceMap": true, 8 | "skipLibCheck": true, 9 | "declaration": true, 10 | "allowJs": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | }, 19 | "include": ["src"], 20 | "exclude": ["dist", "node_modules"] 21 | } 22 | --------------------------------------------------------------------------------