Solana web3.Transaction.from return null - node.js

Here my code:
const web3 = require('#solana/web3.js');
const connection = new web3.Connection('https://solana-api.projectserum.com');
connection.onAccountChange(
wallet.publicKey,
(updatedAccountInfo, context) => {
let tx = web3.Transaction.from(updatedAccountInfo.data);
console.log('TX: ', tx);
},
'confirmed',
);
When Solana comes to my wallet, or when I send Solana via Solana CLI, the onAccountChange event is triggered, but shows null:
What am I doing wrong and how do I read the transaction data?

The callback for onAccountChange() returns an AccountInfo not a transaction - So you can read information about the account (Lamports, Owner, ...), but not the info on the transaction that triggered the change. Typescript will help datatype resolution.
https://solana-labs.github.io/solana-web3.js/classes/Connection.html#onAccountChange

You could use onAccountChange to listen for events when account changes in any way. In the callback that you've provided to the onAccountChange, you can call getConfirmedSignaturesForAddress2 to get transactions signatures and later on you can call getTransactions by providing signatures you've received in previous step.

Related

Get raw transaction from transaction Id using web3js or something similar in nodejs

So i will give an example let's say i have the transaction id https://etherscan.io/tx/0xafef64d0d03db9f13c6c3f8aec5902167ea680bd0ffa0268d89a426d624b2ae1
In etherscan i can use their menu to see the raw transaction which will be
0xf8a915850516aab3ad82be7c947d1afa7b718fb893db30a3abc0cfc608aacfebb080b844095ea7b300000000000000000000000022b1cbb8d98a01a3b71d034bb899775a76eb1cc2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff26a003daa35e6aa4a7cd439133411a390ff0796420d5cb39e9e276db75b01218ed41a028605f36c601527bd435b36da5403ee972fc2fb2c8f70959199bcdba0d0e8c77
I can't get this with etherscan api or geth rpc , i have found eth_getRawTransaction but it give just 0x for transfer transaction is there anyway i can find the raw transaction using nodejs code
thank you
I think i need to rlp encode the transaction or something like that , i'm lost
A raw transaction is an object. This hex string represents a serialized transaction object.
Example code below.
Note that ethers requires you to parse out the signature and other params of an already mined/validated transaction, and pass the signature as a second argument. There's probably a more generic way of doing that, I couldn't think of any right now.
const tx = await provider.getTransaction("0xafef64d0d03db9f13c6c3f8aec5902167ea680bd0ffa0268d89a426d624b2ae1");
const unsignedTx = {
to: tx.to,
nonce: tx.nonce,
gasLimit: tx.gasLimit,
gasPrice: tx.gasPrice,
data: tx.data,
value: tx.value,
chainId: tx.chainId
};
const signature = {
v: tx.v,
r: tx.r,
s: tx.s
}
const serialized = ethers.utils.serializeTransaction(unsignedTx, signature);
console.log(serialized);
Docs:
https://docs.ethers.org/v5/api/providers/provider/#Provider-getTransaction
https://docs.ethers.org/v5/api/utils/transactions/#utils-serializeTransaction
Thank you for your answers
for the ones looking if you are hosting your own geth node , it doesn't index all the transactions
You need to run:
geth with --txlookuplimit 0
Then, the rpc call eth_getRawTransactionByHash will do the job
my mistake

Solana program to send multiple lamport transfers and emit event

I’m building a program intended to manage multiple payments with one call. The program needs to complete the following steps:
Accept a certain amount of lamports
Pay a portion of the received lamports to specified wallets, such that the amount received is exhausted
Emit an event containing the receipt
I’ve built this logic with an Ethereum smart contract and it works perfectly fine, however when attempting to write a Solana program with Solang and #solana/solidity, I’m running into a number of issues.
The first issue I encountered was simply that #solana/solidity seems to not be built for front end use (transactions required a private key as an argument, rather than being signed by a wallet like Phantom) so I built a fork of the repository that exposes the transaction object to be signed. I also found that the signer’s key needed to be manually added to the array of keys in the transaction instruction — see this Stack Overflow post for more information, including the front end code used to sign and send the transaction.
However, after this post I ran into more errors, take the following for example:
Transaction simulation failed: Attempt to debit an account but found no record of a prior credit.
Transaction simulation failed: Error processing Instruction 0: instruction changed the balance of a read-only account
Program jdN1wZjg5P4xi718DG2HraGuxVx1mM7ebjXpxbJ5R3N invoke [1]
Program data: PO+eZwYByRZpDC4BOjWoKPj20gquFc/JtyxU9NsuG/Y= DEjYtM7vwjNW3HPewJU3dvG4aiov5tUUlrD6Zz5ylBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADppnQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATEtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVNC02S0gyMV9Sa3RZZVJIb3FKOFpFAAAAAAAAAAAAAAA=
Program jdN1wZjg5P4xi718DG2HraGuxVx1mM7ebjXpxbJ5R3N consumed 3850 of 200000 compute units
Program jdN1wZjg5P4xi718DG2HraGuxVx1mM7ebjXpxbJ5R3N success
failed to verify account 11111111111111111111111111111111: instruction changed the balance of a read-only account
The error messages seemed to be inconsistent, with some attempts throwing different errors despite the only changes in the code being a server restarting or a library being reinstalled.
Although solutions to the previous errors would be greatly appreciated, at this point I’m more inclined to ask more broadly if what I’m trying to do is possible, and, providing the source code, for help understanding what I need to do to make it work.
Below is the working source code for my Ethereum contract:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
contract MyContract {
event Receipt(
address From,
address Token,
address[] Receivers,
uint256[] Amounts,
string Payment
);
function send(
address[] calldata _receivers,
uint256[] calldata _amounts,
string calldata _payment
) external payable {
require(
_receivers.length == _amounts.length,
"Receiver count does not match amount count."
);
uint256 total;
for (uint8 i; i < _receivers.length; i++) {
total += _amounts[i];
}
require(
total == msg.value,
"Total payment value does not match ether sent"
);
for (uint8 i; i < _receivers.length; i++) {
(bool sent, ) = _receivers[i].call{value: _amounts[i]}("");
require(sent, "Transfer failed.");
}
emit Receipt(
msg.sender,
0x0000000000000000000000000000000000000000,
_receivers,
_amounts,
_payment
);
}
}
The only differences between this code and my Solana program code are types and the method used to transfer lamports. All references to uint256 is replaced by uint64, the placeholder token address is changed from the null address to the system public key (address"11111111111111111111111111111111"), and the payment loop is changed to the following:
for (uint8 i = 0; i < _receivers.length; i++) {
payable(_receivers[i]).transfer(_amounts[i]); // Using .send() throws the same error
}
The code used to then deploy the program to the Solana test validator is as follows, only slightly modified from the example provided by #solana/solidity:
const { Connection, LAMPORTS_PER_SOL, Keypair, PublicKey } = require('#solana/web3.js');
const { Contract } = require('#solana/solidity');
const { readFileSync } = require('fs');
const PROGRAM_ABI = JSON.parse(readFileSync('./build/sol/MyProgram.abi', 'utf8'));
const BUNDLE_SO = readFileSync('./build/sol/bundle.so');
(async function () {
console.log('Connecting to your local Solana node');
const connection = new Connection('http://localhost:8899', 'confirmed');
const payer = Keypair.generate();
async function airdrop(pubkey, amnt) {
const sig = await connection.requestAirdrop(pubkey, amnt * LAMPORTS_PER_SOL);
return connection.confirmTransaction(sig);
}
console.log('Airdropping SOL to a new wallet');
await airdrop(payer.publicKey, 100);
const program = new Keypair({
publicKey: new Uint8Array([...]),
secretKey: new Uint8Array([...])
});
const storage = new Keypair({
publicKey: new Uint8Array([...]),
secretKey: new Uint8Array([...])
});
const contract = new Contract(connection, program.publicKey, storage.publicKey, PROGRAM_ABI, payer);
console.log('Loading the program');
await contract.load(program, BUNDLE_SO);
console.log('Deploying the program');
await contract.deploy('MyProgram', [], program, storage, 4096 * 8);
console.log('Program deployed!');
process.exit(0);
})();
Is there something I’m misunderstanding or misusing here? I find it hard to believe that such simple behavior on the Ethereum blockchain couldn’t be replicated on Solana — especially given the great lengths the community has gone to to make Solana programming accessible through Solidity. If there’s something I’m doing wrong with this code I’d love to learn. Thank you so much in advance.
Edit: After upgrading my solang version, the first error was fixed. However, I'm now getting another error:
Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: instruction changed the balance of a read-only account
I'm not sure which account is supposedly read-only, as it isn't listed in the error response, but I'm pretty sure the only read-only account involved is the program as it's executable. How can I avoid this error?
The error Attempt to debit an account but found no record of a prior credit happens when you attempt to airdrop more than 1 SOL. If you wish to have more than 1 SOL, then airdrop 1 SOL in a loop until you have enough.

Call a smart contract function using INFURA

I can not understand how I can prepare the transaction, sign it and then send it using INFURA. I expect to get something wrong with the NodeJS code I posted. Can anyone kindly help me solve this problem?
Note: I am able to send the transaction on my smart contract, but the EVM reverts the execution.
Part of the Smart Contract (Solidity) (the function that I want to call):
function testFunction() public pure returns (string memory) {
return "testSuccess";
}
The function used in NodeJS:
const printRequestTransaction = async (gcodeHash) => {
log(`Preparing a transaction to register that the gcode has been sent`.yellow);
// With every new transaction send using a specific wallet address, it needed to increase a nonce which is tied to the sender wallet
let nonce = await web3.eth.getTransactionCount(web3.eth.defaultAccount);
// Fetch the current transaction gas prices from https://ethgasstation.info/
let gasPrices = await getCurrentGasPrices();
// Build a new transaction object and sign it locally
let details = {
"to": process.env.SMART_CONTRACT_ADDRESS,
//"value": web3.utils.toHex(web3.utils.toWei(amountToSend.toString(), 'ether')),
"gas": 210000,
"gasPrice": gasPrices.low * 1000000000, // converts the gwei price to wei
"nonce": nonce,
//"data": gcodeHash,
"function": "testFunction",
"chainId": 3 // EIP 155 chainId - mainnet: 1, ropsten: 3, rinkeby: 4 (https://ethereum.stackexchange.com/questions/17051/how-to-select-a-network-id-or-is-there-a-list-of-network-ids/17101#17101)
};
const transaction = new EthereumTx(details);
// This is where the transaction is authorized on Local PC behalf. The private key is what unlocks Local PC wallet.
transaction.sign(Buffer.from(process.env.WALLET_PRIVATE_KEY, 'hex'));
// Compress the transaction info down into a transportable object
const serializedTransaction = transaction.serialize();
// The Web3 library is able to automatically determine the "from" address based on Local PC private key
// Submit the raw transaction details to the provider configured above (INFURA)
const transactionHash = await web3.eth.sendSignedTransaction('0x' + serializedTransaction.toString('hex')).then(function (receipt) {
log(`result of the invokation: ${receipt})`.red);
return receipt.transactionHash;
}).catch((err) => {
log(`error occurred: ${err})`.red);
});
//const transactionHash = "exampleHash";
_transactionHash = transactionHash;
// Now it is known the transaction ID, so let's build the public Etherscan url where the transaction details can be viewed.
const url = `https://ropsten.etherscan.io/tx/${transactionHash}`;
log(url.cyan);
log(`Note: please allow for 30 seconds before transaction appears on Etherscan`.yellow)
};
Output:
error occurred: Error: Transaction has been reverted by the EVM:
{
"blockHash": "0x6205c1b8ce2fde3693e85843dce684ff11472e9df01fd850bc99e5771b3262d5",
"blockNumber": 5024524,
"contractAddress": null,
"cumulativeGasUsed": "0x50170f",
"from": "0x8882528C7104e146E0500203C353C09922575385",
"gasUsed": "0x5236",
"logs": [],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"status": "0x0",
"to": "0x1732089F6F6EeFA942871707c5AE1909B60774EB",
"transactionHash": "0xf748c9a6bdb19e776db4d8a9802d2bf5f8b588158da387029529249aca00a499",
"transactionIndex": 1
})
I would like to pay particular attention to the let details = {...}; object for which I am not very sure it is correct, I have a little confusion about this.

How to get Smart Contract address when it is deployed with web3.js

I have tried to deploy a SmartContract from web3.js node library, I am getting a transaction hash from it but how would I get the contract address after It's been mined by a miner?
finally i got the answer
var Tx=require('ethereumjs-tx')
const Web3=require('web3')
const web3 = new Web3('https://rinkeby.infura.io/xxxxxxxxxxxxxxxxxx')
const account1='0xf2b6xxxxxxxxxxxxxxxxxxx83e9d52d934e5c'
const privateKey1=Buffer.from('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx','hex')
web3.eth.getTransactionCount(account1,(err,txCount)=>{
//smart contract data
const data = 'your data here'
//create transaction object
const txObject={
nonce:web3.utils.toHex(txCount),
gasLimit:web3.utils.toHex(1000000),
gasPrice:web3.utils.toHex(web3.utils.toWei('10','gwei')),
data: data
}
//sign the transaction
const tx = new Tx(txObject)
tx.sign(privateKey1)
const serializedTx = tx.serialize()
const raw='0x'+serializedTx.toString('hex')
//broadcast the transaction
web3.eth.sendSignedTransaction(raw,(err,txHash)=>{
console.log('err : ',err,'txHash : ',txHash)
//use this hash to find smartcontract on etherscan
}).on('receipt', console.log,);
})
.on() method wait till the end of block mining and returns the address of transaction(here contract address). This method is applicable if you don't want to use metamask to sign your transaction and broadcast to the network.
This returns the contract address...
MyContract is the .json file in the build/contracts folder that is created by migration.
const netId = await web3.eth.net.getId();
const deployedNetwork = MyContract.networks[netId];
const contract = new web3.eth.Contract(
MyContract.abi,
deployedNetwork.address
);
Add .address after the object.
var contact = web3.eth.contract.new(abi,{from: web3.eth.accounts[0], data: bc});
console.log(contract.address); // Prints address
If you just want to get the smart contract by transaction hash which deployed the smart contract, you can use the the web3.eth.getTransactionReceipt fetches the receipt for a transaction hash. The receipt has a contactAddress field filled in if the transaction was a deployment.
Check this out: https://github.com/ChainSafe/web3.js/issues/3515

Web3 - can ETH transaction be cancelled?

I am trying to create a website where ethereum transaction can be made.
If I make an eth transaction using eth.sendTransaction({from:sender, to:receiver, value: amount}) can this transaction be cancelled?
I am asking this because I see that it doesn't take any promise or callback parameters, meaning I would have no idea whether the transaction has been made successfully or not?
Is it possible for web3 transactions to be cancelled? and if it is, how do I make sure that I get notified whether transaction has been made or not? (preferrably using promises or callback rather than having to check wallet every time)
It’s technically possible to cancel a transaction, but highly unlikely. Essentially, the only way to do so would be to try to send another transaction using the same account/nonce combination and hope it gets mined before the transaction you want to cancel is mined. It’s merely a side effect of how nonce is used to guarantee transaction order rather than a cancellation feature.
For your other question, web3.eth.sendTransaction does take a callback. It’s an optional second parameter after the options object and uses the error/result callback style. From the Web3js API:
web3.eth.sendTransaction(transactionObject [, callback])
Function - (optional) If you pass a callback the HTTP request is made asynchronous.
Using callbacks
If you want to make an asynchronous request, you can pass an optional callback as the last parameter to most functions. All callbacks are using an error first callback style
To cancel some ethereum pending transaction you need to:
Create a new transaction where you will send 0 ETH to yourselves.
You should increase the value of gas fees
You need to use the same nonce of the pending transaction.
Using web3js you can do something like this:
async function main() {
require('dotenv').config();
const { API_URL, PRIVATE_KEY } = process.env;
const { createAlchemyWeb3 } = require("#alch/alchemy-web3");
const web3 = createAlchemyWeb3(API_URL);
const myAddress = //put your address
const nonce = //put the nonce of pending tx
const maxFeePerGas = //increase the gas fee value comparated to the pending tx
const transaction = {
'to': myAddress,
'value': 0,
'gas': 21000,
'maxFeePerGas': maxFeePerGas,
'nonce': nonce
};
const signedTx = await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY);
web3.eth.sendSignedTransaction(signedTx.rawTransaction, function(error, hash) {
if (!error) {
console.log("The hash of your transaction is: ", hash);
} else {
console.log(error)
}
});
}
main();

Resources