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;
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);
};
// ...
};
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
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)
}
};
I am using the code from Dapp Unversity's trading bot masterclass to try to create a bot that will scan cryptocurrency decentralized exchanges for price differences in token pairs and then execute a smart contract to use a flash loan to profit off of this. When testing, I am able to see run a ganache-cli node and run my scanning bot to listen for swap opportunities on ganache. There is a script designed to create a swap opportunity by swapping a large amount of SHIB for WETH on the test net to see if the smart contract will deploy and execute when a swap opportunity is detected by the bot. However, running this script yields the error
UnhandledPromiseRejectionWarning: Error: Returned error: VM Exception while processing transaction: revert TransferHelper: TRANSFER_FROM_FAILED
Also, in the ganache-cli terminal, I get:
Runtime Error: revert
Revert reason: TransferHelper: TRANSFER_FROM_FAILED
Here are the commands I run to get to the points above:
First, I successfully run ganache-cli -f wss://eth-mainnet.alchemyapi.io/v2/<Your-App-Key> -u 0x0e5069514a3dd613350bab01b58fd850058e5ca4 -p 7545 with my app key.
Then, I successfully run node bot.js in another terminal to scan for swap opportunities on ganache. Finally, I run node scripts\manipulatePrice.JS which outputs "Beginnig Swap... Input token: SHIB Output token: WETH" before outputting the above error.
I have tried using node --trace-warnings to show where the warning was created, but was led nowhere helpful. I am wondering if it has something to do with the Runtime Error: revert message? Below is the code for the manipulatePrice.js script I am trying to run to test my bot. I can attach more code if need be, but don't want to post too much. If anyone has insight as to what or where the issue might be, I would greatly appreciate it!!
require("dotenv").config();
const Web3 = require('web3')
const {
ChainId,
Token,
WETH
} = require("#uniswap/sdk")
const IUniswapV2Router02 = require('#uniswap/v2-periphery/build/IUniswapV2Router02.json')
const IUniswapV2Factory = require("#uniswap/v2-core/build/IUniswapV2Factory.json")
const IERC20 = require('#openzeppelin/contracts/build/contracts/ERC20.json')
// -- SETUP NETWORK & WEB3 -- //
const chainId = ChainId.MAINNET
const web3 = new Web3('http://127.0.0.1:7545')
// -- IMPORT HELPER FUNCTIONS -- //
const { getPairContract, calculatePrice } = require('../helpers/helpers')
// -- IMPORT & SETUP UNISWAP/SUSHISWAP CONTRACTS -- //
const config = require('../config.json')
const uFactory = new web3.eth.Contract(IUniswapV2Factory.abi, config.UNISWAP.FACTORY_ADDRESS) // UNISWAP FACTORY CONTRACT
const sFactory = new web3.eth.Contract(IUniswapV2Factory.abi, config.SUSHISWAP.FACTORY_ADDRESS) // SUSHISWAP FACTORY CONTRACT
const uRouter = new web3.eth.Contract(IUniswapV2Router02.abi, config.UNISWAP.V2_ROUTER_02_ADDRESS) // UNISWAP ROUTER CONTRACT
const sRouter = new web3.eth.Contract(IUniswapV2Router02.abi, config.SUSHISWAP.V2_ROUTER_02_ADDRESS) // UNISWAP ROUTER CONTRACT
// -- CONFIGURE VALUES HERE -- //
const V2_FACTORY_TO_USE = uFactory
const V2_ROUTER_TO_USE = uRouter
const UNLOCKED_ACCOUNT = '0x0e5069514a3Dd613350BAB01B58FD850058E5ca4' // SHIB Unlocked Account
const ERC20_ADDRESS = process.env.ARB_AGAINST
const AMOUNT = '40500000000000' // 40,500,000,000,000 SHIB -- Tokens will automatically be converted to wei
const GAS = 450000
// -- SETUP ERC20 CONTRACT & TOKEN -- //
const ERC20_CONTRACT = new web3.eth.Contract(IERC20.abi, ERC20_ADDRESS)
const WETH_CONTRACT = new web3.eth.Contract(IERC20.abi, WETH[chainId].address)
// -- MAIN SCRIPT -- //
const main = async () => {
const accounts = await web3.eth.getAccounts()
const account = accounts[1] // This will be the account to recieve WETH after we perform the swap to manipulate price
const pairContract = await getPairContract(V2_FACTORY_TO_USE, ERC20_ADDRESS, WETH[chainId].address)
const token = new Token(
ChainId.MAINNET,
ERC20_ADDRESS,
18,
await ERC20_CONTRACT.methods.symbol().call(),
await ERC20_CONTRACT.methods.name().call()
)
// Fetch price of SHIB/WETH before we execute the swap
const priceBefore = await calculatePrice(pairContract)
await manipulatePrice(token, account)
// Fetch price of SHIB/WETH after the swap
const priceAfter = await calculatePrice(pairContract)
const data = {
'Price Before': `1 ${WETH[chainId].symbol} = ${Number(priceBefore).toFixed(0)} ${token.symbol}`,
'Price After': `1 ${WETH[chainId].symbol} = ${Number(priceAfter).toFixed(0)} ${token.symbol}`,
}
console.table(data)
let balance = await WETH_CONTRACT.methods.balanceOf(account).call()
balance = web3.utils.fromWei(balance.toString(), 'ether')
console.log(`\nBalance in reciever account: ${balance} WETH\n`)
}
main()
//
async function manipulatePrice(token, account) {
console.log(`\nBeginning Swap...\n`)
console.log(`Input Token: ${token.symbol}`)
console.log(`Output Token: ${WETH[chainId].symbol}\n`)
const amountIn = new web3.utils.BN(
web3.utils.toWei(AMOUNT, 'ether')
)
const path = [token.address, WETH[chainId].address]
const deadline = Math.floor(Date.now() / 1000) + 60 * 20 // 20 minutes
await ERC20_CONTRACT.methods.approve(V2_ROUTER_TO_USE._address, amountIn).send({ from: UNLOCKED_ACCOUNT })
const receipt = await V2_ROUTER_TO_USE.methods.swapExactTokensForTokens(amountIn, 0, path, account, deadline).send({ from: UNLOCKED_ACCOUNT, gas: GAS });
console.log(`Swap Complete!\n`)
return receipt
}
I ran into this issue for the same bot myself and I found the solution. Instead of using .send you should use .sendSignedTransaction and sign it yourself with the correct parameters. BUT if you are sending from an unlocked account, then use ethers.js to get a signer without knowing the private key!
So, you should replace
await ERC20_CONTRACT.methods.approve(V2_ROUTER_TO_USE._address, amountIn).send({ from: UNLOCKED_ACCOUNT })
const receipt = await V2_ROUTER_TO_USE.methods.swapExactTokensForTokens(amountIn, 0, path, account, deadline).send({ from: UNLOCKED_ACCOUNT, gas: GAS });
With this ethers.js alternative
const { ethers } = require("ethers")
const provider = new ethers.providers.JsonRpcProvider("http://127.0.0.1:8545")
const signer = provider.getSigner(UNLOCKED_ACCOUNT)
console.log(provider.chainId)
/*
Define the above code at the top,
Then put the rest of your code here,
Then use this for the ethers transaction
*/
const approvalReceiptEthers = await signer.sendTransaction({
from: UNLOCKED_ACCOUNT,
to: ERC20_CONTRACT._address,
data: ERC20_CONTRACT.methods.approve(V2_ROUTER_TO_USE._address, web3.utils.fromWei(amountIn).toString()).encodeABI(),
gasLimit: GAS
})
/*
* Verify that your unlocked account is allowed to use the funds
*/
var allowance = await ERC20_CONTRACT.methods.allowance(UNLOCKED_ACCOUNT, V2_ROUTER_TO_USE._address).call()
console.log("ALLOWANCE:\t\t", web3.utils.fromWei(allowance).toString(), 'ether')
console.log("ATTEMPTED AMOUNT:\t", web3.utils.fromWei(amountIn).toString(), 'ether')
const swapReceiptEthers = await signer.sendTransaction({
from: UNLOCKED_ACCOUNT,
to: V2_ROUTER_TO_USE._address,
data: V2_ROUTER_TO_USE.methods.swapExactTokensForTokens(web3.utils.fromWei(amountIn).toString(), 0, path, account, deadline).encodeABI(),
gasLimit: GAS
}).then(console.log)
Note: the following code, will only work provided that you signed the transaction with THE SENDER'S PRIVATE KEY. So, if you are sending a transaction on behalf of your own account, use this code (presumably for bot.js)
const approvalTransaction = {
'from' : 'your account address here',
'to' : ERC20_CONTRACT._address,
'data' : ERC20_CONTRACT.methods.approve(V2_ROUTER_TO_USE._address, web3.utils.fromWei(amountIn).toString()).encodeABI(),
'gas' : GAS
}
const transaction = {
'from' : 'your account address here',
'to' : V2_ROUTER_TO_USE._address,
'data' : V2_ROUTER_TO_USE.methods.swapExactTokensForTokens(web3.utils.fromWei(amountIn).toString(), 0, path, account, deadline).encodeABI(),
'gas' : GAS
}
const signedApprovalTx = await web3.eth.accounts.signTransaction(approvalTransaction, process.env.DEPLOYMENT_ACCOUNT_KEY, )
const signedTx = await web3.eth.accounts.signTransaction(transaction, process.env.DEPLOYMENT_ACCOUNT_KEY)
await web3.eth.sendSignedTransaction(signedApprovalTx.rawTransaction)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
NOTE: For those of you following the same Trading Bot masterclass, this same logic applies to the bot.js code as well when you execute your trade!.
SUMMARY: If you are sending a signed transaction using an unlocked account with ganache-cli, you will need to use ethers.js to sign the message without knowing the private key. Otherwise if you are sending a signed transaction on behalf of yourself, you can use Web3.js to sign the message with your own private
I'm trying to call a Solana Program, and when I run sendAndConfirmTransaction, it gives me Signature Verification Failed, and I'm not sure why.
const {sendAndConfirmTransaction, clusterApiUrl, Connection} = require("#solana/web3.js");
let signer = Keypair.generate();
let anotherKeypair = Keypair.generate();
let transaction = someInstruction(signer, anotherKeypair);
let connection = new Connection(clusterApiUrl('testnet'));
sendAndConfirmTransaction(
connection,
transaction,
[signer]
);
In Solana, you need to pass in both the keypairs of the signer, and the keypairs of the accounts you're creating.
const {sendAndConfirmTransaction, clusterApiUrl, Connection} = require("#solana/web3.js");
let signer = Keypair.generate();
let anotherKeypair = Keypair.generate();
let transaction = someInstruction(signer, anotherKeypair);
let connection = new Connection(clusterApiUrl('testnet'));
sendAndConfirmTransaction(
connection,
transaction,
[signer, anotherKeypair] // <-- If you made the keypair, you probably want it here!
);
If you're using a wallet connect library, like #solana/wallet-adapter-react, you don't have the signer, but you will still have any keypairs of accounts your generating:
const { sendTransaction } = useWallet();
const anotherKeypair = Keypair.generate();
const signature = await sendTransaction(transaction, connection, {
signers: [anotherKeypair] // You probably want to pass in the keypair here!
});
This worked for me.
tx.partialSign
let tx = new Transaction();
const anotherKeypair = Keypair.generate();
tx.recentBlockhash = (await connection.getLatestBlockhash("finalized")).blockhash;
tx.feePayer = new PublicKey(publicKeyStringfromWallet);
console.log(tx)
tx.partialSign(anotherKeypair) // THIS SIGNS for web3 keypair
const { signature } = await window.solana.signAndSendTransaction(tx); //THIS signs wallet keypair
It depends on the isSigner key, if isSigner: true, you need to provide the signer.
For example, the following code will not work because we mention payerAta (Associated Token Account of Payer) has isSigner:true.
const instruction = new TransactionInstruction({
keys: [
{
pubkey: programAta.address,
isSigner: false,
isWritable: true,
},
{
pubkey: payerAta.address,
isSigner: true,
isWritable: true,
},
],
programId,
data: Buffer.alloc(0), // All instructions are hellos
});
await sendAndConfirmTransaction(
connection,
new Transaction().add(instruction),
[payer]
);
}
In this case either we change payerAta to payer, or change the isSigner to false