@simbachain/libsimba-js
Version:
libsimba-js is a library simplifying the use of SIMBAChain APIs. We aim to abstract away the various blockchain concepts, reducing the necessary time needed to get to working code.
624 lines (530 loc) • 25.1 kB
HTML
<html>
<head>
<meta charset="utf-8">
<base data-ice="baseUrl" href="../../../">
<title data-ice="title">src/simba/simba.js | libsimba-js</title>
<link type="text/css" rel="stylesheet" href="css/style.css">
<link type="text/css" rel="stylesheet" href="css/prettify-tomorrow.css">
<script src="script/prettify/prettify.js"></script>
<script src="script/manual.js"></script>
<meta name="description" content="Simbachain Blockchain abstraction and interaction library"><meta property="twitter:card" content="summary"><meta property="twitter:title" content="libsimba-js"><meta property="twitter:description" content="Simbachain Blockchain abstraction and interaction library"></head>
<body class="layout-container" data-ice="rootContainer">
<header>
<a href="./">Home</a>
<a href="identifiers.html">Reference</a>
<a href="source.html">Source</a>
<a href="test.html" data-ice="testLink">Test</a>
<div class="search-box">
<span>
<img src="./image/search.png">
<span class="search-input-edge"></span><input class="search-input"><span class="search-input-edge"></span>
</span>
<ul class="search-result"></ul>
</div>
<a style="position:relative; top:3px;" href="https://github.com/simbachain/libsimba-js"><img width="20px" src="./image/github.png"></a></header>
<nav class="navigation" data-ice="nav"><div>
<ul>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~BadMetadataException.html">BadMetadataException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~BaseException.html">BaseException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~GenerateTransactionException.html">GenerateTransactionException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~GetRequestException.html">GetRequestException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~GetTransactionsException.html">GetTransactionsException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~MethodCallValidationMetadataException.html">MethodCallValidationMetadataException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~MissingMetadataException.html">MissingMetadataException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~NotImplementedException.html">NotImplementedException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~NotInBrowserException.html">NotInBrowserException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~RetriesExceededException.html">RetriesExceededException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~SigningException.html">SigningException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~SubmitTransactionException.html">SubmitTransactionException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~TransactionStatusCheckException.html">TransactionStatusCheckException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~UserRejectedSigningException.html">UserRejectedSigningException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~WalletLockedException.html">WalletLockedException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/exceptions.js~WalletNotFoundException.html">WalletNotFoundException</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-function">F</span><span data-ice="name"><span><a href="function/index.html#static-function-getSimbaInstance">getSimbaInstance</a></span></span></li>
<li data-ice="doc"><a data-ice="dirPath" class="nav-dir-path" href="identifiers.html#simba">simba</a><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/simba/pagedresponse.js~PagedResponse.html">PagedResponse</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/simba/simba.js~Simbachain.html">Simbachain</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/simba/simbabase.js~SimbaBase.html">SimbaBase</a></span></span></li>
<li data-ice="doc"><a data-ice="dirPath" class="nav-dir-path" href="identifiers.html#wallet">wallet</a><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/wallet/localwallet.js~LocalWallet.html">LocalWallet</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/wallet/pkwallet.js~PKWallet.html">PKWallet</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/wallet/wallet.js~Wallet.html">Wallet</a></span></span></li>
</ul>
</div>
</nav>
<div class="content" data-ice="content"><h1 data-ice="title">src/simba/simba.js</h1>
<pre class="source-code line-number raw-source-code"><code class="prettyprint linenums" data-ice="content">import SimbaBase from './simbabase';
import {
WalletNotFoundException,
MissingMetadataException,
GenerateTransactionException,
SubmitTransactionException,
TransactionStatusCheckException,
NotImplementedException,
GetTransactionsException,
GetRequestException,
RetriesExceededException
} from '../exceptions';
import PagedResponse from "./pagedresponse";
/**
* libsimba API Interaction for Simbachain.com
*/
export default class Simbachain extends SimbaBase {
/**
* libsimba API Interaction for Simbachain.com
* @param {string} endpoint - The endpoint of the API
* @param {Wallet} [wallet] - an optional {@link Wallet} instance
*/
constructor(endpoint, wallet) {
super(endpoint, wallet);
}
/**
* @private
* Perform asynchronous actions needed to initialise this class
*/
async initialize() {
let result = await fetch(`${this.endpoint}?format=openapi`);
let swagger = await result.json();
if ('info' in swagger && 'x-simba-attrs' in swagger.info) {
this.metadata = swagger.info['x-simba-attrs'];
}
}
/**
* @override
* Call a method on the API
* @param {string} method - the method to call
* @param {Object} parameters - the parameters for the method
* @return {Promise} - a promise resolving with the transaction details
*/
async callMethod(method, parameters) {
if (!this.wallet) {
throw new WalletNotFoundException("No Wallet found");
}
this.validateCall(method, parameters);
let formData = new FormData();
let address = await this.wallet.getAddress();
formData.append('from', address);
for (let [key, value] of Object.entries(parameters)) {
formData.append(key, value);
}
return this.sendMethodRequest(method, formData);
}
/**
* @override
* Get the status of a transaction by ID
* @param {string} txnId - the transaction ID
* @return {Promise<Object>} - a promise resolving with the transaction details
*/
getTransactionStatus(txnId) {
return fetch(`${this.endpoint}transaction/${txnId}/`, {
method: 'GET',
cache: 'no-cache',
headers: this.apiAuthHeaders(),
})
.then(async (response) => {
let data = await response.json();
if (!response.ok) {
throw new TransactionStatusCheckException(JSON.stringify(data));
}
return data;
});
}
/**
* @override
* @private
* Gets the status of a transaction
* @param {Object} txn - a transaction object
* @return {Object} - an object with status details
*/
checkTransactionStatusFromObject(txn) {
let ret = {
status: '',
transaction_hash: ''
};
if (txn.transaction_hash) {
ret.transaction_hash = txn.transaction_hash;
}
if (txn.error) {
ret.status = 'error';
ret.error = txn.error;
ret.error_details = txn.error_details;
} else if (!txn.receipt) {
ret.status = 'pending';
} else {
ret.status = 'success';
}
return ret;
}
/**
* @override
* @private
* Check if the transaction is complete
* @param {Object} txn - the transaction object
* @return {boolean} - is the transaction complete
*/
checkTransactionDone(txn) {
return txn.status !== 'pending';
}
/**
* @override
* @private
* Gets the status of a transaction by ID
* @param {string} txnId - a transaction ID
* @return {Object} - an object with status details
*/
checkTransactionStatus(txnId) {
return this.getTransactionStatus(txnId)
.then(this.checkTransactionStatusFromObject);
}
/**
* @override
* Get the balance for the attached Wallet
* @return {Promise<Object>} - the balance
*/
async getBalance() {
if (!this.metadata) {
throw new MissingMetadataException("App Metadata not yet retrieved");
}
if (!this.wallet) {
throw new WalletNotFoundException("No Wallet found");
}
if (this.metadata.poa) {
return Promise.resolve({
amount: -1,
currency: "",
poa: true
});
}
let address = await this.wallet.getAddress();
let response = await fetch(
`${this.endpoint}balance/${address}/`,
{
method: 'GET',
cache: 'no-cache',
headers: Object.assign({'Content-Type':'application/json'},this.apiAuthHeaders())
}
);
let data = await response.json();
return Promise.resolve({
...data,
poa: false
});
}
/**
* @override
* Add funds to the attached Wallet.
* Please check the output of this method. It is of the form
* ```
* {
* txnId: null,
* faucet_url: null,
* poa: true
* }
* ```
*
* If successful, txnId will be populated.
* If the network is PoA, then poa will be true, and txnId will be null
* If the faucet for the network is external (e.g. Rinkeby, Ropsten, etc), then txnId will be null,
* and faucet_url will be populated with a URL. You should present this URL to your users to direct them
* to request funds there.
* @return {Promise<Object>} - details of the txn
*/
async addFunds() {
if (!this.metadata) {
throw new MissingMetadataException("App Metadata not yet retrieved");
}
if (!this.wallet) {
throw new WalletNotFoundException("No Wallet found");
}
let address = await this.wallet.getAddress();
if (this.metadata.poa) {
return Promise.resolve({
txnId: null,
poa: true,
faucet_url: null
});
}
if (!this.metadata.simba_faucet) {
return Promise.resolve({
txnId: null,
poa: false,
faucet_url: this.metadata.faucet
});
}
let requestData = {
account: address,
value: "1",
currency: "ether"
};
let response = await fetch(
`${this.endpoint}balance/${address}/`,
{
method: 'POST',
cache: 'no-cache',
headers: Object.assign({'Content-Type':'application/json'}, this.apiAuthHeaders()),
body: JSON.stringify(requestData)
}
);
let data = await response.json();
return Promise.resolve({
...data,
poa: false,
faucet_url: null
});
}
/**
* @override
* Call a method on the API with files
* @param {string} method - the method to call
* @param {Object} parameters- the parameters for the method
* @param {Array<Blob|File>} files - the files
* @return {Promise<Object>} - a promise resolving with the transaction details
*/
async callMethodWithFile(method, parameters, files) {
if (!this.wallet) {
throw new WalletNotFoundException("No Wallet found");
}
this.validateCall(method, parameters, files);
let formData = new FormData();
let address = await this.wallet.getAddress();
formData.append('from', address);
for (let [key, value] of Object.entries(parameters)) {
formData.append(key, value);
}
for(let i = 0; i < files.length; i++){
formData.append(`file[${i}]`, files[i]);
}
return this.sendMethodRequest(method, formData);
}
/**
* @private
* Internal method for submitting method calls and retrying on nonce errors
* @param {string} txnId - the txnId
* @param {string} signed - The signed txn
* @returns {Promise<Response>} - The response with transaction data
*/
async submitTxn(txnId, payload, maxTries, currentTry){
if(!maxTries) maxTries = 5;
if(!currentTry) currentTry = 0;
if(currentTry > maxTries){
throw new RetriesExceededException();
}
// tslint:disable-next-line: no-unsafe-any
const signed = await this.wallet.sign(payload);
return fetch(`${this.endpoint}transaction/${txnId}/`, {
method: 'POST',
cache: 'no-cache',
headers: Object.assign({'Content-Type':'application/json'},this.apiAuthHeaders()),
body: JSON.stringify({payload: signed}),
}).then(async (response) => {
let data = await response.json();
if (!response.ok) {
if(data.errors && data.errors.length){
let error = data.errors[0];
if(error.detail && error.detail.code){
let code = error.detail.code;
//Nonce Error
if(code === "15001" && error.detail.meta && error.detail.meta.suggested_nonce){
console.log("Nonce Too Low, trying again with suggested nonce " + error.detail.meta.suggested_nonce);
payload.nonce = error.detail.meta.suggested_nonce;
return this.submitTxn(txnId, payload, maxTries, currentTry++);
}
}
}
throw new SubmitTransactionException(JSON.stringify(data));
}
// tslint:disable-next-line: no-console
console.log('Success!', data);
return txnId;
})
}
/**
* @private
* Internal method for sending method calls
* @param {string} url - the url
* @param {FormData} formdata - Formdata for the POST
* @returns {Promise<Response>} - The response with transaction data
*/
async sendMethodRequest(method, formdata){
let txnId = null;
let payload;
return fetch(`${this.endpoint}${method}/`, {
method: 'POST',
cache: 'no-cache',
headers: this.apiAuthHeaders(),
body: formdata,
})
.then(async (response) => {
let data = await response.json();
if (!response.ok) {
throw new GenerateTransactionException(JSON.stringify(data));
}
// tslint:disable-next-line: no-unsafe-any
txnId = data.id;
// tslint:disable-next-line: no-unsafe-any
payload = data.payload.raw;
return this.submitTxn(txnId, payload);
});
}
/**
* @override
* Gets a specific transaction
* @param {string} transactionIdOrHash - Either a transaction ID or a transaction hash
* @returns {Promise<Object>} - The transaction
*/
async getTransaction(transactionIdOrHash) {
this.validateAnyGetCall();
let url = new URL(`${this.endpoint}transaction/${transactionIdOrHash}/`);
let response = await fetch(url, {
method: 'GET',
headers: this.apiAuthHeaders()
});
if (!response.ok) {
throw new GetTransactionsException(await response.text());
}
return await response.json();
}
/**
* @override
* Gets a paged list of transactions
* @param {Object} parameters - The query parameters
* @returns {Promise<PagedResponse>} - A response wrapped in a {@link PagedResponse} helper
*/
async getTransactions(parameters) {
this.validateAnyGetCall();
let url = new URL(`${this.endpoint}transaction/`);
for (let [key, value] of Object.entries(parameters)) {
url.searchParams.set(key, value);
}
return this.sendTransactionRequest(url);
}
/**
* @override
* Gets a paged list of transactions for the method
* @param {string} method - The method
* @param {Object} parameters - The query parameters
* @returns {Promise<PagedResponse>} - A response wrapped in a {@link PagedResponse} helper
*/
async getMethodTransactions(method, parameters) {
this.validateGetCall(method, parameters);
let url = new URL(`${this.endpoint}${method}/`);
for (let [key, value] of Object.entries(parameters)) {
url.searchParams.set(key, value);
}
return this.sendTransactionRequest(url);
}
/**
* @protected
* @override
* Internal function for sending transaction GET requests
* @param {URL} url - The URL
* @returns {Promise<PagedResponse>} - A response wrapped in a {@link PagedResponse} helper
*/
async sendTransactionRequest(url){
let response = await fetch(url, {
method: 'GET',
headers: this.apiAuthHeaders()
});
if (!response.ok) {
throw new GetTransactionsException(await response.text());
}
let json = await response.json();
return new PagedResponse(json, url, this);
}
/**
* @override
* Gets a the bundle metadata for a transaction
* @param {string} transactionIdOrHash - Either a transaction ID or a transaction hash
* @returns {Promise<Object>} - The bundle metadata
*/
async getBundleMetadataForTransaction(transactionIdOrHash) {
let url = new URL(`${this.endpoint}transaction/${transactionIdOrHash}/bundle/`);
url.searchParams.append('no_files', true);
let response = await fetch(url, {
method: 'GET',
headers: this.apiAuthHeaders()
});
if (!response.ok) {
throw new GetRequestException(await response.text());
}
return response.json();
}
/**
* @override
* Gets the bundle for a transaction
* @param {string} transactionIdOrHash - Either a transaction ID or a transaction hash
* @param {boolean} stream - If true, returns a {@link ReadableStream}, otherwise returns a {@link Blob}
* @returns {Promise<ReadableStream|Blob>} - The bundle
*/
async getBundleForTransaction(transactionIdOrHash, stream) {
let url = new URL(`${this.endpoint}transaction/${transactionIdOrHash}/bundle_raw/`);
let response = await fetch(url, {
method: 'GET',
headers: this.apiAuthHeaders()
});
if (!response.ok) {
throw new GetRequestException(await response.text());
}
if(!stream){
return response.blob();
}
return response.body;
}
/**
* @override
* Gets a file from the bundle for a transaction
* @param {string} transactionIdOrHash - Either a transaction ID or a transaction hash
* @param {number} fileIdx - The index of the file in the bundle metadata
* @param {boolean} stream - If true, returns a {@link ReadableStream}, otherwise returns a {@link Blob}
* @returns {Promise<ReadableStream|Blob>} - The file
*/
async getFileFromBundleForTransaction(transactionIdOrHash, fileIdx, stream) {
let url = new URL(`${this.endpoint}transaction/${transactionIdOrHash}/file/${fileIdx}/`);
let response = await fetch(url, {
method: 'GET',
headers: this.apiAuthHeaders()
});
if (!response.ok) {
throw new GetRequestException(await response.text());
}
if (!stream) {
return response.blob();
}
return response.body;
}
/**
* @override
* Gets a file from the bundle for a transaction
* @param {string} transactionIdOrHash - Either a transaction ID or a transaction hash
* @param {string} fileName - The name of the file in the bundle metadata
* @param {boolean} stream - If true, returns a {@link ReadableStream}, otherwise returns a {@link Blob}
* @returns {Promise<ReadableStream|Blob>} - The file
*/
async getFileFromBundleByNameForTransaction(transactionIdOrHash, fileName, stream) {
let url = new URL(`${this.endpoint}transaction/${transactionIdOrHash}/fileByName/${fileName}/`);
let response = await fetch(url, {
method: 'GET',
headers: this.apiAuthHeaders()
});
if (!response.ok) {
throw new GetRequestException(await response.text());
}
if (!stream) {
return response.blob();
}
return response.body;
}
}
</code></pre>
</div>
<footer class="footer">
Generated by <a href="https://esdoc.org">ESDoc<span data-ice="esdocVersion">(1.1.0)</span><img src="./image/esdoc-logo-mini-black.png"></a>
</footer>
<script src="script/search_index.js"></script>
<script src="script/search.js"></script>
<script src="script/pretty-print.js"></script>
<script src="script/inherited-summary.js"></script>
<script src="script/test-summary.js"></script>
<script src="script/inner-link.js"></script>
<script src="script/patch-for-local.js"></script>
</body>
</html>