Skip to content

Commit ce49299

Browse files
authored
feat(api-kit): Api keys integration (#1216)
1 parent d3aabb8 commit ce49299

35 files changed

+392
-182
lines changed

.github/workflows/api-kit-e2e-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
run: yarn build
2626

2727
- name: Test
28+
env:
29+
API_KEY: ${{ secrets.API_KEY }}
2830
run: |
2931
cd packages/api-kit
3032
yarn test:ci:ethers

packages/api-kit/.env-sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Transaction Service API Key
2+
API_KEY=

packages/api-kit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@safe-global/api-kit",
3-
"version": "3.0.2",
3+
"version": "4.0.0-alpha.0",
44
"description": "SDK that facilitates the interaction with the Safe Transaction Service API",
55
"types": "dist/src/index.d.ts",
66
"main": "dist/cjs/index.cjs",

packages/api-kit/src/SafeApiKit.ts

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
TokenInfoResponse,
3838
TransferListResponse
3939
} from '@safe-global/api-kit/types/safeTransactionServiceTypes'
40-
import { HttpMethod, sendRequest } from '@safe-global/api-kit/utils/httpRequests'
40+
import { HttpMethod, HttpRequest, sendRequest } from '@safe-global/api-kit/utils/httpRequests'
4141
import { signDelegate } from '@safe-global/api-kit/utils/signDelegate'
4242
import { validateEip3770Address, validateEthereumAddress } from '@safe-global/protocol-kit'
4343
import {
@@ -50,7 +50,7 @@ import {
5050
SafeOperationResponse,
5151
UserOperationV06
5252
} from '@safe-global/types-kit'
53-
import { TRANSACTION_SERVICE_URLS } from './utils/config'
53+
import { getTransactionServiceUrl } from './utils/config'
5454
import { isEmptyData } from './utils'
5555
import { getAddSafeOperationProps, isSafeOperation } from './utils/safeOperation'
5656
import { QUERY_PARAMS_MAP } from './utils/queryParamsMap'
@@ -60,19 +60,43 @@ export interface SafeApiKitConfig {
6060
chainId: bigint
6161
/** txServiceUrl - Safe Transaction Service URL */
6262
txServiceUrl?: string
63+
/**
64+
* apiKey - The API key to access the Safe Transaction Service.
65+
* - Required if txServiceUrl is undefined
66+
* - Required if txServiceUrl contains "safe.global" or "5afe.dev"
67+
* - Optional otherwise
68+
*/
69+
apiKey?: string
6370
}
6471

6572
class SafeApiKit {
6673
#chainId: bigint
74+
#apiKey?: string
6775
#txServiceBaseUrl: string
6876

69-
constructor({ chainId, txServiceUrl }: SafeApiKitConfig) {
77+
constructor({ chainId, txServiceUrl, apiKey }: SafeApiKitConfig) {
7078
this.#chainId = chainId
7179

7280
if (txServiceUrl) {
81+
// If txServiceUrl contains safe.global or 5afe.dev, apiKey is mandatory
82+
if (
83+
(txServiceUrl.includes('api.safe.global') || txServiceUrl.includes('api.5afe.dev')) &&
84+
!apiKey
85+
) {
86+
throw new Error(
87+
'apiKey is mandatory when using api.safe.global or api.5afe.dev domains. Please obtain your API key at https://developer.safe.global.'
88+
)
89+
}
7390
this.#txServiceBaseUrl = txServiceUrl
7491
} else {
75-
const url = TRANSACTION_SERVICE_URLS[chainId.toString()]
92+
// If txServiceUrl is not defined, apiKey is mandatory
93+
if (!apiKey) {
94+
throw new Error(
95+
'apiKey is mandatory when txServiceUrl is not defined. Please obtain your API key at https://developer.safe.global.'
96+
)
97+
}
98+
99+
const url = getTransactionServiceUrl(chainId)
76100
if (!url) {
77101
throw new TypeError(
78102
`There is no transaction service available for chainId ${chainId}. Please set the txServiceUrl property to use a custom transaction service.`
@@ -81,6 +105,8 @@ class SafeApiKit {
81105

82106
this.#txServiceBaseUrl = url
83107
}
108+
109+
this.#apiKey = apiKey
84110
}
85111

86112
#isValidAddress(address: string) {
@@ -119,13 +145,17 @@ class SafeApiKit {
119145
})
120146
}
121147

148+
async #api<T>(request: HttpRequest): Promise<T> {
149+
return sendRequest(request, this.#apiKey)
150+
}
151+
122152
/**
123153
* Returns the information and configuration of the service.
124154
*
125155
* @returns The information and configuration of the service
126156
*/
127157
async getServiceInfo(): Promise<SafeServiceInfoResponse> {
128-
return sendRequest({
158+
return this.#api({
129159
url: `${this.#txServiceBaseUrl}/v1/about`,
130160
method: HttpMethod.Get
131161
})
@@ -137,7 +167,7 @@ class SafeApiKit {
137167
* @returns The list of Safe singletons
138168
*/
139169
async getServiceSingletonsInfo(): Promise<SafeSingletonResponse[]> {
140-
return sendRequest({
170+
return this.#api({
141171
url: `${this.#txServiceBaseUrl}/v1/about/singletons`,
142172
method: HttpMethod.Get
143173
})
@@ -164,7 +194,7 @@ class SafeApiKit {
164194
dataDecoderRequest.to = to
165195
}
166196

167-
return sendRequest({
197+
return this.#api({
168198
url: `${this.#txServiceBaseUrl}/v1/data-decoder/`,
169199
method: HttpMethod.Post,
170200
body: dataDecoderRequest
@@ -210,7 +240,7 @@ class SafeApiKit {
210240
url.searchParams.set('offset', offset.toString())
211241
}
212242

213-
return sendRequest({
243+
return this.#api({
214244
url: url.toString(),
215245
method: HttpMethod.Get
216246
})
@@ -256,7 +286,7 @@ class SafeApiKit {
256286
label,
257287
signature
258288
}
259-
return sendRequest({
289+
return this.#api({
260290
url: `${this.#txServiceBaseUrl}/v2/delegates/`,
261291
method: HttpMethod.Post,
262292
body
@@ -289,7 +319,7 @@ class SafeApiKit {
289319
const { address: delegator } = this.#getEip3770Address(delegatorAddress)
290320
const signature = await signDelegate(signer, delegate, this.#chainId)
291321

292-
return sendRequest({
322+
return this.#api({
293323
url: `${this.#txServiceBaseUrl}/v2/delegates/${delegate}`,
294324
method: HttpMethod.Delete,
295325
body: {
@@ -309,7 +339,7 @@ class SafeApiKit {
309339
throw new Error('Invalid messageHash')
310340
}
311341

312-
return sendRequest({
342+
return this.#api({
313343
url: `${this.#txServiceBaseUrl}/v1/messages/${messageHash}/`,
314344
method: HttpMethod.Get
315345
})
@@ -334,7 +364,7 @@ class SafeApiKit {
334364
// Check if options are given and add query parameters
335365
this.#addUrlQueryParams<GetSafeMessageListOptions>(url, options)
336366

337-
return sendRequest({
367+
return this.#api({
338368
url: url.toString(),
339369
method: HttpMethod.Get
340370
})
@@ -351,7 +381,7 @@ class SafeApiKit {
351381
throw new Error('Invalid safeAddress')
352382
}
353383

354-
return sendRequest({
384+
return this.#api({
355385
url: `${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/messages/`,
356386
method: HttpMethod.Post,
357387
body: addMessageOptions
@@ -368,7 +398,7 @@ class SafeApiKit {
368398
throw new Error('Invalid messageHash or signature')
369399
}
370400

371-
return sendRequest({
401+
return this.#api({
372402
url: `${this.#txServiceBaseUrl}/v1/messages/${messageHash}/signatures/`,
373403
method: HttpMethod.Post,
374404
body: {
@@ -390,7 +420,7 @@ class SafeApiKit {
390420
throw new Error('Invalid owner address')
391421
}
392422
const { address } = this.#getEip3770Address(ownerAddress)
393-
return sendRequest({
423+
return this.#api({
394424
url: `${this.#txServiceBaseUrl}/v1/owners/${address}/safes/`,
395425
method: HttpMethod.Get
396426
})
@@ -409,7 +439,7 @@ class SafeApiKit {
409439
throw new Error('Invalid module address')
410440
}
411441
const { address } = this.#getEip3770Address(moduleAddress)
412-
return sendRequest({
442+
return this.#api({
413443
url: `${this.#txServiceBaseUrl}/v1/modules/${address}/safes/`,
414444
method: HttpMethod.Get
415445
})
@@ -427,7 +457,7 @@ class SafeApiKit {
427457
if (safeTxHash === '') {
428458
throw new Error('Invalid safeTxHash')
429459
}
430-
return sendRequest({
460+
return this.#api({
431461
url: `${this.#txServiceBaseUrl}/v2/multisig-transactions/${safeTxHash}/`,
432462
method: HttpMethod.Get
433463
})
@@ -446,7 +476,7 @@ class SafeApiKit {
446476
if (safeTxHash === '') {
447477
throw new Error('Invalid safeTxHash')
448478
}
449-
return sendRequest({
479+
return this.#api({
450480
url: `${this.#txServiceBaseUrl}/v1/multisig-transactions/${safeTxHash}/confirmations/`,
451481
method: HttpMethod.Get
452482
})
@@ -470,7 +500,7 @@ class SafeApiKit {
470500
if (signature === '') {
471501
throw new Error('Invalid signature')
472502
}
473-
return sendRequest({
503+
return this.#api({
474504
url: `${this.#txServiceBaseUrl}/v1/multisig-transactions/${safeTxHash}/confirmations/`,
475505
method: HttpMethod.Post,
476506
body: {
@@ -492,7 +522,7 @@ class SafeApiKit {
492522
throw new Error('Invalid Safe address')
493523
}
494524
const { address } = this.#getEip3770Address(safeAddress)
495-
return sendRequest({
525+
return this.#api({
496526
url: `${this.#txServiceBaseUrl}/v1/safes/${address}/`,
497527
method: HttpMethod.Get
498528
}).then((response: any) => {
@@ -521,7 +551,7 @@ class SafeApiKit {
521551
throw new Error('Invalid Safe address')
522552
}
523553
const { address } = this.#getEip3770Address(safeAddress)
524-
return sendRequest({
554+
return this.#api({
525555
url: `${this.#txServiceBaseUrl}/v1/safes/${address}/creation/`,
526556
method: HttpMethod.Get
527557
}).then((response: any) => {
@@ -554,7 +584,7 @@ class SafeApiKit {
554584
throw new Error('Invalid Safe address')
555585
}
556586
const { address } = this.#getEip3770Address(safeAddress)
557-
return sendRequest({
587+
return this.#api({
558588
url: `${this.#txServiceBaseUrl}/v1/safes/${address}/multisig-transactions/estimations/`,
559589
method: HttpMethod.Post,
560590
body: safeTransaction
@@ -587,7 +617,7 @@ class SafeApiKit {
587617
if (safeTxHash === '') {
588618
throw new Error('Invalid safeTxHash')
589619
}
590-
return sendRequest({
620+
return this.#api({
591621
url: `${this.#txServiceBaseUrl}/v2/safes/${safe}/multisig-transactions/`,
592622
method: HttpMethod.Post,
593623
body: {
@@ -622,7 +652,7 @@ class SafeApiKit {
622652
// Check if options are given and add query parameters
623653
this.#addUrlQueryParams<GetIncomingTransactionsOptions>(url, options)
624654

625-
return sendRequest({
655+
return this.#api({
626656
url: url.toString(),
627657
method: HttpMethod.Get
628658
})
@@ -651,7 +681,7 @@ class SafeApiKit {
651681
// Check if options are given and add query parameters
652682
this.#addUrlQueryParams<GetModuleTransactionsOptions>(url, options)
653683

654-
return sendRequest({
684+
return this.#api({
655685
url: url.toString(),
656686
method: HttpMethod.Get
657687
})
@@ -680,7 +710,7 @@ class SafeApiKit {
680710
// Check if options are given and add query parameters
681711
this.#addUrlQueryParams<GetMultisigTransactionsOptions>(url, options)
682712

683-
return sendRequest({
713+
return this.#api({
684714
url: url.toString(),
685715
method: HttpMethod.Get
686716
})
@@ -728,7 +758,7 @@ class SafeApiKit {
728758
url.searchParams.set('offset', offset.toString())
729759
}
730760

731-
return sendRequest({
761+
return this.#api({
732762
url: url.toString(),
733763
method: HttpMethod.Get
734764
})
@@ -757,7 +787,7 @@ class SafeApiKit {
757787
// Check if options are given and add query parameters
758788
this.#addUrlQueryParams<AllTransactionsOptions>(url, options)
759789

760-
return sendRequest({
790+
return this.#api({
761791
url: url.toString(),
762792
method: HttpMethod.Get
763793
})
@@ -802,7 +832,7 @@ class SafeApiKit {
802832
// Check if options are given and add query parameters
803833
this.#addUrlQueryParams<TokenInfoListOptions>(url, options)
804834

805-
return sendRequest({
835+
return this.#api({
806836
url: url.toString(),
807837
method: HttpMethod.Get
808838
})
@@ -821,7 +851,7 @@ class SafeApiKit {
821851
throw new Error('Invalid token address')
822852
}
823853
const { address } = this.#getEip3770Address(tokenAddress)
824-
return sendRequest({
854+
return this.#api({
825855
url: `${this.#txServiceBaseUrl}/v1/tokens/${address}/`,
826856
method: HttpMethod.Get
827857
})
@@ -850,7 +880,7 @@ class SafeApiKit {
850880
// Check if options are given and add query parameters
851881
this.#addUrlQueryParams<TokenInfoListOptions>(url, options)
852882

853-
return sendRequest({
883+
return this.#api({
854884
url: url.toString(),
855885
method: HttpMethod.Get
856886
})
@@ -886,7 +916,7 @@ class SafeApiKit {
886916
throw new Error('SafeOperation hash must not be empty')
887917
}
888918

889-
return sendRequest({
919+
return this.#api({
890920
url: `${this.#txServiceBaseUrl}/v1/safe-operations/${safeOperationHash}/`,
891921
method: HttpMethod.Get
892922
})
@@ -947,7 +977,7 @@ class SafeApiKit {
947977

948978
const userOperationV06 = userOperation as UserOperationV06
949979

950-
return sendRequest({
980+
return this.#api({
951981
url: `${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/safe-operations/`,
952982
method: HttpMethod.Post,
953983
body: {
@@ -1000,7 +1030,7 @@ class SafeApiKit {
10001030
url.searchParams.set('offset', offset.toString())
10011031
}
10021032

1003-
return sendRequest({
1033+
return this.#api({
10041034
url: url.toString(),
10051035
method: HttpMethod.Get
10061036
})
@@ -1024,7 +1054,7 @@ class SafeApiKit {
10241054
if (!signature) {
10251055
throw new Error('Invalid signature')
10261056
}
1027-
return sendRequest({
1057+
return this.#api({
10281058
url: `${this.#txServiceBaseUrl}/v1/safe-operations/${safeOperationHash}/confirmations/`,
10291059
method: HttpMethod.Post,
10301060
body: { signature }

0 commit comments

Comments
 (0)