How to filter getSignaturesForAddress by token transfer - solana

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.

Related

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

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 transfer tokens from a smart contract wallet to an EOA wallet

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

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.

AWS Lambda API gateway with Cognito - how to use IdentityId to access and update UserPool attributes?

OK I am now days into this and have made significant progress but am still completely stumped about the fundamentals.
My application uses Cognito User Pools for creating and managing users - these are identified on S3 it seems by their IdentityId. Each of my users has their own S3 folder, and AWS automatically gives them a folder name that is equal to the user's IdentityId.
I need to relate the IdentityId to the other Cognito user information but cannot work out how.
The key thing I need is to be able to identify the username plus other cognito user attributes for a given IdentityId - and it's insanely hard.
So the first battle was to work out how to get the IdentityId when a Cognito user does a request via the AWS API Gateway. Finally I got that worked out, and now I have a Cognito user, who does a request to the API Gateway, and my Lambda function behind that now has the IdentityId. That bit works.
But I am completely stumped as to how to now access the Cognito user's information that is stored in the user pool. I can't find any clear information, and certainly no code, that shows how to use the IdentityId to get the Cognito user's attributes, username etc.
It appears that if I use a "Cognito user pool" to authorize my method in API Gateway, then the body mapping template can be used to put Cognito User information such as the sub and the username and email address into the context, BUT I do NOT get the IdentityId.
BUT if I use the AWS_IAM to authorize my method in the API gateway then the body mapping template does the inverse - it gives me the IdentityId but not the Cognito user fields such as sub and username and email.
It's driving me crazy - how can I get the IdentityId and all the Cognito users fields and attributes together into one data structure? The fact that I seem to be only able to get one or the other just makes no sense.
It turns out that to get the IdentityId AND user details at the same time using AWS Lambda/Cognito/API Gateway, you need to have a Lambda function that is authenticated using AWS_IAM (NOT COGNITO_USER_POOLS), you must send your request the AWS API Gateway, BUT it MUST be a signed request, you must then modify the integration request body mapping templates so that you are given the IdentityId in the event (maybe the context? can't remember). Now you have the IdentityId. Phew. Now you must submit the client's Cognito ID token from the front end to the back end. It is important to validate the token - you cannot trust that it has not been tampered with if you do not validate it. To decode and validate the token you must get the keys from your userpool, put them into your script, ensure that you have jwt decoding libraries plus signature validation libraries included in your AWS lambda zipfile. Now your script must validate the token submitted from the front end and then you can get the user details out of the token. Voila! Now you have both IdentityId plus user details such as their sub, username and email address. So easy.
The above is what is takes to get the username associated with an IdentityId using AWS Cognito/Lambda/API Gateway. This took me days to get working.
Can I please say to any Amazon employees who wander across this ........ well it's WAY too hard to get the user details associated with an IdentityId. You need to fix this. It made me angry that this was so hard and burned so much of my time.
The solution:
I did this by modifying an Amazon employees custom authorizer here:
https://s3.amazonaws.com/cup-resources/cup_custom_authorizer_lambda_function_blueprint.zip
as found and described here:
https://aws.amazon.com/blogs/mobile/integrating-amazon-cognito-user-pools-with-api-gateway/
use strict';
let util = require('util');
var jwt = require('jsonwebtoken');
var jwkToPem = require('jwk-to-pem');
var userPoolId = 'YOUR USERPOOL ID';
var region = 'YOUR REGION'; //e.g. us-east-1
var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId;
//https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
// DOWNLOAD FROM https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
let userPoolKeys = {PUT YOUR DOWNLOADED USER POOL KEYS JSON HERE};
var pems = {};
let convertKeysToPems = () => {
var keys = userPoolKeys['keys'];
for(var i = 0; i < keys.length; i++) {
//Convert each key to PEM
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
pems[key_id] = pem;
}
}
exports.handler = function(event, context) {
convertKeysToPems()
console.log(event);
let token = event['body-json'].cognitoUserToken;
console.log(event['body-json'].cognitoUserToken);
ValidateToken(pems, event, context, token);
};
let ValidateToken = (pems, event, context, token) => {
//Fail if the token is not jwt
var decodedJwt = jwt.decode(token, {complete: true});
console.log(decodedJwt)
if (!decodedJwt) {
console.log("Not a valid JWT token");
context.fail("Unauthorized");
return;
}
//Fail if token is not from your UserPool
if (decodedJwt.payload.iss != iss) {
console.log("invalid issuer");
context.fail("Unauthorized");
return;
}
//Reject the jwt if it's not an 'Access Token'
if (decodedJwt.payload.token_use != 'id') {
console.log("Not an id token");
context.fail("Unauthorized");
return;
}
//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log(pems, 'pems');
console.log(kid, 'kid');
console.log('Invalid token');
context.fail("Unauthorized");
return;
}
//Verify the signature of the JWT token to ensure it's really coming from your User Pool
jwt.verify(token, pem, { issuer: iss }, function(err, payload) {
if(err) {
context.fail("Unauthorized");
} else {
let x = decodedJwt.payload
x.identityId = context.identity.cognitoIdentityId
//let x = {'identityId': context['cognito-identity-id'], 'decodedJwt': decodedJwt}
console.log(x);
context.succeed(x);
}
});
}
This problem -- the problem of using the user's sub instead of their identityId in S3 paths and how to set that up -- is 100% solved for me thanks to the help of #JesseDavda's solution to this problem in this issue: https://github.com/aws-amplify/amplify-js/issues/54
For all of you developers who have been trying to get the identityId in lambdas so that your Amplify default paths in S3 work - this solution simply ends up ignoring identityId altogether - it is a solution that sets up the paths in S3 based on sub instead of the identityId. At the end of this solution, you will never have to deal with more than one id for your users, you will never have to deal with identityId (hopefully) ever again.
If I'm understanding this correctly you want the CognitoIdentityId and the User attributes in the same place. How we do it is the following way:
From the event request context we get the IdentityId:
event.requestContext.identity.cognitoIdentityId
Also from the request context we get the user's sub:
event.requestContext.identity.cognitoAuthenticationProvider.split(':CognitoSignIn:')[1]
Then with the sub you can request the rest of the attributes the following way:
const AWS = require('aws-sdk');
let cognito = new AWS.CognitoIdentityServiceProvider();
let request = {
Username: userSub,
UserPoolId: process.env.userPoolId,
};
let result = await cognito.adminGetUser(request).promise();
const userAttributes = result.UserAttributes.reduce((acc, attribute) => {
const { Name, Value } = attribute;
acc[Name] = Value;
return acc;
}, {});
return userAttributes;

Resources