Skip to content

Commit c6ea29b

Browse files
committed
feat: integrate leather api types into client
1 parent 1e7a8af commit c6ea29b

File tree

9 files changed

+936
-424
lines changed

9 files changed

+936
-424
lines changed

apps/mobile/src/services/init-app-services.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { WALLET_ENVIRONMENT } from '@/shared/environment';
2+
13
import { initServicesContainer } from '@leather.io/services';
24

35
import { MobileHttpCacheService } from './mobile-http-cache.service';
46
import { MobileSettingsService } from './mobile-settings.service';
57

68
export function initAppServices() {
79
initServicesContainer({
10+
walletEnvironment: WALLET_ENVIRONMENT,
811
cacheService: MobileHttpCacheService,
912
settingsService: MobileSettingsService,
1013
});

packages/constants/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ export const LEATHER_GUIDES_CONNECT_DAPPS = `${LEATHER_GUIDES_URL}/connect-dapps
7171

7272
export const LEATHER_LEARN_URL = 'https://leather.io/learn';
7373

74-
export const LEATHER_API_URL = 'https://staging.api.leather.io';
74+
export const LEATHER_API_URL_STAGING = 'https://staging.api.leather.io';
75+
export const LEATHER_API_URL_PRODUCTION = 'https://api.leather.io';
7576

7677
export const bitcoinUnitsKeyedByName: Record<BitcoinUnit, BitcoinUnitInfo> = {
7778
bitcoin: {

packages/services/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"build:watch": "tsup --watch --onSuccess 'tsup --dts-only'",
1717
"format": "prettier . --write --ignore-path ../../.prettierignore",
1818
"format:check": "prettier . --check --ignore-path ../../.prettierignore",
19+
"gen-api-types": "dotenv -e .env -- openapi-typescript --redocly ./redocly.yaml https://staging.api.leather.io/docs/openapi.json -o ./src/infrastructure/api/leather/leather-api.types.ts && pnpm format",
1920
"test:coverage": "vitest run --coverage",
2021
"test:unit": "vitest run",
2122
"typecheck": "tsc --noEmit -p ./tsconfig.json"
@@ -36,6 +37,7 @@
3637
"axios": "1.8.2",
3738
"bignumber.js": "9.1.2",
3839
"inversify": "7.1.0",
40+
"openapi-fetch": "0.13.5",
3941
"p-queue": "8.0.1",
4042
"reflect-metadata": "0.2.2",
4143
"zod": "3.24.2"
@@ -44,7 +46,9 @@
4446
"@leather.io/prettier-config": "workspace:*",
4547
"@leather.io/rpc": "workspace:*",
4648
"@leather.io/tsconfig-config": "workspace:*",
49+
"dotenv-cli": "8.0.0",
4750
"eslint": "9.16.0",
51+
"openapi-typescript": "7.6.1",
4852
"prettier": "3.5.1",
4953
"tslib": "2.6.2",
5054
"tsup": "8.4.0",

packages/services/redocly.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
resolve:
2+
http:
3+
headers:
4+
- matches: https://staging.api.leather.io/docs/openapi.json
5+
name: Authorization
6+
envVariable: LEATHER_API_BASIC_AUTH

packages/services/src/infrastructure/api/leather/leather-api.client.ts

Lines changed: 59 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,53 @@
1-
import axios from 'axios';
21
import { inject, injectable } from 'inversify';
3-
import { z } from 'zod';
2+
import createClient from 'openapi-fetch';
43

5-
import { LEATHER_API_URL } from '@leather.io/constants';
4+
import { LEATHER_API_URL_PRODUCTION, LEATHER_API_URL_STAGING } from '@leather.io/constants';
65
import { SupportedBlockchains } from '@leather.io/models';
76

87
import { Types } from '../../../inversify.types';
98
import type { HttpCacheService } from '../../cache/http-cache.service';
109
import { HttpCacheTimeMs } from '../../cache/http-cache.utils';
1110
import { selectBitcoinNetwork } from '../../settings/settings.selectors';
1211
import type { SettingsService } from '../../settings/settings.service';
13-
import {
14-
LeatherApiPage,
15-
LeatherApiPageRequest,
16-
getPageRequestQueryParams,
17-
} from './leather-api.pagination';
18-
import {
19-
leatherApiBitcoinTransactionPageSchema,
20-
leatherApiBitcoinTransactionSchema,
21-
leatherApiCryptoPricesSchema,
22-
leatherApiFiatRatesSchema,
23-
leatherApiRegisterNotificationsResponseSchema,
24-
leatherApiSip10PricesSchema,
25-
leatherApiUtxoSchema,
26-
} from './leather-api.schemas';
12+
import { LeatherApiPageRequest, getPageRequestQueryParams } from './leather-api.pagination';
13+
import { paths } from './leather-api.types';
2714

28-
export type LeatherApiUtxo = z.infer<typeof leatherApiUtxoSchema>;
29-
export type LeatherApiBitcoinTransaction = z.infer<typeof leatherApiBitcoinTransactionSchema>;
30-
export type LeatherApiFiatRates = z.infer<typeof leatherApiFiatRatesSchema>;
31-
export type LeatherApiCryptoPrices = z.infer<typeof leatherApiCryptoPricesSchema>;
32-
export type LeatherApiSip10Prices = z.infer<typeof leatherApiSip10PricesSchema>;
33-
export type LeatherApiRegisterNotificationsResponse = z.infer<
34-
typeof leatherApiRegisterNotificationsResponseSchema
35-
>;
15+
export type LeatherApiBitcoinTransaction =
16+
paths['/v1/transactions/{descriptor}']['get']['responses'][200]['content']['application/json']['data'][number];
3617

3718
@injectable()
3819
export class LeatherApiClient {
20+
private readonly client;
21+
3922
constructor(
4023
@inject(Types.CacheService) private readonly cacheService: HttpCacheService,
41-
@inject(Types.SettingsService) private readonly settingsService: SettingsService
42-
) {}
24+
@inject(Types.SettingsService) private readonly settingsService: SettingsService,
25+
@inject(Types.WalletEnvironment) environmnet: string
26+
) {
27+
this.client = createClient<paths>({
28+
baseUrl: environmnet === 'production' ? LEATHER_API_URL_PRODUCTION : LEATHER_API_URL_STAGING,
29+
});
30+
this.client.use({
31+
onResponse({ response }) {
32+
if (!response.ok) {
33+
throw new Error(
34+
`Leather API (${response.url}): ${response.status} ${response.statusText}`
35+
);
36+
}
37+
},
38+
});
39+
}
4340

44-
async fetchUtxos(descriptor: string, signal?: AbortSignal): Promise<LeatherApiUtxo[]> {
41+
async fetchUtxos(descriptor: string, signal?: AbortSignal) {
4542
const network = this.settingsService.getSettings().network.chain.bitcoin.bitcoinNetwork;
4643
return await this.cacheService.fetchWithCache(
4744
['leather-api-utxos', network, descriptor],
4845
async () => {
49-
const res = await axios.get<LeatherApiUtxo[]>(
50-
`${LEATHER_API_URL}/v1/utxos/${descriptor}?network=${network}`,
51-
{ signal }
52-
);
53-
return z.array(leatherApiUtxoSchema).parse(res.data);
46+
const { data } = await this.client.GET(`/v1/utxos/{descriptor}`, {
47+
params: { path: { descriptor }, query: { network } },
48+
signal,
49+
});
50+
return data!;
5451
},
5552
{ ttl: HttpCacheTimeMs.fiveSeconds }
5653
);
@@ -60,65 +57,57 @@ export class LeatherApiClient {
6057
descriptor: string,
6158
pageRequest: LeatherApiPageRequest,
6259
signal?: AbortSignal
63-
): Promise<LeatherApiPage<LeatherApiBitcoinTransaction>> {
60+
) {
6461
const params = getPageRequestQueryParams(pageRequest);
65-
params.append('network', selectBitcoinNetwork(this.settingsService.getSettings()));
62+
const network = selectBitcoinNetwork(this.settingsService.getSettings());
6663
return await this.cacheService.fetchWithCache(
6764
['leather-api-transactions', descriptor, params.toString()],
6865
async () => {
69-
const res = await axios.get<LeatherApiPage<LeatherApiBitcoinTransaction>>(
70-
`${LEATHER_API_URL}/v1/transactions/${descriptor}?${params.toString()}`,
71-
{ signal }
72-
);
73-
return leatherApiBitcoinTransactionPageSchema.parse(res.data);
66+
const { data } = await this.client.GET(`/v1/transactions/{descriptor}`, {
67+
params: {
68+
path: { descriptor },
69+
query: {
70+
network,
71+
page: pageRequest.page.toString(),
72+
pageSize: pageRequest.pageSize.toString(),
73+
},
74+
},
75+
signal,
76+
});
77+
return data!;
7478
},
7579
{ ttl: HttpCacheTimeMs.fiveSeconds }
7680
);
7781
}
7882

79-
async fetchFiatExchangeRates(signal?: AbortSignal): Promise<LeatherApiFiatRates> {
83+
async fetchFiatExchangeRates(signal?: AbortSignal) {
8084
return await this.cacheService.fetchWithCache(
8185
['leather-api-fiat-rates'],
8286
async () => {
83-
const res = await axios.get<LeatherApiFiatRates>(
84-
`${LEATHER_API_URL}/v1/market/fiat-rates`,
85-
{
86-
signal,
87-
}
88-
);
89-
return leatherApiFiatRatesSchema.parse(res.data);
87+
const { data } = await this.client.GET('/v1/market/fiat-rates', { signal });
88+
return data!;
9089
},
9190
{ ttl: HttpCacheTimeMs.oneDay }
9291
);
9392
}
9493

95-
async fetchCryptoPrices(signal?: AbortSignal): Promise<LeatherApiCryptoPrices> {
94+
async fetchCryptoPrices(signal?: AbortSignal) {
9695
return await this.cacheService.fetchWithCache(
9796
['leather-api-crypto-prices'],
9897
async () => {
99-
const res = await axios.get<LeatherApiCryptoPrices>(
100-
`${LEATHER_API_URL}/v1/market/crypto-prices`,
101-
{
102-
signal,
103-
}
104-
);
105-
return leatherApiCryptoPricesSchema.parse(res.data);
98+
const { data } = await this.client.GET('/v1/market/crypto-prices', { signal });
99+
return data!;
106100
},
107101
{ ttl: HttpCacheTimeMs.tenMinutes }
108102
);
109103
}
110104

111-
async fetchSip10Prices(signal?: AbortSignal): Promise<LeatherApiSip10Prices> {
105+
async fetchSip10Prices(signal?: AbortSignal) {
112106
return await this.cacheService.fetchWithCache(
113107
['leather-api-sip10-prices'],
114108
async () => {
115-
const res = await axios.get<LeatherApiSip10Prices>(
116-
`${LEATHER_API_URL}/v1/market/sip10-prices`,
117-
{
118-
signal,
119-
}
120-
);
121-
return leatherApiSip10PricesSchema.parse(res.data);
109+
const { data } = await this.client.GET('/v1/market/sip10-prices', { signal });
110+
return data!;
122111
},
123112
{ ttl: HttpCacheTimeMs.tenMinutes }
124113
);
@@ -135,21 +124,20 @@ export class LeatherApiClient {
135124
chain: SupportedBlockchains;
136125
},
137126
signal?: AbortSignal
138-
): Promise<LeatherApiRegisterNotificationsResponse> {
127+
) {
139128
return await this.cacheService.fetchWithCache(
140129
['leather-api-register-notifications', addresses, notificationToken, chain],
141130
async () => {
142-
const res = await axios.post<LeatherApiRegisterNotificationsResponse>(
143-
`${LEATHER_API_URL}/v1/notifications/register`,
144-
{
131+
const { data } = await this.client.POST('/v1/notifications/register', {
132+
body: {
145133
addresses,
146134
notificationToken,
147135
chain,
148136
network: 'mainnet',
149137
},
150-
{ signal }
151-
);
152-
return leatherApiRegisterNotificationsResponseSchema.parse(res.data);
138+
signal,
139+
});
140+
return data!;
153141
},
154142
{ ttl: HttpCacheTimeMs.fiveSeconds }
155143
);

0 commit comments

Comments
 (0)