@robertprp/intents-sdk
Version:
Shogun Network Intent-based cross-chain swaps SDK
266 lines (222 loc) • 10 kB
text/typescript
import {
address,
appendTransactionMessageInstructions,
compileTransactionMessage,
createNoopSigner,
createSolanaRpc,
createTransactionMessage,
fetchEncodedAccount,
getAddressEncoder,
getCompiledTransactionMessageEncoder,
getProgramDerivedAddress,
pipe,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
type Address,
type IInstruction,
type ReadonlyUint8Array,
type TransactionMessageBytes,
} from '@solana/kit';
import { getDefaultSolanaRPC } from './client.js';
import { fetchMaybeLimitOrder, getCancelLimitOrderInstruction } from './generated/single-chain/index.js';
import {
ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
fetchMint,
getCreateAssociatedTokenInstructionAsync,
} from '@solana-program/token';
import { CROSS_CHAIN_GUARD_ADDRESSES, SINGLE_CHAIN_GUARD_ADDRESSES, SOLANA_MINT_TOKEN } from '../../constants.js';
import { ChainID } from '../../chains.js';
import { fetchMaybeOrder, getCancelOrderInstruction } from './generated/cross-chain/index.js';
export async function cancelSingleChainOrderInstructions(
orderAddress: string,
options?: { rpcUrl?: string },
): Promise<IInstruction[]> {
const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
const orderId = address(orderAddress);
const chainOrder = await fetchMaybeLimitOrder(rpc, orderId);
if (!chainOrder.exists) {
throw new Error(`Order with address ${orderAddress} not found`);
}
const instructions: IInstruction[] = [];
const orderUserAddress = chainOrder.data.user;
const tokenInMint = chainOrder.data.tokenInMint;
const tokenMintProgram = await fetchMint(rpc, tokenInMint);
const addressEncoder = getAddressEncoder();
const [tokenInProgramAccount] = await getProgramDerivedAddress({
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
seeds: [
addressEncoder.encode(orderUserAddress),
addressEncoder.encode(tokenMintProgram.programAddress),
addressEncoder.encode(tokenInMint),
],
});
const guardAddress = SINGLE_CHAIN_GUARD_ADDRESSES[ChainID.Solana] as Address;
const [guardProgramAccount] = await getProgramDerivedAddress({
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
seeds: [
// Owner
addressEncoder.encode(guardAddress),
// Token program
addressEncoder.encode(tokenMintProgram.programAddress),
// mint address
addressEncoder.encode(tokenInMint),
],
});
const userTokenInAccount = await fetchEncodedAccount(rpc, tokenInProgramAccount);
if (!userTokenInAccount.exists) {
const createTokenIx = await getCreateAssociatedTokenInstructionAsync({
mint: tokenInMint,
owner: orderUserAddress,
payer: createNoopSigner(orderUserAddress),
tokenProgram: tokenMintProgram.programAddress,
});
instructions.push(createTokenIx);
}
const cancelLimitOrderIx = getCancelLimitOrderInstruction({
user: createNoopSigner(orderUserAddress),
order: orderId,
guard: guardAddress,
tokenInMint: chainOrder.data.tokenInMint,
userTokenInAccount: userTokenInAccount.address,
guardTokenInAccount: guardProgramAccount,
tokenInProgram: tokenMintProgram.programAddress,
});
instructions.push(cancelLimitOrderIx);
return instructions;
}
export async function cancelCrossChainOrderInstructionsAsBytes(
orderAddress: string,
signerAddress: string,
options?: { rpcUrl?: string },
): Promise<{ versionedMessageBytes: ReadonlyUint8Array }> {
const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
const instructions = await cancelCrossChainOrderInstructions(orderAddress, options);
const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
const txMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(createNoopSigner(address(signerAddress)), tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
);
const compiledTxMessage = compileTransactionMessage(txMessage);
const txBytes = getCompiledTransactionMessageEncoder().encode(compiledTxMessage);
return { versionedMessageBytes: txBytes };
}
export async function cancelSingleChainOrderInstructionsAsBytes(
orderAddress: string,
signerAddress: string,
options?: { rpcUrl?: string },
): Promise<{ versionedMessageBytes: ReadonlyUint8Array }> {
const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
const instructions = await cancelSingleChainOrderInstructions(orderAddress, options);
const { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();
const txMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(createNoopSigner(address(signerAddress)), tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
);
const compiledTxMessage = compileTransactionMessage(txMessage);
const txBytes = getCompiledTransactionMessageEncoder().encode(compiledTxMessage) as TransactionMessageBytes;
return { versionedMessageBytes: txBytes };
}
export async function cancelCrossChainOrderInstructions(
orderAddress: string,
options?: { rpcUrl?: string },
): Promise<IInstruction[]> {
const rpc = options?.rpcUrl ? createSolanaRpc(options.rpcUrl) : getDefaultSolanaRPC();
const orderId = address(orderAddress);
const chainOrder = await fetchMaybeOrder(rpc, orderId);
if (!chainOrder.exists) {
throw new Error(`Order with address ${orderAddress} not found`);
}
const instructions: IInstruction[] = [];
const orderUserAddress = chainOrder.data.user;
const guardAddress = CROSS_CHAIN_GUARD_ADDRESSES[ChainID.Solana] as Address;
const addressEncoder = getAddressEncoder();
const isRecoveringTokenIn = chainOrder.data.lockedStablecoins === 0n;
const recoverTokenMint = isRecoveringTokenIn ? chainOrder.data.tokenInMint : address(SOLANA_MINT_TOKEN.mint); // TODO: Use USDC for production
const recoverTokenMintProgram = await fetchMint(rpc, recoverTokenMint);
const [userRecoveredTokenAccount] = await getProgramDerivedAddress({
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
seeds: [
addressEncoder.encode(orderUserAddress),
addressEncoder.encode(recoverTokenMintProgram.programAddress),
addressEncoder.encode(recoverTokenMint),
],
});
const [guardRecoveredTokenAccount] = await getProgramDerivedAddress({
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
seeds: [
addressEncoder.encode(guardAddress),
addressEncoder.encode(recoverTokenMintProgram.programAddress),
addressEncoder.encode(recoverTokenMint),
],
});
// Check if user's recovered token account exists, create if needed
const userRecoveredTokenAccountInfo = await fetchEncodedAccount(rpc, userRecoveredTokenAccount);
if (!userRecoveredTokenAccountInfo.exists) {
const createRecoveredTokenAccountIx = await getCreateAssociatedTokenInstructionAsync({
payer: createNoopSigner(orderUserAddress),
ata: userRecoveredTokenAccount,
owner: orderUserAddress,
mint: recoverTokenMint,
tokenProgram: recoverTokenMintProgram.programAddress,
});
instructions.push(createRecoveredTokenAccountIx);
}
let userCollateralTokenAccount: Address | undefined;
const willClaimCollateral = chainOrder.data.lockedCollateral > 0n;
if (willClaimCollateral) {
// Using SOLANA_DEFAULT_STABLECOIN as collateral mint for now
const collateralTokenMint = address(SOLANA_MINT_TOKEN.mint);
const collateralTokenMintProgram = await fetchMint(rpc, collateralTokenMint);
const [userCollateralAccount] = await getProgramDerivedAddress({
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
seeds: [
addressEncoder.encode(orderUserAddress),
addressEncoder.encode(collateralTokenMintProgram.programAddress),
addressEncoder.encode(collateralTokenMint),
],
});
userCollateralTokenAccount = userCollateralAccount;
// Check if user's collateral token account exists, create if needed
const userCollateralTokenAccountInfo = await fetchEncodedAccount(rpc, userCollateralTokenAccount);
if (!userCollateralTokenAccountInfo.exists) {
const createCollateralTokenAccountIx = await getCreateAssociatedTokenInstructionAsync({
payer: createNoopSigner(orderUserAddress),
ata: userCollateralTokenAccount,
owner: orderUserAddress,
mint: collateralTokenMint,
tokenProgram: collateralTokenMintProgram.programAddress,
});
instructions.push(createCollateralTokenAccountIx);
}
}
const collateralTokenMint = address(SOLANA_MINT_TOKEN.mint);
const collateralTokenMintProgram = await fetchMint(rpc, collateralTokenMint);
const [guardCollateralTokenAccount] = await getProgramDerivedAddress({
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
seeds: [
addressEncoder.encode(guardAddress),
addressEncoder.encode(collateralTokenMintProgram.programAddress),
addressEncoder.encode(collateralTokenMint),
],
});
// Create the cancel order instruction
const cancelOrderIx = getCancelOrderInstruction({
user: createNoopSigner(orderUserAddress),
order: orderId,
guard: guardAddress,
recoveredTokenMint: recoverTokenMint,
userRecoveredTokenAccount: userRecoveredTokenAccount,
guardRecoveredTokenAccount: guardRecoveredTokenAccount,
recoveredTokenProgram: recoverTokenMintProgram.programAddress,
collateralTokenMint: collateralTokenMint,
userCollateralTokenAccount: userCollateralTokenAccount,
guardCollateralTokenAccount: guardCollateralTokenAccount,
collateralTokenProgram: collateralTokenMintProgram.programAddress,
});
instructions.push(cancelOrderIx);
return instructions;
}