How to aggregate multiple smart contract function calls on Rootstock? - solidity

I have multiple ERC20 tokens, deployed on Rootstock,
and I want to track their balances and allowances in a real time from my DApp.
In ethers.js,
I can track their balances by alternately calling the functions
balanceOf(address) and allowance(owner, spender).
However, across two tokens, that equates to about
4 JSON-RPC requests every ~30 seconds.
I would prefer to reduce the frequency of JSON-RPC requests made by my application,
by aggregating these particular requests.
Is it possible to combine multiple smart contract data queries
into a single JSON-RPC request via ethers.js or any other library?

You could take a look at #0xsequence/multicall. It consists of:
MultiCallUtils smart contract with a multiCall() function intended for making aggregated calls
JS frontend library which includes a Multicall wrapper around ethers.js provider.
Smart contract deployments:
MultiCallUtils is deployed on the Rootstock Testnet: 0xb39d1Dea1bF91Aef02484F677925904b9d6936B4
I am not sure whether it is deployed at Rootstock Mainnet or not, anyway, you can deploy it there yourself.
To make aggregated smart contract calls:
Install the npm package:
npm i #0xsequence/multicall
Import the library to your project:
const { providers } = require('#0xsequence/multicall');
Create a Rootstock Testnet configuration:
const multicallConfig = {
// RSK Testnet
31: {
// maximum number of calls to batch into a single JSON-RPC call
batchSize: 50,
// defines the time each call is held on buffer waiting for subsequent calls before aggregation, ms
timeWindow: 50,
// MultiCallUtils smart contract
contract: '0xb39d1Dea1bF91Aef02484F677925904b9d6936B4',
},
};
Wrap your ethers.js current provider with a Multicall provider:
const multicallProvider = new providers.MulticallProvider(ethersProvider, multicallConfig[31]);
Connect your token smart contracts to the Multicall provider instead of ethers.js provider to be to able make aggregated calls:
const token1 = new ethers.Contract(
token1Address,
token1ABI,
multicallProvider,
);
const token2 = ...
Now you are ready to create an aggregated call:
function makeAggregatedCall() {
const aggregatedCall = [
multicallProvider.getBalance(address),
token1.balanceOf(address),
token1.allowance(owner, spender),
token2.balanceOf(address),
token2.allowance(owner, spender),
];
[
rbtcBalance,
balance1,
allowance1,
balance2,
allowance2,
] = await Promise.all(aggregatedCall);
}
The library will attempt to send all these function calls within a single call to MultiCallUtils smart contract multiCall() function.
However if the aggregated call fails,
as a fallback,
its constituent functions calls will be called individually via the ethers.js provider.
In order to subscribe to every newly minted block, attach the makeAggregatedCall function to the ethers provider block event listener:
ethersProvider.on('block', makeAggregatedCall);

You could use the ethereum-multicall project,
which consists of:
Frontend library which connects to
Multicall3 smart contract
Smart contract deployments:
The advantage of this project is that both Rootstock Mainnet and Rootstock Testnet have the Multicall3 deployments:
Mainnet 0xcA11bde05977b3631167028862bE2a173976CA11
Testnet 0xcA11bde05977b3631167028862bE2a173976CA11
... and bonus points for the 0xca11...ca11 vanity address ;)
To make aggregated smart contract calls:
Install the npm package:
npm i ethereum-multicall
Import the library to your project:
const { Multicall } = require('ethereum-multicall');
Create Multicall instance and connect it to ethers.js provider
const multicall = new Multicall({
ethersProvider: ethersProvider,
tryAggregate: true,
});
Create aggregated call:
const aggregatedCall = [
{
reference: 'token1',
contractAddress: token1Address,
abi: token1ABI,
calls: [
{
methodName: 'balanceOf',
methodParameters: [walletAddress],
},
{
methodName: 'allowance',
methodParameters: [ownerAddress, spenderAddress],
},
],
},
{
reference: 'token2',
contractAddress: token2Address,
abi: token2ABI,
calls: [
{
methodName: 'balanceOf',
methodParameters: [walletAddress],
},
{
methodName: 'allowance',
methodParameters: [ownerAddress, spenderAddress],
},
],
},
];
const { results } = await multicall.call(aggregatedCall);
Extract the required data from the results object

Related

Get deployed contract by address

According to this doc. We can use ethers.getContract to get deployed contract.
I have deployed my contract at 0x33F4623337b8F9EDc9529a79F0d68B2BeC98d5E2 and my creator address is 0x6e0F5B57FEdc8911722c92dcD5D7D0cf69ceA385 now to get contract i am doing
deployedContract = await ethers.getContract(
"0x33F4623337b8F9EDc9529a79F0d68B2BeC98d5E2",
"0x6e0F5B57FEdc8911722c92dcD5D7D0cf69ceA385"
)
But its throwing error
Error: No Contract deployed with name 0x33F4623337b8F9EDc9529a79F0d68B2BeC98d5E2
But you can see contract is deployed https://goerli.etherscan.io/address/0x33F4623337b8F9EDc9529a79F0d68B2BeC98d5E2
Can someone help me here?
It's just simple.
You can use getContractAt function.
getContractAt: <T extends ethers.Contract>(
nameOrAbi: string | any[],
address: string,
signer?: ethers.Signer | string
) => Promise<T>;
So in your case, it could be:
const raffle = await ethers.getcontractAt(
"Raffle",
"0x33F4623337b8F9EDc9529a79F0d68B2BeC98d5E2",
"0x6e0F5B57FEdc8911722c92dcD5D7D0cf69ceA385"
);
In addition, you got the error since you used the contract address for the contract name.
getContract: <T extends ethers.Contract>(
name: string,
signer?: ethers.Signer | string
) => Promise<T>;
The 0x33F4... contract is deployed on the Goerli testnet, and it's available only on this network. Your post doesn't state to which network is your ethers provider connected to, but based on the context I'm assuming it's a different network - for example the local emulated hardhat network (which is the default option if you don't specify any provider) or the Ethereum mainnet.
The getContract() function is present only in historic versions of the ethers package. Based on the documentation that you linked, it seems that you're using version 0.0.1 (and it seems that it's not a misconfiguration of the docs page, as the 0.0.1 version in fact exists). Current version (January 2023) of the package is 5.4 - you can find its docs at https://docs.ethers.io/v5/.
Second argument in the originally linked documentation is contractInterface. Which is not the deployer address (passed in your example) but an ABI (Application Binary Interface) - a JSON-formatted specification of public and external methods of the contract and its events. Example of such ABI JSON can be found in the original docs a below the Example headline.
Each contract usually has its own unique ABI that is generated from its source code during contract compilation, but you can also use generic ABI for standardized functions. For example all ERC-20 token contracts have the same functions required by the ERC-20 standard (and these functions are included in the generic ERC-20 ABI), plus they might implement some other functions on top of that (these custom functions are not in the generic ABI).
Here's an example of interacting with the contract with the current version (0.5.4) of ethers, connected to a Goerli network provider:
const { ethers } = require("ethers");
// A 3rd party provider that is connected to the Goerli network
const provider = new ethers.providers.JsonRpcProvider("https://goerli.infura.io/v3/<your_api_key>");
const CONTRACT_ADDRESS = "0x33F4623337b8F9EDc9529a79F0d68B2BeC98d5E2";
// ABI JSON of this specific contract, not included in the answer for readability
const CONTRACT_ABI = [/* ... */];
async function run() {
const myContract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
// `getEntryFee()` function defined in the ABI JSON as well as in the actual contract
const entryFee = await myContract.getEntryFee();
console.log(entryFee.toString());
}
run();
Note: Above is a standalone NodeJS script that is not using the Hardhat framework. You can also add the network in your Hardhat config file, and then run a script from their scripts folder that automatically includes ethers and connects to the selected provider.
require("#nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.17",
networks: {
goerli: {
url: "https://goerli.infura.io/v3/<your_api_key>",
accounts: [<private_key>, <private_key>], // for sending transactions
}
}
};
npx hardhat run --network goerli scripts/myScript.js
Add this to hardhat.config.js
networks: {
goerli: {
url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: [process.env.PRI_KEY],
},
},
create .env and add your env variables
// I am loading env variables
env $(cat .env) npx hardhat run --network goerli scripts/yourScriptFile.js

How can i pass express Request and Response objects into graphql-yoga context using the createYoga function?

I want to pass the express req and res object to my context because i want to use express-sessions to do session-based-auth because the default requests from the context does not know about sessions. Here is what I have tried
app.use("/graphql", (req, res) => {
return createYoga({
context: ({ params }) => {
return {
req,
params,
res,
prisma,
redis: redisClient,
};
},
graphiql: true,
landingPage: false,
cors: false,
schema,
});
});
But if i try this it seems like the request is not going through.
According to the docs, while running in node.js and express runtime, the context will automatically have the request and response.
Server Context
When creating the server instance, GraphQL Yoga accepts an additional
object from your base server framework or library that will be merged
with the default context. Node.js (standalone, express and Next.js
etc.)
If you are using GraphQL Yoga as a standalone server with createServer
from the http(s) module or exposing it as a middleware as we show in
the express or Next.js integration recipes.
req - Node.js IncomingMessage object
res - Node.js ServerResponse object
The req and res objects are added to the initial context object.
const serverContext = { ...defaultContext, req, res }
Thus, when using #graphql-yoga/node, it is possible to access
context.req and context.res within the GraphQL resolvers or the user
context factory function.
However, we recommend avoiding using context.req and context.res
wherever possible and instead favor context.request, as it is more
future-proof and platform independent (as Node.js HTTP servers adopt
the Fetch Response API).
I did try this out and the context does have the req and res objects.

deploy Openzeppelin ERC20 contract with web3JS

The openzeppelin codes of ERC20 token give error in deploying directly. Even after flattern the codes, i am unable to select contract MyToken out of many contracts.
I think i need to change some codes here:
var input = {
language: 'Solidity',
sources: {'test.sol': {content: codes}},
settings: {
outputSelection: {
'*': {
'*': ['*']
}
}
}
}
This is a compiler input. Just install truffle/hardhat, and you can compile and deploy with it. They have default input options for solc that should be enough for you, they will also take care of all the imports, which is a known pain in the butt when compiling with solc directly.

Does Feathers Authentication also require app internal service calls to be authenticated (and how to get around)?

I'm building a FeathersJS service behind an authentication very similar to the messages service that is part of the FeathersJS demo chat app: https://github.com/feathersjs/feathers-chat/
Additionally, I'd like to define an event listener that should store the messages it receives to the app's messages service and call all necessary hooks to notify the client application.
Here's my current approach:
module.exports = function () {
const app = this;
const Model = createModel(app);
const paginate = app.get('paginate');
const options = {
name: 'messages',
Model,
paginate
};
app.use('/messages', createService(options));
const service = app.service('messages');
service.hooks(hooks);
const sender = new MyExternalMessageSender();
sender.on('message', (msg) => {
service.create(msg, {user: {_id: 0}}).then(result => console.log(result));
});
if (service.filter) {
service.filter(filters);
}
};
This sometimes works fine and sometimes it randomly results in an error as soon as MyExternalMessageSender is notified and tries to call the message service's create method.
NotAuthenticated: No auth token
at Error.NotAuthenticated (projects\feathers-chat\node_modules\feathers-errors\lib\index.js:100:17)
at projects\feathers-chat\node_modules\feathers-authentication\lib\hooks\authenticate.js:102:31
How can I store messages the correct way without my application itself needing to use a JWT?
Thanks for your support!
I am not sure what MyExternalMessageSender does but authentication is skipped by default in internal service calls. If it is an internal service call is determined by params.provider being set. So if you pass hook.params from an external call (where provider is normally set to rest or socketio) to subsequent service calls authentication will run (since it thinks it is an external call).
This can be avoided by removing the provider property before passing the original parameters e.g. with Lodash _.omit:
myservice.find(_.omit(params, 'provider'))

Graphql #include with expression

I am implementing a query which should serve some fields in the response depending on user login status.
To be specific, I want to get "pointRate" field only if $authenticationToken is passed & would want to avoid passing $authenticated in below query. The reason I want to avoid sending $authenticated is client can do mistake by sending $authenticated = true but $authenticationToken = null.
query ToDoQuery($authenticationToken: String, $authenticated: Boolean!) {
pointRate(accessToken: $authenticationToken) #include(if: $authenticated) {
status
}
}
So, Actually you want to do that
i) if $authenticationToken is passed, you want to get "pointRate".
ii) and you also want to avoid passing $authenticated in subsequent
queries. Because you are concern about your clients who can make some
mistake like sending authenticated is true where authentication token
was null.
So in generally I want to answer that if you want to handle authentication by yourself using GraphQL, at first you have to create a token, then you have to pass the token in every request or with subsequent requests. Otherwise it is not possible. Because sensitive data's will not be provided without authentication.
On the other hand, you can use session auth. You can access every data until session is closed.
If it is not satisfactory, You can read the following brief description with a scenerio like yours. I also tried to accumulate some related sample solutions for better understanding, it may clarify you more.
As GraphQL API is completely public, you can make authentication by two ways.
Let the web server (e.g. express or nginx) take care of authentication.
Handle authentication in GraphQL itself.
If you do authentication in the web server, you can use a standard auth package (e.g. passport.js for express) and many existing authentication methods will work out of the box. You can also add and remove methods at your liking without modifying the GraphQL schema.
If you’re implementing authentication yourself, do the followings
Make sure to never store passwords in clear text or a MD5 or SHA-256
hash
Use something like bcrypt
Make sure to not store your session tokens as-is on the server, you
should hash them first
You can write a login method, which sets the context. Since mutations
are executed one after the other and not in parallel, you can be sure
the context is set after the login mutation:
mutation {
loginWithToken(token: "6e37a03e-9ee4-42fd-912d-3f67d2d0d852"),
do_stuff(greeting: "Hello", name: "Tom"),
do_more_stuff(submarine_color: "Yellow")
}
Instead of passing in the token via header or query parameter (like JWT, OAuth, etc), we make it part of the GraphQL query. Your schema code can parse the token directly using the JWT library itself or another tool.
Remember to always use HTTPS when passing sensitive information :)
As parallel execution is an important for performance. and mutation and queries are executed serially, in the order given.
So in most cases It is preferred to handle authentication in the web server. It’s not only more generic, but also more flexible.
Scenerio:
First go through the followings
import jwt from'express-jwt';
import graphqlHTTP from'express-graphql';
import express from'express';
import schema from'./mySchema';
const app = express();
app.use('/graphql', jwt({
secret: 'shhhhhhared-secret',
requestProperty: 'auth',
credentialsRequired: false,
}));
app.use('/graphql', function(req, res, done) {
const user = db.User.get(req.auth.sub);
req.context = {
user: user,
}
done();
});
app.use('/graphql', graphqlHTTP(req => ({
schema: schema,
context: req.context,
})
));
If you check in the above section, you will get that API is not secure at all. It might try to verify the JWT but if the JWT doesn’t exist or is invalid, the request will still pass through (see credentialsRequired: false). Why? We have to allow the request to pass through because if we blocked it we would block the entire API. That means, our users wouldn’t even be able to call a loginUser mutation to get a token to authenticate themselves.
Solution#1:
Barebone example using Authenticate resolvers, not endpoints.
import { GraphQLSchema } from 'graphql';
import { Registry } from 'graphql-helpers';
// The registry wraps graphql-js and is more concise
const registry = new Registry();
registry.createType(`
type User {
id: ID!
username: String!
}
`;
registry.createType(`
type Query {
me: User
}
`, {
me: (parent, args, context, info) => {
if (context.user) {
return context.user;
}
throw new Error('User is not logged in (or authenticated).');
},
};
const schema = new GraphQLSchema({
query: registry.getType('Query'),
});
By the time the request gets to our Query.me resolver, the server middleware has already tried to authenticate the user and fetch the user object from the database. In our resolver, we can then check the graphql context for the user (we set the context in our server.js file) and if one exists then return it else throw an error.
Note: you could just as easily return null instead of throwing an error and I would actually recommend it.
Solution#2:
Use functional Composition(middleware based) of express-graphql
import { GraphQLSchema } from 'graphql';
import { Registry } from 'graphql-helpers';
// See an implementation of compose https://gist.github.com/mlp5ab/f5cdee0fe7d5ed4e6a2be348b81eac12
import { compose } from './compose';
const registry = new Registry();
/**
* The authenticated function checks for a user and calls the next function in the composition if
* one exists. If no user exists in the context then an error is thrown.
*/
const authenticated =
(fn: GraphQLFieldResolver) =>
(parent, args, context, info) => {
if (context.user) {
return fn(parent, args, context, info);
}
throw new Error('User is not authenticated');
};
/*
* getLoggedInUser returns the logged in user from the context.
*/
const getLoggedInUser = (parent, args, context, info) => context.user;
registry.createType(`
type User {
id: ID!
username: String!
}
`;
registry.createType(`
type Query {
me: User
}
`, {
me: compose(authenticated)(getLoggedInUser)
};
const schema = new GraphQLSchema({
query: registry.getType('Query'),
});
The above code will work exactly the same as the first snippet. Instead of checking for the user in our main resolver function, we have created a highly reusable and testable middleware function that achieves the same thing. The immediate impact of this design may not be apparent yet but think about what would happen if we wanted to add another protected route as well as log our resolver running times. With our new design its as simple as:
const traceResolve =
(fn: GraphQLFieldResolver) =>
async (obj: any, args: any, context: any, info: any) => {
const start = new Date().getTime();
const result = await fn(obj, args, context, info);
const end = new Date().getTime();
console.log(`Resolver took ${end - start} ms`);
return result;
};
registry.createType(`
type Query {
me: User
otherSecretData: SecretData
}
`, {
me: compose(traceResolve, authenticated)(getLoggedInUser)
otherSecretData: compose(traceResolve, authenticated)(getSecretData)
};
Using this technique will help you build more robust GraphQL APIs. Function composition is a great solution for authentication tasks but you can also use it for logging resolvers, cleaning input, massaging output, and much more.
Solution#3:
A decent solution is to factor out data fetching into a separate layer and do the authorization check there.
Below is an example that follows the principles outlined above. It’s for a query that fetches all todo lists that a user can see.
For the following query,
{
allLists {
name
}
}
Don’t do this:
//in schema.js (just the essential bits)
allLists: {
resolve: (root, _, ctx) => {
return sql.raw("SELECT * FROM lists WHERE owner_id is NULL or owner_id = %s", ctx.user_id);
}
}
Instead, I suggest you do this:
// in schema.js (just the essential bits)
allLists: {
resolve: (root, _, ctx) => {
//factor out data fetching
return DB.Lists.all(ctx.user_id)
.then( lists => {
//enforce auth on each node
return lists.map(auth.List.enforce_read_perm(ctx.user_id) );
});
}
}
//in DB.js
export const DB = {
Lists: {
all: (user_id) => {
return sql.raw("SELECT id FROM lists WHERE owner_id is NULL or owner_id = %s, user_id);
}
}
}
//in auth.js
export const auth = {
List: {
enforce_read_perm: (user_id) => {
return (list) => {
if(list.owner_id !== null && list.owner_id !== user_id){
throw new Error("User not authorized to read list");
} else {
return list;
}
}
}
}
You may think that the DB.Lists.all function is already enforcing permissions, but the way I see it it’s just trying not to fetch too much data, the permissions themselves are enforced not on each node separately. That way you have the auth checks in one place and can be sure that they will be applied consistently, even if you fetch data in many different places.
Solution#4:
Auth flow can be done in many different ways.
i) basic auth,
ii) session auth, or
iii) token auth.
As your issue is according to token auth, I would like to meet you with Scaphold which one uses token authentication. Everything we do, whether it be logging a user into Scaphold or logging your user into your app, we use tokens to manage a user's auth status. The auth flow works like this:
a) User logs in with username and password.
b) The GraphQL server verifies the user in the database against his / her hashed password.
c) If successful, the server returns a JSON Web Token (JWT) that is a Base64 encoded token with an expiration date. This is the authentication token.
d) To use the authentication token, your future requests should include the authentication token in the header as
{ Authorization: 'Bearer' + [Auth_Token] }
Now, each time the server (perhaps Node Express) sees the token in the header, it will parse out the token, verify it, and in the GraphQL world, save the identified user in the context for use in the rest of the application. The user is now logged in.
For more, you can learn more about #include in this tutorial: https://github.com/mugli/learning-graphql/blob/master/4.%20Querying%20with%20Directives.md#include
For learning step by step graphql authentication, you can go through this tutorial: GraphQL Authentication
Resource Link:
Authentication with
GraphQL
A guide to authentication in
GraphQL
Best practices for GraphQL
security
I don't think this is possible since you cannot convert an (empty) String to a Boolean in GraphQL.
Also, some advice from the official GraphQL docs:
Delegate authorization logic to the business logic layer
#include
GraphQL queries are a powerful way to declare data in your application.
The include directive, allows us to include fields based on some condition.
query myAwesomeQuery($isAwesome: Boolean) {
awesomeField #include(if: $isAwesome)
}
Note. #skip always has higher precedence than #include.