Skip to content

Commit

Permalink
Merge pull request #141 from vbrltech/web-sdk
Browse files Browse the repository at this point in the history
SingularityNet Web SDK: Enhancements, Bug fixes & updates
  • Loading branch information
AlbinaPomogalova authored May 21, 2024
2 parents ea67c4c + f0def70 commit de2cceb
Show file tree
Hide file tree
Showing 29 changed files with 15,462 additions and 13,481 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PRIVATE_KEY=
SIGNER_PRIVATE_KEY=
NETWORK_ID=3
PROVIDER_HOST=https://ropsten.infura.io
PROVIDER_HOST=https://goerli.infura.io/v3/xxxxxx
IPFS_ENDPOINT=http://ipfs.singularitynet.io:80
DEFAULT_GAS_PRICE=4700000
DEFAULT_GAS_LIMIT=210000
8 changes: 4 additions & 4 deletions design-docs/class-diagram.plantuml
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,17 @@ PrivateKeyIdentity o-- Web3

Identity <|-- PrivateKeyIdentity

class MetaMaskIdentity {
class WalletRPCIdentity {
+ address(): String
+ signData(sha3Message): Bytes
+ sendTransaction(transactionObject): String
+ setupAccount()
}

MetaMaskIdentity o-- Configuration
MetaMaskIdentity o-- Web3
WalletRPCIdentity o-- Configuration
WalletRPCIdentity o-- Web3

Identity <|-- MetaMaskIdentity
Identity <|-- WalletRPCIdentity

interface PaymentChannelManagementStrategy {
+ selectChannel(ServiceClient serviceClient): PaymentChannel
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/BaseServiceClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ class BaseServiceClient {
async defaultChannelExpiration() {
const currentBlockNumber = await this._web3.eth.getBlockNumber();
const paymentExpirationThreshold = this._getPaymentExpiryThreshold();
return currentBlockNumber + paymentExpirationThreshold;
return toBNString(currentBlockNumber) + paymentExpirationThreshold;
}

_getPaymentExpiryThreshold() {
Expand Down Expand Up @@ -470,15 +470,15 @@ class BaseServiceClient {

async _channelStateRequest(channelId) {
const { currentBlockNumber, signatureBytes } =
await this._channelStateRequestProperties(channelId);
await this._channelStateRequestProperties(toBNString(channelId));
const channelIdBytes = Buffer.alloc(4);
channelIdBytes.writeUInt32BE(channelId, 0);
channelIdBytes.writeUInt32BE(toBNString(channelId), 0);

const ChannelStateRequest = this._getChannelStateRequestMethodDescriptor();
const channelStateRequest = new ChannelStateRequest();
channelStateRequest.setChannelId(channelIdBytes);
channelStateRequest.setSignature(signatureBytes);
channelStateRequest.setCurrentBlock(currentBlockNumber);
channelStateRequest.setCurrentBlock(toBNString(currentBlockNumber));
return channelStateRequest;
}

Expand Down
24 changes: 17 additions & 7 deletions packages/core/src/IPFSMetadataProvider.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { find, map } from 'lodash';
import url from 'url';
import IPFSClient from 'ipfs-http-client';
import RegistryNetworks from 'singularitynet-platform-contracts/networks/Registry.json';
import RegistryAbi from 'singularitynet-platform-contracts/abi/Registry.json';
import { get } from 'axios';

import logger from './utils/logger';

Expand All @@ -11,7 +11,6 @@ export default class IPFSMetadataProvider {
this._web3 = web3;
this._networkId = networkId;
this._ipfsEndpoint = ipfsEndpoint;
this._ipfsClient = this._constructIpfsClient();
const registryAddress = RegistryNetworks[this._networkId].address;
this._registryContract = new this._web3.eth.Contract(RegistryAbi, registryAddress);
}
Expand All @@ -23,8 +22,12 @@ export default class IPFSMetadataProvider {
*/
async metadata(orgId, serviceId) {
logger.debug(`Fetching service metadata [org: ${orgId} | service: ${serviceId}]`);
const orgIdBytes = this._web3.utils.fromAscii(orgId);
const serviceIdBytes = this._web3.utils.fromAscii(serviceId);
let orgIdBytes = this._web3.utils.fromAscii(orgId);
orgIdBytes = orgIdBytes.padEnd(66, '0'); // 66 = '0x' + 64 hex characters

let serviceIdBytes = this._web3.utils.fromAscii(serviceId);
serviceIdBytes = serviceIdBytes.padEnd(66, '0'); // 66 = '0x' + 64 hex characters

const orgMetadata = await this._fetchOrgMetadata(orgIdBytes);
const serviceMetadata = await this._fetchServiceMetadata(orgIdBytes, serviceIdBytes);
return Promise.resolve(this._enhanceServiceGroupDetails(serviceMetadata, orgMetadata));
Expand All @@ -47,10 +50,17 @@ export default class IPFSMetadataProvider {
}

async _fetchMetadataFromIpfs(metadataURI) {
const ipfsCID = `${this._web3.utils.hexToUtf8(metadataURI).substring(7)}`;
let ipfsCID = `${this._web3.utils.hexToUtf8(metadataURI).substring(7)}`;
ipfsCID = ipfsCID.replace(/\0/g, '');
logger.debug(`Fetching metadata from IPFS[CID: ${ipfsCID}]`);
const data = await this._ipfsClient.cat(ipfsCID);
return JSON.parse(data.toString());
try {
const fetchUrl = `${this._ipfsEndpoint}/api/v0/cat?arg=${ipfsCID}`;
const response = await get(fetchUrl);
return response.data;
} catch(error) {
logger.debug(`Error fetching metadata from IPFS[CID: ${ipfsCID}]`);
throw error;
}
}

_enhanceServiceGroupDetails(serviceMetadata, orgMetadata) {
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/MPEContract.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import MPEAbi from 'singularitynet-platform-contracts/abi/MultiPartyEscrow';
import MPENetworks from 'singularitynet-platform-contracts/networks/MultiPartyEscrow';
import { BigNumber } from 'bignumber.js';
import { map } from 'lodash';

import Web3 from 'web3';
import PaymentChannel from './PaymentChannel';
import logger from './utils/logger';
import { toBNString } from './utils/bignumber_helper';
Expand All @@ -12,9 +12,10 @@ class MPEContract {
* @param {Web3} web3
* @param {number} networkId
*/
constructor(web3, networkId) {
constructor(web3, networkId, rpcEndpoint) {
this._web3 = web3;
this._networkId = networkId;
this.rpcEndpoint = rpcEndpoint;
this._contract = new this._web3.eth.Contract(MPEAbi, MPENetworks[networkId].address);
}

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

const address = await account.getAddress();
const decodedData = Buffer.from(service.group.group_id, 'base64').toString('hex');
const groupId = `0x${decodedData}`;
const options = {
filter: {
sender: address,
recipient: service.group.payment_address,
groupId: service.group.group_id_in_bytes,
groupId,
},
fromBlock,
toBlock: 'latest',
};
const channelsOpened = await this.contract.getPastEvents('ChannelOpen', options);
const channelsOpened = await contract.getPastEvents('ChannelOpen', options);
return map(channelsOpened, (channelOpenEvent) => {
const { channelId } = channelOpenEvent.returnValues;
return new PaymentChannel(channelId, this._web3, account, service, this);
Expand All @@ -228,7 +236,8 @@ class MPEContract {

async _fundEscrowAccount(account, amountInCogs) {
const address = await account.getAddress();
const currentEscrowBalance = await this.balance(address);
let currentEscrowBalance = await this.balance(address);
currentEscrowBalance = toBNString(currentEscrowBalance);
if(amountInCogs > currentEscrowBalance) {
await account.depositToEscrowAccount(amountInCogs - currentEscrowBalance);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/PaymentChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js';
import isEmpty from 'lodash/isEmpty';

import logger from './utils/logger';
import { toBNString } from './utils/bignumber_helper';

class PaymentChannel {
/**
Expand Down Expand Up @@ -83,7 +84,7 @@ class PaymentChannel {
const currentState = await this._currentChannelState();
const { currentSignedAmount, nonce: currentNonce } = currentState;
const { nonce, expiration: expiry, value: amountDeposited } = latestChannelInfoOnBlockchain;
const availableAmount = amountDeposited - currentSignedAmount;
const availableAmount = toBNString(amountDeposited) - toBNString(currentSignedAmount);
this._state = {
nonce: nonce.toString(),
currentNonce,
Expand Down
60 changes: 0 additions & 60 deletions packages/core/src/identities/MetaMaskIdentity.js

This file was deleted.

24 changes: 10 additions & 14 deletions packages/core/src/identities/PrivateKeyIdentity.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import Tx from 'ethereumjs-tx';
import logger from '../utils/logger';
import blockChainEvents from '../utils/blockchainEvents';

/**
* @implements Identity
*/
Expand Down Expand Up @@ -30,18 +27,18 @@ class PrivateKeyIdentity {
}

async sendTransaction(transactionObject) {
const signedTransaction = this._signTransaction(transactionObject);
const signedTransaction = await this._signTransaction(transactionObject);
return new Promise((resolve, reject) => {
const method = this._web3.eth.sendSignedTransaction(signedTransaction);
method.once(blockChainEvents.CONFIRMATION, async (_confirmationNumber, receipt) => {
console.log('blockchain confirmation count', _confirmationNumber);
console.log('blockchain confirmation receipt status', receipt.status);
if(receipt.status) {
resolve(receipt);
console.log('blockchain confirmation receipt status', _confirmationNumber.receipt.status);
if(_confirmationNumber.receipt.status) {
resolve(_confirmationNumber.receipt);
} else {
reject(receipt);
reject(_confirmationNumber.receipt);
}
await method.off();
// await method.off();
});
method.on(blockChainEvents.ERROR, (error) => {
console.log('blockchain error', error);
Expand All @@ -56,12 +53,11 @@ class PrivateKeyIdentity {
});
}

_signTransaction(txObject) {
const transaction = new Tx(txObject);
async _signTransaction(txObject) {
delete txObject.chainId;
const privateKey = Buffer.from(this._pk.slice(2), 'hex');
transaction.sign(privateKey);
const serializedTransaction = transaction.serialize();
return `0x${serializedTransaction.toString('hex')}`;
const signedTx = await this._web3.eth.accounts.signTransaction(txObject, privateKey);
return signedTx.rawTransaction;
}

_setupAccount() {
Expand Down
61 changes: 61 additions & 0 deletions packages/core/src/identities/WalletRPCIdentity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Web3 from 'web3';
import { ethereumMethods } from '../utils/ethereumUtils';

import logger from '../utils/logger';
import blockChainEvents from '../utils/blockchainEvents';

/**
* @implements Identity
*/
class WalletRPCIdentity {
/**
* @param {Config} config
* @param {Web3} web3
*/
constructor(config, web3) {
this._eth = new Web3(config.web3Provider);
this._web3 = web3;
this.setupAccount();
}

async getAddress() {
const accounts = await this._web3.eth.getAccounts();
return accounts[0];
}

async signData(sha3Message) {
const address = await this.getAddress();
return this._web3.eth.personal.sign(sha3Message, address, '');
}

async sendTransaction(transactionObject) {
return new Promise((resolve, reject) => {
this._web3.eth.sendTransaction(transactionObject)
.on('transactionHash', (hash) => {
logger.info(`Transaction hash: ${hash}`);
})
.on('error', (error) => {
logger.error(`Couldn't send transaction. ${error}`);
reject(error);
})
.then((receipt) => {
if (receipt.status) {
resolve(receipt);
} else {
reject(receipt);
}
});
});
}

async setupAccount() {
const accounts = await this._web3.eth.getAccounts();
if (accounts.length > 0) {
this._web3.eth.defaultAccount = accounts[0];
} else {
logger.error('No accounts found');
}
}
}

export default WalletRPCIdentity;
2 changes: 1 addition & 1 deletion packages/core/src/identities/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as PrivateKeyIdentity } from './PrivateKeyIdentity';
export { default as MetaMaskIdentity } from './MetaMaskIdentity';
export { default as WalletRPCIdentity } from './WalletRPCIdentity';
5 changes: 4 additions & 1 deletion packages/core/src/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Account from './Account';
import MPEContract from './MPEContract';
import logger from './utils/logger';
import IPFSMetadataProvider from './IPFSMetadataProvider';
import { DefaultPaymentStrategy } from '../../payment_strategies';

const DEFAULT_CONFIG = {
defaultGasLimit: 210000,
Expand All @@ -29,7 +30,9 @@ class SnetSDK {
this._networkId = config.networkId;
this._web3 = new Web3(config.web3Provider, null, options);
const identity = this._createIdentity();
this._mpeContract = new MPEContract(this._web3, this._networkId);
// 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
const rpcEndpoint = config.rpcEndpoint;
this._mpeContract = new MPEContract(this._web3, this._networkId, rpcEndpoint);
this._account = new Account(this._web3, this._networkId, this._mpeContract, identity);
this._metadataProvider = metadataProvider || new IPFSMetadataProvider(this._web3, this._networkId, this._config.ipfsEndpoint);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/utils/logger.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { format, createLogger, transports } from 'winston';
import createLogger from 'winston/dist/winston/create-logger';
import { format } from 'logform';

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

export default logger;
Loading

0 comments on commit de2cceb

Please sign in to comment.