Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Support associated token for JS (Also, make the program testable) #1364

Merged
merged 16 commits into from
Mar 3, 2021

Conversation

ryoqun
Copy link
Contributor

@ryoqun ryoqun commented Mar 2, 2021

Problem

  • There is no easy way of accessing associated tokens from JS land

Changes

  • Support them
  • Also, make testing more robust including ./program's NFC modifications.

@ryoqun ryoqun requested a review from jstarry March 2, 2021 13:23
@jstarry
Copy link
Contributor

jstarry commented Mar 2, 2021

Unfortunately the type definitions in module.d.ts and module.flow.js have to be manually written for now. Hope to make this better soon after doing it for web3.js here: solana-labs/solana#15484

@@ -41,7 +43,7 @@ pub fn process_instruction(

let associated_token_account_signer_seeds: &[&[_]] = &[
&wallet_account_info.key.to_bytes(),
&spl_token::id().to_bytes(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, spl-associated-program wrongly assumed spl_token isn't position-independent-code; but it actually IS. spent some time to debug... I suspected myself.. It turned out I needed these program changes... my js was correct. lol

@@ -10,5 +10,6 @@ npm install
npm run lint
npm run flow
npm run defs
npm run test
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&spl_token::id().to_bytes(),
&spl_token_mint_address.to_bytes(),
],
get_associated_token_address_and_bump_seed_internal(
Copy link
Contributor Author

@ryoqun ryoqun Mar 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these and following ./program diffs should absolutely have no-functional-changes. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, associated token program actually gets more flexible because it will not error for non-Tokenkeg program id's. But I think that's a nice change :) Do you mind adding the program change details to the PR title and description for posterity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's right. I meant in the context of real production environment (mainnet-beta). :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the subtlety is that deployed program and this helper get_associated_token_address are still hard-coded to the Tokenkeg program id...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I thought you planned to deploy an update to associated token program at some point. Are the program changes just for local testing then?

Copy link
Contributor Author

@ryoqun ryoqun Mar 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jstarry According to this (https://explorer.solana.com/address/ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL), the associated token program isn't upgradable? ;) I wonder it's worth side-loading it... Also, I came up with some niceties: #1371

console.log(
' Note: To reload program remove client/util/store/config.json',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no hard-coding, please. :)

Comment on lines +111 to +114
info = await connection.getAccountInfo(programId);
assert(info != null);
info = await connection.getAccountInfo(associatedProgramId);
assert(info != null);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some extra check for stale caches (where associated programs aren't loaded.)

/**
* The address of this account
*/
address: PublicKey,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed this. Othwerwise, getOrCreateAssociatedAccountInfo is too cumbersome to use.

* @param owner User account that will own the new account
* @return Public key of the new associated account
*/
async getOrCreateAssociatedAccountInfo(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some must-to-have helper.. I realized doing right like this is too hard for most of partners, I think...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is super useful!

Comment on lines +318 to +331
associatedAccount = await testToken.getOrCreateAssociatedAccountInfo(dest);
assert(associatedAccount.amount.toNumber() === 0);

await testToken.transferChecked(
testAccount,
associatedAccount.address,
testAccountOwner,
[],
123,
testTokenDecimals,
);

associatedAccount = await testToken.getOrCreateAssociatedAccountInfo(dest);
assert(associatedAccount.amount.toNumber() === 123);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try {
return await this.getAccountInfo(associatedAddress);
} catch (err) {
if (err.message == FAILED_TO_FIND_ACCOUNT) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth handling the Invalid account owner error as well because otherwise, partners won't be able to create associated token accounts for addresses that receive small lamport transfers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, another reason we need to publish this helper in this library. Too hard to write correct. ;)

jstarry
jstarry previously approved these changes Mar 3, 2021
Copy link
Contributor

@jstarry jstarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryoqun very useful work! looks great

* @param owner User account that will own the new account
* @return Public key of the new associated account
*/
async getOrCreateAssociatedAccountInfo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is super useful!

&spl_token::id().to_bytes(),
&spl_token_mint_address.to_bytes(),
],
get_associated_token_address_and_bump_seed_internal(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, associated token program actually gets more flexible because it will not error for non-Tokenkeg program id's. But I think that's a nice change :) Do you mind adding the program change details to the PR title and description for posterity?

@jstarry
Copy link
Contributor

jstarry commented Mar 3, 2021

Can you also please bump the version in token/js/package.json?

* @return Public key of the new associated account
*/
async getOrCreateAssociatedAccountInfo(
owner: PublicKey,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, I thought about checking the owner to avoid nested associated tokens like this: https://explorer.solana.com/address/7RyJpfyH38Vi5PcnNp4Nmiikc1dnXKv3WFXFY5GoC3Nm

But, the check would need extra rpc round-trip...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's gross. Anything can be the owner of a token account, including tokens haha. I think it's better to leave that out, some day tokens owning tokens will probably be a feature that someone wants 😉

@ryoqun ryoqun changed the title Implement some js helpers for associated tokens Add JS associated tokens helpers, making AssociatedTokenProgram testable Mar 3, 2021
@ryoqun ryoqun changed the title Add JS associated tokens helpers, making AssociatedTokenProgram testable Add JS associated tokens helpers while making the program testable Mar 3, 2021
@ryoqun ryoqun changed the title Add JS associated tokens helpers while making the program testable Support associated token for JS (Also, make the program testable) Mar 3, 2021
@mergify mergify bot dismissed jstarry’s stale review March 3, 2021 10:37

Pull request has been modified.

@ryoqun
Copy link
Contributor Author

ryoqun commented Mar 3, 2021

Can you also please bump the version in token/js/package.json?

@jstarry Like this? 6e12b37

// Assuming program derived addressing is safe, this is the only case
// for the INVALID_ACCOUNT_OWNER in this code-path
if (
err.message === FAILED_TO_FIND_ACCOUNT ||
Copy link
Contributor Author

@ryoqun ryoqun Mar 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(small fyi: btw because we take no user-provided callbacks, this is safe; otherwise, we must guard against throw undefined, iirc; nasty JavaScript... lol)

@ryoqun ryoqun merged commit 68b8da2 into solana-labs:master Mar 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants