├── .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 | [](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) |
[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 |
--------------------------------------------------------------------------------