Skip to content

Commit

Permalink
Added a WrappedSolAccountCreateMethod (ata) using syncNative (orca-so#66
Browse files Browse the repository at this point in the history
)

* Added a WrappedSolAccountCreateMethod (ata) using syncNative

* Version bump

* Create ata instruction

* Cleanup

* Set default back to keypair

* Amend

* Update token-util.ts
  • Loading branch information
wjthieme authored Jan 8, 2024
1 parent 8091261 commit 5fb20b4
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 10 deletions.
2 changes: 1 addition & 1 deletion packages/common-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@orca-so/common-sdk",
"version": "0.3.5",
"version": "0.3.6",
"description": "Common Typescript components across Orca",
"repository": "https://github.com/orca-so/orca-sdks",
"author": "Orca Foundation",
Expand Down
2 changes: 1 addition & 1 deletion packages/common-sdk/src/web3/ata-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export async function resolveOrCreateATAs(
ownerAddress,
wrappedSolAmountIn,
accountRentExempt,
undefined, // use default
payer,
undefined, // use default
wrappedSolAccountCreateMethod
);
Expand Down
83 changes: 77 additions & 6 deletions packages/common-sdk/src/web3/token-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import {
AccountLayout,
NATIVE_MINT,
TOKEN_PROGRAM_ID,
createAssociatedTokenAccountIdempotentInstruction,
createCloseAccountInstruction,
createInitializeAccountInstruction,
createSyncNativeInstruction,
createTransferCheckedInstruction,
getAssociatedTokenAddressSync,
} from "@solana/spl-token";
import { Connection, Keypair, PublicKey, SystemProgram } from "@solana/web3.js";
import { Connection, Keypair, PublicKey, SystemProgram, TransactionInstruction } from "@solana/web3.js";
import { sha256 } from '@noble/hashes/sha256';
import BN from "bn.js";
import invariant from "tiny-invariant";
Expand All @@ -23,7 +25,7 @@ export type ResolvedTokenAddressInstruction = {
/**
* @category Util
*/
export type WrappedSolAccountCreateMethod = "keypair" | "withSeed";
export type WrappedSolAccountCreateMethod = "keypair" | "withSeed" | "ata";

/**
* @category Util
Expand Down Expand Up @@ -52,11 +54,36 @@ export class TokenUtil {
createAccountMethod: WrappedSolAccountCreateMethod = "keypair",
): ResolvedTokenAddressInstruction {
const payerKey = payer ?? owner;
const unwrapDestinationKey = unwrapDestination ?? payer ?? owner;
const unwrapDestinationKey = unwrapDestination ?? owner;

return createAccountMethod === "keypair"
? createWrappedNativeAccountInstructionWithKeypair(owner, amountIn, rentExemptLamports, payerKey, unwrapDestinationKey)
: createWrappedNativeAccountInstructionWithSeed(owner, amountIn, rentExemptLamports, payerKey, unwrapDestinationKey);
switch (createAccountMethod) {
case "ata":
return createWrappedNativeAccountInstructionWithATA(
owner,
amountIn,
rentExemptLamports,
payerKey,
unwrapDestinationKey
);
case "keypair":
return createWrappedNativeAccountInstructionWithKeypair(
owner,
amountIn,
rentExemptLamports,
payerKey,
unwrapDestinationKey
);
case "withSeed":
return createWrappedNativeAccountInstructionWithSeed(
owner,
amountIn,
rentExemptLamports,
payerKey,
unwrapDestinationKey
);
default:
throw new Error(`Invalid createAccountMethod: ${createAccountMethod}`);
}
}

/**
Expand Down Expand Up @@ -129,6 +156,50 @@ export class TokenUtil {
}
}

function createWrappedNativeAccountInstructionWithATA(
owner: PublicKey,
amountIn: BN,
_rentExemptLamports: number,
payerKey: PublicKey,
unwrapDestinationKey: PublicKey,
): ResolvedTokenAddressInstruction {
const tempAccount = getAssociatedTokenAddressSync(NATIVE_MINT, owner);

const instructions: TransactionInstruction[] = [
createAssociatedTokenAccountIdempotentInstruction(
payerKey,
tempAccount,
owner,
NATIVE_MINT
)
];

if (amountIn.gt(ZERO)) {
instructions.push(SystemProgram.transfer({
fromPubkey: payerKey,
toPubkey: tempAccount,
lamports: amountIn.toNumber(),
}));

instructions.push(createSyncNativeInstruction(
tempAccount,
));
}

const closeWSOLAccountInstruction = createCloseAccountInstruction(
tempAccount,
unwrapDestinationKey,
owner
);

return {
address: tempAccount,
instructions,
cleanupInstructions: [closeWSOLAccountInstruction],
signers: [],
};
}

function createWrappedNativeAccountInstructionWithKeypair(
owner: PublicKey,
amountIn: BN,
Expand Down
63 changes: 61 additions & 2 deletions packages/common-sdk/tests/web3/ata-util.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
AccountLayout,
NATIVE_MINT,
TOKEN_PROGRAM_ID,
Expand Down Expand Up @@ -300,7 +301,7 @@ describe("ata-util", () => {
fail("should be failed");
} catch (e: any) {
expect(e.name).toMatch("TokenOwnerOffCurveError");
}
}
});

it("resolveOrCreateATA, allowPDAOwnerAddress = true", async () => {
Expand All @@ -322,7 +323,65 @@ describe("ata-util", () => {
);
} catch (e: any) {
fail("should be failed");
}
}
});

it("resolveOrCreateATA, wrappedSolAccountCreateMethod = ata", async () => {
const { connection, wallet } = ctx;

const wrappedSolAccountCreateMethod = "ata";

const resolved = await resolveOrCreateATA(
connection,
wallet.publicKey,
NATIVE_MINT,
() => connection.getMinimumBalanceForRentExemption(AccountLayout.span),
new BN(LAMPORTS_PER_SOL),
wallet.publicKey,
false,
false,
wrappedSolAccountCreateMethod,
);

expect(resolved.instructions.length).toEqual(3);
expect(resolved.instructions[0].programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)).toBeTruthy();
expect(resolved.instructions[1].programId.equals(SystemProgram.programId)).toBeTruthy();
expect(resolved.instructions[2].programId.equals(TOKEN_PROGRAM_ID)).toBeTruthy();
expect(resolved.cleanupInstructions.length).toEqual(1);
expect(resolved.cleanupInstructions[0].programId.equals(TOKEN_PROGRAM_ID)).toBeTruthy();
expect(resolved.signers.length).toEqual(0);

const builder = new TransactionBuilder(connection, wallet);
builder.addInstruction(resolved);
await expect(builder.buildAndExecute()).resolves.toBeTruthy();
});

it("resolveOrCreateATA, wrappedSolAccountCreateMethod = ata, amount = 0", async () => {
const { connection, wallet } = ctx;

const wrappedSolAccountCreateMethod = "ata";

const resolved = await resolveOrCreateATA(
connection,
wallet.publicKey,
NATIVE_MINT,
() => connection.getMinimumBalanceForRentExemption(AccountLayout.span),
ZERO,
wallet.publicKey,
false,
false,
wrappedSolAccountCreateMethod,
);

expect(resolved.instructions.length).toEqual(1);
expect(resolved.instructions[0].programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)).toBeTruthy();
expect(resolved.cleanupInstructions.length).toEqual(1);
expect(resolved.cleanupInstructions[0].programId.equals(TOKEN_PROGRAM_ID)).toBeTruthy();
expect(resolved.signers.length).toEqual(0);

const builder = new TransactionBuilder(connection, wallet);
builder.addInstruction(resolved);
await expect(builder.buildAndExecute()).resolves.toBeTruthy();
});

it("resolveOrCreateATA, wrappedSolAccountCreateMethod = keypair", async () => {
Expand Down

0 comments on commit 5fb20b4

Please sign in to comment.