Sign the payer of the transaction through an API - solana

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);

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)

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.

Stripe returns a "No such token" error (Plaid Integration)

I am getting this error from Stripe when I try to create a new source for the selected bank account. I am using the new (beta) version of the Plaid Node SDK. Here is my code:
let user;
const mode = "sandbox";
const dsService = new CaspioDsService();
// Load the user if not already loaded by cognitoAuth
if (!req.user) {
user = new User(
dsService,
new CaspioRefDataService(),
new AuthUserService({
organizationId: res.locals.organization.Organization_ID,
isAuthenticated: res.locals.isAuthenticated,
})
);
await user.load(req.params.userId);
} else {
user = req.user.userObject;
}
const configuration = new Configuration({
basePath: PlaidEnvironments[mode],
baseOptions: {
headers: {
"PLAID-CLIENT-ID": config.plaid.clientId,
"PLAID-SECRET": mode === "sandbox" ? config.plaid.secretSandbox : config.plaid.secretProduction,
"Plaid-Version": "2020-09-14",
},
},
});
const plaidClient = new PlaidApi(configuration);
console.log(configuration.basePath); // https://sandbox.plaid.com
// Exchange the public token for the Plaid access token
const plaidTokenRes = await plaidClient.itemPublicTokenExchange({
public_token: req.body.publicToken,
});
const accessToken = plaidTokenRes.data.access_token;
console.log(accessToken); // access-sandbox-d92396c2-1f49-4780-9ae9-23d50645f364
// Get the Stripe bank account token from Plaid
const stripeTokenRes = await plaidClient.processorStripeBankAccountTokenCreate({
access_token: accessToken,
account_id: req.body.accountId
});
const bankAccountToken = stripeTokenRes.data.stripe_bank_account_token;
console.log(bankAccountToken); // btok_1JFMGwGq7ejZoSiwGmM8WSSm
let stripeCustomerId = user.getStripeToken();
const stripeClient = await StripeHelper.getStripeClient(mode); // Get Stripe client in sandbox mode
console.log(stripeCustomerId); // cus_Jt7AWZjC8rHPzt
// Add the source to the Stripe customer and get the bank account info
const bankAccount = await stripeClient.customers.createSource(stripeCustomerId, {
source: bankAccountToken,
}); // Error: No such token: 'btok_1JFMGwGq7ejZoSiwGmM8WSSm'
Any ideas what I might be doing wrong? I expect the issue is with my code, or possibly Plaid (I don't think it is a Stripe problem).
It sounds like you're getting a token from Plaid, but Stripe is rejecting it, which suggests a problem with the relationship between your Plaid and Stripe setups. Are you sure that you enabled the Plaid/Stripe integration in the Plaid dashboard and that the client id / secret you're using matches the Plaid account where the integration is enabled? The Plaid docs also suggest that this error can be caused by using a mismatched set of environments (e.g. using Production with Stripe but Sandbox with Plaid).
The problem was that we had the wrong Stripe account connected. Silly one, but I'm posting this in case anyone else makes the same mistake.

Google Chat API (G Suite): Request contains an invalid argument (Node JS)

const { google } = require('googleapis')
const privatekey = require('./a.json')
const scopes = ['https://www.googleapis.com/auth/chat.bot'];
const a = async () => {
try {
const jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
scopes,
'adminEmail#org.com'
);
await jwtClient.authorize();
const chat = google.chat({ version: 'v1', auth: jwtClient });
const res = await chat.spaces.messages.get({name:'spaces/XXX/messages/XX.XX'})
console.log(res)
}
catch(e) {
console.log(e)
}
}
a()
Error: Request contains an invalid argument
I am unable to find the invalid argument
Thanks in advance
Many Hangouts API request require the usage of a service account
You can consult in the documentation which type of requests are affected
For the requests requiring the usage of a service account - it is meant that the service account acts on its own behalf
Impersonation means that the service account acts on behalf of another user
Thus, impersonation is not allowed for requests that need to be carried out by a service account
Also mind that https://www.googleapis.com/auth/chat.bot is the scope to be used by the service account without domain-wide delegation
Users or impersonated service accounts need to use the scope https://www.googleapis.com/auth/chat instead - see also here
Last but not least, chat bots are not allowed to delete messages of other users

Resources