@robertprp/intents-sdk
Version:
Shogun Network Intent-based cross-chain swaps SDK
143 lines (119 loc) • 4.75 kB
text/typescript
import {
address,
addSignersToTransactionMessage,
appendTransactionMessageInstructions,
createNoopSigner,
createSolanaRpc,
createTransactionMessage,
fetchEncodedAccount,
generateKeyPairSigner,
getAddressEncoder,
getProgramDerivedAddress,
getTransactionCodec,
partiallySignTransactionMessageWithSigners,
pipe,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
type Address,
type IInstruction,
} from '@solana/kit';
import type { DcaSingleChainOrder } from '../../orders/dca-single-chain.js';
import type { SolanaOrderInstructionResult } from '../order-instructions.js';
import { getDefaultSolanaRPC } from '../client.js';
import { genSecretHashAndNumber } from '../utils.js';
import {
NATIVE_SOLANA_TOKEN_ADDRESS,
SINGLE_CHAIN_GUARD_ADDRESSES,
WRAPPED_SOL_MINT_ADDRESS,
} from '../../../constants.js';
import {
ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
fetchMint,
getCreateAssociatedTokenInstructionAsync,
getSyncNativeInstruction,
} from '@solana-program/token';
import { ChainID } from '../../../chains.js';
import { getTransferSolInstruction } from '@solana-program/system';
import { getCreateDcaOrderInstructionAsync } from '../generated/single-chain/index.js';
export async function getSolanaDcaSingleChainOrderInstructions(
order: DcaSingleChainOrder,
options?: { rpcUrl?: string },
): Promise<SolanaOrderInstructionResult & { secretNumber: string }> {
const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
const orderSigner = await generateKeyPairSigner();
const signer = createNoopSigner(order.user as Address);
let tokenInMint = address(order.tokenIn);
const { secretHash, secretNumber } = genSecretHashAndNumber(order);
const orderUserAddress = address(order.user);
const spendingNative = tokenInMint === NATIVE_SOLANA_TOKEN_ADDRESS;
if (spendingNative) {
tokenInMint = WRAPPED_SOL_MINT_ADDRESS;
}
const tokenMintProgram = await fetchMint(rpc, tokenInMint);
const guardAddress = address(SINGLE_CHAIN_GUARD_ADDRESSES[ChainID.Solana]); // Assuming DCA uses same guard
const addressEncoder = getAddressEncoder();
const instructions: Array<IInstruction> = [];
const [tokenInProgramAccount] = await getProgramDerivedAddress({
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
seeds: [
addressEncoder.encode(orderUserAddress),
addressEncoder.encode(tokenMintProgram.programAddress),
addressEncoder.encode(tokenInMint),
],
});
const userTokenInAccount = await fetchEncodedAccount(rpc, tokenInProgramAccount);
if (!userTokenInAccount.exists) {
const createAccountIx = await getCreateAssociatedTokenInstructionAsync({
payer: signer,
ata: tokenInProgramAccount,
owner: orderUserAddress,
mint: tokenMintProgram.address,
tokenProgram: tokenMintProgram.programAddress,
});
instructions.push(createAccountIx);
}
const totalAmountIn = BigInt(order.amountInPerInterval) * BigInt(order.totalIntervals);
if (spendingNative) {
const transferIx = getTransferSolInstruction({
amount: totalAmountIn,
destination: userTokenInAccount.address,
source: signer,
});
instructions.push(transferIx);
const syncNativeIx = getSyncNativeInstruction({
account: userTokenInAccount.address,
});
instructions.push(syncNativeIx);
}
const createDcaOrderIx = await getCreateDcaOrderInstructionAsync({
user: signer,
order: orderSigner,
guard: guardAddress,
tokenInMint: tokenInMint,
userTokenInAccount: userTokenInAccount.address,
tokenInProgram: tokenMintProgram.programAddress,
amountInPerInterval: order.amountInPerInterval,
totalIntervals: order.totalIntervals,
intervalDuration: order.intervalDuration,
deadline: order.deadline,
amountOutMin: order.amountOutMin,
secretHash: Uint8Array.from(secretHash),
extraTransfersAmounts: [], // TODO: Implement extra transfers
});
instructions.push(createDcaOrderIx);
const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
const txMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
(tx) => addSignersToTransactionMessage([orderSigner], tx),
);
const partiallySignedTransaction = await partiallySignTransactionMessageWithSigners(txMessage);
const txBytes = getTransactionCodec().encode(partiallySignedTransaction);
return {
orderAddress: orderSigner.address,
txBytes,
secretNumber,
};
}