project-serum / serum-ts

Project Serum TypeScript monorepo
https://projectserum.com
Apache License 2.0
270 stars 245 forks source link

Owner and payer #209

Open stasdem opened 2 years ago

stasdem commented 2 years ago

Hi I want to place orders but I dont understand who are owner and payer and what I need to put in this variables. // Placing orders let owner = new Account('????????'); console.log(owner) let payer = new PublicKey('????????'); // spl-token account console.log(payer) await market.placeOrder(connection, { owner, payer, side: 'buy', // 'buy' or 'sell' price: 14.77, size: 1.0, orderType: 'limit', // 'limit', 'ioc', 'postOnly' }); I have tried to put in owner private key, mnemonic, address. It's all not working Help me please

ashpoolin commented 2 years ago

Hi @stasdem

The correct data type for "owner" is actually Keypair. Don't use Account type since it's deprecated. Confusingly, though, if you want to pay with the owner address you will need to use its public key: const ownerPubkey = owner.publicKey. If you have the tokens in your wallet ("settled" funds), then the payer will be either the ownerPubkey (if paying w/ SOL), or the PublicKey of your SPL token address (your address; NOT the token mint address) for the amount that you are trying to convert (which can also be wrapped SOL). Think of it this way: even for a buy order, the transaction always has a "sell" associated with it (you do not get tokens for free), so the token that is being sold is always the payer. Similarly, if you are placing a sell order, the payer is the SPL token account where the coins come from. Forming those fields may look something like this:

// load owner private key
const owner = Keypair.fromSecretKey(<[64 byte uint8Array]>);
// extract the public key, use owner as payer
const payer = owner.publicKey 

// use public key for any of your SPL token addresses as payer
const payer = new PublicKey('<your SPL token's base58 address>');

For unsettled funds, serum has two holding accounts called base token account and quote token account
In the pair ray/sol, ray is the base token, and sol is the quote token. So, say you want to buy ray, you will use the "quote" token account as the payer (which contains sol). If you were selling ray, then the payer is the "base" token account (containing ray). Either of these accounts' public keys may be accessed as follows:

// use "quote" token account as payer
const quote = await market.findQuoteTokenAccountsForOwner(connection, owner.publicKey, true);
const payer = quote[0].pubkey; 

// use "base" token account as payer
const base = await market.findBaseTokenAccountsForOwner(connection, owner.publicKey, true);
const payer = base[0].pubkey;

Based on where the coins are coming from (settled funds = main SOL address | SPL token address; unsettled funds = quote token account | base token account), you place the public key in place as the payer, and you should be good to go. Again payer is a publicKey type, while owner is actually Keypair.

Hope that helps! ASH

aspin commented 2 years ago

@ashpoolin are there any official docs on this? I also found this confusing.

aspin commented 2 years ago

After playing around with this a bit more, I find myself confused. The two function calls you listed findQuoteTokenAccountsForOwner and findBaseTokenAccountsForOwner just seem to return my SPL Token addresses.

// copy this from Phantom: Settings > Export Private Key
const secretKey = process.env.WALLET_SECRET

const connection = new Connection('https://solana-api.projectserum.com');
const owner = Keypair.fromSecretKey(bs58.decode(secretKey));
const ownerPublicKey = owner.publicKey

// create associated token accounts for spl-token if they don't exist yet
const usdcMint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v')
const usdcToken = new Token(connection, usdcMint, TOKEN_PROGRAM_ID, owner)
const ownerTokenAccount = await usdcToken.getOrCreateAssociatedAccountInfo(ownerPublicKey);

const solUsdcMarket = new PublicKey('9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT');
const serumAddress = new PublicKey('9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin')
const market = await Market.load(connection, solUsdcMarket, {}, serumAddress)

const base = await market.findBaseTokenAccountsForOwner(connection, ownerPublicKey, true)
const solHoldingAccount = base[0].pubkey  // logged this returns my sol address

const quote = await market.findQuoteTokenAccountsForOwner(connection, ownerPublicKey, true)
const usdcHoldingAccount = quote[0].pubkey // logged, this returns my spl token address
ashpoolin commented 2 years ago

It's not documented, but I tried to write down my theories on the different payer accounts in a table in here (about halfway through): https://ashpoolin.github.io/how-tf-do-you-use-serum-ts-client

In the case you mentioned, do you have any unsettled funds in a market? If so maybe the base and quote token accounts might be different. But currently, your base token is SOL, so it makes sense that it's just looking at your main address, and quote token USDC is pointing to the SPL token address. Please pardon my sloppy language, I'm just a blind person going by feel... Anybody who knows the answer please feel free to correct me.

aspin commented 2 years ago

That table is very useful. And yeah, no unsettled funds, so your explanation makes sense. Would be nice if we could figure out a way to map error hex codes to the underlying causes: https://github.com/project-serum/serum-dex/blob/master/dex/src/error.rs That'd at least make debugging these sort of things easier

GiveMeSomething commented 2 years ago

This is actually related to how Solana work. Each account will have many related accounts called Associated Token Account and Solana support a program called Associated Token Account Program. Those account hold token for your account, e.g. USDC, SRM, ... So when you need to pay in token, it will need the correspond token account address as the payer

Link to the Solana docs: Associated Token Account Program

When placeOrder or settleFunds, serum-ts actually try to find those accounts for you so it can deposit/withdraw the fund. In case it don't find any, it will add another instruction to create token accounts using Associated Token Account Program.

A special case is when the token is SOL, where it just considered your main wallet address as a token account.

p/s: The findBaseTokenAccountsForOwner and findQuoteTokenAccountsForOwner implementation is actually the same, they just copy paste it and add market.baseMintAddress and market.quoteMintAddress where needed 😂