[WIP] update a lot

@types
neeboo 6 years ago
parent e0b73bb178
commit eac87f49f3
  1. 39
      README.md
  2. 19
      examples/contracts/Calc.sol
  3. 8
      examples/contracts/MyContract.sol
  4. 23
      examples/contracts/SimpleStorage.sol
  5. 261
      examples/testContract.js
  6. 12
      examples/testGanache.js
  7. 5
      examples/testNode.js
  8. 3
      examples/testWallet.js
  9. 5
      package.json
  10. 17
      packages/harmony-account/src/account.ts
  11. 7
      packages/harmony-account/src/utils.ts
  12. 22
      packages/harmony-account/src/wallet.ts
  13. 71
      packages/harmony-contract/__test__/abiCoder.test.ts
  14. 20683
      packages/harmony-contract/__test__/fixtures/abiv2.ts
  15. 4
      packages/harmony-contract/package.json
  16. 8
      packages/harmony-contract/src/abi/abiCoder.ts
  17. 34
      packages/harmony-contract/src/abi/abiModel.ts
  18. 2
      packages/harmony-contract/src/abi/utils.ts
  19. 103
      packages/harmony-contract/src/contract.ts
  20. 14
      packages/harmony-contract/src/contractFactory.ts
  21. 90
      packages/harmony-contract/src/events/event.ts
  22. 41
      packages/harmony-contract/src/events/eventFactory.ts
  23. 3
      packages/harmony-contract/src/index.ts
  24. 222
      packages/harmony-contract/src/methods/method.ts
  25. 42
      packages/harmony-contract/src/methods/methodFactory.ts
  26. 61
      packages/harmony-contract/src/models/AbiItemModel.ts
  27. 53
      packages/harmony-contract/src/models/AbiModel.ts
  28. 43
      packages/harmony-contract/src/models/types.ts
  29. 39
      packages/harmony-contract/src/utils/decoder.ts
  30. 33
      packages/harmony-contract/src/utils/encoder.ts
  31. 142
      packages/harmony-contract/src/utils/formatter.ts
  32. 97
      packages/harmony-contract/src/utils/mapper.ts
  33. 12
      packages/harmony-contract/src/utils/options.ts
  34. 10
      packages/harmony-contract/src/utils/status.ts
  35. 1
      packages/harmony-core/package.json
  36. 28
      packages/harmony-core/src/blockchain.ts
  37. 15
      packages/harmony-core/src/harmony.ts
  38. 3
      packages/harmony-core/tsconfig.json
  39. 3
      packages/harmony-network/src/index.ts
  40. 7
      packages/harmony-network/src/messenger/messenger.ts
  41. 66
      packages/harmony-network/src/providers/emitter.ts
  42. 22
      packages/harmony-network/src/providers/ws.ts
  43. 22
      packages/harmony-network/src/rpcMethod/rpc.ts
  44. 14
      packages/harmony-transaction/src/factory.ts
  45. 1
      packages/harmony-transaction/src/index.ts
  46. 94
      packages/harmony-transaction/src/transaction.ts
  47. 21
      packages/harmony-transaction/src/utils.ts
  48. 31
      packages/harmony-utils/src/chain.ts
  49. 1
      scripts/jest/jest.src.config.js

@ -38,14 +38,39 @@ It's a mono-repo library, not yet published to npm.
3. you can see `mnemonic` and `simple password` and 10 accounts imported
# Test Harmony node
1. install harmony-node and use branch `ricl-web3`
2. build it and run
3. open `examples`
4. run `node testNode.js`
# Test with Harmony node
First you have to run harmony's test node.
1. git clone
``` bash
git clone git@github.com:harmony-one/harmony.git
```
2. follow the `Build all executables` instruction, [here](https://github.com/harmony-one/harmony/tree/master)
3. open your editor, inside `core/resharding.go` , edit `GenesisShardSize = 50` to `GenesisShardSize = 5`
4. use this script to run
```bash
./test/deploy.sh ./test/configs/ten-oneshard.txt
```
Wait for the test-node running for 30 seconds,
Then **open another console** , go back to our `Harmony-sdk-core/examples` folder,
Run:
``` bash
node testNode.js
```
# Test with `ganache-cli`
** ganache-cli runs in js file **,
In this case, we use geth to simulate the result, we don't need harmony's testnode running.
# Test With `ganache-cli`
** ganache-cli runs in js file **
1. open `examples`
2. run `node testGanache.js`

@ -0,0 +1,19 @@
pragma solidity ^0.5.1;
contract Calc {
uint count;
function getCount() public returns (uint) {
return count;
}
function add(uint a, uint b) public returns (uint) {
count++;
return a + b;
}
}

@ -0,0 +1,8 @@
pragma solidity ^0.5.1;
contract MyContract {
function myFunction() public returns(uint256 myNumber, string memory myString) {
return (23456, "Hello!%");
}
}

@ -0,0 +1,23 @@
pragma solidity ^0.5.1;
contract SimpleStorage {
event ValueChanged(address indexed author, string oldValue, string newValue);
string _value;
constructor(string memory value) public {
emit ValueChanged(msg.sender, _value, value);
_value = value;
}
function getValue() view public returns (string memory) {
return _value;
}
function setValue(string memory value) public {
emit ValueChanged(msg.sender, _value, value);
_value = value;
}
}

@ -1,57 +1,212 @@
const { AbiCoder, toUtf8Bytes } = require('@harmony/contract');
const abiDecoder = require('abi-decoder');
const {
keccak256,
hexToByteArray,
hexToIntArray,
arrayify,
hexlify,
padZeros,
bytesPadRight,
bytesPadLeft,
BN,
} = require('@harmony/crypto');
const { hexToBN } = require('@harmony/utils');
const abiCoder = new AbiCoder();
// console.log(arrayify('8bd02b7b'));
// const encoded = abiCoder.encodeParameter('bytes32', '0xdf3234');
// console.log(encoded);
const encoded = abiCoder.encodeParameter('uint256', '2345675643');
console.log(encoded);
const decoded = abiCoder.decodeLog(
[
{
type: 'string',
name: 'myString',
},
{
type: 'uint256',
name: 'myNumber',
indexed: true,
},
{
type: 'uint8',
name: 'mySmallNumber',
indexed: true,
const { Harmony } = require('@harmony/core');
const { BN } = require('@harmony/crypto');
const { isArray, ChainType, ChainID } = require('@harmony/utils');
const fs = require('fs');
const solc = require('solc');
function constructInput(file) {
const content = fs.readFileSync(`./contracts/${file}`, { encoding: 'utf8' });
const input = {
language: 'Solidity',
sources: {},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
},
],
'0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000748656c6c6f252100000000000000000000000000000000000000000000000000',
[
'0x000000000000000000000000000000000000000000000000000000000000f310',
'0x0000000000000000000000000000000000000000000000000000000000000010',
],
};
input.sources[file] = { content };
return JSON.stringify(input);
}
const fileName = 'MyContract.sol';
const output = JSON.parse(solc.compile(constructInput(fileName)));
let abi;
let bin;
// `output` here contains the JSON output as specified in the documentation
for (var contractName in output.contracts[fileName]) {
let contractAbi = output.contracts[fileName][contractName].abi;
let contractBin =
output.contracts[fileName][contractName].evm.bytecode.object;
if (contractAbi) {
abi = contractAbi;
}
if (contractBin) {
bin = contractBin;
}
}
// const harmony = new Harmony('ws://localhost:18545', 1);
const harmony = new Harmony(
// 'https://ropsten.infura.io/v3/4f3be7f5bbe644b7a8d95c151c8f52ec',
'wss://Ropsten.infura.io/ws/v3/4f3be7f5bbe644b7a8d95c151c8f52ec',
// 'https://testnet-rpc.thundercore.com:8544',
ChainType.Ethereum,
ChainID.Ropsten,
);
console.log(decoded);
// const mm = hexToBN(
// '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
// );
const mne =
'food response winner warfare indicate visual hundred toilet jealous okay relief tornado';
const acc1 = harmony.wallet.addByMnemonic(mne, 0);
const myContract = harmony.contracts.createContract(abi);
acc1.getBalance().then((res) => {
console.log(`-- hint: account balance of ${acc1.address}`);
console.log(``);
console.log({ account: res });
console.log(``);
console.log(``);
});
const deployContract = async () => {
const deployed = await myContract
.deploy({
data: `0x${bin}`,
arguments: [],
})
.send({
gasLimit: new harmony.crypto.BN('1000000'),
gasPrice: new harmony.crypto.BN('10000'),
})
.on('transactionHash', (transactionHash) => {
console.log(`-- hint: we got Transaction Hash`);
console.log(``);
console.log(`${transactionHash}`);
console.log(``);
console.log(``);
harmony.blockchain
.getTransactionByHash({
txnHash: transactionHash,
})
.then((res) => {
console.log(`-- hint: we got transaction detail`);
console.log(``);
console.log(res);
console.log(``);
console.log(``);
});
})
.on('receipt', (receipt) => {
console.log(`-- hint: we got transaction receipt`);
console.log(``);
console.log(receipt);
console.log(``);
console.log(``);
})
.on('confirmation', (confirmation) => {
console.log(`-- hint: the transaction is`);
console.log(``);
console.log(confirmation);
console.log(``);
console.log(``);
})
.on('error', (error) => {
console.log(`-- hint: someting wrong happens`);
console.log(``);
console.log(error);
console.log(``);
console.log(``);
});
return deployed;
};
deployContract().then((deployed) => {
harmony.blockchain.getCode({ address: deployed.address }).then((res) => {
if (res.result) {
console.log(`--hint: contract :${deployed.address}--`);
console.log(``);
console.log(`${res.result}`);
console.log(``);
console.log(``);
deployed.methods
.myFunction()
.call()
.then((result) => {
console.log(`--hint: we got contract called, this is result`);
console.log(``);
console.log(result);
console.log(``);
console.log(``);
});
}
});
});
// harmony.blockchain.newPendingTransactions().then((p) => {
// console.log({ txns: p });
// p.onData(async (res) => {
// const txn = await harmony.blockchain.getTransactionByHash({
// txnHash: res.params.result,
// });
// console.log(txn);
// });
// });
// const getValue = async (address) => {
// const newContract = harmony.contracts.createContract(abi, address);
// const value = await newContract.methods
// .getValue()
// .call({ from: acc1.address });
// return value;
// };
// async function setValue(address) {
// const newContract = harmony.contracts.createContract(abi, address);
// await newContract.methods.setValue('KKKK').call({
// from: acc1.address,
// });
// }
// async function myfunc(address) {
// const newContract = harmony.contracts.createContract(abi, address);
// const result = await newContract.methods
// .myFunction()
// .call({ from: acc1.address });
// return result;
// }
// const contractAddress = '0xbdce52076e5d8b95cadf5086ff429e33ce641374';
// const newContract = harmony.contracts.createContract(abi, contractAddress);
// harmony.blockchain.getCode({ address: contractAddress }).then(console.log);
// newContract.methods
// .myFunction()
// .call({ from: acc1.address, gas: new harmony.crypto.BN(30000000) }, 'latest')
// .then(console.log);
// deployContract().then((deployed) => {
// deployed.methods
// .add(1, 3)
// .call()
// .then(console.log);
// });
// myContract.methods
// .getValue()
// .call({ from: acc1.address })
// .then((res) => {
// console.log(res);
// });
// myContract.methods
// .setValue('shit!')
// .call({ from: acc1.address })
// .then((res) => {
// console.log(res);
// });
// const ttt = new BN(434);
// console.log(ttt.lt(new BN(0)));
// console.log(ttt.gt(mm.maskn(64)));
// .on('confirmation', (e) => {
// console.log(e);
// });
// .then((res) => {
// console.log(res);
// });

@ -1,4 +1,6 @@
const { Harmony } = require('@harmony/core');
const { ChainID, ChainType } = require('@harmony/utils');
const ganache = require('ganache-cli');
const port = 18545;
@ -19,9 +21,11 @@ console.log('-------------------------------------');
// if we set it to 1, we use `eth` as our settings.
// here 1 is used, which means we use ethereum-node.
const harmony = new Harmony(wsUrl, 1);
console.log(ChainType);
const harmony = new Harmony(wsUrl, ChainType.Ethereum, ChainID.Geth);
const wsHarmony = new Harmony(wsUrl, 1);
const wsHarmony = new Harmony(wsUrl, ChainType.Ethereum, ChainID.Geth);
async function createAndEncrypt(words, index, password) {
for (let i = 0; i < index; i++) {
@ -40,6 +44,8 @@ console.log('-------------------------------------');
const server = ganache.server({
accounts: [{ secretKey: acc.privateKey, balance: '0x21e19e0c9bab2400000' }],
default_balance_ether: 10000,
gasLimit: '0x3000000',
allowUnlimitedContractSize: true,
});
// now it is async time
@ -234,5 +240,5 @@ server.listen(port, function(err, blockchain) {
console.log(txn);
});
});
setTimeout(() => main(), 5000);
main();
});

@ -1,14 +1,15 @@
const { Harmony } = require('@harmony/core');
// const ganache = require('ganache-cli');
var port = 9128;
var port = 9015;
const url = `http://localhost:${port}`;
// const url = `https://testnet-rpc.thundercore.com:8544`;
// we use ChainType=0 to indicate we are using `harmony node`
// if we set it to 1, we use `eth` as our settings.
// here 0 is by default, which means we use harmony-node by default.
const harmony = new Harmony(url, 0);
const harmony = new Harmony(url);
const mne =
'food response winner warfare indicate visual hundred toilet jealous okay relief tornado';

@ -25,7 +25,8 @@ async function main() {
console.log(harmony.wallet.accounts);
console.log(harmony.wallet.signer);
console.log('---hint:now the signer has been encrypted');
console.log(harmony.wallet.signer.privateKey);
}
main();

@ -21,7 +21,8 @@
"test:build": "cross-env TEST_ENV=unit jest -c jest.build.config.js",
"test:integration": "cross-env TEST_ENV=integration jest -c jest.iconfig.js --runInBand --verbose --collectCoverage=false",
"release": "yarn bootstrap && yarn bundle && lerna publish --exact",
"format": "prettier --write '**/*.{ts,tsx,js}' --config .prettierrc"
"format": "prettier --write '**/*.{ts,tsx,js}' --config .prettierrc",
"formatSol": "prettier --list-different **/*.sol"
},
"devDependencies": {
"@babel/cli": "^7.0.0-beta.56",
@ -86,6 +87,7 @@
"mitt": "^1.1.3",
"mkdirp": "^0.5.1",
"prettier": "^1.14.3",
"prettier-plugin-solidity": "^1.0.0-alpha.22",
"pretty-quick": "^1.8.0",
"protobufjs": "^6.8.8",
"rimraf": "^2.6.2",
@ -99,6 +101,7 @@
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-typescript2": "^0.17.1",
"solc": "^0.5.8",
"ts-jest": "^23.1.3",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",

@ -14,7 +14,7 @@ import { isPrivateKey, add0xToString, hexToNumber } from '@harmony/utils';
import { Transaction } from '@harmony/transaction';
import { Messenger, RPCMethod, getResultForData } from '@harmony/network';
import { Shards } from './types';
import { RLPSign } from './utils';
import { RLPSign, defaultMessenger } from './utils';
class Account {
/**
@ -41,7 +41,7 @@ class Account {
balance?: string = '0';
nonce?: number = 0;
shards: Shards = new Map().set('default', '');
messenger?: Messenger;
messenger: Messenger;
encrypted: boolean = false;
/**
@ -60,7 +60,7 @@ class Account {
return this.shards.size;
}
constructor(key?: string, messenger?: Messenger) {
constructor(key?: string, messenger: Messenger = defaultMessenger) {
this.messenger = messenger;
if (!key) {
this._new();
@ -101,19 +101,19 @@ class Account {
* @function getBalance get Account's balance
* @return {type} {description}
*/
async getBalance(): Promise<object> {
async getBalance(blockNumber: string = 'latest'): Promise<object> {
if (this.messenger) {
const balance = getResultForData(
await this.messenger.send(RPCMethod.GetBalance, [
this.address,
'latest',
blockNumber,
]),
);
const nonce = getResultForData(
await this.messenger.send(RPCMethod.GetTransactionCount, [
this.address,
'latest',
blockNumber,
]),
);
@ -139,13 +139,14 @@ class Account {
transaction: Transaction,
updateNonce: boolean = false,
encodeMode: string = 'rlp',
blockNumber: string = 'latest',
): Promise<Transaction> {
if (!this.privateKey || !isPrivateKey(this.privateKey)) {
throw new Error(`${this.privateKey} is not found or not correct`);
}
// let signed = '';
if (updateNonce) {
const balanceObject: any = await this.getBalance();
const balanceObject: any = await this.getBalance(blockNumber);
transaction.setParams({
...transaction.txParams,
from: this.address || '0x',
@ -166,7 +167,7 @@ class Account {
return transaction;
}
}
setMessenger(messenger?: Messenger) {
setMessenger(messenger: Messenger) {
this.messenger = messenger;
}
/**

@ -1,5 +1,7 @@
import { HttpProvider, Messenger } from '@harmony/network';
import { Transaction, TxParams } from '@harmony/transaction';
import { sign, keccak256, Signature } from '@harmony/crypto';
import { ChainType } from '@harmony/utils';
export const RLPSign = (
transaction: Transaction,
@ -15,3 +17,8 @@ export const RLPSign = (
const signed = transaction.getRLPSigned(raw, signature);
return [signature, signed];
};
export const defaultMessenger = new Messenger(
new HttpProvider('http://localhost:8545'),
ChainType.Harmony,
);

@ -3,9 +3,10 @@ import { Messenger } from '@harmony/network';
import { isPrivateKey, isAddress } from '@harmony/utils';
import { Account } from './account';
import { Transaction } from '@harmony/transaction';
import { defaultMessenger } from './utils';
class Wallet {
messenger?: Messenger;
messenger: Messenger;
protected defaultSigner?: string;
/**
* @memberof Wallet
@ -31,7 +32,7 @@ class Wallet {
}
}
constructor(messenger?: Messenger) {
constructor(messenger: Messenger = defaultMessenger) {
this.messenger = messenger;
}
/**
@ -220,6 +221,9 @@ class Wallet {
transaction: Transaction,
account?: Account,
password?: string,
updateNonce: boolean = true,
encodeMode: string = 'rlp',
blockNumber: string = 'latest',
): Promise<Transaction> {
const toSignWith = account || this.signer;
if (!toSignWith) {
@ -238,7 +242,12 @@ class Wallet {
toSignWith.address,
password,
);
const signed = await decrypted.signTransaction(transaction, true);
const signed = await decrypted.signTransaction(
transaction,
updateNonce,
encodeMode,
blockNumber,
);
await this.encryptAccount(toSignWith.address, password);
return signed;
} catch (error) {
@ -250,7 +259,12 @@ class Wallet {
toSignWith.address
) {
try {
const signed = await toSignWith.signTransaction(transaction, true);
const signed = await toSignWith.signTransaction(
transaction,
updateNonce,
encodeMode,
blockNumber,
);
return signed;
} catch (error) {
throw error;

@ -0,0 +1,71 @@
import { AbiCoder } from '../src/abi/abiCoder';
import { BN } from '@harmony/crypto';
import { isArray } from '@harmony/utils';
import { abis } from './fixtures/abiv2';
function getValues(object: any, format?: any, named?: any): any {
if (isArray(object)) {
// tslint:disable-next-line: no-shadowed-variable
const result: any[] = [];
// tslint:disable-next-line: no-shadowed-variable
object.forEach((object: any) => {
result.push(getValues(object, format, named));
});
return result;
}
switch (object.type) {
case 'number':
return new BN(object.value);
case 'boolean':
case 'string':
return object.value;
case 'buffer':
return object.value;
case 'tuple':
const result = getValues(object.value, format, named);
if (named) {
const namedResult: any = {};
result.forEach((value: any, index: number) => {
namedResult['r' + String(index)] = value;
});
return namedResult;
}
return result;
default:
throw new Error('invalid type - ' + object.type);
}
}
describe('test abiv2', () => {
it('should encode abiv2', () => {
const coder = new AbiCoder();
for (const abiItem of abis) {
const test: any = abiItem;
const values = getValues(JSON.parse(test.values));
const types = JSON.parse(test.types);
const expected = test.result;
const encoded = coder.encode(types, values);
expect(JSON.stringify(encoded)).toEqual(JSON.stringify(expected));
const namedEncoded = coder.encode(types, values);
expect(JSON.stringify(namedEncoded)).toEqual(JSON.stringify(expected));
}
});
it('should decode abiv2', () => {
const coder = new AbiCoder();
for (const abiItem of abis) {
const test = abiItem;
const values = getValues(JSON.parse(test.values));
const types = JSON.parse(test.types);
const expected = test.result;
const decoded = coder.decode(types, expected);
expect(JSON.stringify(decoded)).toEqual(JSON.stringify(values));
}
});
});

File diff suppressed because one or more lines are too long

@ -17,9 +17,11 @@
},
"license": "ISC",
"dependencies": {
"@harmony/account": "^0.0.1",
"@harmony/crypto": "^0.0.1",
"@harmony/network": "^0.0.1",
"@harmony/transaction": "^0.0.1",
"@harmony/utils": "^0.0.1",
"abi-decoder": "^1.2.0"
}
}

@ -79,7 +79,8 @@ export const defaultCoerceFunc: CoerceFunc = (
): any => {
const match = type.match(paramTypeNumber);
if (match && parseInt(match[2], 10) <= 48) {
return value.toNumber();
// return value.toNumber();
return value.toString('hex');
}
return value;
};
@ -1092,6 +1093,7 @@ class CoderTuple extends Coder {
decode(data: Uint8Array, offset: number): DecodedResult {
const result = unpack(this.coders, data, offset);
result.value = this.coerceFunc(this.type, result.value);
return result;
}
}
@ -1563,11 +1565,11 @@ export class AbiCoder {
coders.push(getParamCoder(this.coerceFunc, typeObject));
}, this);
return new CoderTuple(this.coerceFunc, coders, '_').decode(
const result = new CoderTuple(this.coerceFunc, coders, '_').decode(
arrayify(data) || new Uint8Array(),
0,
).value;
return result;
}
}

@ -1,34 +0,0 @@
interface AbiModel {
getMethod(name: string): AbiItemModel | false;
getMethods(): AbiItemModel[];
hasMethod(name: string): boolean;
getEvent(name: string): AbiItemModel | false;
getEvents(): AbiItemModel[];
getEventBySignature(signature: string): AbiItemModel;
hasEvent(name: string): boolean;
}
interface AbiItemModel {
name: string;
signature: string;
payable: boolean;
anonymous: boolean;
getInputLength(): Number;
getInputs(): AbiInput[];
getIndexedInputs(): AbiInput[];
getOutputs(): AbiOutput[];
isOfType(): boolean;
}
interface AbiInput {
name: string;
type: string;
indexed?: boolean;
components?: AbiInput[];
}
interface AbiOutput {
name: string;
type: string;
components?: AbiOutput[];
}

@ -1,7 +1,7 @@
import { isObject, isArray } from '@harmony/utils';
import { BN } from '@harmony/crypto';
export const jsonInterfaceMethodToString = (json: any) => {
export const jsonInterfaceMethodToString = (json: any): string => {
if (isObject(json) && json.name && json.name.includes('(')) {
return json.name;
}

@ -0,0 +1,103 @@
import { Wallet } from '@harmony/account';
// import { Emitter } from '@harmony/network';
import { Transaction } from '@harmony/transaction';
import { AbiCoder } from './abi/index';
import { abiMapper } from './utils/mapper';
import { ContractOptions } from './utils/options';
import { AbiModel } from './models/types';
import { AbiCoderClass } from './abi/api';
import { MethodFactory } from './methods/methodFactory';
import { EventFactory } from './events/eventFactory';
import { ContractStatus } from './utils/status';
// class Contract
export class Contract {
methods: any;
events: any;
abiModel: any | AbiModel;
abiCoder: AbiCoderClass;
options: ContractOptions | any;
wallet: Wallet;
transaction?: Transaction;
status: ContractStatus;
constructor(
abi: any = [],
address: string = '0x',
options: ContractOptions = {},
wallet: Wallet,
status: ContractStatus = ContractStatus.INITIALISED,
) {
// super();
this.abiCoder = AbiCoder();
this.abiModel = abiMapper(abi, this.abiCoder);
this.options = options;
this.address = this.options.address || address;
this.wallet = wallet;
this.methods = {};
this.events = {};
this.runMethodFactory();
this.runEventFactory();
this.status = status;
// tslint:disable-next-line: no-unused-expression
}
isInitialised() {
return this.status === ContractStatus.INITIALISED;
}
isSigned() {
return this.status === ContractStatus.SIGNED;
}
isSent() {
return this.status === ContractStatus.SENT;
}
isDeployed() {
return this.status === ContractStatus.DEPLOYED;
}
isRejected() {
return this.status === ContractStatus.REJECTED;
}
isCalled() {
return this.status === ContractStatus.CALLED;
}
setStatus(status: ContractStatus) {
this.status = status;
}
get jsonInterface(): any[] {
return this.abiModel;
}
set jsonInterface(value: any[]) {
this.abiModel = abiMapper(value, this.abiCoder);
this.runMethodFactory();
this.runEventFactory();
}
get address() {
return this.options.address || this.address;
}
set address(value: string) {
this.options.address = value;
}
get data() {
return this.options.data;
}
set data(value) {
this.options.data = value;
}
// deploy
deploy(options: any) {
return this.methods.contractConstructor(options);
}
runMethodFactory(): Contract {
return new MethodFactory(this).addMethodsToContract();
}
runEventFactory(): Contract {
return new EventFactory(this).addEventsToContract();
}
}

@ -0,0 +1,14 @@
import { Wallet } from '@harmony/account';
import { Contract } from './contract';
import { ContractOptions } from './utils/options';
export class ContractFactory {
wallet: Wallet;
constructor(wallet: Wallet) {
this.wallet = wallet;
}
createContract(abi: any[], address?: string, options?: ContractOptions) {
return new Contract(abi, address, options, this.wallet);
}
}

@ -0,0 +1,90 @@
import { AbiItemModel } from '../models/types';
import { Messenger, RPCMethod, WSProvider } from '@harmony/network';
import { Contract } from '../contract';
import { decode as eventLogDecoder } from '../utils/decoder';
import { inputLogFormatter, outputLogFormatter } from '../utils/formatter';
export class EventMethod {
params: any;
methodKey: string;
contract: Contract;
messenger?: Messenger;
abiItem: AbiItemModel;
constructor(
methodKey: string,
params: any,
abiItem: AbiItemModel,
contract: Contract,
) {
this.methodKey = methodKey;
this.contract = contract;
this.messenger = contract.wallet.messenger;
this.params = params;
this.abiItem = abiItem;
}
send() {}
call() {}
estimateGas() {}
encodeABI() {}
subscribe(options: any) {
if (
options &&
typeof options.filter !== 'undefined' &&
typeof options.topics !== 'undefined'
) {
throw new Error(
'Invalid subscription options: Only filter or topics are allowed and not both',
);
}
if (this.emitter && this.messenger) {
const messenger = this.messenger;
const emitter = this.emitter;
const inputOptions = inputLogFormatter(options);
// 1. getLog
// 2. subscribe pastlog
// 3. emit data
messenger
.send(RPCMethod.GetPastLogs, [inputOptions])
.then((logs: any) => {
logs.forEach((log: any) => {
const formattedLog = this.onNewSubscriptionItem(log);
emitter.emit('data', formattedLog);
});
messenger.subscribe('logs', [inputOptions] || []);
})
.catch((error) => {
emitter.emit('error', error);
});
}
return this.contract;
// return this.eventSubscriptionFactory
// .createEventLogSubscription(
// this.eventLogDecoder,
// this.contract,
// this.eventOptionsMapper.map(abiItemModel, this.contract, options),
// abiItemModel,
// )
// .subscribe(callback);
// this.messenger.subscribe()
}
onNewSubscriptionItem(subscriptionItem: any) {
// const log = outputLogFormatter(subscriptionItem);
const log = eventLogDecoder(
this.contract.abiCoder,
this.abiItem,
outputLogFormatter(subscriptionItem),
);
if (log.removed && this.emitter) {
this.emitter.emit('changed', log);
}
return log;
}
get emitter() {
if (this.messenger && this.messenger.provider instanceof WSProvider) {
return this.messenger.provider.emitter;
} else {
return undefined;
}
}
}

@ -0,0 +1,41 @@
import { AbiCoderClass } from '../abi/api';
import { AbiModel } from '../models/types';
import { Contract } from '../contract';
import { EventMethod } from './event';
export class EventFactory {
contract: Contract;
abiModel: any | AbiModel;
abiCoder: AbiCoderClass;
private eventKeys: string[];
// constructor
constructor(contract: Contract) {
this.contract = contract;
this.abiModel = this.contract.abiModel;
this.abiCoder = this.contract.abiCoder;
this.eventKeys = this.mapEventKeys();
}
addEventsToContract() {
this.eventKeys.forEach((key: string) => {
const newObject: any = {};
newObject[key] = (params: any) =>
new EventMethod(
key,
params,
this.abiModel.getEvent(key),
this.contract,
);
Object.assign(this.contract.events, newObject);
});
return this.contract;
}
/**
* @function mapMethodKeys
* @return {string[]} {description}
*/
private mapEventKeys(): string[] {
return Object.keys(this.abiModel.abi.events);
}
}

@ -5,3 +5,6 @@ export {
formatBytes32String,
parseBytes32String,
} from './abi/abiCoder';
export { Contract } from './contract';
export { ContractFactory } from './contractFactory';

@ -0,0 +1,222 @@
import { Wallet } from '@harmony/account';
import {
TransactionFactory,
Transaction,
// TxStatus,
} from '@harmony/transaction';
import { RPCMethod, getResultForData } from '@harmony/network';
import { hexToNumber, hexToBN } from '@harmony/utils';
import { AbiItemModel } from '../models/types';
import { Contract } from '../contract';
import { methodEncoder } from '../utils/encoder';
import { ContractStatus } from '../utils/status';
// todo: have to judge if it is contractConstructor
export class ContractMethod {
contract: Contract;
params: any;
methodKey: string;
wallet: Wallet;
abiItem: AbiItemModel;
protected transaction: Transaction;
constructor(
methodKey: string,
params: any,
abiItem: AbiItemModel,
contract: Contract,
) {
this.methodKey = methodKey;
this.contract = contract;
this.wallet = contract.wallet;
this.params = params;
this.abiItem = abiItem;
this.transaction = this.createTransaction();
// this.addEventListeners();
}
send(params: any) {
try {
this.transaction = this.transaction.map((tx: any) => {
return { ...tx, ...params };
});
this.signTransaction().then((signed) => {
this.sendTransaction(signed).then((sent) => {
const [txn, id] = sent;
this.transaction = txn;
this.contract.transaction = this.transaction;
this.confirm(id).then(() => {
this.transaction.emitter.resolve(this.contract);
});
});
});
return this.transaction.emitter;
} catch (error) {
throw error;
}
}
async call(options: any, blockNumber: any = 'latest') {
try {
const nonce = getResultForData(
await this.wallet.messenger.send(RPCMethod.GetTransactionCount, [
this.wallet.signer ? this.wallet.signer.address : options.from,
blockNumber,
]),
);
let gasLimit: any;
// tslint:disable-next-line: prefer-conditional-expression
if (options) {
gasLimit = options.gas || options.gasLimit;
} else {
gasLimit = hexToBN(await this.estimateGas());
}
this.transaction = this.transaction.map((tx: any) => {
return {
...tx,
...options,
from: options
? options.from
: this.wallet.signer
? this.wallet.signer.address
: tx.from,
gasPrice: options ? options.gasPrice : tx.gasPrice,
gasLimit: gasLimit || tx.gasLimit,
nonce: Number.parseInt(hexToNumber(nonce), 10),
};
});
const result = getResultForData(
await this.wallet.messenger.send(RPCMethod.Call, [
this.transaction.txPayload,
blockNumber,
]),
);
if (result.responseType === 'result') {
return this.afterCall(result);
} else if (result.responseType === 'error') {
throw result.message;
} else {
return this.afterCall(undefined);
}
} catch (error) {
throw error;
}
}
async estimateGas() {
try {
const result = getResultForData(
await this.wallet.messenger.send(RPCMethod.EstimateGas, [
{
to: this.transaction.txParams.to,
data: this.transaction.txParams.data,
},
]),
);
if (result.responseType === 'result') {
return result;
} else if (result.responseType === 'error') {
throw result.message;
} else if (result.responseType === 'raw') {
throw new Error('Get estimateGas fail');
}
} catch (error) {
throw error;
}
}
encodeABI() {}
protected async signTransaction() {
try {
const signed = await this.wallet.signTransaction(
this.transaction,
undefined,
undefined,
true,
'rlp',
'pending',
);
this.contract.address = TransactionFactory.getContractAddress(signed);
this.contract.setStatus(ContractStatus.SIGNED);
return signed;
} catch (error) {
throw error;
}
}
protected async sendTransaction(signed: Transaction) {
try {
const result = await signed.sendTransaction();
this.contract.setStatus(ContractStatus.SENT);
return result;
} catch (error) {
throw error;
}
}
protected async confirm(id: string) {
try {
const result = await this.transaction.confirm(id);
if (result.receipt && result.receipt.status === '0x1') {
if (this.methodKey === 'contractConstructor') {
this.contract.setStatus(ContractStatus.DEPLOYED);
} else {
this.contract.setStatus(ContractStatus.CALLED);
}
} else {
this.contract.setStatus(ContractStatus.REJECTED);
}
} catch (error) {
throw error;
}
}
protected createTransaction() {
if (this.wallet.messenger) {
if (this.methodKey === 'contractConstructor') {
// tslint:disable-next-line: no-string-literal
this.contract.data = this.params[0]['data'] || '0x';
this.abiItem.contractMethodParameters =
// tslint:disable-next-line: no-string-literal
this.params[0]['arguments'] || [];
} else {
this.abiItem.contractMethodParameters = this.params || [];
}
const txObject = {
...this.params[0],
to: this.contract.address,
data: this.encodeData(),
};
const result = new TransactionFactory(this.wallet.messenger).newTx(
txObject,
);
return result;
} else {
throw new Error('Messenger is not found');
}
}
protected encodeData() {
return methodEncoder(
this.contract.abiCoder,
this.abiItem,
this.contract.data,
);
}
protected afterCall(response: any) {
if (!response || response === '0x') {
return null;
}
const outputs = this.abiItem.getOutputs();
if (outputs.length > 1) {
return this.contract.abiCoder.decodeParameters(outputs, response);
}
return this.contract.abiCoder.decodeParameter(outputs[0], response);
// return outputs;
}
}

@ -0,0 +1,42 @@
import { AbiCoderClass } from '../abi/api';
import { AbiModel } from '../models/types';
import { Contract } from '../contract';
import { ContractMethod } from './method';
export class MethodFactory {
contract: Contract;
abiModel: any | AbiModel;
abiCoder: AbiCoderClass;
private methodKeys: string[];
// constructor
constructor(contract: Contract) {
this.contract = contract;
this.abiModel = this.contract.abiModel;
this.abiCoder = this.contract.abiCoder;
this.methodKeys = this.mapMethodKeys();
}
addMethodsToContract() {
this.methodKeys.forEach((key: string) => {
const newObject: any = {};
newObject[key] = (...params: any[]) =>
new ContractMethod(
key,
params,
this.abiModel.getMethod(key),
this.contract,
);
Object.assign(this.contract.methods, newObject);
});
return this.contract;
}
/**
* @function mapMethodKeys
* @return {string[]} {description}
*/
private mapMethodKeys(): string[] {
return Object.keys(this.abiModel.abi.methods);
}
}

@ -0,0 +1,61 @@
import { isArray } from '@harmony/utils';
import { AbiItemModel, AbiOutput, AbiInput } from './types';
export class AbiItem {
abiItem: AbiItemModel;
signature: string;
name: string;
payable: boolean;
anonymous: boolean;
type?: string;
inputs?: AbiInput[];
outputs?: AbiOutput[];
contractMethodParameters: any[];
// constructor
constructor(abiItem: AbiItemModel | any) {
this.abiItem = abiItem;
this.signature = this.abiItem.signature;
this.name = this.abiItem.name;
this.payable = this.abiItem.payable;
this.anonymous = this.abiItem.anonymous;
this.type = this.abiItem.type;
this.inputs = this.abiItem.inputs;
this.outputs = this.abiItem.outputs;
this.contractMethodParameters = [];
}
getInputLength() {
if (isArray(this.abiItem.inputs)) {
return this.abiItem.inputs.length;
}
return 0;
}
getInputs() {
if (isArray(this.abiItem.inputs)) {
return this.abiItem.inputs;
}
return [];
}
getOutputs() {
if (isArray(this.abiItem.outputs)) {
return this.abiItem.outputs;
}
return [];
}
getIndexedInputs() {
return this.getInputs().filter((input) => {
return input.indexed === true;
});
}
isOfType(type: string) {
return this.abiItem.type === type;
}
}

@ -0,0 +1,53 @@
import { AbiItemModel } from './types';
export class AbiModel {
abi: any;
constructor(mappedAbi: any) {
this.abi = mappedAbi;
}
getMethod(name: string): AbiItemModel | false {
if (this.hasMethod(name)) {
return this.abi.methods[name];
}
return false;
}
getMethods(): AbiItemModel[] {
return this.abi.methods;
}
getEvent(name: string): AbiItemModel | false {
if (this.hasEvent(name)) {
return this.abi.events[name];
}
return false;
}
getEvents(): AbiItemModel[] {
return this.abi.events;
}
getEventBySignature(signature: string): AbiItemModel | undefined {
let event;
Object.keys(this.abi.events).forEach((key) => {
if (this.abi.events[key].signature === signature) {
event = this.abi.events[key];
}
});
return event;
}
hasMethod(name: string): boolean {
return typeof this.abi.methods[name] !== 'undefined';
}
hasEvent(name: string): boolean {
return typeof this.abi.events[name] !== 'undefined';
}
}

@ -0,0 +1,43 @@
// defined by web3.js
// fixed
export interface AbiModel {
getMethod(name: string): AbiItemModel | false;
getMethods(): AbiItemModel[];
hasMethod(name: string): boolean;
getEvent(name: string): AbiItemModel | false;
getEvents(): AbiItemModel[];
getEventBySignature(signature: string): AbiItemModel | undefined;
hasEvent(name: string): boolean;
}
export interface AbiItemModel {
name: string;
signature: string;
payable: boolean;
anonymous: boolean;
inputs: AbiInput[];
outputs: AbiOutput[];
type: string;
stateMutability?: string;
constant?: boolean;
funcName: string;
contractMethodParameters: any[];
getInputLength(): number;
getInputs(): AbiInput[];
getIndexedInputs(): AbiInput[];
getOutputs(): AbiOutput[];
isOfType(value: string): boolean;
}
export interface AbiInput {
name: string;
type: string;
indexed?: boolean;
components?: AbiInput[];
}
export interface AbiOutput {
name: string;
type: string;
components?: AbiOutput[];
}

@ -0,0 +1,39 @@
import { AbiItemModel } from '../models/types';
import { AbiCoderClass } from '../abi/api';
export const decode = (
abiCoder: AbiCoderClass,
abiItemModel: AbiItemModel,
response: any,
) => {
let argumentTopics = response.topics;
if (!abiItemModel.anonymous) {
argumentTopics = response.topics.slice(1);
}
if (response.data === '0x') {
response.data = null;
}
response.returnValues = abiCoder.decodeLog(
abiItemModel.getInputs(),
response.data,
argumentTopics,
);
response.event = abiItemModel.name;
response.signature = abiItemModel.signature;
response.raw = {
data: response.data,
topics: response.topics,
};
if (abiItemModel.anonymous || !response.topics[0]) {
response.signature = null;
}
delete response.data;
delete response.topics;
return response;
};

@ -0,0 +1,33 @@
import { AbiItemModel } from '../models/types';
import { AbiCoderClass } from '../abi/api';
export const methodEncoder = (
abiCoder: AbiCoderClass,
abiItemModel: AbiItemModel,
deployData: string,
) => {
let encodedParameters = abiCoder.encodeParameters(
abiItemModel.getInputs(),
abiItemModel.contractMethodParameters,
);
if (encodedParameters.startsWith('0x')) {
encodedParameters = encodedParameters.slice(2);
}
if (abiItemModel.isOfType('constructor')) {
if (!deployData) {
throw new Error(
'The contract has no contract data option set. This is necessary to append the constructor parameters.',
);
}
return deployData + encodedParameters;
}
if (abiItemModel.isOfType('function')) {
return abiItemModel.signature + encodedParameters;
}
return encodedParameters;
};

@ -0,0 +1,142 @@
import {
hexlify,
isHexString,
keccak256,
toChecksumAddress,
} from '@harmony/crypto';
import {
numberToHex,
isArray,
hexToNumber,
isString,
isAddress,
} from '@harmony/utils';
import { toUtf8Bytes } from '../abi/abiCoder';
export const inputLogFormatter = (options: any) => {
if (options.fromBlock) {
options.fromBlock = inputBlockNumberFormatter(options.fromBlock);
}
if (options.toBlock) {
options.toBlock = inputBlockNumberFormatter(options.toBlock);
}
// make sure topics, get converted to hex
options.topics = options.topics || [];
options.topics = options.topics.map((topic: any) => {
return isArray(topic) ? topic.map(toTopic) : toTopic(topic);
});
if (options.address) {
if (isArray(options.address)) {
options.address = options.address.map((addr: string) => {
return inputAddressFormatter(addr);
});
} else {
options.address = inputAddressFormatter(options.address);
}
}
return options;
};
/**
* Formats the output of a log
*
* @method outputLogFormatter
*
* @param {Object} log object
*
* @returns {Object} log
*/
export const outputLogFormatter = (log: any) => {
// generate a custom log id
if (
typeof log.blockHash === 'string' &&
typeof log.transactionHash === 'string' &&
typeof log.logIndex === 'string'
) {
const shaId = keccak256(
log.blockHash.replace('0x', '') +
log.transactionHash.replace('0x', '') +
log.logIndex.replace('0x', ''),
);
shaId.replace('0x', '').substr(0, 8);
log.id = `log_${shaId}`;
} else if (!log.id) {
log.id = null;
}
if (log.blockNumber !== null) {
log.blockNumber = hexToNumber(log.blockNumber);
}
if (log.transactionIndex !== null) {
log.transactionIndex = hexToNumber(log.transactionIndex);
}
if (log.logIndex !== null) {
log.logIndex = hexToNumber(log.logIndex);
}
if (log.address) {
log.address = toChecksumAddress(log.address);
}
return log;
};
export const inputBlockNumberFormatter = (blockNumber: any) => {
if (
blockNumber === undefined ||
blockNumber === null ||
isPredefinedBlockNumber(blockNumber)
) {
return blockNumber;
}
if (isHexString(blockNumber)) {
if (isString(blockNumber)) {
return blockNumber.toLowerCase();
}
return blockNumber;
}
return numberToHex(blockNumber);
};
export const isPredefinedBlockNumber = (blockNumber: string) => {
return (
blockNumber === 'latest' ||
blockNumber === 'pending' ||
blockNumber === 'earliest'
);
};
export const inputAddressFormatter = (address: string) => {
if (isAddress(address)) {
return `0x${address.toLowerCase().replace('0x', '')}`;
}
throw new Error(
`Provided address "${address}" is invalid, the capitalization checksum test failed, or its an indrect IBAN address which can't be converted.`,
);
};
export const toTopic = (value: any) => {
if (value === null || typeof value === 'undefined') {
return null;
}
value = String(value);
if (value.indexOf('0x') === 0) {
return value;
}
return hexlify(toUtf8Bytes(value));
};

@ -0,0 +1,97 @@
import { isArray } from '@harmony/utils';
import { AbiItem } from '../models/AbiItemModel';
import { AbiModel } from '../models/AbiModel';
import { AbiItemModel } from '../models/types';
import { jsonInterfaceMethodToString } from '../abi/utils';
import { AbiCoderClass } from '../abi/api';
export const abiMapper = (abi: any[], abiCoder: AbiCoderClass): AbiModel => {
const mappedAbiItems: any = {
methods: {},
events: {},
};
let hasConstructor = false;
abi.forEach((abiItem: AbiItemModel) => {
abiItem.constant = isConstant(abiItem);
abiItem.payable = isPayable(abiItem);
if (abiItem.name) {
abiItem.funcName = jsonInterfaceMethodToString(abiItem);
}
let abiItemModel;
if (abiItem.type === 'function') {
abiItem.signature = abiCoder.encodeFunctionSignature(abiItem.funcName);
abiItemModel = new AbiItem(abiItem);
// Check if an method already exists with this name and if it exists than create an array and push this abiItem
// into it. This will be used if there are methods with the same name but with different arguments.
if (!mappedAbiItems.methods[abiItem.name]) {
mappedAbiItems.methods[abiItem.name] = abiItemModel;
} else {
if (isArray(mappedAbiItems.methods[abiItem.name])) {
mappedAbiItems.methods[abiItem.name].push(abiItemModel);
} else {
mappedAbiItems.methods[abiItem.name] = [
mappedAbiItems.methods[abiItem.name],
abiItemModel,
];
}
}
mappedAbiItems.methods[abiItem.signature] = abiItemModel;
mappedAbiItems.methods[abiItem.funcName] = abiItemModel;
return;
}
if (abiItem.type === 'event') {
abiItem.signature = abiCoder.encodeEventSignature(abiItem.funcName);
abiItemModel = new AbiItem(abiItem);
if (
!mappedAbiItems.events[abiItem.name] ||
mappedAbiItems.events[abiItem.name].name === 'bound '
) {
mappedAbiItems.events[abiItem.name] = abiItemModel;
}
mappedAbiItems.events[abiItem.signature] = abiItemModel;
mappedAbiItems.events[abiItem.funcName] = abiItemModel;
}
if (abiItem.type === 'constructor') {
abiItem.signature = abiItem.type;
// tslint:disable-next-line: no-string-literal
mappedAbiItems.methods['contractConstructor'] = new AbiItem(abiItem);
hasConstructor = true;
}
});
if (!hasConstructor) {
// tslint:disable-next-line: no-string-literal
mappedAbiItems.methods['contractConstructor'] = new AbiItem({
inputs: [],
payable: false,
constant: false,
type: 'constructor',
});
}
return new AbiModel(mappedAbiItems);
};
export const isConstant = (abiItem: AbiItemModel) => {
return (
abiItem.stateMutability === 'view' ||
abiItem.stateMutability === 'pure' ||
abiItem.constant
);
};
export const isPayable = (abiItem: AbiItemModel) => {
return abiItem.stateMutability === 'payable' || abiItem.payable;
};

@ -0,0 +1,12 @@
export interface ContractOptions {
data?: string;
address?: string;
defaultAccount?: string;
defaultBlock?: string;
defaultGas?: string;
defaultGasPrice?: string;
transactionBlockTimeout?: number;
transactionConfirmationBlocks?: string;
transactionPollingTimeout?: number;
transactionSigner?: any;
}

@ -0,0 +1,10 @@
export enum ContractStatus {
INITIALISED = 'initialised',
TESTED = 'tested',
ERROR = 'error',
SIGNED = 'signed',
SENT = 'sent',
REJECTED = 'rejected',
DEPLOYED = 'deployed',
CALLED = 'called',
}

@ -18,6 +18,7 @@
"license": "ISC",
"dependencies": {
"@harmony/account": "^0.0.1",
"@harmony/contract": "^0.0.1",
"@harmony/crypto": "^0.0.1",
"@harmony/network": "^0.0.1",
"@harmony/transaction": "^0.0.1",

@ -9,7 +9,6 @@ import {
assertObject,
AssertType,
HarmonyCore,
ChainType,
DefaultBlockParams,
} from '@harmony/utils';
@ -17,12 +16,10 @@ import { Transaction } from '@harmony/transaction';
class Blockchain extends HarmonyCore {
messenger: Messenger;
chainType: ChainType;
constructor(messenger: Messenger, chainType: ChainType = ChainType.Harmony) {
super(chainType);
constructor(messenger: Messenger) {
super(messenger.chainType);
this.messenger = messenger;
this.chainType = chainType;
}
setMessenger(messenger: Messenger) {
this.messenger = messenger;
@ -299,6 +296,27 @@ class Blockchain extends HarmonyCore {
return result;
}
}
@assertObject({
to: ['isAddress', AssertType.optional],
data: ['isHex', AssertType.optional],
})
async estimateGas({ to, data }: { to: string; data: string }) {
const result = await this.messenger.send(
RPCMethod.EstimateGas,
[{ to, data }],
this.chainPrefix,
);
return this.getRpcResult(result);
}
async gasPrice() {
const result = await this.messenger.send(
RPCMethod.GasPrice,
[],
this.chainPrefix,
);
return this.getRpcResult(result);
}
newPendingTransactions() {
if (this.messenger.provider instanceof WSProvider) {

@ -3,10 +3,11 @@ import * as utils from '@harmony/utils';
import { HttpProvider, Messenger, WSProvider } from '@harmony/network';
import { TransactionFactory, Transaction } from '@harmony/transaction';
import { ContractFactory, Contract } from '@harmony/contract';
import { Wallet, Account } from '@harmony/account';
import { Blockchain } from './blockchain';
class Harmony extends utils.HarmonyCore {
export class Harmony extends utils.HarmonyCore {
Modules = {
HttpProvider,
WSProvider,
@ -16,29 +17,33 @@ class Harmony extends utils.HarmonyCore {
Wallet,
Transaction,
Account,
Contract,
};
messenger: Messenger;
transactions: TransactionFactory;
wallet: Wallet;
blockchain: Blockchain;
contracts: ContractFactory;
crypto: any;
utils: any;
private provider: HttpProvider | WSProvider;
constructor(
url: string,
chainType: utils.ChainType = utils.ChainType.Harmony,
chainId: utils.ChainID = utils.ChainID.Default,
) {
super(chainType);
super(chainType, chainId);
this.provider = utils.isHttp(url)
? new HttpProvider(url)
: utils.isWs(url)
? new WSProvider(url)
: new HttpProvider('http://localhost:9128');
this.messenger = new Messenger(this.provider, this.chainType);
this.blockchain = new Blockchain(this.messenger, this.chainType);
this.messenger = new Messenger(this.provider, this.chainType, this.chainId);
this.blockchain = new Blockchain(this.messenger);
this.transactions = new TransactionFactory(this.messenger);
this.wallet = new Wallet(this.messenger);
this.contracts = new ContractFactory(this.wallet);
this.crypto = crypto;
this.utils = utils;
}
@ -58,5 +63,3 @@ class Harmony extends utils.HarmonyCore {
this.transactions.setMessenger(this.messenger);
}
}
export { Harmony };

@ -10,6 +10,7 @@
{ "path": "../harmony-crypto" },
{ "path": "../harmony-utils" },
{ "path": "../harmony-network" },
{ "path": "../harmony-transaction" }
{ "path": "../harmony-transaction" },
{ "path": "../harmony-contract" }
]
}

@ -1,9 +1,12 @@
import mitt from 'mitt';
export { mitt };
// provider related
export * from './providers/baseProvider';
export * from './providers/baseSocket';
export * from './providers/defaultFetcher';
export * from './providers/http';
export * from './providers/ws';
export * from './providers/emitter';
// messenger and middlewares
export * from './messenger/messenger';
export * from './messenger/responseMiddleware';

@ -1,4 +1,4 @@
import { HarmonyCore, ChainType, isString } from '@harmony/utils';
import { HarmonyCore, ChainType, isString, ChainID } from '@harmony/utils';
import { JsonRpc } from '../rpcMethod/rpcbuilder';
import { ResponseMiddleware } from './responseMiddleware';
import { HttpProvider } from '../providers/http';
@ -46,9 +46,10 @@ class Messenger extends HarmonyCore {
constructor(
provider: HttpProvider | WSProvider,
chainType: ChainType = ChainType.Harmony,
chainId: ChainID = ChainID.Default,
config?: object,
) {
super(chainType);
super(chainType, chainId);
/**
* @var {Provider} provider
* @memberof Messenger.prototype
@ -181,7 +182,7 @@ class Messenger extends HarmonyCore {
subscribe = async (
method: RPCMethod | string,
params?: string | any[] | undefined,
rpcPrefix?: string,
rpcPrefix: string = this.chainPrefix,
) => {
let rpcMethod = method;
if (rpcPrefix && isString(rpcPrefix) && rpcPrefix !== this.chainPrefix) {

@ -0,0 +1,66 @@
import mitt from 'mitt';
class Emitter {
handlers?: any = {};
emitter: mitt.Emitter;
off: (type: string, handler: mitt.Handler) => void;
emit: (type: string, event?: any) => void;
promise: Promise<{}>;
resolve?: any;
reject?: any;
then?: any;
constructor() {
this.emitter = new mitt(this.handlers);
this.off = this.emitter.off.bind(this);
this.emit = this.emitter.emit.bind(this);
// tslint:disable-next-line: no-empty
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
this.then = this.promise.then.bind(this.promise);
}
resetHandlers() {
// tslint:disable-next-line: forin
for (const i in this.handlers) {
delete this.handlers[i];
}
}
on(type: string, handler: mitt.Handler) {
this.emitter.on(type, handler);
return this;
}
once(type: string, handler: mitt.Handler) {
this.emitter.on(type, (e: any) => {
handler(e);
this.removeEventListener(type);
});
}
addEventListener(type: string, handler: mitt.Handler) {
this.emitter.on(type, handler);
}
removeEventListener(type?: string, handler?: mitt.Handler) {
if (!type) {
this.handlers = {};
return;
}
if (!handler) {
delete this.handlers[type];
} else {
return this.emitter.off(type, handler);
}
}
onError(error: any) {
this.emitter.on('error', error);
this.removeEventListener('*');
}
onData(data: any) {
this.emitter.on('data', data);
this.removeEventListener('*');
}
}
export { Emitter };

@ -85,18 +85,20 @@ class WSProvider extends BaseSocket {
const [tReq, tRes] = this.getMiddleware(payload.method);
const reqMiddleware = composeMiddleware(...tReq);
const resMiddleware = composeMiddleware(...tRes);
try {
this.connection.send(reqMiddleware(JSON.stringify(payload)));
} catch (error) {
throw error;
}
return new Promise((resolve, reject) => {
this.emitter.on(`${payload.id}`, (data) => {
resolve(resMiddleware(data));
this.removeEventListener(`${payload.id}`);
});
this.emitter.on('connect', () => {
if (!this.isConnecting()) {
try {
this.connection.send(reqMiddleware(JSON.stringify(payload)));
} catch (error) {
throw error;
}
this.emitter.on(`${payload.id}`, (data) => {
resolve(resMiddleware(data));
this.removeEventListener(`${payload.id}`);
});
}
this.emitter.on(SocketConnection.CONNECT, () => {
this.send(payload)
.then(resolve)
.catch(reject);

@ -33,6 +33,28 @@ export const enum RPCMethod {
SendRawTransaction = 'hmy_sendRawTransaction',
// 16. hmy_subscribe
Subscribe = 'hmy_subscribe',
// 17. hmy_getlogs
GetPastLogs = 'hmy_getLogs',
// 18. hmy_getWork
GetWork = 'hmy_getWork',
// 19. hmy_submitWork
// SubmitWork = 'hmy_submitWork',
// 20. hmy_getProof
GetProof = 'hmy_getProof',
// 21, hmy_getFilterChanges
GetFilterChanges = 'hmy_getFilterChanges',
// 22. hmy_newPendingTransactionFilter
NewPendingTransactionFilter = 'hmy_newPendingTransactionFilter',
// 23. hmy_newBlockFilter
NewBlockFilter = 'hmy_newBlockFilter',
// 24. hmy_newFilter
NewFilter = 'hmy_newFilter',
// 25. hmy_call
Call = 'hmy_call',
// 26. hmy_estimateGas
EstimateGas = 'hmy_estimateGas',
// 27. hmy_gasPrice
GasPrice = 'hmy_gasPrice',
}
export const enum RPCErrorCode {

@ -2,20 +2,22 @@ import { getContractAddress } from '@harmony/crypto';
import { Messenger } from '@harmony/network';
import { Transaction } from './transaction';
import { TxParams, TxStatus } from './types';
class TransactionFactory {
messenger: Messenger;
static getContractAddress(tx: Transaction) {
const { from, nonce } = tx.txParams;
return getContractAddress(from, Number.parseInt(`${nonce}`, 10));
}
messenger: Messenger;
constructor(messenger: Messenger) {
this.messenger = messenger;
}
setMessenger(messenger: Messenger) {
this.messenger = messenger;
}
getContractAddress(tx: Transaction) {
const { from, nonce } = tx.txParams;
return getContractAddress(from, Number.parseInt(`${nonce}`, 10));
}
newTx(txParams: TxParams): Transaction {
newTx(txParams?: TxParams | any): Transaction {
return new Transaction(txParams, this.messenger, TxStatus.INTIALIZED);
}
recover(txHash: string): Transaction {

@ -1,3 +1,4 @@
export * from './factory';
export * from './transaction';
export * from './types';
export * from './utils';

@ -8,12 +8,24 @@ import {
splitSignature,
} from '@harmony/crypto';
import { add0xToString, numberToHex } from '@harmony/utils';
import { Messenger, RPCMethod, getResultForData } from '@harmony/network';
import {
Messenger,
RPCMethod,
getResultForData,
Emitter,
} from '@harmony/network';
import { TxParams, TxStatus, TransasctionReceipt } from './types';
import { recover, transactionFields, sleep } from './utils';
import {
recover,
transactionFields,
sleep,
TransactionEvents,
defaultMessenger,
} from './utils';
class Transaction {
messenger?: Messenger;
emitter: Emitter;
messenger: Messenger;
txStatus: TxStatus;
receipt?: TransasctionReceipt;
private id: string;
@ -32,9 +44,14 @@ class Transaction {
// constructor
constructor(
params?: TxParams,
messenger?: Messenger,
messenger: Messenger = defaultMessenger,
txStatus = TxStatus.INTIALIZED,
) {
this.messenger = messenger;
this.txStatus = txStatus;
this.emitter = new Emitter();
// intialize transaction
this.id = params ? params.id : '0x';
this.from = params ? params.from : '0x';
this.nonce = params ? params.nonce : 0;
@ -43,7 +60,8 @@ class Transaction {
this.to = params ? params.to : '0x';
this.value = params ? params.value : new BN(0);
this.data = params ? params.data : '0x';
this.chainId = params ? params.chainId : 0;
// chainid should change with different network settings
this.chainId = params ? params.chainId : this.messenger.chainId;
this.txnHash = params ? params.txnHash : '0x';
this.unsignedTxnHash = params ? params.unsignedTxnHash : '0x';
this.signature = params
@ -55,8 +73,6 @@ class Transaction {
v: 0,
};
this.receipt = params ? params.receipt : undefined;
this.messenger = messenger;
this.txStatus = txStatus;
}
setMessenger(messenger: Messenger) {
@ -127,13 +143,15 @@ class Transaction {
// use when using eth_sendTransaction
get txPayload() {
return {
from: this.from,
to: this.to,
gas: numberToHex(this.gasLimit),
gasPrice: numberToHex(this.gasPrice),
value: numberToHex(this.value),
data: this.data || '0x',
nonce: numberToHex(this.nonce),
from: this.txParams.from || '0x',
to: this.txParams.to || '0x',
gas: this.txParams.gasLimit ? numberToHex(this.txParams.gasLimit) : '0x',
gasPrice: this.txParams.gasPrice
? numberToHex(this.txParams.gasPrice)
: '0x',
value: this.txParams.value ? numberToHex(this.txParams.value) : '0x',
data: this.txParams.data || '0x',
nonce: this.txParams.nonce ? numberToHex(this.nonce) : '0x',
};
}
@ -223,16 +241,18 @@ class Transaction {
const result = getResultForData(
await this.messenger.send(RPCMethod.SendRawTransaction, this.txnHash),
);
// temporarilly hard coded
if (typeof result === 'string') {
if (typeof result === 'string' || result === null) {
this.id = result;
this.emitTransactionHash(this.id);
this.setTxStatus(TxStatus.PENDING);
return [this, result];
} else if (typeof result !== 'string' && result.responseType === 'error') {
this.emitConfirm(`transaction failed:${result.message}`);
this.setTxStatus(TxStatus.REJECTED);
return [this, `transaction failed:${result.message}`];
} else {
this.emitError('transaction failed');
throw new Error('transaction failed');
}
}
@ -248,14 +268,26 @@ class Transaction {
if (res.responseType === 'error') {
return false;
}
this.receipt = res;
this.id = res.transactionHash;
if (res.responseType === 'raw') {
return false;
}
if (res.responseType === 'result') {
this.receipt = res;
this.emitReceipt(this.receipt);
this.id = res.transactionHash;
this.txStatus =
this.receipt.status && this.receipt.status === '0x1'
? TxStatus.CONFIRMED
: TxStatus.REJECTED;
return true;
if (this.receipt) {
if (this.receipt.status && this.receipt.status === '0x1') {
this.txStatus = TxStatus.CONFIRMED;
} else if (this.receipt.status && this.receipt.status === '0x0') {
this.txStatus = TxStatus.REJECTED;
}
return true;
} else {
this.txStatus = TxStatus.PENDING;
return false;
}
}
}
async confirm(
@ -267,10 +299,12 @@ class Transaction {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
try {
if (await this.trackTx(txHash)) {
this.emitConfirm(this.txStatus);
return this;
}
} catch (err) {
this.txStatus = TxStatus.REJECTED;
this.emitConfirm(this.txStatus);
throw err;
}
if (attempt + 1 < maxAttempts) {
@ -278,9 +312,23 @@ class Transaction {
}
}
this.txStatus = TxStatus.REJECTED;
this.emitConfirm(this.txStatus);
throw new Error(
`The transaction is still not confirmed after ${maxAttempts} attempts.`,
);
}
emitTransactionHash(transactionHash: string) {
this.emitter.emit(TransactionEvents.transactionHash, transactionHash);
}
emitReceipt(receipt: any) {
this.emitter.emit(TransactionEvents.receipt, receipt);
}
emitError(error: any) {
this.emitter.emit(TransactionEvents.error, error);
}
emitConfirm(data: any) {
this.emitter.emit(TransactionEvents.confirmation, data);
}
}
export { Transaction };

@ -1,4 +1,10 @@
import { hexToNumber, isHex, isAddress, strip0x } from '@harmony/utils';
import {
hexToNumber,
isHex,
isAddress,
strip0x,
ChainType,
} from '@harmony/utils';
import {
decode,
encode,
@ -8,6 +14,7 @@ import {
hexZeroPad,
recoverAddress,
} from '@harmony/crypto';
import { HttpProvider, Messenger } from '@harmony/network';
import { TxParams } from './types';
export const transactionFields = [
@ -127,3 +134,15 @@ export const sleep = async (ms: number) =>
new Promise((resolve) => {
setTimeout(() => resolve(), ms);
});
export enum TransactionEvents {
transactionHash = 'transactionHash',
error = 'error',
confirmation = 'confirmation',
receipt = 'receipt',
}
export const defaultMessenger = new Messenger(
new HttpProvider('http://localhost:8545'),
ChainType.Harmony,
);

@ -1,12 +1,28 @@
const enum ChainType {
Harmony,
Ethereum,
export const enum ChainType {
Harmony = 'hmy',
Ethereum = 'eth',
}
abstract class HarmonyCore {
export const enum ChainID {
Default = 0,
EthMainnet = 1,
Morden = 2,
Ropsten = 3,
Rinkeby = 4,
RootstockMainnet = 30,
RootstockTestnet = 31,
Kovan = 42,
EtcMainnet = 61,
EtcTestnet = 62,
Geth = 1337,
}
export abstract class HarmonyCore {
chainType: ChainType;
constructor(chainType: ChainType) {
chainId: ChainID;
constructor(chainType: ChainType, chainId: ChainID = ChainID.Default) {
this.chainType = chainType;
this.chainId = chainId;
}
get chainPrefix(): string {
switch (this.chainType) {
@ -21,6 +37,7 @@ abstract class HarmonyCore {
}
}
}
get getChainId(): ChainID {
return this.chainId;
}
}
export { HarmonyCore, ChainType };

@ -16,6 +16,7 @@ const config = {
// '<rootDir>/packages/laksa-core/__test__/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa-core-contract/__test__/?(*.)+(spec|test).js'
'<rootDir>/packages/harmony-crypto/__test__/?(*.)+(spec|test).ts',
'<rootDir>/packages/harmony-contract/__test__/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-core-messenger/__test__/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa-core-provider/__test__/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-core-transaction/__test__/?(*.)+(spec|test).js',

Loading…
Cancel
Save