Sign a function call using private key solana/web3 - solana

I am trying to call a certain function of my custom smart contract on the solana blockchain using solana web3. I am trying to sign the function call using my private key instead of signing it through the phantom wallet. Is there any possible way to do that?

You can always compose a call for a solana program manually. Someting like this:
import * as fs from "fs";
import * as web3 from "#solana/web3.js";
const rawPayerKeypair = JSON.parse(fs.readFileSync("PATH_TO_KEYPAIR", "utf-8"));
const payerKeypair = web3.Keypair.fromSecretKey(Buffer.from(rawPayerKeypair));
const programId = new web3.Pubkey("CALLED_PROGRAM_ID");
const url = web3.clusterApiUrl("devnet");
const connection = new web3.Connection(url);
const instruction = new web3.TransactionInstruction({
keys: [
{ pubkey: payerKeypair.publicKey, isSigner: true, isWritable: true }
],
programId,
// Put the transaction instruction here.
data: Buffer.from([])
});
const transaction = new web3.Transaction().add(instruction);
const response = await web3.sendAndConfirmTransaction(connection, transaction, [payerKeypair]);
console.log(response);

Related

Transaction with "SystemProgram.createAccount" results in "Error: Signature verification failed at Transaction.serialize"

What I'm tryding to achieve is a webpage on which the user connects its phantom wallet and then by a simple press of a button creates a token.
In order to achieve that, I created a simple transaction for now which creates an account for the future token (based on https://www.programcreek.com/typescript/?api=#solana/spl-token.createInitializeMintInstruction)
this is the function which is called when the user press the button:
const onClickCreate = useCallback( async () => {
const lamports = await getMinimumBalanceForRentExemptMint(connection)
const mint = Keypair.generate()
console.log(mint.publicKey.toBase58())
const transaction = new Transaction({feePayer: publicKey})
transaction.add(
SystemProgram.createAccount({
fromPubkey: publicKey,
newAccountPubkey: mint.publicKey,
space: MINT_SIZE,
lamports,
programId: TOKEN_PROGRAM_ID
}),
)
const signature = await sendTransaction(transaction, connection);
await connection.confirmTransaction(signature, 'processed');
}, [publicKey, signTransaction, sendTransaction, connection]);
Executing the code results in the error saying that the signature failed.
I use wallet-adapter to connect the user's wallet (which gives me access to the variable publicKey)
If I replace the createAccount function by SystemProgram.transfer everything works ok :
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: Keypair.generate().publicKey,
lamports: lamportsI,
})
);
Thank you for your time
Ok so I checked a little bit more and found someone on the solana forums in Feb 2022 who had kind of the same problem I have : https://forums.solana.com/t/signature-error-when-create-a-new-account/5348/2
the answer by #zicklag worked out for me.
for short, you simply have to make the mint keypair sign the transaction (which seems logic after you think about it)
final code (inside the function called by pressing the button):
if (!publicKey) throw new WalletNotConnectedError();
const lamports = await getMinimumBalanceForRentExemptMint(connection)
const mint = Keypair.generate()
const transaction = new Transaction({ feePayer: publicKey, recentBlockhash: (await connection.getLatestBlockhash()).blockhash });
transaction.add(
SystemProgram.createAccount({
fromPubkey: publicKey,
newAccountPubkey: mint.publicKey,
space: MINT_SIZE,
lamports,
programId: TOKEN_PROGRAM_ID
})
)
transaction.sign(mint)
const txID = await sendTransaction(transaction, connection)

Sign the payer of the transaction through an API

What I would like to achieve is to make the user use the solana program for "free" and make the company pay for the transaction, what I have in mind is:
Extrapolate the transaction in the frontend
Send it to my backend server through an api
Use the wallet that I have on my BE to sing and set this wallet as payer
Send back the transaction
Sign the transaction with the user that is interacting with the FE
Send the transaction from the FE to the solana program.
Let's consider the hello world example https://github.com/solana-labs/example-helloworld
export async function sayHello(): Promise<void> {
console.log('Saying hello to', greetedPubkey.toBase58());
const instruction = new TransactionInstruction({
keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}],
programId,
data: createSetInstruction()
});
console.log(instruction)
await sendAndConfirmTransaction(
connection,
new Transaction().add(instruction),
[payer],
);
}
I guess that in some way I could extrapolate the transaction before the sendAndConfirmTransaction
How can I achieve that and the sign it with my Backend wallet?
Update
In order to manage this problem, I started developing this service: cowsigner.com
You have the entire flow correct, so what you would do is:
const transaction = new Transaction(... your instructions here ...);
const wireTransaction = transaction.serialize();
// send wireTransaction to backend to be signed
// on backend:
const latestBlockhash = await connection.getLatestBlockhash();
transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
transaction.recentBlockhash = latestBlockhash.blockhash;
transaction.feePayer = backendKey.publicKey;
transaction.partialSign(backendKey);
const wireTransactionToSendBack = transaction.serialize();
// send back wireTransactionToSendBack to frontend to be signed by user
// back on frontend:
transaction.partialSign(userKey); // or use a wallet adapter, most likely
const finalWireTransaction = transaction.serialize();
const signature = await connection.sendRawTransaction(finalWireTransaction);

How to create an account on NEAR protocol?

I would like to learn how to create an account using RPC or REST calls on NEAR protocol.
If you want to create a subaccount (a.frol.near when you own frol.near): Submit a transaction with CREATE_ACCOUNT, TRANSFER, ADD_KEY actions. Here is an example of such a transaction.
If you want to create *.near account, you need to submit a transaction with create_account function call on near contract. Here is an example of such a transaction, and here is a code snippet from the tutorial in the docs using near-api-js JS library:
const HELP = `Please run this script in the following format:
node create-testnet-account.js CREATOR_ACCOUNT.testnet NEW_ACCOUNT.testnet AMOUNT
`;
const { connect, KeyPair, keyStores, utils } = require("near-api-js");
const path = require("path");
const homedir = require("os").homedir();
const CREDENTIALS_DIR = ".near-credentials";
const credentialsPath = path.join(homedir, CREDENTIALS_DIR);
const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath);
const config = {
keyStore,
networkId: "testnet",
nodeUrl: "https://rpc.testnet.near.org",
};
if (process.argv.length !== 5) {
console.info(HELP);
process.exit(1);
}
createAccount(process.argv[2], process.argv[3], process.argv[4]);
async function createAccount(creatorAccountId, newAccountId, amount) {
const near = await connect({ ...config, keyStore });
const creatorAccount = await near.account(creatorAccountId);
const keyPair = KeyPair.fromRandom("ed25519");
const publicKey = keyPair.publicKey.toString();
await keyStore.setKey(config.networkId, newAccountId, keyPair);
return await creatorAccount.functionCall({
contractId: "testnet",
methodName: "create_account",
args: {
new_account_id: newAccountId,
new_public_key: publicKey,
},
gas: "300000000000000",
attachedDeposit: utils.format.parseNearAmount(amount),
});
}
If you don't need a named account, you can just generate a new ed25519 key-pair, and the hex representation of the public key will be your account id (it won't be recorded on chain until you/someone transfers some NEAR tokens to it, and so it is called "implicit" account). Example for such an account.
Here is a detailed tutorial on how to construct a transaction. Ultimately, you will submit your transaction via JSON RPC broadcast_tx* endpoints.

Getting address of a token account given a Solana wallet address

I've been trying the code below to get the spl-token account address for a specific token in a Solana wallet from the Solana wallet address, but I am having issues getting the result I am looking for. I run:
const web3 = require('#solana/web3.js');
(async () => {
const solana = new web3.Connection("https://api.mainnet-beta.solana.com");
//the public solana address
const accountPublicKey = new web3.PublicKey(
"2B1Uy1UTnsaN1rBNJLrvk8rzTf5V187wkhouWJSApvGT"
);
//mintAccount = the token mint address
const mintAccount = new web3.PublicKey(
"GLmaRDRmYd4u3YLfnj9eq1mrwxa1YfSweZYYZXZLTRdK"
);
console.log(
await solana.getTokenAccountsByOwner(accountPublicKey, {
mint: mintAccount,
})
);
})();
I'm looking for the token account address in the return, 6kRT2kAVsBThd5cz6gaQtomaBwLxSp672RoRPGizikH4. I get:
{ context: { slot: 116402202 }, value: [ { account: [Object],
pubkey: [PublicKey] } ] }
I can drill down through this a bit using .value[0].pubkey or .value[0].account but ultimately can't get to the information i'm looking for, which is a return of 6kRT2kAVsBThd5cz6gaQtomaBwLxSp672RoRPGizikH4
Does anyone know what is going wrong?
(Note I do not want to use the getOrCreateAssociatedAccountInfo() method, i'm trying to get the token account address without handling the wallets keypair)
ISSUE SOLVED:
I needed to grab the correct _BN data and convert, solution below.
const web3 = require('#solana/web3.js');
(async () => {
const solana = new web3.Connection("https://api.mainnet-beta.solana.com");
//the public solana address
const accountPublicKey = new web3.PublicKey(
"2B1Uy1UTnsaN1rBNJLrvk8rzTf5V187wkhouWJSApvGT"
);
//mintAccount = the token mint address
const mintAccount = new web3.PublicKey(
"GLmaRDRmYd4u3YLfnj9eq1mrwxa1YfSweZYYZXZLTRdK"
);
const account = await solana.getTokenAccountsByOwner(accountPublicKey, {
mint: mintAccount});
console.log(account.value[0].pubkey.toString());
})();
That will certainly work as a heavy-handed approach. As an alternative, you can try to just search for the associated token account owned by that wallet without creating it if it doesn't exist.
You would essentially follow the logic at https://github.com/solana-labs/solana-program-library/blob/0a61bc4ea30f818d4c86f4fe1863100ed261c64d/token/js/client/token.js#L539
except without the whole catch block.

How can i solve a type error causing heroku app to crash?

I am getting a weird error from heroku stating: TypeError: OAuth2Strategy requires a clientID option.
I solved one error with this regarding a script task. I have tried removing files and rebuilding the entire apllication
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const mongoose = require('mongoose');
const keys = require('../config/keys');
const googleClientID = keys.googleClientID
const googleClientSecret = keys.googleClientSecret
const User = mongoose.model('users');
//Takes user model and passes it into a cookie
//Use Mongo autogenerated ID
passport.serializeUser((user, done)=>{
done(null, user.id)
});
passport.deserializeUser((id, done)=>{
User.findById(id)
.then((user)=>{
done(null, user);
})
});
// Creates a new instance of google strategy
// Tells Google how to authenticate Google strategies
passport.use(new GoogleStrategy({
clientID: googleClientID,
clientSecret: googleClientSecret,
callbackURL: '/auth/google/callback',
proxy: true
},
//Whenever we reach out to our database it is an asynchronous
async (accessToken, refreshToken, profile, done ) =>{
const existingUser = await User.findOne({googleID: profile.id})
if(existingUser){
console.log(profile.emails[0].value)
done(null, existingUser)
}
const user = await new User({googleID: profile.id, email: profile.emails[0].value, name: profile.displayName}).save()
done(null, user);
}));

Resources