Skip to content

Commit

Permalink
Merge pull request #133 from ElrondNetwork/network-provider
Browse files Browse the repository at this point in the history
Merge IProvider and IApiProvider into INetworkProvider - part 1
  • Loading branch information
andreibancioiu authored Feb 24, 2022
2 parents 117f213 + 0244886 commit 861eda2
Show file tree
Hide file tree
Showing 16 changed files with 1,273 additions and 5 deletions.
6 changes: 3 additions & 3 deletions src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ export class AccountOnNetwork {

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

return result;
}
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const ESDTNFT_TRANSFER_FUNCTION_NAME = "ESDTNFTTransfer";
export const MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME = "MultiESDTNFTTransfer";
export const ESDT_TRANSFER_VALUE = "0";

// TODO: Rename fo "AxiosDefaultConfig" (less ambiguous).
export const defaultConfig = {
timeout: 1000,
// See: https://github.com/axios/axios/issues/983 regarding transformResponse
Expand Down
10 changes: 10 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,16 @@ export class ErrInvalidFunctionName extends Err {
}
}

/**
* Signals an error that happened during a request against the Network.
*/
export class ErrNetworkProvider extends Err {
public constructor(url: string, error: string, inner?: Error) {
let message = `Request error on url [${url}]: [${error}]`;
super(message, inner);
}
}

/**
* Signals an error that happened during a HTTP GET request.
*/
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export * from "./nullSigner";

export * from "./smartcontracts";

export * from "./networkProvider";
export * from "./dapp";
192 changes: 192 additions & 0 deletions src/networkProvider/apiNetworkProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import axios, { AxiosRequestConfig } from "axios";
import { AccountOnNetwork } from "../account";
import { Address } from "../address";
import { defaultConfig } from "../constants";
import { ErrNetworkProvider } from "../errors";
import { IContractQueryResponse, IDefinitionOfFungibleTokenOnNetwork, IDefinitionOfTokenCollectionOnNetwork, IFungibleTokenOfAccountOnNetwork, INetworkProvider, INonFungibleTokenOfAccountOnNetwork, ITransactionOnNetwork, Pagination } from "./interface";
import { Logger } from "../logger";
import { NetworkConfig } from "../networkConfig";
import { NetworkStake } from "../networkStake";
import { NetworkStatus } from "../networkStatus";
import { Nonce } from "../nonce";
import { Query } from "../smartcontracts";
import { Stats } from "../stats";
import { Transaction, TransactionHash, TransactionStatus } from "../transaction";
import { ContractQueryResponse } from "./contractResults";
import { ProxyNetworkProvider } from "./proxyNetworkProvider";
import { DefinitionOfFungibleTokenOnNetwork, DefinitionOfTokenCollectionOnNetwork } from "./tokenDefinitions";
import { FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork } from "./tokens";
import { TransactionOnNetwork } from "./transactions";

// TODO: Find & remove duplicate code between "ProxyNetworkProvider" and "ApiNetworkProvider".
export class ApiNetworkProvider implements INetworkProvider {
private url: string;
private config: AxiosRequestConfig;
private backingProxyNetworkProvider;

constructor(url: string, config?: AxiosRequestConfig) {
this.url = url;
this.config = { ...defaultConfig, ...config };
this.backingProxyNetworkProvider = new ProxyNetworkProvider(url, config);
}

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<Stats> {
let response = await this.doGetGeneric("stats");
let stats = Stats.fromHttpResponse(response)
return stats;
}

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

async getFungibleTokensOfAccount(address: Address, pagination?: Pagination): Promise<IFungibleTokenOfAccountOnNetwork[]> {
pagination = pagination || Pagination.default();

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: Address, pagination?: Pagination): Promise<INonFungibleTokenOfAccountOnNetwork[]> {
pagination = pagination || Pagination.default();

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: Address, tokenIdentifier: string): Promise<IFungibleTokenOfAccountOnNetwork> {
let response = await this.doGetGeneric(`accounts/${address.bech32()}/tokens/${tokenIdentifier}`);
let tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response);
return tokenData;
}

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

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

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

async sendTransaction(tx: Transaction): Promise<TransactionHash> {
let response = await this.doPostGeneric("transactions", tx.toSendable());
let hash = new TransactionHash(response.txHash);
return hash;
}

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

async queryContract(query: Query): Promise<IContractQueryResponse> {
let data = query.toHttpRequest();
let response = await this.doPostGeneric("query", data);
let queryResponse = ContractQueryResponse.fromHttpResponse(response);
return queryResponse;
}

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

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

async getNonFungibleToken(collection: string, nonce: Nonce): Promise<INonFungibleTokenOfAccountOnNetwork> {
let response = await this.doGetGeneric(`nfts/${collection}-${nonce.hex()}`);
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: Pagination) {
return `from=${pagination.from}&size=${pagination.size}`;
}

private async doGet(resourceUrl: string): Promise<any> {
try {
let url = `${this.url}/${resourceUrl}`;
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> {
try {
let url = `${this.url}/${resourceUrl}`;
let response = await axios.post(url, payload, {
...this.config,
headers: {
"Content-Type": "application/json",
},
});
let responsePayload = response.data;
return responsePayload;
} catch (error) {
this.handleApiError(error, resourceUrl);
}
}

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

let errorData = error.response.data;
let originalErrorMessage = errorData.error || errorData.message || JSON.stringify(errorData);
throw new ErrNetworkProvider(resourceUrl, originalErrorMessage, error);
}
}
123 changes: 123 additions & 0 deletions src/networkProvider/contractResults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { BigNumber } from "bignumber.js";
import { Address } from "../address";
import { Balance } from "../balance";
import { Hash } from "../hash";
import { IContractQueryResponse, IContractResultItem, IContractResults } from "./interface";
import { GasLimit, GasPrice } from "../networkParams";
import { Nonce } from "../nonce";
import { ArgSerializer, EndpointDefinition, MaxUint64, ReturnCode, TypedValue } from "../smartcontracts";
import { TransactionHash } from "../transaction";

export class ContractResults implements IContractResults {
readonly items: IContractResultItem[];

constructor(items: IContractResultItem[]) {
this.items = items;

this.items.sort(function (a: IContractResultItem, b: IContractResultItem) {
return a.nonce.valueOf() - b.nonce.valueOf();
});
}

static empty(): ContractResults {
return new ContractResults([]);
}

static fromProxyHttpResponse(results: any[]): ContractResults {
let items = results.map(item => ContractResultItem.fromProxyHttpResponse(item));
return new ContractResults(items);
}

static fromApiHttpResponse(results: any[]): ContractResults {
let items = results.map(item => ContractResultItem.fromApiHttpResponse(item));
return new ContractResults(items);
}
}

export class ContractResultItem implements IContractResultItem {
hash: Hash = Hash.empty();
nonce: Nonce = new Nonce(0);
value: Balance = Balance.Zero();
receiver: Address = new Address();
sender: Address = new Address();
data: string = "";
previousHash: Hash = Hash.empty();
originalHash: Hash = Hash.empty();
gasLimit: GasLimit = new GasLimit(0);
gasPrice: GasPrice = new GasPrice(0);
callType: number = 0;
returnMessage: string = "";

static fromProxyHttpResponse(response: any): ContractResultItem {
let item = ContractResultItem.fromHttpResponse(response);
return item;
}

static fromApiHttpResponse(response: any): ContractResultItem {
let item = ContractResultItem.fromHttpResponse(response);

item.data = Buffer.from(item.data, "base64").toString();
item.callType = Number(item.callType);

return item;
}

private static fromHttpResponse(response: any): ContractResultItem {
let item = new ContractResultItem();

item.hash = new TransactionHash(response.hash);
item.nonce = new Nonce(response.nonce || 0);
item.value = Balance.fromString(response.value);
item.receiver = new Address(response.receiver);
item.sender = new Address(response.sender);
item.previousHash = new TransactionHash(response.prevTxHash);
item.originalHash = new TransactionHash(response.originalTxHash);
item.gasLimit = new GasLimit(response.gasLimit);
item.gasPrice = new GasPrice(response.gasPrice);
item.data = response.data || "";
item.callType = response.callType;
item.returnMessage = response.returnMessage;

return item;
}

getOutputUntyped(): Buffer[] {
// TODO: Decide how to parse "data" (immediate results vs. other results).
throw new Error("Method not implemented.");
}

getOutputTyped(_endpointDefinition: EndpointDefinition): TypedValue[] {
// TODO: Decide how to parse "data" (immediate results vs. other results).
throw new Error("Method not implemented.");
}
}

export class ContractQueryResponse implements IContractQueryResponse {
returnData: string[] = [];
returnCode: ReturnCode = ReturnCode.None;
returnMessage: string = "";
gasUsed: GasLimit = new GasLimit(0);

static fromHttpResponse(payload: any): ContractQueryResponse {
let response = new ContractQueryResponse();
let gasRemaining = new BigNumber(payload["gasRemaining"] || payload["GasRemaining"] || 0);

response.returnData = payload["returnData"] || [];
response.returnCode = payload["returnCode"] || "";
response.returnMessage = payload["returnMessage"] || "";
response.gasUsed = new GasLimit(MaxUint64.minus(gasRemaining).toNumber());

return response;
}

getOutputUntyped(): Buffer[] {
let buffers = this.returnData.map((item) => Buffer.from(item || "", "base64"));
return buffers;
}

getOutputTyped(endpointDefinition: EndpointDefinition): TypedValue[] {
let buffers = this.getOutputUntyped();
let values = new ArgSerializer().buffersToValues(buffers, endpointDefinition!.output);
return values;
}
}
5 changes: 5 additions & 0 deletions src/networkProvider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./apiNetworkProvider";
export * from "./proxyNetworkProvider";

// we do not export "./tokens"
// we do not export "./transactions"
Loading

0 comments on commit 861eda2

Please sign in to comment.