How to transfer tokens from a smart contract wallet to an EOA wallet - metamask

I'm using Next.js, web3-react, ethers.js, MetaMask and Ganache to run a local blockchain.
What I'm trying to do: when the user clicks a button on the front-end, that button opens MetaMask where some tokens (I "minted/created" these tokens) are sent from the smart contract/smart contract creator to the user upon user consent.
The problem is: when the user clicks the button, the MetaMask pop-up appears, but the sender/recipient addresses are the same.
What I want is for the sender address to be the same as the smart contract/smart contract creator and this transaction should be done by MetaMask.
The following code is what I've done so far:
import { ethers, Contract } from 'ethers'
import { useWeb3React } from '#web3-react/core'
import HRC20 from '../../assets/HRC20.json'
const { account: userAccountAddress, library } = useWeb3React()
const provider = new ethers.providers.Web3Provider(
library.provider // this is the same as `window.ethereum.provider` injected by MetaMask
)
const signer = provider.getSigner()
const tokenContract = new ethers.Contract(
'0x4feEc53a54e36C80A2F0a47454Ab285B99A1a240',
HRC20.abi,
provider
)
const tokenContractWithSigner = contract.connect(signer)
const tx = tokenContractWithSigner.transfer(
userAccountAddress,
10000000000
)
My guess is that I need to specify the sender address when creating the provider or signer or something.

Based on calling tokenContractWithSigner.transfer, you have 2 args
function transfer(address _to, uint _value) public returns (bool success){
// make sure callee has enough balance
require(balanceOf[msg.sender]>=_value);
// ... more logic
to=payable(_to)
to.transfer(value);
}
Or:
function transfer(address _to, uint _value) public returns (bool success){
// make sure callee has enough balance
require(balanceOf[msg.sender]>=_value);
// ... more logic
to=payable(_to)
(bool success,)=to.call{value:_value}("");
require(success,"Transfer failed!");
}
When you call this:
tokenContractWithSigner.transfer(
userAccountAddress,
10000000000
).send({from:getAccount})

According to the ERC20 standart, the transfer method of an ERC20 contract is used to transfer tokens from the sender of the transaction, to the address specified. Because it is the same address that is sent the transaction and is given to the transfer method, the contract thinks you want to transfer tokens from an address to the same address.
To mint new tokens you need to add a mint method to your ERC20 contract.

I did find a solution to my problem: first, I can only make a transaction using MetaMask when the sender is me. I cannot send tokens on behalf of another wallet, even if I own it.
My approach was to create an endpoint on the backend that would receive the user's wallet address; then I connect to the wallet that holds the funds (usually the wallet that deployed the token) using its private key and that wallet signs the transaction. On the front end, I listen for a Transfer event so I can notify the user when their transactions are mined/confirmed.
Reference: https://ethereum.org/en/developers/tutorials/send-token-etherjs/#send-token-method

Related

How to filter getSignaturesForAddress by token transfer

When a user sends a spl token to my wallet, i want amount of money in my app increases.
The way I thought of is to continuously load the transaction list of a specific wallet and compare it with the value in the DB to apply the change.
Loading transaction list was successful. But among these, how to filter the transfer of the spl Token?
import * as web3 from '#solana/web3.js';
const solanaConnection = new web3.Connection(web3.clusterApiUrl("mainnet-beta"));
const getTransactions = async(address,lastTransaction) => {
const pubKey = new web3.PublicKey(address);
let transactionList = await solanaConnection.getSignaturesForAddress(pubKey,{until:lastTransaction});
let signatureList = transactionList.map(transaction=>transaction.signature);
console.log(signatureList);
for await (const sig of signatureList) {
console.log(await solanaConnection.getParsedTransaction(sig));
}
}
getTransactions(myAddress,lastTransaction);
Should I parse LogMessage to parsedTransaction to filter it?
and I want to know how many splTokens have been sent.
The easiest way is to fetch those transactions with jsonParsed encoding with getTransaction https://docs.solana.com/developing/clients/jsonrpc-api#gettransaction
Once you have the transaction, you can compare the preTokenBalances and postTokenBalances to see how much has been moved in the transaction.
However, if you're just giving the wallet address, it might not be part of the transaction. If I send you USDC from my wallet into your USDC account, your wallet is not included in that transaction! Because of that, for total coverage, you'll need to poll for the signatures for all of your spl token accounts.

Transfer NFT minted via Metaplex(candy machine)

Does the minted NFT from Metaplex(candy machine) automatically generate the Token Account (as I am planning to transfer it to other owner via Javascript). Also, i am using the function getOrCreateAssociatedTokenAccount to create a token account but my problem is it needs keypair and I dont know where to locate if I a only have a wallet address? Here is my code:
// wallet address of the owner of NFT but I want to get the keypair here since it is the one that is required for getOrCreateAssociatedTokenAccount
const fromWallet = owner_wallet;
// Public Key of buyer
const toWallet = buyer_wallet;
// NFT mint address
let mint = mint_address;
//connection
const connection = use_connection;
// Get the token account of the toWallet address, and if it does not exist, create it
const toTokenAccount = await getOrCreateAssociatedTokenAccount(connection, fromWallet, mint, toWallet);
// Get the token account of the fromWallet address, and if it does not exist, create it (my problem here is 2nd parameter is looking for keypair but i can only provide a wallet address
const fromTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
fromWallet,
mint,
fromWallet.publicKey
);
const signature = await transfer(
connection,
fromWallet,
fromTokenAccount.address,
toTokenAccount.address,
fromWallet.publicKey,
0
);
console.log(`finished transfer with ${signature}`);
Does the minted NFT from Metaplex(candy machine) automatically generate the Token Account --> Yes
(as I am planning to transfer it to other owner via Javascript) --> you need / should create a new token account and not just transfer the ownership of the token account
Also, i am using the function getOrCreateAssociatedTokenAccount to create a token account but my problem is it needs keypair and I dont know where to locate if I a only have a wallet address? --> You need to use the wallet / keypair of the wallet that is the current holder of the NFT.
This question is not related to candy machine but to transferring NFTs. Have a look here to learn how to transfer tokens: https://solanacookbook.com/references/basic-transactions.html#how-to-send-spl-tokens

What's "ATA" mean in Solana/Anchor?

Noticed this code comment in an Anchor test:
What's the ATA stand for there and why does it matter for DAO controlled authorities?
ATA is an associated token address.
Associated token addresses can be derived from another public key using findProgramAddress.
function getAssociatedTokenAddress(
mint: PublicKey,
owner: PublicKey,
allowOwnerOffCurve = false,
programId = TOKEN_PROGRAM_ID,
associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID
): Promise<PublicKey> {
if (!allowOwnerOffCurve && !PublicKey.isOnCurve(owner.toBuffer())) throw new TokenOwnerOffCurveError();
const [address] = await PublicKey.findProgramAddress(
[owner.toBuffer(), programId.toBuffer(), mint.toBuffer()],
associatedTokenProgramId
);
return address;
}
There is another type of token account that predates the associated token account, an ancillary token account. These legacy accounts still exist and can be created today.
The reason that the code says it can be either ATA or something else is because it is validating that the account is a token account that it expects.

Getting Token Account of Solana Account

I have following account Solana Explorer Account
And i can see there is a Mint account ( Account that store my tokens ) how i can get the mint account having my public key is there any relation or how this thing is working in general
Some terminology to be sure we're on the same page:
the "wallet" is the system account that owns other accounts, your linked "Solana Explorer Account" of CGP6sKHyrZGPJRoUAy8XbyzmX7YD4tVBQG9SEe9ekZM6
the mint account defines the token, and does not hold any tokens, is Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr
the account that holds your tokens is actually G6ogFW6YzBpYKhwZrckZJa4yejcjNTfHLE2mUAQFv3ic: https://explorer.solana.com/address/G6ogFW6YzBpYKhwZrckZJa4yejcjNTfHLE2mUAQFv3ic?cluster=devnet -- this is an "associated token account", whose address is derived from your wallet. The tools and explorer default to using that account when dealing with the Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr token. To get this account, you can use getAssociatedTokenAccount, which boils down to:
import { PublicKey } from '#solana/web3.js';
const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
const owner = new PublicKey('CGP6sKHyrZGPJRoUAy8XbyzmX7YD4tVBQG9SEe9ekZM6');
const mint = new PublicKey('Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr');
const [address] = await PublicKey.findProgramAddress(
[owner.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()],
ASSOCIATED_TOKEN_PROGRAM_ID
);
The real code is at: https://github.com/solana-labs/solana-program-library/blob/5611ad8bd595d9e3666f8b115cd28f8116038645/token/js/src/state/mint.ts#L146
const connection = new Connection("https://api.devnet.solana.com");
let response = await connection.getTokenAccountsByOwner(
new PublicKey("27kVX7JpPZ1bsrSckbR76mV6GeRqtrjoddubfg2zBpHZ"), // owner here
{
programId: TOKEN_PROGRAM_ID,
}
);
This is to get all token account owned by specific wallet address. Dont forget to adjust connection url if you are using mainnet or testnet or localhost. You also can filter it to a specific mint address as well.

How to get the result of a payable transaction using near-api-js?

When calling a contract method with attached deposits, you are redirected to the NEAR wallet for approving the transaction. How can the contract frontend app get the result of the transaction after returning from the wallet?
I've faced the same problem. For this moment near-api set transaction info in the browser url. So you get the transaction hash from url after returning from the wallet. Then using transaction hash get info about it using near-api-js:
const { providers } = require("near-api-js");
//network config (replace testnet with mainnet or betanet)
const provider = new providers.JsonRpcProvider(
"https://archival-rpc.testnet.near.org"
);
const TX_HASH = "9av2U6cova7LZPA9NPij6CTUrpBbgPG6LKVkyhcCqtk3";
// account ID associated with the transaction
const ACCOUNT_ID = "sender.testnet";
getState(TX_HASH, ACCOUNT_ID);
async function getState(txHash, accountId) {
const result = await provider.txStatus(txHash, accountId);
console.log("Result: ", result);
}
Documentation: https://docs.near.org/docs/api/naj-cookbook#get-transaction-status
There are 2 options:
Use provider.txStatus like Tom Links said. But the cons : we only know transaction success or fail but not the response from smart contract.
Seperate deposit api and actions api -> User must deposit before call actions api, so we can read the response.

Resources