Skip to content

Commit

Permalink
Merge remote-tracking branch 'network-providers/main' into merge-netw…
Browse files Browse the repository at this point in the history
…ork-providers
  • Loading branch information
andreibancioiu committed Sep 25, 2024
2 parents dbe49e2 + 6587282 commit fd5be7c
Show file tree
Hide file tree
Showing 31 changed files with 2,454 additions and 0 deletions.
1 change: 1 addition & 0 deletions src-network-providers/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/testscommon/**
80 changes: 80 additions & 0 deletions src-network-providers/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import BigNumber from "bignumber.js";
import { IAddress } from "./interface";
import { Address } from "./primitives";

/**
* A plain view of an account, as queried from the Network.
*/
export class AccountOnNetwork {
address: IAddress = new Address("");
nonce: number = 0;
balance: BigNumber = new BigNumber(0);
code: string = "";
userName: string = "";

constructor(init?: Partial<AccountOnNetwork>) {
Object.assign(this, init);
}

static fromHttpResponse(payload: any): AccountOnNetwork {
let result = new AccountOnNetwork();

result.address = new Address(payload["address"] || "");
result.nonce = Number(payload["nonce"] || 0);
result.balance = new BigNumber(payload["balance"] || 0);
result.code = payload["code"] || "";
result.userName = payload["username"] || "";

return result;
}
}

export class GuardianData {
guarded: boolean = false;
activeGuardian?: Guardian;
pendingGuardian?: Guardian;

constructor(init?: Partial<GuardianData>) {
Object.assign(this, init);
}

static fromHttpResponse(response: any): GuardianData {
const result = new GuardianData();

result.guarded = response["guarded"] || false;

if (response["activeGuardian"]) {
result.activeGuardian = Guardian.fromHttpResponse(response["activeGuardian"]);
}

if (response["pendingGuardian"]) {
result.pendingGuardian = Guardian.fromHttpResponse(response["pendingGuardian"]);
}

return result;
}

getCurrentGuardianAddress(): IAddress | undefined {
if (!this.guarded) {
return undefined;
}

return this.activeGuardian?.address;
}
}

class Guardian {
activationEpoch: number = 0;
address: IAddress = new Address("");
serviceUID: string = "";

static fromHttpResponse(responsePart: any): Guardian {
const result = new Guardian();

result.activationEpoch = Number(responsePart["activationEpoch"] || 0);
result.address = new Address(responsePart["address"] || "");
result.serviceUID = responsePart["serviceUID"] || "";

return result;
}
}
229 changes: 229 additions & 0 deletions src-network-providers/apiNetworkProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import axios from "axios";
import { AccountOnNetwork, GuardianData } from "./accounts";
import { defaultAxiosConfig, defaultPagination } from "./config";
import { BaseUserAgent } from "./constants";
import { ContractQueryRequest } from "./contractQueryRequest";
import { ContractQueryResponse } from "./contractQueryResponse";
import { ErrContractQuery, ErrNetworkProvider } from "./errors";
import { IAddress, IContractQuery, INetworkProvider, IPagination, ITransaction, ITransactionNext } from "./interface";
import { NetworkConfig } from "./networkConfig";
import { NetworkGeneralStatistics } from "./networkGeneralStatistics";
import { NetworkProviderConfig } from "./networkProviderConfig";
import { NetworkStake } from "./networkStake";
import { NetworkStatus } from "./networkStatus";
import { PairOnNetwork } from "./pairs";
import { Nonce } from "./primitives";
import { ProxyNetworkProvider } from "./proxyNetworkProvider";
import { DefinitionOfFungibleTokenOnNetwork, DefinitionOfTokenCollectionOnNetwork } from "./tokenDefinitions";
import { FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork } from "./tokens";
import { TransactionOnNetwork, prepareTransactionForBroadcasting } from "./transactions";
import { TransactionStatus } from "./transactionStatus";
import { extendUserAgent } from "./userAgent";

// TODO: Find & remove duplicate code between "ProxyNetworkProvider" and "ApiNetworkProvider".
export class ApiNetworkProvider implements INetworkProvider {
private url: string;
private config: NetworkProviderConfig;
private backingProxyNetworkProvider;
private userAgentPrefix = `${BaseUserAgent}/api`

constructor(url: string, config?: NetworkProviderConfig) {
this.url = url;
let proxyConfig = this.getProxyConfig(config);
this.config = { ...defaultAxiosConfig, ...config };
this.backingProxyNetworkProvider = new ProxyNetworkProvider(url, proxyConfig);
extendUserAgent(this.userAgentPrefix, this.config);
}

private getProxyConfig(config: NetworkProviderConfig | undefined) {
let proxyConfig = JSON.parse(JSON.stringify(config || {}));
proxyConfig = { ...defaultAxiosConfig, ...proxyConfig };
return proxyConfig;
}

async getNetworkConfig(): Promise<NetworkConfig> {
return await this.backingProxyNetworkProvider.getNetworkConfig();
}

async getNetworkStatus(): Promise<NetworkStatus> {
return await this.backingProxyNetworkProvider.getNetworkStatus();
}

async getNetworkStakeStatistics(): Promise<NetworkStake> {
let response = await this.doGetGeneric("stake");
let networkStake = NetworkStake.fromHttpResponse(response);
return networkStake;
}

async getNetworkGeneralStatistics(): Promise<NetworkGeneralStatistics> {
let response = await this.doGetGeneric("stats");
let stats = NetworkGeneralStatistics.fromHttpResponse(response);
return stats;
}

async getAccount(address: IAddress): Promise<AccountOnNetwork> {
let response = await this.doGetGeneric(`accounts/${address.bech32()}`);
let account = AccountOnNetwork.fromHttpResponse(response);
return account;
}

async getGuardianData(address: IAddress): Promise<GuardianData> {
return await this.backingProxyNetworkProvider.getGuardianData(address);
}

async getFungibleTokensOfAccount(address: IAddress, pagination?: IPagination): Promise<FungibleTokenOfAccountOnNetwork[]> {
pagination = pagination || defaultPagination;

let url = `accounts/${address.bech32()}/tokens?${this.buildPaginationParams(pagination)}`;
let response: any[] = await this.doGetGeneric(url);
let tokens = response.map(item => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item));

// TODO: Fix sorting
tokens.sort((a, b) => a.identifier.localeCompare(b.identifier));
return tokens;
}

async getNonFungibleTokensOfAccount(address: IAddress, pagination?: IPagination): Promise<NonFungibleTokenOfAccountOnNetwork[]> {
pagination = pagination || defaultPagination;

let url = `accounts/${address.bech32()}/nfts?${this.buildPaginationParams(pagination)}`;
let response: any[] = await this.doGetGeneric(url);
let tokens = response.map(item => NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(item));

// TODO: Fix sorting
tokens.sort((a, b) => a.identifier.localeCompare(b.identifier));
return tokens;
}

async getFungibleTokenOfAccount(address: IAddress, tokenIdentifier: string): Promise<FungibleTokenOfAccountOnNetwork> {
let response = await this.doGetGeneric(`accounts/${address.bech32()}/tokens/${tokenIdentifier}`);
let tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response);
return tokenData;
}

async getNonFungibleTokenOfAccount(address: IAddress, collection: string, nonce: number): Promise<NonFungibleTokenOfAccountOnNetwork> {
let nonceAsHex = new Nonce(nonce).hex();
let response = await this.doGetGeneric(`accounts/${address.bech32()}/nfts/${collection}-${nonceAsHex}`);
let tokenData = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response);
return tokenData;
}

async getMexPairs(pagination?: IPagination): Promise<PairOnNetwork[]> {
let url = `mex/pairs`;
if (pagination) {
url = `${url}?from=${pagination.from}&size=${pagination.size}`;
}

let response: any[] = await this.doGetGeneric(url);

return response.map(item => PairOnNetwork.fromApiHttpResponse(item));
}

async getTransaction(txHash: string): Promise<TransactionOnNetwork> {
let response = await this.doGetGeneric(`transactions/${txHash}`);
let transaction = TransactionOnNetwork.fromApiHttpResponse(txHash, response);
return transaction;
}

async getTransactionStatus(txHash: string): Promise<TransactionStatus> {
let response = await this.doGetGeneric(`transactions/${txHash}?fields=status`);
let status = new TransactionStatus(response.status);
return status;
}

async sendTransaction(tx: ITransaction | ITransactionNext): Promise<string> {
const transaction = prepareTransactionForBroadcasting(tx);
const response = await this.doPostGeneric("transactions", transaction);
return response.txHash;
}

async sendTransactions(txs: (ITransaction | ITransactionNext)[]): Promise<string[]> {
return await this.backingProxyNetworkProvider.sendTransactions(txs);
}

async simulateTransaction(tx: ITransaction | ITransactionNext): Promise<any> {
return await this.backingProxyNetworkProvider.simulateTransaction(tx);
}

async queryContract(query: IContractQuery): Promise<ContractQueryResponse> {
try {
let request = new ContractQueryRequest(query).toHttpRequest();
let response = await this.doPostGeneric("query", request);
return ContractQueryResponse.fromHttpResponse(response);
} catch (error: any) {
throw new ErrContractQuery(error);
}
}

async getDefinitionOfFungibleToken(tokenIdentifier: string): Promise<DefinitionOfFungibleTokenOnNetwork> {
let response = await this.doGetGeneric(`tokens/${tokenIdentifier}`);
let definition = DefinitionOfFungibleTokenOnNetwork.fromApiHttpResponse(response);
return definition;
}

async getDefinitionOfTokenCollection(collection: string): Promise<DefinitionOfTokenCollectionOnNetwork> {
let response = await this.doGetGeneric(`collections/${collection}`);
let definition = DefinitionOfTokenCollectionOnNetwork.fromApiHttpResponse(response);
return definition;
}

async getNonFungibleToken(collection: string, nonce: number): Promise<NonFungibleTokenOfAccountOnNetwork> {
let nonceAsHex = new Nonce(nonce).hex();
let response = await this.doGetGeneric(`nfts/${collection}-${nonceAsHex}`);
let token = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response);
return token;
}

async doGetGeneric(resourceUrl: string): Promise<any> {
let response = await this.doGet(resourceUrl);
return response;
}

async doPostGeneric(resourceUrl: string, payload: any): Promise<any> {
let response = await this.doPost(resourceUrl, payload);
return response;
}

private buildPaginationParams(pagination: IPagination) {
return `from=${pagination.from}&size=${pagination.size}`;
}

private async doGet(resourceUrl: string): Promise<any> {
let url = `${this.url}/${resourceUrl}`;

try {
let response = await axios.get(url, this.config);
return response.data;
} catch (error) {
this.handleApiError(error, resourceUrl);
}
}

private async doPost(resourceUrl: string, payload: any): Promise<any> {
let url = `${this.url}/${resourceUrl}`;

try {
let response = await axios.post(url, payload, {
...this.config,
headers: {
"Content-Type": "application/json",
...this.config.headers,
},
});
let responsePayload = response.data;
return responsePayload;
} catch (error) {
this.handleApiError(error, resourceUrl);
}
}

private handleApiError(error: any, resourceUrl: string) {
if (!error.response) {
throw new ErrNetworkProvider(resourceUrl, error.toString(), error);
}

const errorData = error.response.data;
const originalErrorMessage = errorData.message || errorData.error || JSON.stringify(errorData);
throw new ErrNetworkProvider(resourceUrl, originalErrorMessage, error);
}
}
18 changes: 18 additions & 0 deletions src-network-providers/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IPagination } from "./interface";

const JSONbig = require("json-bigint")({ constructorAction: 'ignore' });

export const defaultAxiosConfig = {
timeout: 5000,
// See: https://github.com/axios/axios/issues/983 regarding transformResponse
transformResponse: [
function (data: any) {
return JSONbig.parse(data);
}
]
};

export const defaultPagination: IPagination = {
from: 0,
size: 100
};
7 changes: 7 additions & 0 deletions src-network-providers/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import BigNumber from "bignumber.js";
import { Address } from "./primitives";

export const MaxUint64AsBigNumber = new BigNumber("18446744073709551615");
export const EsdtContractAddress = new Address("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u");
export const BaseUserAgent = "multiversx-sdk"
export const UnknownClientName = "unknown"
21 changes: 21 additions & 0 deletions src-network-providers/contractQueryRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IContractQuery } from "./interface";

export class ContractQueryRequest {
private readonly query: IContractQuery;

constructor(query: IContractQuery) {
this.query = query;
}

toHttpRequest() {
let request: any = {};
let query = this.query;
request.scAddress = query.address.bech32();
request.caller = query.caller?.bech32() ? query.caller.bech32() : undefined;
request.funcName = query.func.toString();
request.value = query.value ? query.value.toString() : undefined;
request.args = query.getEncodedArguments();

return request;
}
}
Loading

0 comments on commit fd5be7c

Please sign in to comment.