Skip to content

Commit

Permalink
Gateway adapter (#35)
Browse files Browse the repository at this point in the history
* Work on gateway adapter

* Logging in working

* Create map

* Edit configs

* Abstract processing logic

* Remove logs

* Remove comment

* Integrate network config

* Cancel some requests

* Minor fixes

* Adapter for transactions working

* Update CHANGELOG
  • Loading branch information
arhtudormorar authored Aug 5, 2024
1 parent 0e01b94 commit eb35bb6
Show file tree
Hide file tree
Showing 18 changed files with 356 additions and 114 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [Added gateway adapter](https://github.com/multiversx/mx-wallet-dapp/pull/35)


## [[1.0.1](https://github.com/multiversx/mx-lite-wallet-dapp/pull/34)] - 2024-07-18

- [Removed cross shard rounds from polling interval calculations](https://github.com/multiversx/mx-wallet-dapp/pull/34)
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
"@fortawesome/free-solid-svg-icons": "6.5.1",
"@fortawesome/react-fontawesome": "0.2.0",
"@multiversx/sdk-core": "13.0.1",
"@multiversx/sdk-dapp": "2.34.1",
"@multiversx/sdk-dapp": "^2.35.0-alpha.0",
"@multiversx/sdk-dapp-core": "0.0.0-alpha.6",
"@multiversx/sdk-dapp-form": "0.10.10",
"@multiversx/sdk-js-web-wallet-io": "0.1.0",
"@multiversx/sdk-network-providers": "2.2.1",
"@multiversx/sdk-wallet": "4.2.0",
"@reduxjs/toolkit": "1.9.1",
"axios": "1.6.5",
"bignumber.js": "9.0.2",
Expand Down
1 change: 1 addition & 0 deletions src/config/config.devnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { EnvironmentsEnum } from 'types';
export * from './sharedConfig';

export const API_URL = 'https://devnet-api.multiversx.com';
export const GATEWAY_URL = ''; // either GATEWAY_URL or API_URL must be set
export const sampleAuthenticatedDomains = [API_URL];
export const environment = EnvironmentsEnum.devnet;
8 changes: 8 additions & 0 deletions src/config/config.gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { EnvironmentsEnum } from 'types';

export * from './sharedConfig';

export const API_URL = ''; // either GATEWAY_URL or API_URL must be set
export const GATEWAY_URL = 'https://devnet-gateway.multiversx.com';
export const sampleAuthenticatedDomains = [API_URL];
export const environment = EnvironmentsEnum.devnet;
1 change: 1 addition & 0 deletions src/config/config.mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { EnvironmentsEnum } from 'types';
export * from './sharedConfig';

export const API_URL = 'https://api.multiversx.com';
export const GATEWAY_URL = ''; // either GATEWAY_URL or API_URL must be set
export const sampleAuthenticatedDomains = [API_URL];
export const environment = EnvironmentsEnum.mainnet;
1 change: 1 addition & 0 deletions src/config/config.sovereign.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './sharedConfig';

export const API_URL = 'https://devnet-api.multiversx.com'; // replace here with actual sovereign URL
export const GATEWAY_URL = ''; // either GATEWAY_URL or API_URL must be set
export const sampleAuthenticatedDomains = [API_URL];
export const environment = 'sovereign';
1 change: 1 addition & 0 deletions src/config/config.testnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { EnvironmentsEnum } from 'types';
export * from './sharedConfig';

export const API_URL = 'https://testnet-api.multiversx.com';
export const GATEWAY_URL = ''; // either GATEWAY_URL or API_URL must be set
export const sampleAuthenticatedDomains = [API_URL];
export const environment = EnvironmentsEnum.testnet;
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './styles/globals.css';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import 'utils/adapter/gatewayAdapter';

async function start() {
if (import.meta.env.VITE_APP_MSW === 'true') {
Expand Down
2 changes: 2 additions & 0 deletions src/localConstants/sdkDapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export {
} from '@multiversx/sdk-dapp/constants';
export {
ACCOUNTS_ENDPOINT,
ADDRESS_ENDPOINT,
TRANSACTIONS_ENDPOINT,
NETWORK_CONFIG_ENDPOINT,
TOKENS_ENDPOINT,
NFTS_ENDPOINT
} from '@multiversx/sdk-dapp/apiCalls/endpoints';
Expand Down
1 change: 1 addition & 0 deletions src/types/sdkDapp.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {
} from '@multiversx/sdk-dapp/types/enums.types';
export type { AccountType } from '@multiversx/sdk-dapp/types';
export type { RouteType } from '@multiversx/sdk-dapp/types/index';
export { matchPath } from '@multiversx/sdk-dapp/wrappers/AuthenticatedRoutesWrapper/helpers/matchPath';
export type { ServerTransactionType } from '@multiversx/sdk-dapp/types/serverTransactions.types';
export type { WithClassnameType } from '@multiversx/sdk-dapp/UI/types';
export type {
Expand Down
39 changes: 39 additions & 0 deletions src/utils/adapter/gatewayAdapter/gatewayAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import axios from 'axios';
import { API_URL, GATEWAY_URL } from 'config';
import { getGatewayConfigForCurrentRequest } from './helpers/getGatewayConfigForCurrentRequest';
import { getGatewayResponse } from './helpers/getGatewayResponse';

axios.interceptors.request.use(
function (config) {
if (!config.url || (API_URL && !GATEWAY_URL)) {
return config;
}

const newConfig = getGatewayConfigForCurrentRequest(config);

return newConfig;
},
function (error) {
return Promise.reject(error);
}
);

axios.interceptors.response.use(
async function (response) {
const { config } = response;

const isGatewayRequest = config.baseURL === GATEWAY_URL;
const url = config.url || '';

if (!isGatewayRequest || !url) {
return response;
}

const gatewayResponse = await getGatewayResponse(url, response);

return gatewayResponse;
},
function (error) {
return Promise.reject(error);
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
ACCOUNTS_ENDPOINT,
ADDRESS_ENDPOINT,
TRANSACTIONS_ENDPOINT,
NETWORK_CONFIG_ENDPOINT,
NFTS_ENDPOINT,
TOKENS_ENDPOINT
} from 'localConstants/sdkDapp';

export const gatewayEndpoints = {
[ADDRESS_ENDPOINT]: ADDRESS_ENDPOINT,
[NETWORK_CONFIG_ENDPOINT]: NETWORK_CONFIG_ENDPOINT,
[TRANSACTIONS_ENDPOINT]: TRANSACTIONS_ENDPOINT,
[TOKENS_ENDPOINT]: null,
[NFTS_ENDPOINT]: null
};

export const endpointMap = {
[ACCOUNTS_ENDPOINT]: gatewayEndpoints[ADDRESS_ENDPOINT],
[NETWORK_CONFIG_ENDPOINT]: gatewayEndpoints[NETWORK_CONFIG_ENDPOINT],
[TRANSACTIONS_ENDPOINT]: gatewayEndpoints[TRANSACTIONS_ENDPOINT],
// not configured
[TOKENS_ENDPOINT]: gatewayEndpoints[TOKENS_ENDPOINT],
[NFTS_ENDPOINT]: gatewayEndpoints[NFTS_ENDPOINT]
};

export const apiRoutes: Record<keyof typeof endpointMap, string> = {
[ACCOUNTS_ENDPOINT]: `/${ACCOUNTS_ENDPOINT}/:id`,
[NETWORK_CONFIG_ENDPOINT]: `/${gatewayEndpoints[NETWORK_CONFIG_ENDPOINT]}`,
[TRANSACTIONS_ENDPOINT]: `/${gatewayEndpoints[TRANSACTIONS_ENDPOINT]}`,
// not configured
[TOKENS_ENDPOINT]: `/${ACCOUNTS_ENDPOINT}/:id/${TOKENS_ENDPOINT}`,
[NFTS_ENDPOINT]: `/${ACCOUNTS_ENDPOINT}/:id/${NFTS_ENDPOINT}`
};
24 changes: 24 additions & 0 deletions src/utils/adapter/gatewayAdapter/helpers/arraybufferToJSON.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AxiosResponse } from 'axios';

export const arraybufferToJSON = async <T extends AxiosResponse>(
response: T
) => {
const needsParsing = response.config.responseType === 'arraybuffer';

if (!needsParsing) {
return response.data;
}

const decoder = new TextDecoder('utf-8');
const text = decoder.decode(new Uint8Array(response.data));

try {
const data = JSON.parse(text);
return data;
} catch (e) {
// Handle JSON parse error if needed
return Promise.reject(
new Error('Failed to parse JSON from arraybuffer response.')
);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import axios, { InternalAxiosRequestConfig } from 'axios';
import { API_URL, GATEWAY_URL } from 'config';
import { matchPath } from 'types/sdkDapp.types';
import { apiRoutes, endpointMap } from './apiToGatewayEndpointMap';

export const getGatewayConfigForCurrentRequest = (
config: InternalAxiosRequestConfig<any>
): InternalAxiosRequestConfig<any> => {
const newConfig = config;

const needsGateway =
config.baseURL?.startsWith(API_URL) || config.url?.startsWith(API_URL);

const isGatewayRequest =
needsGateway &&
Object.keys(endpointMap).some((key) => config.url?.includes(key));

if (!isGatewayRequest) {
return config;
}

config.baseURL = GATEWAY_URL;
const configUrl = String(config.url);

let url = configUrl.startsWith(API_URL)
? configUrl.replace(API_URL, '')
: configUrl;

if (
newConfig.method?.toLowerCase() === 'post' &&
newConfig.url?.endsWith('transactions')
) {
newConfig.url = '/transaction/send';
return newConfig;
}

Object.entries(endpointMap).forEach(([key, value]) => {
const matchesPath = matchPath(
apiRoutes[key as keyof typeof apiRoutes],
url
);

const needsReplacement = Boolean(matchesPath);

if (!needsReplacement) {
return;
}

if (key.includes('transactions') && url.includes('transactions')) {
const hash = config.params?.hashes;
url = `/transaction/${hash}`;
} else {
url = url.replace(`/${key}`, `/${value}`);
}

newConfig.url = url;

if (value === null) {
const source = axios.CancelToken.source();
newConfig.cancelToken = source.token;
// Cancel the request
source.cancel(
`Request canceled: ${key} cannot be fetched from the gateway`
);
}
});

return newConfig;
};
50 changes: 50 additions & 0 deletions src/utils/adapter/gatewayAdapter/helpers/getGatewayResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { AxiosResponse } from 'axios';
import { NETWORK_CONFIG_ENDPOINT } from 'localConstants';
import { matchPath } from 'types/sdkDapp.types';
import { gatewayEndpoints } from './apiToGatewayEndpointMap';
import { arraybufferToJSON } from './arraybufferToJSON';
import { jsonToArrayBuffer } from './jsonToArrayBuffer';

export const getGatewayResponse = async (
gatewayUrl: string,
response: AxiosResponse<any, any>
): Promise<AxiosResponse<any, any>> => {
if (gatewayUrl.includes('/transaction/send')) {
const transaction = await arraybufferToJSON(response);

return {
...response,
data: jsonToArrayBuffer(transaction.data)
};
}

if (gatewayUrl.includes(`/${gatewayEndpoints.address}`)) {
const account = await arraybufferToJSON(response);

return {
...response,
data: jsonToArrayBuffer(account.data.account)
};
}

if (gatewayUrl.includes(`/${gatewayEndpoints[NETWORK_CONFIG_ENDPOINT]}`)) {
const networkConfig = await arraybufferToJSON(response);
return {
...response,
data: jsonToArrayBuffer(networkConfig.data.config)
};
}

const isFetchTransaction = matchPath('/transaction/:hash', gatewayUrl);

if (Boolean(isFetchTransaction)) {
const data = await arraybufferToJSON(response);

return {
...response,
data: [{ ...data.data.transaction, txHash: data.data.transaction.hash }]
};
}

return response;
};
6 changes: 6 additions & 0 deletions src/utils/adapter/gatewayAdapter/helpers/jsonToArrayBuffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const jsonToArrayBuffer = (json: Record<string, unknown>) => {
const jsonString = JSON.stringify(json);
const encoder = new TextEncoder();
const arrayBuffer = encoder.encode(jsonString);
return arrayBuffer;
};
1 change: 1 addition & 0 deletions src/utils/adapter/gatewayAdapter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './gatewayAdapter';
Loading

0 comments on commit eb35bb6

Please sign in to comment.