I'm trying to figure out how to share signer between multiple js files. I have a file walletConnect.js where I connect to Metamask and get ERC20 token contract. This works fine.
async function connect(){
try{
const accounts = await ethereum.request({
method: "eth_requestAccounts",
});
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
// ERC20 token contract
const tokenContract = new ethers.Contract(
tokenAddress,
tokenAbi,
signer
);
} catch (e) {
console.log(e);
}
}
and then I have another file stake.js where I deposit a token into a contract
async function stakeToken() {
try {
const stakingContract = new ethers.Contract(
contractAddress,
contractAbi,
signer
);
// approve contract
await tokenContract.approve(stakingContract.address, maxAmount);
// stake tokens
await stakingContract.stakeTokens(
tokenContract.address,
amount
);
} catch (e) {
console.log(e);
}
}
Everything works fine if I have all code in one file. I use useState hook and all is good. However I will have multiple staking contracts I would like to keep in separate javascript files. How do I do it when having multiple files?
I have created a hook contracts.js like this
import { ethers } from "ethers";
export const useContract = (address, abi) => {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(address, abi, signer);
return contract;
} catch (e) {
console.log(e);
return false;
}
};
Now I just call useContract from any script and it returns a contract
const stakingContract = useContract(someAddress, someAbi);
this way I save a lot of redundant code
Related
I'm making a dApp and I want to add a button where a user (the one with their wallet connected) can send exactly 0.01 SOL to another user. I already wrote the function in my Rust program and after testing it with anchor test it seems to be working when I use my own personal wallet's Keypair to sign the transaction. However, now I am writing the event handler function in my web app's frontend and I'm not sure what to pass for the signers parameter if I want the user to sign the transaction. What do I pass if I don't know their secret key? Is there a way that I can generate a user's Keypair from their public key alone or would I need to use the Solana Wallet Adapter for this? Any help would be appreciated. This is my first time working with Solana!
This is the function:
const tipSol = async (receiverAddress) => {
try {
const provider = getProvider();
const program = new Program(idl, programID, provider);
const lamportsToSend = LAMPORTS_PER_SOL / 100;
const amount = new anchor.BN(lamportsToSend);
await program.rpc.sendSol(amount, {
accounts: {
from: walletAddress,
to: receiverAddress,
systemProgram: SystemProgram.programId,
},
signers: ?
})
console.log('Successfully sent 0.01 SOL!')
window.alert(`You successfully tipped ${receiverAddress} 0.01 SOL!`)
} catch (error) {
console.error('Failed to send SOL:', error);
window.alert('Failed to send SOL:', error);
}
}
Frontends never access private keys. Instead the flow is something like:
Frontend creates the transaction
Frontend sends the transaction to the wallet
Wallet signs the transaction
Wallet returns the signed transaction to the frontend
Frontend send the transaction
You can use the #solana/wallet-adapter to implement this on your frontend https://github.com/solana-labs/wallet-adapter
In practice it would be something like this in your frontend
export const Component = () => {
const { connection } = useConnection();
const { sendTransaction } = useWallet();
const handle = async () => {
const ix: TransactionInstruction = await tipSol(receiverKey);
const tx = new Transaction().add(ix);
const sig = await sendTransaction(tx, connection);
};
// ...
};
My goal is to setApprovalForAll for a token contract before executing the safeTransferFrom function for each tokenId in the NFT collection. This way I will be able to transfer NFTs to another address without MetaMask asking for several approvals.
However I am getting an error upon executing the safeTransferFrom function, the following error is triggered:
This happens even after I have called the setApprovalForAll function. The setApprovalForAll transaction seems to have also went through successfully:
but calling isApprovedForAll says otherwise (view comment on line 16 in code).
I believe it is possible the error is raised because of me not calling the setApprovalForAll function properly, because why else would isApprovedForAll return false?
document.querySelector('.click-me').onclick = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = await provider.getSigner();
const CONTRACT_ADDRESS = '0x...'; // token contract address
const RECEIVER_ADDRESS = '0x...'; // this address expected to get approval for all
const ABI = [
'function setApprovalForAll(address operator, bool _approved)',
'function safeTransferFrom(address from, address to, uint256 tokenId)',
'function isApprovedForAll(address owner, address operator) view returns (bool)'
];
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
try {
const isApproved = await contract.isApprovedForAll(CONTRACT_ADDRESS, RECEIVER_ADDRESS);
console.log(isApproved); // returns false even after several attempts
await contract.setApprovalForAll(RECEIVER_ADDRESS, true); // seems to work fine, even shows in MetaMask activity
// ERROR SEEMS TO OCCUR HERE
const test = await contract.safeTransferFrom(CONTRACT_ADDRESS, RECEIVER_ADDRESS, 749); // 749 is my NFT tokenId
console.log(test);
} catch (error) {
console.log(error.message)
}
};
You need to change contract address to the owner of the NFT see below
document.querySelector('.click-me').onclick = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = await provider.getSigner();
const CONTRACT_ADDRESS = '0x...'; // token contract address
const RECEIVER_ADDRESS = '0x...'; // this address expected to get approval for all
const ABI = [
'function setApprovalForAll(address operator, bool _approved)',
'function safeTransferFrom(address from, address to, uint256 tokenId)',
'function isApprovedForAll(address owner, address operator) view returns (bool)'
];
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
try {
const isApproved = await contract.isApprovedForAll(CONTRACT_ADDRESS, RECEIVER_ADDRESS);
console.log(isApproved); // returns false even after several attempts
await contract.setApprovalForAll(RECEIVER_ADDRESS, true); // seems to work fine, even shows in MetaMask activity
// FIXED
const test = await contract.safeTransferFrom(signer.address, RECEIVER_ADDRESS, 749); // 749 is my NFT tokenId
console.log(test);
} catch (error) {
console.log(error.message)
}
};
Am working with Solana Blockchain. Am trying to transfer Solana SOL via Phantom. To this effect I used the code below which was found on Stack Overflow: source link
I already have Phantom installed in Chrome. When I run the code, it displays error:
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'toString')
I think its this line of code that is causing the error above
console.log("Public key of the emitter: ",provider.publicKey.toString());
Here is the Code
import * as web3 from '#solana/web3.js';
import * as splToken from '#solana/spl-token';
const getProvider = async () => {
if ("solana" in window) {
const provider = window.solana;
if (provider.isPhantom) {
console.log("Is Phantom installed? ", provider.isPhantom);
return provider;
}
} else {
window.open("https://www.phantom.app/", "_blank");
}
};
async function transferSOL() {
// Detecing and storing the phantom wallet of the user (creator in this case)
var provider = await getProvider();
console.log("Public key of the emitter: ",provider.publicKey.toString());
// Establishing connection
var connection = new web3.Connection(
web3.clusterApiUrl('devnet'),
);
// I have hardcoded my secondary wallet address here. You can take this address either from user input or your DB or wherever
var recieverWallet = new web3.PublicKey("CkiKLEa9eSEoG6CoTSuaahsF2WqNgArnvoCSbNZjJ7BQ");
// Airdrop some SOL to the sender's wallet, so that it can handle the txn fee
var airdropSignature = await connection.requestAirdrop(
provider.publicKey,
web3.LAMPORTS_PER_SOL,
);
// Confirming that the airdrop went through
await connection.confirmTransaction(airdropSignature);
console.log("Airdropped");
var transaction = new web3.Transaction().add(
web3.SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: recieverWallet,
lamports: web3.LAMPORTS_PER_SOL //Investing 1 SOL. Remember 1 Lamport = 10^-9 SOL.
}),
);
// Setting the variables for the transaction
transaction.feePayer = await provider.publicKey;
let blockhashObj = await connection.getRecentBlockhash();
transaction.recentBlockhash = await blockhashObj.blockhash;
// Transaction constructor initialized successfully
if(transaction) {
console.log("Txn created successfully");
}
// Request creator to sign the transaction (allow the transaction)
let signed = await provider.signTransaction(transaction);
// The signature is generated
let signature = await connection.sendRawTransaction(signed.serialize());
// Confirm whether the transaction went through or not
await connection.confirmTransaction(signature);
//Signature chhap diya idhar
console.log("Signature: ", signature);
}
You need to connect to the wallet. That part is missing
const getProvider = async () => {
if ("solana" in window) {
// opens wallet to connect to
await window.solana.connect();
const provider = window.solana;
if (provider.isPhantom) {
console.log("Is Phantom installed? ", provider.isPhantom);
return provider;
}
} else {
window.open("https://www.phantom.app/", "_blank");
}
};
I'm not sure if this is the best solution going ahead, but yours is a problem of the persistence of the phantom wallet after the user signs in. You'll have to go for front-end heavy solution for this. One of which is:
Assuming you are using React, use context APIs to persist the data about the wallet. Here is a rough guideline on how to do that by creating a file under the context folder in your project root:
import React, { createContext, useState} from "react";
export const WalletDataContext=createContext();
export const WalletDataContextProvider=(props)=>{
const [publicKey,setPublicKey]=useState(null);
const [wallet,setWallet]=useState(null);
return (
<WalletDataContext.Provider
value={{publicKey,setPublicKey,wallet,setWallet}}
>
{props.children}
</WalletDataContext.Provider>
)
}
Create a connectWallet function, something like this:
//import {WalletDataContext}
//import other stuff:
const {setPublicKey,setWallet}=useContext(WalletDataContext)
const connectWallet = async() {
const provider = await getProvider();
if(provider) {
await provider.connect();
let publicKey = "";
provider.on("connect", async () => {
setWallet(provider);
publicKey = provider.pubicKey.toString();
setPublicKey(publicKey);
/*
// more things that you would like to do here
*/
});
}
}
Make the following changes in your transferSOL function:
async function transferSOL() {
//Changes are only here, in the beginning
const phantomProvider = wallet;
if(!phantomProvider){
//Urge the user to sign in(connect) again
}
const pubKey = await phantomProvider.publicKey;
console.log("Public Key: ", pubKey);
// Establishing connection
var connection = new web3.Connection(
web3.clusterApiUrl('devnet'),
);
// I have hardcoded my secondary wallet address here. You can take this address either from user input or your DB or wherever
var recieverWallet = new web3.PublicKey("CkiKLEa9eSEoG6CoTSuaahsF2WqNgArnvoCSbNZjJ7BQ");
// Airdrop some SOL to the sender's wallet, so that it can handle the txn fee
var airdropSignature = await connection.requestAirdrop(
provider.publicKey,
web3.LAMPORTS_PER_SOL,
);
// Confirming that the airdrop went through
await connection.confirmTransaction(airdropSignature);
console.log("Airdropped");
var transaction = new web3.Transaction().add(
web3.SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: recieverWallet,
lamports: web3.LAMPORTS_PER_SOL //Investing 1 SOL. Remember 1 Lamport = 10^-9 SOL.
}),
);
// Setting the variables for the transaction
transaction.feePayer = await provider.publicKey;
let blockhashObj = await connection.getRecentBlockhash();
transaction.recentBlockhash = await blockhashObj.blockhash;
// Transaction constructor initialized successfully
if(transaction) {
console.log("Txn created successfully");
}
// Request creator to sign the transaction (allow the transaction)
let signed = await provider.signTransaction(transaction);
// The signature is generated
let signature = await connection.sendRawTransaction(signed.serialize());
// Confirm whether the transaction went through or not
await connection.confirmTransaction(signature);
//Signature or the txn hash
console.log("Signature: ", signature);
}
I think you problem is here:
// Request creator to sign the transaction (allow the transaction)
let signed = await provider.signTransaction(transaction);
// The signature is generated
let signature = await connection.sendRawTransaction(signed.serialize());
// Confirm whether the transaction went through or not
await connection.confirmTransaction(signature);
You sign using provider, but you send using connection, try change this:
const { signature } = await provider?.signAndSendTransaction(
transaction
);
await connection.getSignatureStatus(signature);
return signature;
I'm trying to connect to Metamask using ethers.js.
However, all the solution I found keep returning this error
"await is only valid in async function"
Was there a recent change in Javascript syntax that affects await?
How to connect ethers.js with metamask?
const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
// Prompt user for account connections
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
console.log("Account:", await signer.getAddress());
how to connect metamask with ethers.js and fetch balance?
await window.ethereum.enable();
const provider = new ethers.providers.Web3Provider(window.ethereum);
const contract = new ethers.Contract(smartContractAddress, abi, provider);
balance = await contract.getBalance("0x7C76C63DB86bfB5437f7426F4C37b15098Bb81da");
You should put await in async function to work, like this:
async function connectToMetamask(){
const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
// Prompt user for account connections
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
console.log("Account:", await signer.getAddress());
}
I am able to acquire access token but not sure how to send messages because it requires a user and my app is a backend app(nodejs script). On graph explorer, it works.
The code snippet on graph explorer is:
const options = {
authProvider, //I need this value
};
const client = Client.init(options);
const chatMessage = {body: {content: '#.####.#'}};
await client.api('/teams/my-team-id/channels/my-channel-id/messages')
.post(chatMessage);
How do I get authProvider in nodejs?
I tried using MSALAuthenticationProviderOptions but there seems to be an issue (as mentioned in their github repo) by following these steps: https://www.npmjs.com/package/#microsoft/microsoft-graph-client.
You need to run this in the context of an application instead of a user. The Microsoft Graph JavaScript library now supports Azure TokenCredential for acquiring tokens.
const { Client } = require("#microsoft/microsoft-graph-client");
const { TokenCredentialAuthenticationProvider } = require("#microsoft/microsoft-graph-client/authProviders/azureTokenCredentials");
const { ClientSecretCredential } = require("#azure/identity");
const { clientId, clientSecret, scopes, tenantId } = require("./secrets"); // Manage your secret better than this please.
require("isomorphic-fetch");
async function runExample() {
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
const authProvider = new TokenCredentialAuthenticationProvider(credential, { scopes: [scopes] });
const client = Client.initWithMiddleware({
debugLogging: true,
authProvider,
});
const chatMessage = {body: {content: '#.####.#'}};
const res = await client.api('/teams/my-team-id/channels/my-channel-id/messages')
.post(chatMessage);
console.log(res);
}
runExample().catch((err) => {
console.log("Encountered an error:\n\n", err);
});
This sample came from:
https://github.com/microsoftgraph/msgraph-sdk-javascript/tree/dev/samples/tokenCredentialSamples/ClientCredentialFlow