Using ethereum.request to Retrieve Data from Smart Contract in JavaScript - javascript

Using javascript, I am currently able to write data to a smart contract I built on the Ethereum Rovan test network using this code (taken from the MetaMask docs)
const transactionParameters = {
to: '0xacb241f59e1a8c7a61f0781aed7ad067269feb26',
from: account,
data: '0xfcc74f71aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
};
const txHash = await ethereum.request({
method: 'eth_sendTransaction',
params: [ transactionParameters ],
});
However, I am not able to read data. How can I do this? The hex code of the method for reading data is 0x1f1bd692, so I thought using these parameters would work:
to: '0xacb241f59e1a8c7a61f0781aed7ad067269feb26',
from: account,
data: '0x1f1bd692',
Unfortunately, this just returns the transaction hash, not the data I want from the smart contract.
Note: If possible, please do not suggest any libraries.

Here is the solution:
const data = await ethereum.request({
method: 'eth_getStorageAt',
params: [ '0xacb241f59e1a8c7a61f0781aed7ad067269feb26', '0x0' ],
})
The first argument in params[] is the address of the contract. The second argument is the index of the item in storage you want to retrieve. It must be a hex number starting with '0x'.

Related

Adyen Drop-in - how to pass unique order ID?

I have been trying to use the Adyen Drop-in component to make payments on the Razor pages site I am developing. I have got a test version running that makes a payment for a hard-coded amount but I have yet to figure out how to pass a unique order ID to my API endpoint making the payment request.
Taking the examples from https://docs.adyen.com/online-payments/drop-in-web, the drop-in component is mounted via JavaScript using
const checkout = new AdyenCheckout(configuration);
const dropin = checkout.create('dropin').mount('#dropin-container');
where the configuration object is created with something like
const configuration = {
paymentMethodsResponse: paymentMethodsResponse, // The `/paymentMethods` response from the server.
clientKey: "YOUR_CLIENT_KEY", // Web Drop-in versions before 3.10.1 use originKey instead of clientKey.
locale: "en-US",
environment: "test",
onSubmit: (state, dropin) => {
// Your function calling your server to make the `/payments` request
makePayment(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
},
onAdditionalDetails: (state, dropin) => {
// Your function calling your server to make a `/payments/details` request
makeDetailsCall(state.data)
.then(response => {
if (response.action) {
// Drop-in handles the action object from the /payments response
dropin.handleAction(response.action);
} else {
// Your function to show the final result to the shopper
showFinalResult(response);
}
})
.catch(error => {
throw Error(error);
});
}
};
Adyen's own JavaScript then supplies the state object for the onSubmit method, so that my API endpoint gets called with a PaymentRequest object created (somehow) from the state.data.
However, without being able to get a unique order ID into this PaymentRequest object, my server-side code does not know what amount to set. Note that one can set an Amount object in the configuration object but this is just used to display the value on the Drop-in component - the value is not passed to the server.
So how does one pass a unique order ID via the Drop-in component?
The Adyen docs don't explicitly provide an example here, but the makePayment() and makeDetailsCall() presume that you will take the state.data and post back to your server. You need to implement your own code here. At that point, you could add additional information like any identifiers.
Here is an example implementation as a reference:
async function makePayment(state_data) {
const order_id = ""; // You need to provide this however your client stores it.
const json_data = {
order_id,
state_data,
};
const res = await fetch("[url to your server's endpoint]", {
method: "POST",
body: JSON.stringify(json_data),
headers: {
"Content-Type": "application/json",
},
});
return await res.json();
}
Another helpful resource could be the Adyen node.js/express tutorial. It is more explicit on implementation details so might help remove some ambiguity.

Firebase and Algolia secured search no search hits

I'm having some issues implementing a secured search with Firebase and Algolia.
I've been following the guide at https://firebase.google.com/docs/firestore/solutions/search -> Adding security
My problem is that everything seems to be working fine, however I do not receive any search hits.
When a user searches for an item, it invokes a function which first hits a firebase function to generate a Secured API Key, which also includes the security filter for the queries.
The firebase function:
app.use(getFirebaseUser);
app.get('/', (req, res) => {
// #ts-ignore
const uid = req.user.uid;
// Create the params object as described in the Algolia documentation:
// https://www.algolia.com/doc/guides/security/api-keys/#generating-api-keys
const params = {
// This filter ensures that only documents where author == uid will be readable
filters: `ownerID:${uid}`,
// We also proxy the uid as a unique token for this key.
userToken: uid,
};
// Call the Algolia API to generate a unique key based on our search key
const key = client.generateSecuredApiKey(ALGOLIA_SEARCH_KEY, params);
// Then return this key as {key: '...key'}
res.json({key});
});
This is literally copy/pasted from the firebase example, except I've replaced "author" with "ownerID", which is the term I want to match in my indices.
The getFirebaseUser() is an exact copy/paste from the official firebase example.
Once the client has received the secured API key, it get passed to algolia with the query:
const getSearchResults = async (query: string) => {
const index = client.initIndex('things');
try {
const userToken = await auth.currentUser?.getIdToken();
const response = await fetch(GOOGLE_URL, {
headers: { Authorization: `Bearer ${userToken}` },
});
const data = await response.json();
const client = algoliasearch(ALGOLIA_APP_ID, data.key);
const responses = await index.search(query);
console.log(responses);
} catch (err) {
console.error(err.message);
}
};
This is also the same function as the example, except written with async/await. (I did try an exact copy/paste, and the result is the same).
Also, the example shows index.search({query}) where the query string gets passed in as an object. This does not work either, and Typescript states that a string is expected.
If I try to change the initIndex to an index that doesn't exist, I get an error stating that it doesn't exist. This tells me the connection between my app and algolia is working as expected.
The returned object looks like this:
exhaustiveNbHits: true
hits: []
hitsPerPage: 20
nbHits: 0
nbPages: 0
page: 0
params: "query=test&filters=ownerID%3AKK3oXEkrIKb31BECDSJPMdgvTBz2&userToken=KK3oXEkrIKb31BECDSJPMdgvTBz2"
processingTimeMS: 1
query: "test"
If I do a regular search:
const getSearchResults = async (query: string) => {
const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY);
const index = client.initIndex('things');
const responses = await index.search(query);
console.log(responses);
};
I do get hits:
exhaustiveNbHits: true
hits: Array(1)
0: {productName: "test ja", purchaseDate: "15.03.2021", purchasedFrom: "ok", ownerID: "KK3oXEkrIKb31BECDSJPMdgvTBz2", objectID: "25BadjQxUjRjkjH9ZnwW", …}
length: 1
__proto__: Array(0)
hitsPerPage: 20
nbHits: 1
nbPages: 1
page: 0
params: "query=test"
processingTimeMS: 1
query: "test"
This is literally bypassing the whole cloud function, which means the problem must be with generating the Secured API Key, the parameters or something.
The index data stored in Algolia looks like this:
objectID "25BadjQxUjRjkjH9ZnwW"
productName "test ja"
purchaseDate "15.03.2021"
purchasedFrom "ok"
ownerID "KK3oXEkrIKb31BECDSJPMdgvTBz2"
Which means I should get hits on ownerID, which I have in the cloud function.
EDIT:
When setting the filter from the parameter object to an empty string, I receive search hits. There might be something wrong with the way the filter is implemented.
Thanks for looking into this. I would appreciate all the help I can get!
Stephan Valois
I was in contact with algolia, and figured out what was wrong.
There was nothing wrong with the code, or the way the search was set up.
One thing that wasn't mentioned in the documentation is that the attribute I was matching (ownerID) wasn't declared as an attribute for faceting.
By going to Configuration -> Facets in the Algolia app, and adding the object key there, everything works as expected. (Sat it as 'not searchable'). This attribute is not supposed to be searchable. It should only be used to match the index to the correct user.
If anyone is using Algolia for secure search, whether or not you're using a Firebase function or another backend, the attribute in your filter must be set in Facets.

Spotify PKCE code_verifier was incorrect

I was excited to hear that I can now use the Spotify web API without having a backend application via PKCE. Unfortunately, I seem to have some sort of misunderstanding and have been unable to get it to work.
I am likely making some minor mistake along the way, but I did it once to no avail and I wiped the slate clean and tried again but still without luck. From this I gather that I must be misunderstanding the documentation.
I will explain what I am doing and hopefully someone here can point out what I'm missing or doing wrong. I'm assuming I have a fundamental conceptual misunderstanding.
I first generate a cryptographically random string using an npm package called crypto-random-string. I store that in the browser's local storage before using js-sha256 to hash it and then using another npm package called base64url to encode it.
let verifier = cryptoRandomString({length: 50})
window.localStorage.setItem('verifier', verifier)
let params = {
client_id: '[MY CLIENT ID]',
response_type: 'code',
redirect_uri: 'http://localhost:3000/callback',
code_challenge_method: 'S256',
code_challenge: base64url(sha256(verifier))
}
let endpoint = new URL('https://accounts.spotify.com/authorize');
endpoint.search = new URLSearchParams(params);
window.location = endpoint.toString();
From here, I redirect to the /authorize endpoint with the proper url parameters. I have gotten this far successfully and then been redirected accordingly to my provided redirect_uri, where I grab the given code from the url parameters.
At this point, I try the fetch to the /api/token endpoint with the client_id, grant_type, the code I got from the url params, my redirect_uri, and the locally stored code_verifier.
let params = new URLSearchParams(window.location.search);
console.log(params.get('code'));
let newParams = {
client_id: '[MY CLIENT ID]',
grant_type: 'authorization_code',
code: params.get('code'),
redirect_uri: 'http://localhost:3000/callback',
code_verifier: window.localStorage.getItem('verifier')
}
let endpoint = new URL('https://accounts.spotify.com/api/token');
endpoint.search = new URLSearchParams(newParams);
fetch(endpoint.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(data => data.json()).then(console.log)
At this point, after both of my attempts I received the error:
{ error: "invalid_grant", error_description: "code_verifier was incorrect" }
Is there anything that I am obviously doing wrong? The error leads me to believe I'm doing something wrong as far as the actual generation of the code_verifier, but I am at a loss to what that issue may be.
Someone on the Spotify forum pointed me to this answer. Not sure why exactly, but doing the encoding the following way does work:
async function sha256(plain) {
const encoder = new TextEncoder()
const data = encoder.encode(plain)
return window.crypto.subtle.digest('SHA-256', data)
}
function base64urlencode(a){
return btoa(String.fromCharCode.apply(null, new Uint8Array(a))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
const hashed = await sha256(verifyCode)
const codeChallenge = base64urlencode(hashed)
Previous answers and comments, in addition to OP, has documented most information needed so I will only add what helped me:
The verifier itself most be encoded as a Base64-URL.
Pseduo-code (as I myself code in C#):
verifier = Base64UrlEncode(GetRandomString(length: 50))
challenge = Base64UrlEncode(HashWithSha256(verifier))

GraphQL/Apollo Client: get data from cache if a query is a subset of a previous query

I'm new to GraphQL and I thought it would do this automatically.
E.g. if I fetch a list of posts, then open an individual post, the data for that post is already in memory. I expected GraphQL to be able to re-use the data from cache, but it makes another network request.
The schema is something like:
type Query {
posts: [Post]
post(id: ID): Post
}
I'm using React. In PostsRoute, I have:
useQuery(gql`
query {
posts {
id
title
}
}
`);
In PostRoute, I have:
useQuery(gql`
query {
post(id: ${postId}) {
id
title
}
}
`);
Is there something I need to configure for Apollo to use the cached data automatically? Or is this something that Apollo doesn't support by default?
Apollo 2
If you are using Apollo 2, what you're looking for is probably cacheRedirects. It is used to intercept a query and resolve it using the cache. The linked document actually explains exactly your use-case so I recommend you look at it, but for posterity, here's how I do it personally (adapted to your situation).
In your InMemoryCache options when you instantiate it, add a cacheRedirects field and specify a custom resolver for your post query, like so:
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
post: (_, {id}, {getCacheKey}) => getCacheKey({__typename: "Post", id}),
},
},
});
This assumes that the __typename of your post is Post. First argument of the custom resolver is the result of the ROOT_QUERY, which we don't use here. Second argument is the arguments that are passed to the query ({id: "some_id"} here) and the third one is the context, containing client, cache and getCacheKey. Only getCacheKey is useful to us here. That method take a __typename and an id, and returns its key in the cache.
This completes the custom resolver. Now when you query post(id: some_id) it will look at the custom resolver, receive the cache key and return the result from the cache matching that key.
Apollo 3
cacheRedirects has been removed in Apollo 3, but the same functionality can be achieved with field policies. You still need to set it up when instantiating InMemoryCache, but it's slightly different:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
post: (_, {args, toReference}) => toReference({__typename: "Post", id: args.id}),
},
},
},
});
We define a field policy read on the field post on type Query. Note that the second argument now includes both args and the helper utilities. This includes toReference, that's basically the new getCacheKey.

How to integrate Stripe payments with Google Apps Script

According to this answer, Gmail does not expose an API for sending and receiving payments. Therefore, I am trying to use Stripe to accomplish that.
Code.js
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
(async () => {
const product = await stripe.products.create({
name: 'My SaaS Platform',
type: 'service',
});
})();
However, GAS does not directly support async and require at this time. Is there any possible workaround so I can use Stripe to send and receive payments in my GAS app?
If that's not possible, what direction should I go from here?
How about this answer? Please think of this as just one of several answers.
Issue and workaround:
Unfortunately, the module of Node.js cannot be directly used for Google Apps Script. So it is required to prepare the script for Google Apps Script. Fortunately, at the official document of the link in your question, there are several samples. Using this, how about converting to the script of Google Apps Script?
Sample script:
When your script in your question is converted to Google Apps Script, it becomes as follows.
From:
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
(async () => {
const product = await stripe.products.create({
name: 'My SaaS Platform',
type: 'service',
});
})();
To
function myFunction() {
var url = "https://api.stripe.com/v1/products";
var params = {
method: "post",
headers: {Authorization: "Basic " + Utilities.base64Encode("sk_test_4eC39HqLyjWDarjtT1zdp7dc:")},
payload: {name: "My SaaS Platform", type: "service"}
};
var res = UrlFetchApp.fetch(url, params);
Logger.log(res.getContentText())
}
In this case, both requests of Node.js and Google Apps Script are the same.
Note:
At the sample script of Node.js, sk_test_4eC39HqLyjWDarjtT1zdp7dc is used for the key. But in this case, because the basic authorization is used, please use sk_test_4eC39HqLyjWDarjtT1zdp7dc: by adding :.
References:
Billing Quickstart
Class UrlFetchApp
If I misunderstood your question and this was not the direction you want, I apologize.

Categories

Resources