namillum
Version:
Bubble Protocol SDK
142 lines (118 loc) • 4.61 kB
text/typescript
import * as fs from "fs";
import pify from "pify";
import pMap from "p-map";
import { flatten, range } from "lodash";
import { util } from "@zilliqa-js/account";
import { isValidChecksumAddress, sign } from "@zilliqa-js/crypto";
import { BN, Long, units, bytes } from "@zilliqa-js/util";
import { Zilliqa } from "@zilliqa-js/zilliqa";
export class ZilliqaService {
accounts: string[] = [];
zil: Zilliqa;
constructor(api: string, mnemonics: { [mnemonic: string]: number }) {
const zilliqa = new Zilliqa(api);
this.zil = zilliqa;
// you can use one or more mnemonics to manage/generate a large number of accounts
for (let m in mnemonics) {
const num = mnemonics[m];
range(num).forEach((i) => {
const address = this.zil.wallet.addByMnemonic(m, i);
this.accounts.push(address);
});
}
}
addAccount(privateKey: string) {
// this returns the address of the newly-added account.
return this.zil.wallet.addByPrivateKey(privateKey);
}
async addKeystoreFile(path: string, passphrase: string) {
const buf = await pify(fs.readFile)(path);
const json = buf.toString();
const address = await this.zil.wallet.addByKeystore(json, passphrase);
return address;
}
createAccount(mnemonic: string, index: number) {
// this returns the address of the freshly-created account.
return this.zil.wallet.addByMnemonic(mnemonic, index);
}
async getTxBlock() {
const res = await this.zil.blockchain.getLatestTxBlock();
return parseInt(res.result!.header.BlockNum, 10);
}
async getDeposits(addresses: string[], block: number) {
const res = await this.zil.blockchain.getTransactionsForTxBlock(block);
if (res.error) {
// in this case, there are no transactions in the block; we can return
// an empty array.
if (res.error.code === -1) {
return [];
}
// you may not wish to throw, depending on the response.
throw res.error;
}
// if there is no error, result will definitely exist.
const transactions = await pMap(flatten(res.result), (tx) =>
this.zil.blockchain.getTransaction(tx)
);
// filter out everything that isn't in the list of deposit addresses we
// are interested in.
return transactions.filter(
(tx) => addresses.indexOf(tx.txParams.toAddr.toLowerCase()) !== -1
);
}
async withdraw(from: string, to: string, amount: BN) {
try {
// always check whether the to minimally passes the checksum.
// this prevents users from trying to withdraw native ZILs to an
// ethereum address, and subsequently being unable to recover them!
if (!isValidChecksumAddress(to)) {
throw new Error(`${to} is not a valid Zilliqa checksum address!`);
}
if (!this.zil.wallet.accounts[from]) {
throw new Error(`We do not control the ${from} address!`);
}
const fromPubKey = this.zil.wallet.accounts[from].publicKey;
// can also be sent using this.zil.blockchain.createTransaction
// use the manual way here to demonstrate lower-level functions
const tx = await this.zil.wallet.signWith(
this.zil.transactions.new({
version: bytes.pack(2, 1),
amount,
gasLimit: Long.fromNumber(50), // normal (non-contract) transactions cost 50 gas
gasPrice: new BN(units.toQa(2000, units.Units.Li)), // the minimum gas price is 2,000 li
toAddr: to, // toAddr is self-explanatory
pubKey: fromPubKey, // this determines which account is used to send the tx
}),
from
);
const res = await this.zil.provider.send(
"CreateTransaction",
tx.txParams
);
if (res.error) {
throw res.error;
}
tx.confirm(res.result.TranID, 33, 1000).then((tx) => {
// at this point, the tx has been confirmed
// we don't wait for the confirmation before resonding to the request,
// but we can use the final result to update the database, etc.
console.log(`Transaction successfully confirmed.`);
if (tx.isRejected()) {
// you may not wish to throw. instead, you may wish to log this
// failed confirmation to some database, or retry.
throw new Error("Transaction was rejected");
}
});
return res.result;
} catch (err) {
console.log(err);
// handle the error
// can be:
// - timeout
// - insufficient balance
// - wrong nonce
// - did not pass checksum validation
// - js error
}
}
}