ATA ownership changed to token program - solana

I have been spending hours trying to understand why my ATA address owner suddenly changed to the token program instead of my account address. I am using PDA to transfer the token from the PDA ATA account but unable to do so since the PDA is no longer the ATA owner.
I tried testing Anchor to dissect the problem and to find solutions, here are my tests console logs:
Mint test result:
Mint: A2ojTC6aQZYP6bwUq1FmWN9kwaQTB7NKQmMs89j4FUkx
Sender ATA: 2KcR41e2NxnYY5DWDzvgzHiKpSoaZJ55kvBiqU111DaY
Sender ATA owner: 7QzoE1okkpgsn7Rx5pxyGDkXMSc3nsqhWitDHc6c8rKb
program ATA: H9SEYZsU5ao1WoUNoVTQjVMBbJLNjJmKA5N1cGfjxLqE
Supply: 100
PDA: 10
User: 90
✔ Mint token! (7001ms)
Mint test script:
it("Mint token!", async () => {
mintPubkey = await createMint(
program.provider.connection, // conneciton
user, // fee payer
user.publicKey, // mint authority
user.publicKey, // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again)
9 // decimals
);
console.log("Mint:", mintPubkey.toBase58())
let tokenAccountPubkeyUser = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, user.publicKey)
console.log("Sender ATA:", tokenAccountPubkeyUser.address.toBase58())
let tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyUser.address);
console.log("Sender ATA owner:", tokenAccountPubkeyUser.owner.toBase58())
let tokenAccountPubkeyPda = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, program.programId)
console.log("program ATA:", tokenAccountPubkeyPda.address.toBase58())
let txhash = await mintToChecked(
program.provider.connection, // connection
user, // fee payer
mintPubkey, // mint
tokenAccountPubkeyUser.address, // receiver (sholud be a token account)
user, // mint authority
100e9, // amount. if your decimals is 9, you mint 10^9 for 1 token.
9 // decimals
);
let tokenSupply = await program.provider.connection.getTokenSupply(mintPubkey);
console.log("Supply:", tokenSupply.value.uiAmount)
txhash = await transferChecked(
program.provider.connection, // connection
user, // payer
tokenAccountPubkeyUser.address, // from (should be a token account)
mintPubkey, // mint
tokenAccountPubkeyPda.address, // to (should be a token account)
user, // from's owner
10e9, // amount, if your deciamls is 9, send 10^9 for 1 token
9 // decimals
);
let tokenAmount = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyPda.address);
console.log("PDA:", tokenAmount.value.uiAmount)
let tokenAmountUser = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyUser.address);
console.log("User:", tokenAmountUser.value.uiAmount)
})
Remove vault test result:
Mint: A2ojTC6aQZYP6bwUq1FmWN9kwaQTB7NKQmMs89j4FUkx
userProfilePDA CanbMWdj5UT8KWCAUwsmMyZFeyG8kWQER2tZQdxTohEK
Last vault: 3
vaultAccountPDA: 8tvqq4zWMGZuoe4tsjuC85WRQY8n5qxZeoyY2Ro7UwGi
vaultInfoPDA: A5E257kztkqdwxeqrjgFzjG2uPmECNX7LD96Vp6Tve7z
tokenProgram: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
receiver ATA: 2KcR41e2NxnYY5DWDzvgzHiKpSoaZJ55kvBiqU111DaY
receiver (user) ATA owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
sender (VaultInfo) ATA: BQhNK47ygEYARanGqJnSjKBco3Crot9ihDMJzT8u7yLU
VaultInfo ATA supply: 10
VaultATA owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Program owner: BPFLoaderUpgradeab1e11111111111111111111111
vaultInfoPDA owner: GsCu69BThDsobWHorHkNf8h8zobN6VsexiYkwkH2VtfV
Remove vault test script:
it("User Vault removed!", async () => {
// Add your test here.
// const tx = await program.methods.initializeUser().rpc();
console.log("user:", user.publicKey)
console.log("program:", program.programId)
console.log("token program:", TOKEN_PROGRAM_ID)
console.log("Mint:", mintPubkey.toBase58())
const [userProfilePDA] = await anchor.web3.PublicKey.findProgramAddress([
utf8.encode("USER_STATE"),
user.publicKey.toBuffer(),
],
program.programId
);
console.log("userProfilePDA", userProfilePDA.toBase58());
const userProfile = await program.account.userProfile.fetch(userProfilePDA);
// console.log("UserProfile:", userProfile)
console.log("Last vault:", userProfile.lastVault)
const [vaultAccountPDA] = await anchor.web3.PublicKey.findProgramAddress([
utf8.encode("VAULT_STATE"),
user.publicKey.toBuffer(),
new anchor.BN(0).toBuffer()
],
program.programId
);
console.log("vaultAccountPDA:", vaultAccountPDA.toBase58());
const [vaultInfoPDA] = await anchor.web3.PublicKey.findProgramAddress([
utf8.encode("INFO_STATE"),
// user.publicKey.toBuffer(),
],
program.programId
);
console.log("vaultInfoPDA:", vaultInfoPDA.toBase58());
let tokenAccountPubkeyUser = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, user.publicKey)
console.log("tokenProgram:", TOKEN_PROGRAM_ID.toBase58())
console.log("receiver ATA:", tokenAccountPubkeyUser.address.toBase58())
let tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyUser.address);
console.log("receiver (user) ATA owner:", tokenAuth.owner.toBase58())
let tokenAccountPubkeyVault = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, vaultInfoPDA, true)
console.log("sender (VaultInfo) ATA:", tokenAccountPubkeyVault.address.toBase58())
let txhash = await transferChecked(
program.provider.connection, // connection
user, // payer
tokenAccountPubkeyUser.address, // from (should be a token account)
mintPubkey, // mint
tokenAccountPubkeyVault.address, // to (should be a token account)
user, // from's owner
10e9, // amount, if your deciamls is 9, send 10^9 for 1 token
9 // decimals
);
let tokenAmount = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyVault.address);
console.log("VaultInfo ATA supply:", tokenAmount.value.uiAmount)
tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyVault.address);
console.log("VaultATA owner:", tokenAuth.owner.toBase58())
tokenAuth = await program.provider.connection.getAccountInfo(program.programId);
console.log("Program owner:", tokenAuth.owner.toBase58())
tokenAuth = await program.provider.connection.getAccountInfo(vaultInfoPDA);
console.log("vaultInfoPDA owner:", tokenAuth.owner.toBase58())
const tx = await program.rpc.removeVault(0, {
accounts: {
authority: user.publicKey,
userProfile: userProfilePDA,
vaultAccount: vaultAccountPDA,
vaultInfo: vaultInfoPDA,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
from: tokenAccountPubkeyVault.address,
to: tokenAccountPubkeyUser.address,
owner: vaultInfoPDA,
// sender: vaultInfoPDA
},
signers: []
})
console.log("Your transaction signature", tx);
});
I am expecting to use the vaultInfoPDA (which should be the owner of the ATA) to send the token out to user removing their vault.
Thanks!

The term owner is overloaded in the context of SPL tokens, which often causes confusion.
When you're logging the owner of the account after calling getAccountInfo, that gives you the program that owns the account, which must be the SPL Token program. The SPL Token program has the right to change the data in the account.
Within that account is also data. In that data, bytes 32-64 define the pubkey that can authorize movements from the account, the SPL token "owner". So there's two owners in one account, one defined by the Solana runtime, and another defined by the SPL Token program.
You can read more about the ownership model at https://docs.solana.com/developing/programming-model/accounts#ownership-and-assignment-to-programs

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 make an auction with the new metaplex js sdk?

I am working on building a solana marketplace and trying to update from the old solana marketplace to auction house.
code:
const authority = metaplex.identity();
const auctionHouse = await metaplex
.auctions()
.createAuctionHouse({
sellerFeeBasisPoints: 200,
requiresSignOff: false,
treasuryMint: WRAPPED_SOL_MINT,
authority: authority.publicKey,
canChangeSalePrice: true,
feeWithdrawalDestination: publicKey,
treasuryWithdrawalDestinationOwner: publicKey,
})
.run();
const listNft = async () => {
try {
const NFTs = await metaplex.nfts().findAllByOwner(publicKey).run();
console.log(NFTs[4].mintAddress.toString(), " nft");
const auctionHouse = await metaplex
.auctions()
.findAuctionHouseByCreatorAndMint(publicKey, WRAPPED_SOL_MINT)
.run();
const listtx = metaplex
.auctions()
.builders()
.createListing({
mintAccount: NFTs[4].mintAddress,
price: lamports(1 * LAMPORTS_PER_SOL),
auctionHouse: auctionHouse,
});
var rpcClient = await metaplex.rpc().sendTransaction(listtx);
console.log(rpcClient, "listed nft");
} catch (error) {
console.log(error);
}
};
const getListing = async () => {
const NFTs = await metaplex.nfts().findAllByOwner(publicKey).run();
const retrievedAuctionHouse = await metaplex
.auctions()
.findAuctionHouseByCreatorAndMint(publicKey, WRAPPED_SOL_MINT)
.run();
console.log(retrievedAuctionHouse?.address?.toString(), "auction pubkey");
const retrieveListing = await metaplex
.auctions()
.for(retrievedAuctionHouse)
.findListingByAddress(NFTs[4].mintAddress)
.run();
console.log(retrieveListing, "retrieving listings");
};
const bidNft = async () => {
const NFTs = await metaplex.nfts().findAllByOwner(publicKey).run();
const auctionHouse = await metaplex
.auctions()
.findAuctionHouseByCreatorAndMint(publicKey, WRAPPED_SOL_MINT)
.run();
const { bid, buyerTradeState } = await metaplex
.auctions()
.for(auctionHouse)
.bid({
mintAccount: NFTs[5].mintAddress,
price: lamports(0.5 * LAMPORTS_PER_SOL),
});
console.log(bid, buyerTradeState, "bidding");
};
errors:
when trying to list nft:
MetaplexError: AuctionHouseProgram > Account is already initialized"
Source: Program > AuctionHouseProgram [hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk]
Problem: The program [AuctionHouseProgram] at address [hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk] raised an error of code [1] that translates to "Account is already initialized"".
Solution: Check the error message provided by the program.
Caused By: TokenLendingError#AlreadyInitialized: Account is already initialized"
at RpcClient.parseProgramError (RpcClient.mjs?e98e:206:1)
at RpcClient.sendTransaction (RpcClient.mjs?e98e:48:1)
When trying to fetch listings:
Account.mjs?9f45:47 Uncaught (in promise) MetaplexError: Account Not Found
Source: SDK
Problem: The account of type [ListingReceipt] was not found at the provided address [3m517hu6UuV5HjdLmb2GxZkttKTtQ8VseHRCPuUTDJmz].
Solution: Ensure the provided address is correct and that an account exists at this address.
at assertAccountExists (Account.mjs?9f45:47:1)
at eval (Account.mjs?9f45:39:1)
at Object.handle (findListingByAddress.mjs?1810:23:44)
at async eval (Task.mjs?1340:58:1)
at async Disposable.run (Disposable.mjs?b308:22:1)
Found a way around
Instead of using metaplex.auction().builder().createListing i used metaplex.auctions().for(auctionHouse).list().
Then funded the auction fee payer account which is a pda.
Finally in findListingByAddress i passed the sellerTradeState
const { listing, sellerTradeState } = await metaplex
.auctions()
.for(auctionHouse)
.list({
mintAccount: NFTs[1].mintAddress,
price: sol(1.5),
})
.run();
For fetching the listed nft
const retrieveListing = await metaplex
.auctions()
.for(retrievedAuctionHouse)
.findListingByAddress(
sellerTradeState
)
.run();
Just from the information provided above, there are two issues:
You might have run the createAuctionHouse instruction already once which created the Auction house and now that you are running the listNft function which also runs createAuctionHouse instruction, it throws an Account already exists error. You can see why this happens here: https://stackoverflow.com/a/72808405/19264629
You are using the NFTs mint address in findListingByAddress instruction, which is wrong. You have to create a ListingReceipt as stated here: https://docs.metaplex.com/guides/auction-house/#auction-house-receipts while creating the listing itself (this needs to be done in one single transaction). Once this is done, you need to plugin the receipt address in the findListingByAddress to fetch the listings.
Got it to work. The trick was to fund the auction house "Fee Account" as explained above and in Auction House CLI: I cannot execute the sale instruction to list my NFT
const ah = await mx
.auctionHouse()
.findByAddress({ address:ahKey })
.run();
const nft = await mx
.nfts()
.create({
name:"TESTNFT",
symbol: "TESTNFT",
uri: "https://blah.com",
sellerFeeBasisPoints : 100,
creators: [
{
address: wallet.publicKey,
share: 100,
}]
})
.run();
const price = sol(0.1);
const { listing, sellerTradeState } = await mx
.auctionHouse()
.list({
auctionHouse: ah,
mintAccount: nft.mintAddress,
price
})
.run()

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

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 to get "ownerof" token on Solana

I'm trying to see how you can get the "ownerof" token like on Ethereum but on the Solana blockchain instead.
For example, I want a user to access a certain part of the website only if they have x token on their phantom wallet (or other Solana wallet).
If you want to understand if a user owns a specific token, you'll have to check if they current own that specific mint's token account and have a balance > 0.
Code would be as follows checking amount that address GKNcUmNacSJo4S2Kq3DuYRYRGw3sNUfJ4tyqd198t6vQ owns of USDC:
import {clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL, ParsedAccountData, PublicKey} from '#solana/web3.js';
import {ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID} from "#solana/spl-token";
const publicKey = new PublicKey("GKNcUmNacSJo4S2Kq3DuYRYRGw3sNUfJ4tyqd198t6vQ");
const mint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const connection = new Connection(clusterApiUrl('mainnet-beta'));
const associatedAddress = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mint,
publicKey
);
console.log(associatedAddress.toBase58());
const tokenAccountInfo = await connection.getParsedAccountInfo(associatedAddress);
console.log((tokenAccountInfo.value?.data as ParsedAccountData).parsed.info.tokenAmount.amount);

Resources