Skip to content

Commit de2cceb

Browse files
Merge pull request #141 from vbrltech/web-sdk
SingularityNet Web SDK: Enhancements, Bug fixes & updates
2 parents ea67c4c + f0def70 commit de2cceb

29 files changed

+15462
-13481
lines changed

.env.example

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PRIVATE_KEY=
22
SIGNER_PRIVATE_KEY=
33
NETWORK_ID=3
4-
PROVIDER_HOST=https://ropsten.infura.io
4+
PROVIDER_HOST=https://goerli.infura.io/v3/xxxxxx
55
IPFS_ENDPOINT=http://ipfs.singularitynet.io:80
66
DEFAULT_GAS_PRICE=4700000
77
DEFAULT_GAS_LIMIT=210000

design-docs/class-diagram.plantuml

+4-4
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,17 @@ PrivateKeyIdentity o-- Web3
137137

138138
Identity <|-- PrivateKeyIdentity
139139

140-
class MetaMaskIdentity {
140+
class WalletRPCIdentity {
141141
+ address(): String
142142
+ signData(sha3Message): Bytes
143143
+ sendTransaction(transactionObject): String
144144
+ setupAccount()
145145
}
146146

147-
MetaMaskIdentity o-- Configuration
148-
MetaMaskIdentity o-- Web3
147+
WalletRPCIdentity o-- Configuration
148+
WalletRPCIdentity o-- Web3
149149

150-
Identity <|-- MetaMaskIdentity
150+
Identity <|-- WalletRPCIdentity
151151

152152
interface PaymentChannelManagementStrategy {
153153
+ selectChannel(ServiceClient serviceClient): PaymentChannel

packages/core/src/BaseServiceClient.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ class BaseServiceClient {
441441
async defaultChannelExpiration() {
442442
const currentBlockNumber = await this._web3.eth.getBlockNumber();
443443
const paymentExpirationThreshold = this._getPaymentExpiryThreshold();
444-
return currentBlockNumber + paymentExpirationThreshold;
444+
return toBNString(currentBlockNumber) + paymentExpirationThreshold;
445445
}
446446

447447
_getPaymentExpiryThreshold() {
@@ -470,15 +470,15 @@ class BaseServiceClient {
470470

471471
async _channelStateRequest(channelId) {
472472
const { currentBlockNumber, signatureBytes } =
473-
await this._channelStateRequestProperties(channelId);
473+
await this._channelStateRequestProperties(toBNString(channelId));
474474
const channelIdBytes = Buffer.alloc(4);
475-
channelIdBytes.writeUInt32BE(channelId, 0);
475+
channelIdBytes.writeUInt32BE(toBNString(channelId), 0);
476476

477477
const ChannelStateRequest = this._getChannelStateRequestMethodDescriptor();
478478
const channelStateRequest = new ChannelStateRequest();
479479
channelStateRequest.setChannelId(channelIdBytes);
480480
channelStateRequest.setSignature(signatureBytes);
481-
channelStateRequest.setCurrentBlock(currentBlockNumber);
481+
channelStateRequest.setCurrentBlock(toBNString(currentBlockNumber));
482482
return channelStateRequest;
483483
}
484484

packages/core/src/IPFSMetadataProvider.js

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { find, map } from 'lodash';
22
import url from 'url';
3-
import IPFSClient from 'ipfs-http-client';
43
import RegistryNetworks from 'singularitynet-platform-contracts/networks/Registry.json';
54
import RegistryAbi from 'singularitynet-platform-contracts/abi/Registry.json';
5+
import { get } from 'axios';
66

77
import logger from './utils/logger';
88

@@ -11,7 +11,6 @@ export default class IPFSMetadataProvider {
1111
this._web3 = web3;
1212
this._networkId = networkId;
1313
this._ipfsEndpoint = ipfsEndpoint;
14-
this._ipfsClient = this._constructIpfsClient();
1514
const registryAddress = RegistryNetworks[this._networkId].address;
1615
this._registryContract = new this._web3.eth.Contract(RegistryAbi, registryAddress);
1716
}
@@ -23,8 +22,12 @@ export default class IPFSMetadataProvider {
2322
*/
2423
async metadata(orgId, serviceId) {
2524
logger.debug(`Fetching service metadata [org: ${orgId} | service: ${serviceId}]`);
26-
const orgIdBytes = this._web3.utils.fromAscii(orgId);
27-
const serviceIdBytes = this._web3.utils.fromAscii(serviceId);
25+
let orgIdBytes = this._web3.utils.fromAscii(orgId);
26+
orgIdBytes = orgIdBytes.padEnd(66, '0'); // 66 = '0x' + 64 hex characters
27+
28+
let serviceIdBytes = this._web3.utils.fromAscii(serviceId);
29+
serviceIdBytes = serviceIdBytes.padEnd(66, '0'); // 66 = '0x' + 64 hex characters
30+
2831
const orgMetadata = await this._fetchOrgMetadata(orgIdBytes);
2932
const serviceMetadata = await this._fetchServiceMetadata(orgIdBytes, serviceIdBytes);
3033
return Promise.resolve(this._enhanceServiceGroupDetails(serviceMetadata, orgMetadata));
@@ -47,10 +50,17 @@ export default class IPFSMetadataProvider {
4750
}
4851

4952
async _fetchMetadataFromIpfs(metadataURI) {
50-
const ipfsCID = `${this._web3.utils.hexToUtf8(metadataURI).substring(7)}`;
53+
let ipfsCID = `${this._web3.utils.hexToUtf8(metadataURI).substring(7)}`;
54+
ipfsCID = ipfsCID.replace(/\0/g, '');
5155
logger.debug(`Fetching metadata from IPFS[CID: ${ipfsCID}]`);
52-
const data = await this._ipfsClient.cat(ipfsCID);
53-
return JSON.parse(data.toString());
56+
try {
57+
const fetchUrl = `${this._ipfsEndpoint}/api/v0/cat?arg=${ipfsCID}`;
58+
const response = await get(fetchUrl);
59+
return response.data;
60+
} catch(error) {
61+
logger.debug(`Error fetching metadata from IPFS[CID: ${ipfsCID}]`);
62+
throw error;
63+
}
5464
}
5565

5666
_enhanceServiceGroupDetails(serviceMetadata, orgMetadata) {

packages/core/src/MPEContract.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import MPEAbi from 'singularitynet-platform-contracts/abi/MultiPartyEscrow';
22
import MPENetworks from 'singularitynet-platform-contracts/networks/MultiPartyEscrow';
33
import { BigNumber } from 'bignumber.js';
44
import { map } from 'lodash';
5-
5+
import Web3 from 'web3';
66
import PaymentChannel from './PaymentChannel';
77
import logger from './utils/logger';
88
import { toBNString } from './utils/bignumber_helper';
@@ -12,9 +12,10 @@ class MPEContract {
1212
* @param {Web3} web3
1313
* @param {number} networkId
1414
*/
15-
constructor(web3, networkId) {
15+
constructor(web3, networkId, rpcEndpoint) {
1616
this._web3 = web3;
1717
this._networkId = networkId;
18+
this.rpcEndpoint = rpcEndpoint;
1819
this._contract = new this._web3.eth.Contract(MPEAbi, MPENetworks[networkId].address);
1920
}
2021

@@ -207,19 +208,26 @@ class MPEContract {
207208
*/
208209
async getPastOpenChannels(account, service, startingBlockNumber) {
209210
const fromBlock = startingBlockNumber || await this._deploymentBlockNumber();
211+
let contract = this._contract;
212+
if(this.rpcEndpoint) {
213+
const _web3 = new Web3(this.rpcEndpoint);
214+
contract = new _web3.eth.Contract(MPEAbi, MPENetworks[this._networkId].address);
215+
}
210216
logger.debug(`Fetching all payment channel open events starting at block: ${fromBlock}`, { tags: ['MPE'] });
211217

212218
const address = await account.getAddress();
219+
const decodedData = Buffer.from(service.group.group_id, 'base64').toString('hex');
220+
const groupId = `0x${decodedData}`;
213221
const options = {
214222
filter: {
215223
sender: address,
216224
recipient: service.group.payment_address,
217-
groupId: service.group.group_id_in_bytes,
225+
groupId,
218226
},
219227
fromBlock,
220228
toBlock: 'latest',
221229
};
222-
const channelsOpened = await this.contract.getPastEvents('ChannelOpen', options);
230+
const channelsOpened = await contract.getPastEvents('ChannelOpen', options);
223231
return map(channelsOpened, (channelOpenEvent) => {
224232
const { channelId } = channelOpenEvent.returnValues;
225233
return new PaymentChannel(channelId, this._web3, account, service, this);
@@ -228,7 +236,8 @@ class MPEContract {
228236

229237
async _fundEscrowAccount(account, amountInCogs) {
230238
const address = await account.getAddress();
231-
const currentEscrowBalance = await this.balance(address);
239+
let currentEscrowBalance = await this.balance(address);
240+
currentEscrowBalance = toBNString(currentEscrowBalance);
232241
if(amountInCogs > currentEscrowBalance) {
233242
await account.depositToEscrowAccount(amountInCogs - currentEscrowBalance);
234243
}

packages/core/src/PaymentChannel.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js';
22
import isEmpty from 'lodash/isEmpty';
33

44
import logger from './utils/logger';
5+
import { toBNString } from './utils/bignumber_helper';
56

67
class PaymentChannel {
78
/**
@@ -83,7 +84,7 @@ class PaymentChannel {
8384
const currentState = await this._currentChannelState();
8485
const { currentSignedAmount, nonce: currentNonce } = currentState;
8586
const { nonce, expiration: expiry, value: amountDeposited } = latestChannelInfoOnBlockchain;
86-
const availableAmount = amountDeposited - currentSignedAmount;
87+
const availableAmount = toBNString(amountDeposited) - toBNString(currentSignedAmount);
8788
this._state = {
8889
nonce: nonce.toString(),
8990
currentNonce,

packages/core/src/identities/MetaMaskIdentity.js

-60
This file was deleted.

packages/core/src/identities/PrivateKeyIdentity.js

+10-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import Tx from 'ethereumjs-tx';
2-
import logger from '../utils/logger';
31
import blockChainEvents from '../utils/blockchainEvents';
4-
52
/**
63
* @implements Identity
74
*/
@@ -30,18 +27,18 @@ class PrivateKeyIdentity {
3027
}
3128

3229
async sendTransaction(transactionObject) {
33-
const signedTransaction = this._signTransaction(transactionObject);
30+
const signedTransaction = await this._signTransaction(transactionObject);
3431
return new Promise((resolve, reject) => {
3532
const method = this._web3.eth.sendSignedTransaction(signedTransaction);
3633
method.once(blockChainEvents.CONFIRMATION, async (_confirmationNumber, receipt) => {
3734
console.log('blockchain confirmation count', _confirmationNumber);
38-
console.log('blockchain confirmation receipt status', receipt.status);
39-
if(receipt.status) {
40-
resolve(receipt);
35+
console.log('blockchain confirmation receipt status', _confirmationNumber.receipt.status);
36+
if(_confirmationNumber.receipt.status) {
37+
resolve(_confirmationNumber.receipt);
4138
} else {
42-
reject(receipt);
39+
reject(_confirmationNumber.receipt);
4340
}
44-
await method.off();
41+
// await method.off();
4542
});
4643
method.on(blockChainEvents.ERROR, (error) => {
4744
console.log('blockchain error', error);
@@ -56,12 +53,11 @@ class PrivateKeyIdentity {
5653
});
5754
}
5855

59-
_signTransaction(txObject) {
60-
const transaction = new Tx(txObject);
56+
async _signTransaction(txObject) {
57+
delete txObject.chainId;
6158
const privateKey = Buffer.from(this._pk.slice(2), 'hex');
62-
transaction.sign(privateKey);
63-
const serializedTransaction = transaction.serialize();
64-
return `0x${serializedTransaction.toString('hex')}`;
59+
const signedTx = await this._web3.eth.accounts.signTransaction(txObject, privateKey);
60+
return signedTx.rawTransaction;
6561
}
6662

6763
_setupAccount() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Web3 from 'web3';
2+
import { ethereumMethods } from '../utils/ethereumUtils';
3+
4+
import logger from '../utils/logger';
5+
import blockChainEvents from '../utils/blockchainEvents';
6+
7+
/**
8+
* @implements Identity
9+
*/
10+
class WalletRPCIdentity {
11+
/**
12+
* @param {Config} config
13+
* @param {Web3} web3
14+
*/
15+
constructor(config, web3) {
16+
this._eth = new Web3(config.web3Provider);
17+
this._web3 = web3;
18+
this.setupAccount();
19+
}
20+
21+
async getAddress() {
22+
const accounts = await this._web3.eth.getAccounts();
23+
return accounts[0];
24+
}
25+
26+
async signData(sha3Message) {
27+
const address = await this.getAddress();
28+
return this._web3.eth.personal.sign(sha3Message, address, '');
29+
}
30+
31+
async sendTransaction(transactionObject) {
32+
return new Promise((resolve, reject) => {
33+
this._web3.eth.sendTransaction(transactionObject)
34+
.on('transactionHash', (hash) => {
35+
logger.info(`Transaction hash: ${hash}`);
36+
})
37+
.on('error', (error) => {
38+
logger.error(`Couldn't send transaction. ${error}`);
39+
reject(error);
40+
})
41+
.then((receipt) => {
42+
if (receipt.status) {
43+
resolve(receipt);
44+
} else {
45+
reject(receipt);
46+
}
47+
});
48+
});
49+
}
50+
51+
async setupAccount() {
52+
const accounts = await this._web3.eth.getAccounts();
53+
if (accounts.length > 0) {
54+
this._web3.eth.defaultAccount = accounts[0];
55+
} else {
56+
logger.error('No accounts found');
57+
}
58+
}
59+
}
60+
61+
export default WalletRPCIdentity;

packages/core/src/identities/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { default as PrivateKeyIdentity } from './PrivateKeyIdentity';
2-
export { default as MetaMaskIdentity } from './MetaMaskIdentity';
2+
export { default as WalletRPCIdentity } from './WalletRPCIdentity';

packages/core/src/sdk.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Account from './Account';
55
import MPEContract from './MPEContract';
66
import logger from './utils/logger';
77
import IPFSMetadataProvider from './IPFSMetadataProvider';
8+
import { DefaultPaymentStrategy } from '../../payment_strategies';
89

910
const DEFAULT_CONFIG = {
1011
defaultGasLimit: 210000,
@@ -29,7 +30,9 @@ class SnetSDK {
2930
this._networkId = config.networkId;
3031
this._web3 = new Web3(config.web3Provider, null, options);
3132
const identity = this._createIdentity();
32-
this._mpeContract = new MPEContract(this._web3, this._networkId);
33+
// Some RPCs have a block size limit of 5000, but for the getPastEvents/log function, we need a higher limit. So this parameter will be used in getPastOpenChannels function at packages/core/src/MPEContract.js
34+
const rpcEndpoint = config.rpcEndpoint;
35+
this._mpeContract = new MPEContract(this._web3, this._networkId, rpcEndpoint);
3336
this._account = new Account(this._web3, this._networkId, this._mpeContract, identity);
3437
this._metadataProvider = metadataProvider || new IPFSMetadataProvider(this._web3, this._networkId, this._config.ipfsEndpoint);
3538
}

packages/core/src/utils/logger.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { format, createLogger, transports } from 'winston';
1+
import createLogger from 'winston/dist/winston/create-logger';
2+
import { format } from 'logform';
23

34
const logger = createLogger({
45
format: format.simple(),
56
level: process.env.DEBUG ? 'silly' : 'info',
6-
transports: [new transports.Console({ level : 'debug'})]
77
});
88

99
export default logger;

0 commit comments

Comments
 (0)