From 1247d0ac2fbcafdbf1a48874e3fa8a61612d88af Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Wed, 4 Dec 2024 22:14:04 +0100 Subject: [PATCH 1/9] feat: handle shapeshift fees for jupiter --- package.json | 1 + .../src/solana/SolanaChainAdapter.ts | 4 + .../swappers/JupiterSwapper/idls/referral.ts | 1477 +++++++++++++++++ .../swapperApi/getTradeQuote.ts | 64 +- .../JupiterSwapper/utils/constants.ts | 6 + .../swappers/JupiterSwapper/utils/helpers.ts | 85 +- yarn.lock | 95 +- 7 files changed, 1725 insertions(+), 7 deletions(-) create mode 100644 packages/swapper/src/swappers/JupiterSwapper/idls/referral.ts diff --git a/package.json b/package.json index c576f161639..8f461a0a34a 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@chakra-ui/react": "^2.8.2", "@chakra-ui/system": "^2.6.2", "@chakra-ui/tag": "^3.1.1", + "@coral-xyz/anchor": "0.29.0", "@cowprotocol/app-data": "^2.3.0", "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", diff --git a/packages/chain-adapters/src/solana/SolanaChainAdapter.ts b/packages/chain-adapters/src/solana/SolanaChainAdapter.ts index a2cc85d81af..ab22e5aa920 100644 --- a/packages/chain-adapters/src/solana/SolanaChainAdapter.ts +++ b/packages/chain-adapters/src/solana/SolanaChainAdapter.ts @@ -140,6 +140,10 @@ export class ChainAdapter implements IChainAdapter return this.chainId } + getConnection(): Connection { + return this.connection + } + getBIP44Params({ accountNumber }: GetBIP44ParamsInput): BIP44Params { if (accountNumber < 0) throw new Error('accountNumber must be >= 0') return { ...ChainAdapter.defaultBIP44Params, accountNumber } diff --git a/packages/swapper/src/swappers/JupiterSwapper/idls/referral.ts b/packages/swapper/src/swappers/JupiterSwapper/idls/referral.ts new file mode 100644 index 00000000000..a6a0be5a4f4 --- /dev/null +++ b/packages/swapper/src/swappers/JupiterSwapper/idls/referral.ts @@ -0,0 +1,1477 @@ +export type Referral = { + version: '0.1.0' + name: 'referral' + instructions: [ + { + name: 'initializeProject' + accounts: [ + { + name: 'payer' + isMut: true + isSigner: true + }, + { + name: 'base' + isMut: false + isSigner: true + }, + { + name: 'admin' + isMut: false + isSigner: false + }, + { + name: 'project' + isMut: true + isSigner: false + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'InitializeProjectParams' + } + }, + ] + }, + { + name: 'initializeReferralAccount' + accounts: [ + { + name: 'payer' + isMut: true + isSigner: true + }, + { + name: 'partner' + isMut: false + isSigner: false + }, + { + name: 'project' + isMut: false + isSigner: false + }, + { + name: 'referralAccount' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'InitializeReferralAccountParams' + } + }, + ] + }, + { + name: 'initializeReferralAccountWithName' + accounts: [ + { + name: 'payer' + isMut: true + isSigner: true + }, + { + name: 'partner' + isMut: false + isSigner: false + }, + { + name: 'project' + isMut: false + isSigner: false + }, + { + name: 'referralAccount' + isMut: true + isSigner: false + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'InitializeReferralAccountWithNameParams' + } + }, + ] + }, + { + name: 'updateProject' + accounts: [ + { + name: 'admin' + isMut: false + isSigner: true + }, + { + name: 'project' + isMut: true + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'UpdateProjectParams' + } + }, + ] + }, + { + name: 'transferProject' + accounts: [ + { + name: 'admin' + isMut: false + isSigner: true + }, + { + name: 'newAdmin' + isMut: false + isSigner: false + }, + { + name: 'project' + isMut: true + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'TransferProjectParams' + } + }, + ] + }, + { + name: 'updateReferralAccount' + accounts: [ + { + name: 'admin' + isMut: false + isSigner: true + }, + { + name: 'project' + isMut: false + isSigner: false + }, + { + name: 'referralAccount' + isMut: true + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'UpdateReferralAccountParams' + } + }, + ] + }, + { + name: 'transferReferralAccount' + accounts: [ + { + name: 'partner' + isMut: false + isSigner: true + }, + { + name: 'newPartner' + isMut: false + isSigner: false + }, + { + name: 'referralAccount' + isMut: true + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'TransferReferralAccountParams' + } + }, + ] + }, + { + name: 'initializeReferralTokenAccount' + accounts: [ + { + name: 'payer' + isMut: true + isSigner: true + }, + { + name: 'project' + isMut: false + isSigner: false + }, + { + name: 'referralAccount' + isMut: false + isSigner: false + }, + { + name: 'referralTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + { + name: 'tokenProgram' + isMut: false + isSigner: false + }, + ] + args: [] + }, + { + name: 'claim' + accounts: [ + { + name: 'payer' + isMut: true + isSigner: true + }, + { + name: 'project' + isMut: false + isSigner: false + }, + { + name: 'admin' + isMut: false + isSigner: false + }, + { + name: 'projectAdminTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'referralAccount' + isMut: false + isSigner: false + }, + { + name: 'referralTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'partner' + isMut: false + isSigner: false + }, + { + name: 'partnerTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'associatedTokenProgram' + isMut: false + isSigner: false + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + { + name: 'tokenProgram' + isMut: false + isSigner: false + }, + ] + args: [] + }, + { + name: 'createAdminTokenAccount' + accounts: [ + { + name: 'project' + isMut: false + isSigner: false + }, + { + name: 'projectAuthority' + isMut: true + isSigner: false + }, + { + name: 'admin' + isMut: false + isSigner: false + }, + { + name: 'projectAdminTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + { + name: 'tokenProgram' + isMut: false + isSigner: false + }, + { + name: 'associatedTokenProgram' + isMut: false + isSigner: false + }, + ] + args: [] + }, + { + name: 'withdrawFromProject' + accounts: [ + { + name: 'admin' + isMut: true + isSigner: true + }, + { + name: 'project' + isMut: false + isSigner: false + }, + { + name: 'projectAuthority' + isMut: true + isSigner: false + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'params' + type: { + defined: 'WithdrawFromProjectParams' + } + }, + ] + }, + ] + accounts: [ + { + name: 'project' + type: { + kind: 'struct' + fields: [ + { + name: 'base' + type: 'publicKey' + }, + { + name: 'admin' + type: 'publicKey' + }, + { + name: 'name' + type: 'string' + }, + { + name: 'defaultShareBps' + type: 'u16' + }, + ] + } + }, + { + name: 'referralAccount' + type: { + kind: 'struct' + fields: [ + { + name: 'partner' + type: 'publicKey' + }, + { + name: 'project' + type: 'publicKey' + }, + { + name: 'shareBps' + type: 'u16' + }, + { + name: 'name' + type: { + option: 'string' + } + }, + ] + } + }, + ] + types: [ + { + name: 'InitializeProjectParams' + type: { + kind: 'struct' + fields: [ + { + name: 'name' + type: 'string' + }, + { + name: 'defaultShareBps' + type: 'u16' + }, + ] + } + }, + { + name: 'InitializeReferralAccountWithNameParams' + type: { + kind: 'struct' + fields: [ + { + name: 'name' + type: 'string' + }, + ] + } + }, + { + name: 'InitializeReferralAccountParams' + type: { + kind: 'struct' + fields: [] + } + }, + { + name: 'TransferProjectParams' + type: { + kind: 'struct' + fields: [] + } + }, + { + name: 'TransferReferralAccountParams' + type: { + kind: 'struct' + fields: [] + } + }, + { + name: 'UpdateProjectParams' + type: { + kind: 'struct' + fields: [ + { + name: 'name' + type: { + option: 'string' + } + }, + { + name: 'defaultShareBps' + type: { + option: 'u16' + } + }, + ] + } + }, + { + name: 'UpdateReferralAccountParams' + type: { + kind: 'struct' + fields: [ + { + name: 'shareBps' + type: 'u16' + }, + ] + } + }, + { + name: 'WithdrawFromProjectParams' + type: { + kind: 'struct' + fields: [ + { + name: 'amount' + type: 'u64' + }, + ] + } + }, + ] + events: [ + { + name: 'InitializeProjectEvent' + fields: [ + { + name: 'project' + type: 'publicKey' + index: false + }, + { + name: 'admin' + type: 'publicKey' + index: false + }, + { + name: 'name' + type: 'string' + index: false + }, + { + name: 'defaultShareBps' + type: 'u16' + index: false + }, + ] + }, + { + name: 'UpdateProjectEvent' + fields: [ + { + name: 'project' + type: 'publicKey' + index: false + }, + { + name: 'name' + type: 'string' + index: false + }, + { + name: 'defaultShareBps' + type: 'u16' + index: false + }, + ] + }, + { + name: 'InitializeReferralAccountEvent' + fields: [ + { + name: 'project' + type: 'publicKey' + index: false + }, + { + name: 'partner' + type: 'publicKey' + index: false + }, + { + name: 'referralAccount' + type: 'publicKey' + index: false + }, + { + name: 'shareBps' + type: 'u16' + index: false + }, + { + name: 'name' + type: { + option: 'string' + } + index: false + }, + ] + }, + { + name: 'UpdateReferralAccountEvent' + fields: [ + { + name: 'referralAccount' + type: 'publicKey' + index: false + }, + { + name: 'shareBps' + type: 'u16' + index: false + }, + ] + }, + { + name: 'InitializeReferralTokenAccountEvent' + fields: [ + { + name: 'project' + type: 'publicKey' + index: false + }, + { + name: 'referralAccount' + type: 'publicKey' + index: false + }, + { + name: 'referralTokenAccount' + type: 'publicKey' + index: false + }, + { + name: 'mint' + type: 'publicKey' + index: false + }, + ] + }, + { + name: 'ClaimEvent' + fields: [ + { + name: 'project' + type: 'publicKey' + index: false + }, + { + name: 'projectAdminTokenAccount' + type: 'publicKey' + index: false + }, + { + name: 'referralAccount' + type: 'publicKey' + index: false + }, + { + name: 'referralTokenAccount' + type: 'publicKey' + index: false + }, + { + name: 'partnerTokenAccount' + type: 'publicKey' + index: false + }, + { + name: 'mint' + type: 'publicKey' + index: false + }, + { + name: 'referralAmount' + type: 'u64' + index: false + }, + { + name: 'projectAmount' + type: 'u64' + index: false + }, + ] + }, + ] + errors: [ + { + code: 6000 + name: 'InvalidCalculation' + }, + { + code: 6001 + name: 'InvalidSharePercentage' + }, + { + code: 6002 + name: 'NameTooLong' + }, + ] +} + +export const referralIdl: Referral = { + version: '0.1.0', + name: 'referral', + instructions: [ + { + name: 'initializeProject', + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'base', + isMut: false, + isSigner: true, + }, + { + name: 'admin', + isMut: false, + isSigner: false, + }, + { + name: 'project', + isMut: true, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'InitializeProjectParams', + }, + }, + ], + }, + { + name: 'initializeReferralAccount', + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'partner', + isMut: false, + isSigner: false, + }, + { + name: 'project', + isMut: false, + isSigner: false, + }, + { + name: 'referralAccount', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'InitializeReferralAccountParams', + }, + }, + ], + }, + { + name: 'initializeReferralAccountWithName', + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'partner', + isMut: false, + isSigner: false, + }, + { + name: 'project', + isMut: false, + isSigner: false, + }, + { + name: 'referralAccount', + isMut: true, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'InitializeReferralAccountWithNameParams', + }, + }, + ], + }, + { + name: 'updateProject', + accounts: [ + { + name: 'admin', + isMut: false, + isSigner: true, + }, + { + name: 'project', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'UpdateProjectParams', + }, + }, + ], + }, + { + name: 'transferProject', + accounts: [ + { + name: 'admin', + isMut: false, + isSigner: true, + }, + { + name: 'newAdmin', + isMut: false, + isSigner: false, + }, + { + name: 'project', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'TransferProjectParams', + }, + }, + ], + }, + { + name: 'updateReferralAccount', + accounts: [ + { + name: 'admin', + isMut: false, + isSigner: true, + }, + { + name: 'project', + isMut: false, + isSigner: false, + }, + { + name: 'referralAccount', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'UpdateReferralAccountParams', + }, + }, + ], + }, + { + name: 'transferReferralAccount', + accounts: [ + { + name: 'partner', + isMut: false, + isSigner: true, + }, + { + name: 'newPartner', + isMut: false, + isSigner: false, + }, + { + name: 'referralAccount', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'TransferReferralAccountParams', + }, + }, + ], + }, + { + name: 'initializeReferralTokenAccount', + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'project', + isMut: false, + isSigner: false, + }, + { + name: 'referralAccount', + isMut: false, + isSigner: false, + }, + { + name: 'referralTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'tokenProgram', + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: 'claim', + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'project', + isMut: false, + isSigner: false, + }, + { + name: 'admin', + isMut: false, + isSigner: false, + }, + { + name: 'projectAdminTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'referralAccount', + isMut: false, + isSigner: false, + }, + { + name: 'referralTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'partner', + isMut: false, + isSigner: false, + }, + { + name: 'partnerTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'associatedTokenProgram', + isMut: false, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'tokenProgram', + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: 'createAdminTokenAccount', + accounts: [ + { + name: 'project', + isMut: false, + isSigner: false, + }, + { + name: 'projectAuthority', + isMut: true, + isSigner: false, + }, + { + name: 'admin', + isMut: false, + isSigner: false, + }, + { + name: 'projectAdminTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'tokenProgram', + isMut: false, + isSigner: false, + }, + { + name: 'associatedTokenProgram', + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: 'withdrawFromProject', + accounts: [ + { + name: 'admin', + isMut: true, + isSigner: true, + }, + { + name: 'project', + isMut: false, + isSigner: false, + }, + { + name: 'projectAuthority', + isMut: true, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'WithdrawFromProjectParams', + }, + }, + ], + }, + ], + accounts: [ + { + name: 'project', + type: { + kind: 'struct', + fields: [ + { + name: 'base', + type: 'publicKey', + }, + { + name: 'admin', + type: 'publicKey', + }, + { + name: 'name', + type: 'string', + }, + { + name: 'defaultShareBps', + type: 'u16', + }, + ], + }, + }, + { + name: 'referralAccount', + type: { + kind: 'struct', + fields: [ + { + name: 'partner', + type: 'publicKey', + }, + { + name: 'project', + type: 'publicKey', + }, + { + name: 'shareBps', + type: 'u16', + }, + { + name: 'name', + type: { + option: 'string', + }, + }, + ], + }, + }, + ], + types: [ + { + name: 'InitializeProjectParams', + type: { + kind: 'struct', + fields: [ + { + name: 'name', + type: 'string', + }, + { + name: 'defaultShareBps', + type: 'u16', + }, + ], + }, + }, + { + name: 'InitializeReferralAccountWithNameParams', + type: { + kind: 'struct', + fields: [ + { + name: 'name', + type: 'string', + }, + ], + }, + }, + { + name: 'InitializeReferralAccountParams', + type: { + kind: 'struct', + fields: [], + }, + }, + { + name: 'TransferProjectParams', + type: { + kind: 'struct', + fields: [], + }, + }, + { + name: 'TransferReferralAccountParams', + type: { + kind: 'struct', + fields: [], + }, + }, + { + name: 'UpdateProjectParams', + type: { + kind: 'struct', + fields: [ + { + name: 'name', + type: { + option: 'string', + }, + }, + { + name: 'defaultShareBps', + type: { + option: 'u16', + }, + }, + ], + }, + }, + { + name: 'UpdateReferralAccountParams', + type: { + kind: 'struct', + fields: [ + { + name: 'shareBps', + type: 'u16', + }, + ], + }, + }, + { + name: 'WithdrawFromProjectParams', + type: { + kind: 'struct', + fields: [ + { + name: 'amount', + type: 'u64', + }, + ], + }, + }, + ], + events: [ + { + name: 'InitializeProjectEvent', + fields: [ + { + name: 'project', + type: 'publicKey', + index: false, + }, + { + name: 'admin', + type: 'publicKey', + index: false, + }, + { + name: 'name', + type: 'string', + index: false, + }, + { + name: 'defaultShareBps', + type: 'u16', + index: false, + }, + ], + }, + { + name: 'UpdateProjectEvent', + fields: [ + { + name: 'project', + type: 'publicKey', + index: false, + }, + { + name: 'name', + type: 'string', + index: false, + }, + { + name: 'defaultShareBps', + type: 'u16', + index: false, + }, + ], + }, + { + name: 'InitializeReferralAccountEvent', + fields: [ + { + name: 'project', + type: 'publicKey', + index: false, + }, + { + name: 'partner', + type: 'publicKey', + index: false, + }, + { + name: 'referralAccount', + type: 'publicKey', + index: false, + }, + { + name: 'shareBps', + type: 'u16', + index: false, + }, + { + name: 'name', + type: { + option: 'string', + }, + index: false, + }, + ], + }, + { + name: 'UpdateReferralAccountEvent', + fields: [ + { + name: 'referralAccount', + type: 'publicKey', + index: false, + }, + { + name: 'shareBps', + type: 'u16', + index: false, + }, + ], + }, + { + name: 'InitializeReferralTokenAccountEvent', + fields: [ + { + name: 'project', + type: 'publicKey', + index: false, + }, + { + name: 'referralAccount', + type: 'publicKey', + index: false, + }, + { + name: 'referralTokenAccount', + type: 'publicKey', + index: false, + }, + { + name: 'mint', + type: 'publicKey', + index: false, + }, + ], + }, + { + name: 'ClaimEvent', + fields: [ + { + name: 'project', + type: 'publicKey', + index: false, + }, + { + name: 'projectAdminTokenAccount', + type: 'publicKey', + index: false, + }, + { + name: 'referralAccount', + type: 'publicKey', + index: false, + }, + { + name: 'referralTokenAccount', + type: 'publicKey', + index: false, + }, + { + name: 'partnerTokenAccount', + type: 'publicKey', + index: false, + }, + { + name: 'mint', + type: 'publicKey', + index: false, + }, + { + name: 'referralAmount', + type: 'u64', + index: false, + }, + { + name: 'projectAmount', + type: 'u64', + index: false, + }, + ], + }, + ], + errors: [ + { + code: 6000, + name: 'InvalidCalculation', + }, + { + code: 6001, + name: 'InvalidSharePercentage', + }, + { + code: 6002, + name: 'NameTooLong', + }, + ], +} diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts index c305cda2e1b..46e353c9ac7 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts @@ -1,3 +1,4 @@ +import { BorshInstructionCoder } from '@coral-xyz/anchor' import type { Instruction } from '@jup-ag/api' import type { AssetId } from '@shapeshiftoss/caip' import { @@ -28,8 +29,18 @@ import type { } from '../../../types' import { SwapperName, TradeQuoteError } from '../../../types' import { getInputOutputRate, makeSwapErrorRight } from '../../../utils' -import { JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER } from '../utils/constants' -import { getJupiterPrice, getJupiterSwapInstructions, isSupportedChainId } from '../utils/helpers' +import { referralIdl } from '../idls/referral' +import { + JUPITER_AFFILIATE_CONTRACT_ADDRESS, + JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER, + SHAPESHIFT_JUPITER_REFERRAL_KEY, +} from '../utils/constants' +import { + getFeeTokenAccountAndInstruction, + getJupiterPrice, + getJupiterSwapInstructions, + isSupportedChainId, +} from '../utils/helpers' export const getTradeQuote = async ( input: CommonTradeQuoteInput, @@ -131,6 +142,50 @@ export const getTradeQuote = async ( }) : { instruction: undefined, destinationTokenAccount: undefined } + const buyAssetAddress = + buyAsset.assetId === solAssetId + ? fromAssetId(wrappedSolAssetId).assetReference + : fromAssetId(buyAsset.assetId).assetReference + + const sellAssetAddress = + sellAsset.assetId === solAssetId + ? fromAssetId(wrappedSolAssetId).assetReference + : fromAssetId(sellAsset.assetId).assetReference + + const [buyAssetAccount] = await PublicKey.findProgramAddressSync( + [ + Buffer.from('referral_ata'), + new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), + new PublicKey(buyAssetAddress).toBuffer(), + ], + new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + ) + + const [sellAssetAccount] = await PublicKey.findProgramAddressSync( + [ + Buffer.from('referral_ata'), + new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), + new PublicKey(sellAssetAddress).toBuffer(), + ], + new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + ) + + const instructionData = new BorshInstructionCoder(referralIdl).encode( + 'initializeReferralTokenAccount', + {}, + ) + + const { instruction: feeAccountInstruction, tokenAccount } = + await getFeeTokenAccountAndInstruction({ + feePayerPubKey: new PublicKey(sendAddress), + buyAssetAccount, + sellAssetAccount, + programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + instructionData, + mint: buyAssetAddress, + connection: adapter.getConnection(), + }) + const maybeSwapResponse = await getJupiterSwapInstructions({ apiUrl: jupiterUrl, fromAddress: sendAddress, @@ -138,6 +193,7 @@ export const getTradeQuote = async ( rawQuote: priceResponse, // Shared account is not supported for simple AMMs useSharedAccounts: priceResponse.routePlan.length > 1 && isCrossAccountTrade ? true : false, + feeAccount: tokenAccount.toString(), }) if (maybeSwapResponse.isErr()) { @@ -163,6 +219,10 @@ export const getTradeQuote = async ( convertJupiterInstruction(swapResponse.swapInstruction), ] + if (feeAccountInstruction && affiliateBps !== '0') { + instructions.unshift(feeAccountInstruction) + } + if (createTokenAccountInstruction) { instructions.unshift(createTokenAccountInstruction) } diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts index a196d3a652c..a254ba26488 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts @@ -14,3 +14,9 @@ export const SOLANA_RANDOM_ADDRESS = '2zHKF6tqam3tnNFPK2E9nBDkV7GMXnvdJautmzqQdn // Jupiter use 40% as a compute unit margin while calculating them, some TX reverts without this export const JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER = 1.4 + +export const SHAPESHIFT_JUPITER_REFERRAL_KEY = 'Ajgmo453yGmcHDPoJBrMUj3GFwLVL7HaaZGNLkB8vREG' + +export const JUPITER_AFFILIATE_CONTRACT_ADDRESS = 'REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3' + +export const JUPITER_REFERALL_FEE_PROJECT_ACCOUNT = '45ruCyfdRkWpRNGEqWzjCiXRHkZs8WXCLQ67Pnpye7Hp' diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts index a6ee7ccf62a..adac53c5a7b 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts @@ -1,12 +1,19 @@ +import { TOKEN_PROGRAM_ID } from '@coral-xyz/anchor/dist/cjs/utils/token' import type { QuoteResponse, SwapInstructionsResponse } from '@jup-ag/api' import type { ChainId } from '@shapeshiftoss/caip' import { fromAssetId } from '@shapeshiftoss/caip' import type { KnownChainIds } from '@shapeshiftoss/types' import type { Result } from '@sniptt/monads' +import type { Connection } from '@solana/web3.js' +import { PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js' import type { AxiosResponse } from 'axios' import type { SwapErrorRight } from '../../../types' -import { jupiterSupportedChainIds } from './constants' +import { + JUPITER_REFERALL_FEE_PROJECT_ACCOUNT, + jupiterSupportedChainIds, + SHAPESHIFT_JUPITER_REFERRAL_KEY, +} from './constants' import { jupiterService } from './jupiterService' export const isSupportedChainId = (chainId: ChainId): chainId is KnownChainIds.SolanaMainnet => { @@ -30,6 +37,7 @@ type GetJupiterSwapArgs = { rawQuote: unknown toAddress?: string useSharedAccounts: boolean + feeAccount: string | undefined } export const getJupiterPrice = ({ @@ -50,13 +58,13 @@ export const getJupiterPrice = ({ `&platformFeeBps=${commissionBps}`, ) -// @TODO: Add DAO's fee account export const getJupiterSwapInstructions = ({ apiUrl, fromAddress, toAddress, rawQuote, useSharedAccounts, + feeAccount, }: GetJupiterSwapArgs): Promise< Result, SwapErrorRight> > => @@ -67,4 +75,77 @@ export const getJupiterSwapInstructions = ({ quoteResponse: rawQuote, dynamicComputeUnitLimit: true, prioritizationFeeLamports: 'auto', + feeAccount, }) + +export const getFeeTokenAccountAndInstruction = async ({ + feePayerPubKey, + programId, + buyAssetAccount, + sellAssetAccount, + mint, + instructionData, + connection, +}: { + feePayerPubKey: PublicKey + programId: PublicKey + buyAssetAccount: PublicKey + sellAssetAccount: PublicKey + mint: string + instructionData: Buffer + connection: Connection +}): Promise<{ + tokenAccount: PublicKey + instruction?: TransactionInstruction | undefined +}> => { + const buyAssetTokenAccount = await connection.getAccountInfo(buyAssetAccount) + + if (buyAssetTokenAccount) return { tokenAccount: buyAssetAccount } + + const sellAssetTokenAccount = await connection.getAccountInfo(sellAssetAccount) + + if (sellAssetTokenAccount) return { tokenAccount: sellAssetAccount } + + const project = new PublicKey(JUPITER_REFERALL_FEE_PROJECT_ACCOUNT) + + return { + tokenAccount: buyAssetAccount, + instruction: new TransactionInstruction({ + keys: [ + { + pubkey: feePayerPubKey, + isSigner: true, + isWritable: true, + }, + { + pubkey: project, + isWritable: false, + isSigner: false, + }, + { + pubkey: new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY), + isSigner: false, + isWritable: false, + }, + { + pubkey: buyAssetAccount, + isSigner: false, + isWritable: true, + }, + { + pubkey: new PublicKey(mint), + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + ], + data: instructionData, + programId, + }), + } +} diff --git a/yarn.lock b/yarn.lock index 368a7914784..735cbc49688 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5424,6 +5424,40 @@ __metadata: languageName: node linkType: hard +"@coral-xyz/anchor@npm:0.29.0": + version: 0.29.0 + resolution: "@coral-xyz/anchor@npm:0.29.0" + dependencies: + "@coral-xyz/borsh": ^0.29.0 + "@noble/hashes": ^1.3.1 + "@solana/web3.js": ^1.68.0 + bn.js: ^5.1.2 + bs58: ^4.0.1 + buffer-layout: ^1.2.2 + camelcase: ^6.3.0 + cross-fetch: ^3.1.5 + crypto-hash: ^1.3.0 + eventemitter3: ^4.0.7 + pako: ^2.0.3 + snake-case: ^3.0.4 + superstruct: ^0.15.4 + toml: ^3.0.0 + checksum: 10c4e6c5557653419683f5ae22ec47ac266b64e5b422d466885cf2dc7efa8f836239bdf321495d3e2b3ce03e766667c0e2192cc573fbd66bc12cc652f5146e10 + languageName: node + linkType: hard + +"@coral-xyz/borsh@npm:^0.29.0": + version: 0.29.0 + resolution: "@coral-xyz/borsh@npm:0.29.0" + dependencies: + bn.js: ^5.1.2 + buffer-layout: ^1.2.0 + peerDependencies: + "@solana/web3.js": ^1.68.0 + checksum: 37006c75cd012672adf48e10234062624634da2a9335e34b7ff30969f58aff78cc3073b66a3edc806b52f038469f0c477a5a3ed35aaa075f3cbd44d7133ac218 + languageName: node + linkType: hard + "@cosmjs/amino@npm:0.25.0-alpha.2": version: 0.25.0-alpha.2 resolution: "@cosmjs/amino@npm:0.25.0-alpha.2" @@ -11740,6 +11774,7 @@ __metadata: "@chakra-ui/tag": ^3.1.1 "@commitlint/cli": ^15.0.0 "@commitlint/config-conventional": ^15.0.0 + "@coral-xyz/anchor": 0.29.0 "@cowprotocol/app-data": ^2.3.0 "@emotion/react": ^11.13.0 "@emotion/styled": ^11.13.0 @@ -12227,6 +12262,29 @@ __metadata: languageName: node linkType: hard +"@solana/web3.js@npm:^1.68.0": + version: 1.95.8 + resolution: "@solana/web3.js@npm:1.95.8" + dependencies: + "@babel/runtime": ^7.25.0 + "@noble/curves": ^1.4.2 + "@noble/hashes": ^1.4.0 + "@solana/buffer-layout": ^4.0.1 + agentkeepalive: ^4.5.0 + bigint-buffer: ^1.1.5 + bn.js: ^5.2.1 + borsh: ^0.7.0 + bs58: ^4.0.1 + buffer: 6.0.3 + fast-stable-stringify: ^1.0.0 + jayson: ^4.1.1 + node-fetch: ^2.7.0 + rpc-websockets: ^9.0.2 + superstruct: ^2.0.2 + checksum: 875a65c2e16ea797b2a1e842fe9a53ae74f627b8678d946a12f4a6acd1922dfcb107429b2b6d93131d406d30deffaa366132fe9b87dbf0b62c7b83e83184b3fa + languageName: node + linkType: hard + "@solana/web3.js@npm:^1.70.1": version: 1.73.0 resolution: "@solana/web3.js@npm:1.73.0" @@ -18734,7 +18792,7 @@ __metadata: languageName: node linkType: hard -"buffer-layout@npm:^1.2.0": +"buffer-layout@npm:^1.2.0, buffer-layout@npm:^1.2.2": version: 1.2.2 resolution: "buffer-layout@npm:1.2.2" checksum: e5809ba275530bf4e52fd09558b7c2111fbda5b405124f581acf364261d9c154e271800271898cd40473f9bcbb42c31584efb04219bde549d3460ca4bafeaa07 @@ -18998,7 +19056,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.2.0, camelcase@npm:^6.2.1": +"camelcase@npm:^6.2.0, camelcase@npm:^6.2.1, camelcase@npm:^6.3.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d @@ -20402,6 +20460,13 @@ __metadata: languageName: node linkType: hard +"crypto-hash@npm:^1.3.0": + version: 1.3.0 + resolution: "crypto-hash@npm:1.3.0" + checksum: a3a507e0d2b18fbd2da8088a1c62d0c53c009a99bbfa6d851cac069734ffa546922fa51bdd776d006459701cdda873463e5059ece3431aca048fd99e7573d138 + languageName: node + linkType: hard + "crypto-js@npm:4.2.0": version: 4.2.0 resolution: "crypto-js@npm:4.2.0" @@ -31188,6 +31253,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:^2.0.3": + version: 2.1.0 + resolution: "pako@npm:2.1.0" + checksum: 71666548644c9a4d056bcaba849ca6fd7242c6cf1af0646d3346f3079a1c7f4a66ffec6f7369ee0dc88f61926c10d6ab05da3e1fca44b83551839e89edd75a3e + languageName: node + linkType: hard + "pako@npm:~1.0.5": version: 1.0.11 resolution: "pako@npm:1.0.11" @@ -35932,6 +36004,16 @@ pvutils@latest: languageName: node linkType: hard +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: ^3.0.4 + tslib: ^2.0.3 + checksum: 0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 + languageName: node + linkType: hard + "snakecase-keys@npm:3.2.1": version: 3.2.1 resolution: "snakecase-keys@npm:3.2.1" @@ -36863,7 +36945,7 @@ pvutils@latest: languageName: node linkType: hard -"superstruct@npm:^0.15.3": +"superstruct@npm:^0.15.3, superstruct@npm:^0.15.4": version: 0.15.5 resolution: "superstruct@npm:0.15.5" checksum: 6d1f5249fee789424b7178fa0a1ffb2ace629c5480c39505885bd8c0046a4ff8b267569a3442fa53b8c560a7ba6599cf3f8af94225aebeb2cf6023f7dd911050 @@ -37533,6 +37615,13 @@ pvutils@latest: languageName: node linkType: hard +"toml@npm:^3.0.0": + version: 3.0.0 + resolution: "toml@npm:3.0.0" + checksum: 5d7f1d8413ad7780e9bdecce8ea4c3f5130dd53b0a4f2e90b93340979a137739879d7b9ce2ce05c938b8cc828897fe9e95085197342a1377dd8850bf5125f15f + languageName: node + linkType: hard + "totalist@npm:^3.0.0": version: 3.0.1 resolution: "totalist@npm:3.0.1" From 67c0d3cfc124b8eb6ba713a1822e7294bb967c6d Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:28:55 +0100 Subject: [PATCH 2/9] fix: finish --- package.json | 1 - packages/swapper/package.json | 1 + .../swapperApi/getTradeQuote.ts | 27 ++++- .../JupiterSwapper/swapperApi/getTradeRate.ts | 98 ++++++++++++++++++- .../JupiterSwapper/utils/constants.ts | 2 + .../swappers/JupiterSwapper/utils/helpers.ts | 20 +++- .../swapper/helpers/validateTradeQuote.ts | 8 ++ yarn.lock | 2 +- 8 files changed, 149 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 8f461a0a34a..c576f161639 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "@chakra-ui/react": "^2.8.2", "@chakra-ui/system": "^2.6.2", "@chakra-ui/tag": "^3.1.1", - "@coral-xyz/anchor": "0.29.0", "@cowprotocol/app-data": "^2.3.0", "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", diff --git a/packages/swapper/package.json b/packages/swapper/package.json index f279ac32dee..f047a2a63e6 100644 --- a/packages/swapper/package.json +++ b/packages/swapper/package.json @@ -17,6 +17,7 @@ "dev": "yarn run -T tsc --build --watch" }, "dependencies": { + "@coral-xyz/anchor": "0.29.0", "@shapeshiftoss/caip": "workspace:^", "@shapeshiftoss/chain-adapters": "workspace:^", "@shapeshiftoss/types": "workspace:^", diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts index 46e353c9ac7..241f8bfaa8a 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts @@ -33,6 +33,7 @@ import { referralIdl } from '../idls/referral' import { JUPITER_AFFILIATE_CONTRACT_ADDRESS, JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER, + PDA_ACCOUNT_CREATION_COST, SHAPESHIFT_JUPITER_REFERRAL_KEY, } from '../utils/constants' import { @@ -61,6 +62,8 @@ export const getTradeQuote = async ( const jupiterUrl = deps.config.REACT_APP_JUPITER_API_URL + const solAsset = assetsById[solAssetId] + if (accountNumber === undefined) { return Err( makeSwapErrorRight({ @@ -99,6 +102,15 @@ export const getTradeQuote = async ( ) } + if (!solAsset) { + return Err( + makeSwapErrorRight({ + message: `solAsset is required`, + code: TradeQuoteError.UnknownError, + }), + ) + } + const maybePriceResponse = await getJupiterPrice({ apiUrl: jupiterUrl, sourceAsset: sellAsset.assetId === solAssetId ? wrappedSolAssetId : sellAsset.assetId, @@ -182,7 +194,8 @@ export const getTradeQuote = async ( sellAssetAccount, programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), instructionData, - mint: buyAssetAddress, + buyTokenId: buyAssetAddress, + sellTokenId: sellAssetAddress, connection: adapter.getConnection(), }) @@ -193,7 +206,7 @@ export const getTradeQuote = async ( rawQuote: priceResponse, // Shared account is not supported for simple AMMs useSharedAccounts: priceResponse.routePlan.length > 1 && isCrossAccountTrade ? true : false, - feeAccount: tokenAccount.toString(), + feeAccount: affiliateBps !== '0' ? tokenAccount?.toString() : undefined, }) if (maybeSwapResponse.isErr()) { @@ -279,6 +292,16 @@ export const getTradeQuote = async ( {} as Record, ) + if (feeAccountInstruction) { + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + .plus(PDA_ACCOUNT_CREATION_COST) + .toFixed(), + asset: solAsset, + } + } + const quotes: TradeQuote[] = [] const feeData = await getFeeData() diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts index 6c2d32b69aa..01fa9f9c212 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts @@ -1,3 +1,4 @@ +import { BorshInstructionCoder } from '@coral-xyz/anchor' import type { AssetId } from '@shapeshiftoss/caip' import { ASSET_NAMESPACE, @@ -13,6 +14,7 @@ import type { KnownChainIds } from '@shapeshiftoss/types' import { bn, bnOrZero, convertDecimalPercentageToBasisPoints } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' import { Err, Ok } from '@sniptt/monads' +import { PublicKey } from '@solana/web3.js' import { v4 as uuid } from 'uuid' import type { @@ -24,8 +26,18 @@ import type { } from '../../../types' import { SwapperName, TradeQuoteError } from '../../../types' import { getInputOutputRate, makeSwapErrorRight } from '../../../utils' -import { SOLANA_RANDOM_ADDRESS } from '../utils/constants' -import { getJupiterPrice, isSupportedChainId } from '../utils/helpers' +import { referralIdl } from '../idls/referral' +import { + JUPITER_AFFILIATE_CONTRACT_ADDRESS, + PDA_ACCOUNT_CREATION_COST, + SHAPESHIFT_JUPITER_REFERRAL_KEY, + SOLANA_RANDOM_ADDRESS, +} from '../utils/constants' +import { + getFeeTokenAccountAndInstruction, + getJupiterPrice, + isSupportedChainId, +} from '../utils/helpers' export const getTradeRate = async ( input: GetTradeRateInput, @@ -45,6 +57,8 @@ export const getTradeRate = async ( const jupiterUrl = deps.config.REACT_APP_JUPITER_API_URL + const solAsset = assetsById[solAssetId] + if (!isSupportedChainId(sellAsset.chainId)) { return Err( makeSwapErrorRight({ @@ -114,6 +128,52 @@ export const getTradeRate = async ( return { networkFeeCryptoBaseUnit: fast.txFee } } + const buyAssetAddress = + buyAsset.assetId === solAssetId + ? fromAssetId(wrappedSolAssetId).assetReference + : fromAssetId(buyAsset.assetId).assetReference + + const sellAssetAddress = + sellAsset.assetId === solAssetId + ? fromAssetId(wrappedSolAssetId).assetReference + : fromAssetId(sellAsset.assetId).assetReference + + const [buyAssetAccount] = await PublicKey.findProgramAddressSync( + [ + Buffer.from('referral_ata'), + new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), + new PublicKey(buyAssetAddress).toBuffer(), + ], + new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + ) + + const [sellAssetAccount] = await PublicKey.findProgramAddressSync( + [ + Buffer.from('referral_ata'), + new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), + new PublicKey(sellAssetAddress).toBuffer(), + ], + new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + ) + + const instructionData = new BorshInstructionCoder(referralIdl).encode( + 'initializeReferralTokenAccount', + {}, + ) + + const adapter = deps.assertGetSolanaChainAdapter(sellAsset.chainId) + + const { instruction: feeAccountInstruction } = await getFeeTokenAccountAndInstruction({ + feePayerPubKey: new PublicKey(input.sendAddress ?? SOLANA_RANDOM_ADDRESS), + buyAssetAccount, + sellAssetAccount, + programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + instructionData, + buyTokenId: buyAssetAddress, + sellTokenId: sellAssetAddress, + connection: adapter.getConnection(), + }) + const protocolFees: Record = priceResponse.routePlan.reduce( (acc, route) => { const feeAssetId = toAssetId({ @@ -139,6 +199,40 @@ export const getTradeRate = async ( {} as Record, ) + if (feeAccountInstruction) { + if (solAsset) { + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + .plus(PDA_ACCOUNT_CREATION_COST) + .toFixed(), + asset: solAsset, + } + } + } + + if (input.sendAddress && solAsset) { + const { instruction: createTokenAccountInstruction } = buyAsset + ? await adapter.createAssociatedTokenAccountInstruction({ + from: input.sendAddress, + to: receiveAddress ?? input.sendAddress, + tokenId: fromAssetId( + buyAsset.assetId === solAssetId ? wrappedSolAssetId : buyAsset.assetId, + ).assetReference, + }) + : { instruction: undefined } + + if (createTokenAccountInstruction) { + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + .plus(PDA_ACCOUNT_CREATION_COST) + .toFixed(), + asset: solAsset, + } + } + } + const rates: TradeRate[] = [] const feeData = await getFeeData() diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts index a254ba26488..d1e830efb70 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts @@ -10,6 +10,8 @@ export const JUPITER_SUPPORTED_CHAIN_IDS: SupportedChainIds = { buy: jupiterSupportedChainIds, } +export const PDA_ACCOUNT_CREATION_COST = 2040000 + export const SOLANA_RANDOM_ADDRESS = '2zHKF6tqam3tnNFPK2E9nBDkV7GMXnvdJautmzqQdn8A' // Jupiter use 40% as a compute unit margin while calculating them, some TX reverts without this diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts index adac53c5a7b..002c0a30558 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts @@ -83,7 +83,8 @@ export const getFeeTokenAccountAndInstruction = async ({ programId, buyAssetAccount, sellAssetAccount, - mint, + buyTokenId, + sellTokenId, instructionData, connection, }: { @@ -91,11 +92,12 @@ export const getFeeTokenAccountAndInstruction = async ({ programId: PublicKey buyAssetAccount: PublicKey sellAssetAccount: PublicKey - mint: string + buyTokenId: string + sellTokenId: string instructionData: Buffer connection: Connection }): Promise<{ - tokenAccount: PublicKey + tokenAccount?: PublicKey instruction?: TransactionInstruction | undefined }> => { const buyAssetTokenAccount = await connection.getAccountInfo(buyAssetAccount) @@ -106,6 +108,16 @@ export const getFeeTokenAccountAndInstruction = async ({ if (sellAssetTokenAccount) return { tokenAccount: sellAssetAccount } + const buyTokenInfo = await connection.getAccountInfo(new PublicKey(buyTokenId)) + const sellTokenInfo = await connection.getAccountInfo(new PublicKey(sellTokenId)) + + if ( + buyTokenInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toString() && + sellTokenInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toString() + ) { + return { tokenAccount: undefined, instruction: undefined } + } + const project = new PublicKey(JUPITER_REFERALL_FEE_PROJECT_ACCOUNT) return { @@ -133,7 +145,7 @@ export const getFeeTokenAccountAndInstruction = async ({ isWritable: true, }, { - pubkey: new PublicKey(mint), + pubkey: new PublicKey(buyTokenId), isSigner: false, isWritable: false, }, diff --git a/src/state/apis/swapper/helpers/validateTradeQuote.ts b/src/state/apis/swapper/helpers/validateTradeQuote.ts index f1260baad7d..11a664dda36 100644 --- a/src/state/apis/swapper/helpers/validateTradeQuote.ts +++ b/src/state/apis/swapper/helpers/validateTradeQuote.ts @@ -216,6 +216,14 @@ export const validateTradeQuote = ( const accountId = portfolioAccountIdByNumberByChainId[sellAssetAccountNumber][protocolFee.asset.chainId] const balanceCryptoBaseUnit = portfolioAccountBalancesBaseUnit[accountId][assetId] + + if (firstHopSellFeeAsset?.assetId === assetId) { + return bnOrZero(balanceCryptoBaseUnit) + .minus(sellAmountCryptoBaseUnit) + .minus(protocolFee.amountCryptoBaseUnit) + .lt(0) + } + return bnOrZero(balanceCryptoBaseUnit).lt(protocolFee.amountCryptoBaseUnit) }) .map(([_assetId, protocolFee]: [AssetId, ProtocolFee]) => { diff --git a/yarn.lock b/yarn.lock index 735cbc49688..8d5e7954552 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11699,6 +11699,7 @@ __metadata: version: 0.0.0-use.local resolution: "@shapeshiftoss/swapper@workspace:packages/swapper" dependencies: + "@coral-xyz/anchor": 0.29.0 "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/chain-adapters": "workspace:^" "@shapeshiftoss/types": "workspace:^" @@ -11774,7 +11775,6 @@ __metadata: "@chakra-ui/tag": ^3.1.1 "@commitlint/cli": ^15.0.0 "@commitlint/config-conventional": ^15.0.0 - "@coral-xyz/anchor": 0.29.0 "@cowprotocol/app-data": ^2.3.0 "@emotion/react": ^11.13.0 "@emotion/styled": ^11.13.0 From 05f3dd7b53205b8439af5326a3b70bb81b0fcd69 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:35:07 +0100 Subject: [PATCH 3/9] fix: apps crash randomly while navigating --- src/state/apis/swapper/helpers/validateTradeQuote.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/state/apis/swapper/helpers/validateTradeQuote.ts b/src/state/apis/swapper/helpers/validateTradeQuote.ts index 11a664dda36..d7f4f89467e 100644 --- a/src/state/apis/swapper/helpers/validateTradeQuote.ts +++ b/src/state/apis/swapper/helpers/validateTradeQuote.ts @@ -217,13 +217,20 @@ export const validateTradeQuote = ( portfolioAccountIdByNumberByChainId[sellAssetAccountNumber][protocolFee.asset.chainId] const balanceCryptoBaseUnit = portfolioAccountBalancesBaseUnit[accountId][assetId] - if (firstHopSellFeeAsset?.assetId === assetId) { + if ( + firstHopSellFeeAsset?.assetId === assetId && + firstHop.sellAsset.assetId === assetId + ) { return bnOrZero(balanceCryptoBaseUnit) .minus(sellAmountCryptoBaseUnit) .minus(protocolFee.amountCryptoBaseUnit) .lt(0) } + console.log( + 'not sell asset', + bnOrZero(balanceCryptoBaseUnit).lt(protocolFee.amountCryptoBaseUnit), + ) return bnOrZero(balanceCryptoBaseUnit).lt(protocolFee.amountCryptoBaseUnit) }) .map(([_assetId, protocolFee]: [AssetId, ProtocolFee]) => { From 6840b5c3ff9eefd79239117bbd0c17cdb1c4bebf Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:52:58 +0100 Subject: [PATCH 4/9] fix: review feedbacks --- .../swapperApi/getTradeQuote.ts | 12 ++-- .../JupiterSwapper/swapperApi/getTradeRate.ts | 57 +++++++++++-------- .../swappers/JupiterSwapper/utils/helpers.ts | 20 +++---- .../swapper/helpers/validateTradeQuote.ts | 4 -- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts index 241f8bfaa8a..96e2f534e0b 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts @@ -164,7 +164,7 @@ export const getTradeQuote = async ( ? fromAssetId(wrappedSolAssetId).assetReference : fromAssetId(sellAsset.assetId).assetReference - const [buyAssetAccount] = await PublicKey.findProgramAddressSync( + const [buyAssetReferralPubKey] = await PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), @@ -173,7 +173,7 @@ export const getTradeQuote = async ( new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), ) - const [sellAssetAccount] = await PublicKey.findProgramAddressSync( + const [sellAssetReferralPubKey] = await PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), @@ -190,8 +190,8 @@ export const getTradeQuote = async ( const { instruction: feeAccountInstruction, tokenAccount } = await getFeeTokenAccountAndInstruction({ feePayerPubKey: new PublicKey(sendAddress), - buyAssetAccount, - sellAssetAccount, + buyAssetReferralPubKey, + sellAssetReferralPubKey, programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), instructionData, buyTokenId: buyAssetAddress, @@ -293,9 +293,11 @@ export const getTradeQuote = async ( ) if (feeAccountInstruction) { + const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + protocolFees[solAssetId] = { requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) .plus(PDA_ACCOUNT_CREATION_COST) .toFixed(), asset: solAsset, diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts index 01fa9f9c212..c1c2107c5c4 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts @@ -88,6 +88,15 @@ export const getTradeRate = async ( ) } + if (!solAsset) { + return Err( + makeSwapErrorRight({ + message: `solAsset should be defined`, + code: TradeQuoteError.UnknownError, + }), + ) + } + const maybePriceResponse = await getJupiterPrice({ apiUrl: jupiterUrl, sourceAsset: sellAsset.assetId === solAssetId ? wrappedSolAssetId : sellAsset.assetId, @@ -138,7 +147,7 @@ export const getTradeRate = async ( ? fromAssetId(wrappedSolAssetId).assetReference : fromAssetId(sellAsset.assetId).assetReference - const [buyAssetAccount] = await PublicKey.findProgramAddressSync( + const [buyAssetReferralPubKey] = await PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), @@ -147,7 +156,7 @@ export const getTradeRate = async ( new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), ) - const [sellAssetAccount] = await PublicKey.findProgramAddressSync( + const [sellAssetReferralPubKey] = await PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), @@ -165,8 +174,8 @@ export const getTradeRate = async ( const { instruction: feeAccountInstruction } = await getFeeTokenAccountAndInstruction({ feePayerPubKey: new PublicKey(input.sendAddress ?? SOLANA_RANDOM_ADDRESS), - buyAssetAccount, - sellAssetAccount, + buyAssetReferralPubKey, + sellAssetReferralPubKey, programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), instructionData, buyTokenId: buyAssetAddress, @@ -200,32 +209,34 @@ export const getTradeRate = async ( ) if (feeAccountInstruction) { - if (solAsset) { - protocolFees[solAssetId] = { - requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) - .plus(PDA_ACCOUNT_CREATION_COST) - .toFixed(), - asset: solAsset, - } + const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) + .plus(PDA_ACCOUNT_CREATION_COST) + .toFixed(), + asset: solAsset, } } - if (input.sendAddress && solAsset) { - const { instruction: createTokenAccountInstruction } = buyAsset - ? await adapter.createAssociatedTokenAccountInstruction({ - from: input.sendAddress, - to: receiveAddress ?? input.sendAddress, - tokenId: fromAssetId( - buyAsset.assetId === solAssetId ? wrappedSolAssetId : buyAsset.assetId, - ).assetReference, - }) - : { instruction: undefined } + if (input.sendAddress) { + const { instruction: createTokenAccountInstruction } = + await adapter.createAssociatedTokenAccountInstruction({ + from: input.sendAddress, + // If we have a receive address, we use that as the receive address to verify the receive addy has an associated token account (ATA) or not, + // else we verify if our own addy has an ATA + to: receiveAddress ?? input.sendAddress, + tokenId: fromAssetId(buyAsset.assetId === solAssetId ? wrappedSolAssetId : buyAsset.assetId) + .assetReference, + }) if (createTokenAccountInstruction) { + const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + protocolFees[solAssetId] = { requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) .plus(PDA_ACCOUNT_CREATION_COST) .toFixed(), asset: solAsset, diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts index 002c0a30558..c61a06b5fe3 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts @@ -81,8 +81,8 @@ export const getJupiterSwapInstructions = ({ export const getFeeTokenAccountAndInstruction = async ({ feePayerPubKey, programId, - buyAssetAccount, - sellAssetAccount, + buyAssetReferralPubKey, + sellAssetReferralPubKey, buyTokenId, sellTokenId, instructionData, @@ -90,8 +90,8 @@ export const getFeeTokenAccountAndInstruction = async ({ }: { feePayerPubKey: PublicKey programId: PublicKey - buyAssetAccount: PublicKey - sellAssetAccount: PublicKey + buyAssetReferralPubKey: PublicKey + sellAssetReferralPubKey: PublicKey buyTokenId: string sellTokenId: string instructionData: Buffer @@ -100,13 +100,13 @@ export const getFeeTokenAccountAndInstruction = async ({ tokenAccount?: PublicKey instruction?: TransactionInstruction | undefined }> => { - const buyAssetTokenAccount = await connection.getAccountInfo(buyAssetAccount) + const buyAssetTokenAccount = await connection.getAccountInfo(buyAssetReferralPubKey) - if (buyAssetTokenAccount) return { tokenAccount: buyAssetAccount } + if (buyAssetTokenAccount) return { tokenAccount: buyAssetReferralPubKey } - const sellAssetTokenAccount = await connection.getAccountInfo(sellAssetAccount) + const sellAssetTokenAccount = await connection.getAccountInfo(sellAssetReferralPubKey) - if (sellAssetTokenAccount) return { tokenAccount: sellAssetAccount } + if (sellAssetTokenAccount) return { tokenAccount: sellAssetReferralPubKey } const buyTokenInfo = await connection.getAccountInfo(new PublicKey(buyTokenId)) const sellTokenInfo = await connection.getAccountInfo(new PublicKey(sellTokenId)) @@ -121,7 +121,7 @@ export const getFeeTokenAccountAndInstruction = async ({ const project = new PublicKey(JUPITER_REFERALL_FEE_PROJECT_ACCOUNT) return { - tokenAccount: buyAssetAccount, + tokenAccount: buyAssetReferralPubKey, instruction: new TransactionInstruction({ keys: [ { @@ -140,7 +140,7 @@ export const getFeeTokenAccountAndInstruction = async ({ isWritable: false, }, { - pubkey: buyAssetAccount, + pubkey: buyAssetReferralPubKey, isSigner: false, isWritable: true, }, diff --git a/src/state/apis/swapper/helpers/validateTradeQuote.ts b/src/state/apis/swapper/helpers/validateTradeQuote.ts index d7f4f89467e..8b4d164f4fd 100644 --- a/src/state/apis/swapper/helpers/validateTradeQuote.ts +++ b/src/state/apis/swapper/helpers/validateTradeQuote.ts @@ -227,10 +227,6 @@ export const validateTradeQuote = ( .lt(0) } - console.log( - 'not sell asset', - bnOrZero(balanceCryptoBaseUnit).lt(protocolFee.amountCryptoBaseUnit), - ) return bnOrZero(balanceCryptoBaseUnit).lt(protocolFee.amountCryptoBaseUnit) }) .map(([_assetId, protocolFee]: [AssetId, ProtocolFee]) => { From 8b35c89df9553f2d38e0ec0412672fc9f60d931a Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:49:06 +0100 Subject: [PATCH 5/9] fix: invert created account --- .../src/swappers/JupiterSwapper/utils/helpers.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts index c61a06b5fe3..b383b783729 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts @@ -100,14 +100,14 @@ export const getFeeTokenAccountAndInstruction = async ({ tokenAccount?: PublicKey instruction?: TransactionInstruction | undefined }> => { - const buyAssetTokenAccount = await connection.getAccountInfo(buyAssetReferralPubKey) - - if (buyAssetTokenAccount) return { tokenAccount: buyAssetReferralPubKey } - const sellAssetTokenAccount = await connection.getAccountInfo(sellAssetReferralPubKey) if (sellAssetTokenAccount) return { tokenAccount: sellAssetReferralPubKey } + const buyAssetTokenAccount = await connection.getAccountInfo(buyAssetReferralPubKey) + + if (buyAssetTokenAccount) return { tokenAccount: buyAssetReferralPubKey } + const buyTokenInfo = await connection.getAccountInfo(new PublicKey(buyTokenId)) const sellTokenInfo = await connection.getAccountInfo(new PublicKey(sellTokenId)) @@ -121,7 +121,7 @@ export const getFeeTokenAccountAndInstruction = async ({ const project = new PublicKey(JUPITER_REFERALL_FEE_PROJECT_ACCOUNT) return { - tokenAccount: buyAssetReferralPubKey, + tokenAccount: sellAssetReferralPubKey, instruction: new TransactionInstruction({ keys: [ { @@ -140,7 +140,7 @@ export const getFeeTokenAccountAndInstruction = async ({ isWritable: false, }, { - pubkey: buyAssetReferralPubKey, + pubkey: sellAssetReferralPubKey, isSigner: false, isWritable: true, }, From 10c98b6156314e71f8ec309e25bbd54fd475ec53 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:13:48 +0100 Subject: [PATCH 6/9] feat: review feedbacks --- .../src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts | 4 ++-- .../src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts | 6 +++--- src/state/apis/swapper/helpers/validateTradeQuote.ts | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts index 96e2f534e0b..998e040a137 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts @@ -164,7 +164,7 @@ export const getTradeQuote = async ( ? fromAssetId(wrappedSolAssetId).assetReference : fromAssetId(sellAsset.assetId).assetReference - const [buyAssetReferralPubKey] = await PublicKey.findProgramAddressSync( + const [buyAssetReferralPubKey] = PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), @@ -173,7 +173,7 @@ export const getTradeQuote = async ( new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), ) - const [sellAssetReferralPubKey] = await PublicKey.findProgramAddressSync( + const [sellAssetReferralPubKey] = PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts index c1c2107c5c4..f82c88501d3 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts @@ -147,7 +147,7 @@ export const getTradeRate = async ( ? fromAssetId(wrappedSolAssetId).assetReference : fromAssetId(sellAsset.assetId).assetReference - const [buyAssetReferralPubKey] = await PublicKey.findProgramAddressSync( + const [buyAssetReferralPubKey] = PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), @@ -156,7 +156,7 @@ export const getTradeRate = async ( new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), ) - const [sellAssetReferralPubKey] = await PublicKey.findProgramAddressSync( + const [sellAssetReferralPubKey] = PublicKey.findProgramAddressSync( [ Buffer.from('referral_ata'), new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), @@ -208,7 +208,7 @@ export const getTradeRate = async ( {} as Record, ) - if (feeAccountInstruction) { + if (feeAccountInstruction && affiliateBps !== '0') { const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) protocolFees[solAssetId] = { diff --git a/src/state/apis/swapper/helpers/validateTradeQuote.ts b/src/state/apis/swapper/helpers/validateTradeQuote.ts index 8b4d164f4fd..482a8d145ca 100644 --- a/src/state/apis/swapper/helpers/validateTradeQuote.ts +++ b/src/state/apis/swapper/helpers/validateTradeQuote.ts @@ -217,9 +217,12 @@ export const validateTradeQuote = ( portfolioAccountIdByNumberByChainId[sellAssetAccountNumber][protocolFee.asset.chainId] const balanceCryptoBaseUnit = portfolioAccountBalancesBaseUnit[accountId][assetId] + // @TODO: seems like this condition should be applied for all the swappers, verify by smoke testing all of them + // them kick the swapperName bit out of the condition if ( firstHopSellFeeAsset?.assetId === assetId && - firstHop.sellAsset.assetId === assetId + firstHop.sellAsset.assetId === assetId && + swapperName === SwapperName.Jupiter ) { return bnOrZero(balanceCryptoBaseUnit) .minus(sellAmountCryptoBaseUnit) From 0ca482c0c0cb841e4a7b1064dbc634325975b241 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:45:57 +0100 Subject: [PATCH 7/9] fix: invert created account --- .../swapperApi/getTradeQuote.ts | 33 +++++++++++++++++++ .../JupiterSwapper/swapperApi/getTradeRate.ts | 27 +++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts index 998e040a137..a19df97d4c4 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts @@ -143,6 +143,15 @@ export const getTradeQuote = async ( const adapter = deps.assertGetSolanaChainAdapter(sellAsset.chainId) + const { instruction: createWSOLTokenAccount } = + sellAsset.assetId === solAssetId + ? await adapter.createAssociatedTokenAccountInstruction({ + from: sendAddress, + to: receiveAddress!, + tokenId: fromAssetId(wrappedSolAssetId).assetReference, + }) + : { instruction: undefined } + const isCrossAccountTrade = receiveAddress ? receiveAddress !== sendAddress : false const { instruction: createTokenAccountInstruction, destinationTokenAccount } = @@ -292,6 +301,30 @@ export const getTradeQuote = async ( {} as Record, ) + if (createWSOLTokenAccount) { + const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) + .plus(PDA_ACCOUNT_CREATION_COST) + .toFixed(), + asset: solAsset, + } + } + + if (createTokenAccountInstruction) { + const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) + .plus(PDA_ACCOUNT_CREATION_COST) + .toFixed(), + asset: solAsset, + } + } + if (feeAccountInstruction) { const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts index f82c88501d3..d594715436d 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts @@ -221,7 +221,30 @@ export const getTradeRate = async ( } if (input.sendAddress) { - const { instruction: createTokenAccountInstruction } = + const { instruction: createSellTokenAccountInstruction } = + await adapter.createAssociatedTokenAccountInstruction({ + from: input.sendAddress, + // If we have a receive address, we use that as the receive address to verify the receive addy has an associated token account (ATA) or not, + // else we verify if our own addy has an ATA + to: receiveAddress ?? input.sendAddress, + tokenId: fromAssetId( + sellAsset.assetId === solAssetId ? wrappedSolAssetId : sellAsset.assetId, + ).assetReference, + }) + + if (createSellTokenAccountInstruction) { + const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) + .plus(PDA_ACCOUNT_CREATION_COST) + .toFixed(), + asset: solAsset, + } + } + + const { instruction: createBuyTokenAccountInstruction } = await adapter.createAssociatedTokenAccountInstruction({ from: input.sendAddress, // If we have a receive address, we use that as the receive address to verify the receive addy has an associated token account (ATA) or not, @@ -231,7 +254,7 @@ export const getTradeRate = async ( .assetReference, }) - if (createTokenAccountInstruction) { + if (createBuyTokenAccountInstruction) { const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) protocolFees[solAssetId] = { From b3e000639137f842e3edf43fcad71412b4a77872 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:41:04 +0100 Subject: [PATCH 8/9] feat: handle every amounts --- .../swapperApi/getTradeQuote.ts | 57 ++++--------------- .../JupiterSwapper/swapperApi/getTradeRate.ts | 4 +- .../JupiterSwapper/utils/constants.ts | 2 + .../swappers/JupiterSwapper/utils/helpers.ts | 18 +++++- 4 files changed, 33 insertions(+), 48 deletions(-) diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts index a19df97d4c4..99a7a6ac476 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts @@ -33,10 +33,10 @@ import { referralIdl } from '../idls/referral' import { JUPITER_AFFILIATE_CONTRACT_ADDRESS, JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER, - PDA_ACCOUNT_CREATION_COST, SHAPESHIFT_JUPITER_REFERRAL_KEY, } from '../utils/constants' import { + calculateAccountCreationCosts, getFeeTokenAccountAndInstruction, getJupiterPrice, getJupiterSwapInstructions, @@ -143,15 +143,6 @@ export const getTradeQuote = async ( const adapter = deps.assertGetSolanaChainAdapter(sellAsset.chainId) - const { instruction: createWSOLTokenAccount } = - sellAsset.assetId === solAssetId - ? await adapter.createAssociatedTokenAccountInstruction({ - from: sendAddress, - to: receiveAddress!, - tokenId: fromAssetId(wrappedSolAssetId).assetReference, - }) - : { instruction: undefined } - const isCrossAccountTrade = receiveAddress ? receiveAddress !== sendAddress : false const { instruction: createTokenAccountInstruction, destinationTokenAccount } = @@ -301,53 +292,29 @@ export const getTradeQuote = async ( {} as Record, ) - if (createWSOLTokenAccount) { - const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + const quotes: TradeQuote[] = [] - protocolFees[solAssetId] = { - requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) - .plus(PDA_ACCOUNT_CREATION_COST) - .toFixed(), - asset: solAsset, - } - } + const feeData = await getFeeData() - if (createTokenAccountInstruction) { - const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + const rate = getInputOutputRate({ + sellAmountCryptoBaseUnit: priceResponse.inAmount, + buyAmountCryptoBaseUnit: priceResponse.outAmount, + sellAsset, + buyAsset, + }) - protocolFees[solAssetId] = { - requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) - .plus(PDA_ACCOUNT_CREATION_COST) - .toFixed(), - asset: solAsset, - } - } + const accountCreationFees = calculateAccountCreationCosts(instructions) - if (feeAccountInstruction) { + if (accountCreationFees !== '0') { const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) protocolFees[solAssetId] = { requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) - .plus(PDA_ACCOUNT_CREATION_COST) - .toFixed(), + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount).plus(accountCreationFees).toFixed(), asset: solAsset, } } - const quotes: TradeQuote[] = [] - - const feeData = await getFeeData() - - const rate = getInputOutputRate({ - sellAmountCryptoBaseUnit: priceResponse.inAmount, - buyAmountCryptoBaseUnit: priceResponse.outAmount, - sellAsset, - buyAsset, - }) - const tradeQuote: TradeQuote = { id: uuid(), rate, diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts index d594715436d..e3eba27e631 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts @@ -221,7 +221,7 @@ export const getTradeRate = async ( } if (input.sendAddress) { - const { instruction: createSellTokenAccountInstruction } = + const { instruction: createWSOLTokenAccountInstruction } = await adapter.createAssociatedTokenAccountInstruction({ from: input.sendAddress, // If we have a receive address, we use that as the receive address to verify the receive addy has an associated token account (ATA) or not, @@ -232,7 +232,7 @@ export const getTradeRate = async ( ).assetReference, }) - if (createSellTokenAccountInstruction) { + if (createWSOLTokenAccountInstruction) { const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) protocolFees[solAssetId] = { diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts index d1e830efb70..032a7a2c2fb 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/constants.ts @@ -14,6 +14,8 @@ export const PDA_ACCOUNT_CREATION_COST = 2040000 export const SOLANA_RANDOM_ADDRESS = '2zHKF6tqam3tnNFPK2E9nBDkV7GMXnvdJautmzqQdn8A' +export const TOKEN_2022_PROGRAM_ID = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb' + // Jupiter use 40% as a compute unit margin while calculating them, some TX reverts without this export const JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER = 1.4 diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts index b383b783729..308577f15a2 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts @@ -1,8 +1,9 @@ -import { TOKEN_PROGRAM_ID } from '@coral-xyz/anchor/dist/cjs/utils/token' +import { ASSOCIATED_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@coral-xyz/anchor/dist/cjs/utils/token' import type { QuoteResponse, SwapInstructionsResponse } from '@jup-ag/api' import type { ChainId } from '@shapeshiftoss/caip' import { fromAssetId } from '@shapeshiftoss/caip' import type { KnownChainIds } from '@shapeshiftoss/types' +import { bnOrZero } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' import type { Connection } from '@solana/web3.js' import { PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js' @@ -12,6 +13,7 @@ import type { SwapErrorRight } from '../../../types' import { JUPITER_REFERALL_FEE_PROJECT_ACCOUNT, jupiterSupportedChainIds, + PDA_ACCOUNT_CREATION_COST, SHAPESHIFT_JUPITER_REFERRAL_KEY, } from './constants' import { jupiterService } from './jupiterService' @@ -161,3 +163,17 @@ export const getFeeTokenAccountAndInstruction = async ({ }), } } + +export const calculateAccountCreationCosts = (instructions: TransactionInstruction[]): string => { + let totalCost = bnOrZero(0) + + for (const ix of instructions) { + if (ix.programId.toString() === ASSOCIATED_PROGRAM_ID.toString()) { + if (ix.data?.[0] === 1) { + totalCost = totalCost.plus(PDA_ACCOUNT_CREATION_COST) + } + } + } + + return totalCost.toString() +} From e7f017888407a20acd46204a3b9f681223c38c53 Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:57:16 +0100 Subject: [PATCH 9/9] fix: im done --- .../swapperApi/getTradeQuote.ts | 131 ++-------------- .../JupiterSwapper/swapperApi/getTradeRate.ts | 139 ++++------------- .../swappers/JupiterSwapper/utils/helpers.ts | 142 +++++++++++++++++- 3 files changed, 177 insertions(+), 235 deletions(-) diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts index 99a7a6ac476..0865311c85c 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeQuote.ts @@ -1,11 +1,8 @@ -import { BorshInstructionCoder } from '@coral-xyz/anchor' -import type { Instruction } from '@jup-ag/api' import type { AssetId } from '@shapeshiftoss/caip' import { ASSET_NAMESPACE, CHAIN_NAMESPACE, CHAIN_REFERENCE, - fromAssetId, solAssetId, toAssetId, wrappedSolAssetId, @@ -15,9 +12,6 @@ import type { KnownChainIds } from '@shapeshiftoss/types' import { bn, bnOrZero, convertDecimalPercentageToBasisPoints } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' import { Err, Ok } from '@sniptt/monads' -import type { TransactionInstruction } from '@solana/web3.js' -import { PublicKey } from '@solana/web3.js' -import type { AxiosError } from 'axios' import { v4 as uuid } from 'uuid' import type { @@ -29,17 +23,11 @@ import type { } from '../../../types' import { SwapperName, TradeQuoteError } from '../../../types' import { getInputOutputRate, makeSwapErrorRight } from '../../../utils' -import { referralIdl } from '../idls/referral' -import { - JUPITER_AFFILIATE_CONTRACT_ADDRESS, - JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER, - SHAPESHIFT_JUPITER_REFERRAL_KEY, -} from '../utils/constants' +import { JUPITER_COMPUTE_UNIT_MARGIN_MULTIPLIER } from '../utils/constants' import { calculateAccountCreationCosts, - getFeeTokenAccountAndInstruction, + createSwapInstructions, getJupiterPrice, - getJupiterSwapInstructions, isSupportedChainId, } from '../utils/helpers' @@ -138,112 +126,19 @@ export const getTradeQuote = async ( // e.g for 0.5% bps, Jupiter represents this as 50. 50/100 = 0.5, then we div by 100 again to honour our decimal format e.g 0.5/100 = 0.005 bn(priceResponse.slippageBps).div(100).div(100).toString() - const contractAddress = - buyAsset.assetId === solAssetId ? undefined : fromAssetId(buyAsset.assetId).assetReference - const adapter = deps.assertGetSolanaChainAdapter(sellAsset.chainId) - const isCrossAccountTrade = receiveAddress ? receiveAddress !== sendAddress : false - - const { instruction: createTokenAccountInstruction, destinationTokenAccount } = - contractAddress && isCrossAccountTrade - ? await adapter.createAssociatedTokenAccountInstruction({ - from: sendAddress, - to: receiveAddress!, - tokenId: contractAddress, - }) - : { instruction: undefined, destinationTokenAccount: undefined } - - const buyAssetAddress = - buyAsset.assetId === solAssetId - ? fromAssetId(wrappedSolAssetId).assetReference - : fromAssetId(buyAsset.assetId).assetReference - - const sellAssetAddress = - sellAsset.assetId === solAssetId - ? fromAssetId(wrappedSolAssetId).assetReference - : fromAssetId(sellAsset.assetId).assetReference - - const [buyAssetReferralPubKey] = PublicKey.findProgramAddressSync( - [ - Buffer.from('referral_ata'), - new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), - new PublicKey(buyAssetAddress).toBuffer(), - ], - new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), - ) - - const [sellAssetReferralPubKey] = PublicKey.findProgramAddressSync( - [ - Buffer.from('referral_ata'), - new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), - new PublicKey(sellAssetAddress).toBuffer(), - ], - new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), - ) - - const instructionData = new BorshInstructionCoder(referralIdl).encode( - 'initializeReferralTokenAccount', - {}, - ) - - const { instruction: feeAccountInstruction, tokenAccount } = - await getFeeTokenAccountAndInstruction({ - feePayerPubKey: new PublicKey(sendAddress), - buyAssetReferralPubKey, - sellAssetReferralPubKey, - programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), - instructionData, - buyTokenId: buyAssetAddress, - sellTokenId: sellAssetAddress, - connection: adapter.getConnection(), - }) - - const maybeSwapResponse = await getJupiterSwapInstructions({ - apiUrl: jupiterUrl, - fromAddress: sendAddress, - toAddress: isCrossAccountTrade ? destinationTokenAccount?.toString() : undefined, - rawQuote: priceResponse, - // Shared account is not supported for simple AMMs - useSharedAccounts: priceResponse.routePlan.length > 1 && isCrossAccountTrade ? true : false, - feeAccount: affiliateBps !== '0' ? tokenAccount?.toString() : undefined, - }) - - if (maybeSwapResponse.isErr()) { - const error = maybeSwapResponse.unwrapErr() - const cause = error.cause as AxiosError - throw Error(cause.response!.data.detail) - } - - const { data: swapResponse } = maybeSwapResponse.unwrap() - - const convertJupiterInstruction = (instruction: Instruction): TransactionInstruction => ({ - ...instruction, - keys: instruction.accounts.map(account => ({ - ...account, - pubkey: new PublicKey(account.pubkey), - })), - data: Buffer.from(instruction.data, 'base64'), - programId: new PublicKey(instruction.programId), + const { instructions, addressLookupTableAddresses } = await createSwapInstructions({ + priceResponse, + sendAddress, + receiveAddress, + affiliateBps, + buyAsset, + sellAsset, + adapter, + jupiterUrl, }) - const instructions: TransactionInstruction[] = [ - ...swapResponse.setupInstructions.map(convertJupiterInstruction), - convertJupiterInstruction(swapResponse.swapInstruction), - ] - - if (feeAccountInstruction && affiliateBps !== '0') { - instructions.unshift(feeAccountInstruction) - } - - if (createTokenAccountInstruction) { - instructions.unshift(createTokenAccountInstruction) - } - - if (swapResponse.cleanupInstruction) { - instructions.push(convertJupiterInstruction(swapResponse.cleanupInstruction)) - } - const getFeeData = async () => { const sellAdapter = deps.assertGetSolanaChainAdapter(sellAsset.chainId) const getFeeDataInput: GetFeeDataInput = { @@ -251,7 +146,7 @@ export const getTradeQuote = async ( value: '0', chainSpecific: { from: sendAddress, - addressLookupTableAccounts: swapResponse.addressLookupTableAddresses, + addressLookupTableAccounts: addressLookupTableAddresses, instructions, }, } @@ -330,7 +225,7 @@ export const getTradeQuote = async ( sellAmountIncludingProtocolFeesCryptoBaseUnit: priceResponse.inAmount, jupiterQuoteResponse: priceResponse, jupiterTransactionMetadata: { - addressLookupTableAddresses: swapResponse.addressLookupTableAddresses, + addressLookupTableAddresses, instructions, }, feeData: { diff --git a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts index e3eba27e631..677dc3dfacc 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/swapperApi/getTradeRate.ts @@ -1,4 +1,3 @@ -import { BorshInstructionCoder } from '@coral-xyz/anchor' import type { AssetId } from '@shapeshiftoss/caip' import { ASSET_NAMESPACE, @@ -14,7 +13,6 @@ import type { KnownChainIds } from '@shapeshiftoss/types' import { bn, bnOrZero, convertDecimalPercentageToBasisPoints } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' import { Err, Ok } from '@sniptt/monads' -import { PublicKey } from '@solana/web3.js' import { v4 as uuid } from 'uuid' import type { @@ -26,15 +24,10 @@ import type { } from '../../../types' import { SwapperName, TradeQuoteError } from '../../../types' import { getInputOutputRate, makeSwapErrorRight } from '../../../utils' -import { referralIdl } from '../idls/referral' +import { SOLANA_RANDOM_ADDRESS } from '../utils/constants' import { - JUPITER_AFFILIATE_CONTRACT_ADDRESS, - PDA_ACCOUNT_CREATION_COST, - SHAPESHIFT_JUPITER_REFERRAL_KEY, - SOLANA_RANDOM_ADDRESS, -} from '../utils/constants' -import { - getFeeTokenAccountAndInstruction, + calculateAccountCreationCosts, + createSwapInstructions, getJupiterPrice, isSupportedChainId, } from '../utils/helpers' @@ -137,52 +130,8 @@ export const getTradeRate = async ( return { networkFeeCryptoBaseUnit: fast.txFee } } - const buyAssetAddress = - buyAsset.assetId === solAssetId - ? fromAssetId(wrappedSolAssetId).assetReference - : fromAssetId(buyAsset.assetId).assetReference - - const sellAssetAddress = - sellAsset.assetId === solAssetId - ? fromAssetId(wrappedSolAssetId).assetReference - : fromAssetId(sellAsset.assetId).assetReference - - const [buyAssetReferralPubKey] = PublicKey.findProgramAddressSync( - [ - Buffer.from('referral_ata'), - new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), - new PublicKey(buyAssetAddress).toBuffer(), - ], - new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), - ) - - const [sellAssetReferralPubKey] = PublicKey.findProgramAddressSync( - [ - Buffer.from('referral_ata'), - new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), - new PublicKey(sellAssetAddress).toBuffer(), - ], - new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), - ) - - const instructionData = new BorshInstructionCoder(referralIdl).encode( - 'initializeReferralTokenAccount', - {}, - ) - const adapter = deps.assertGetSolanaChainAdapter(sellAsset.chainId) - const { instruction: feeAccountInstruction } = await getFeeTokenAccountAndInstruction({ - feePayerPubKey: new PublicKey(input.sendAddress ?? SOLANA_RANDOM_ADDRESS), - buyAssetReferralPubKey, - sellAssetReferralPubKey, - programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), - instructionData, - buyTokenId: buyAssetAddress, - sellTokenId: sellAssetAddress, - connection: adapter.getConnection(), - }) - const protocolFees: Record = priceResponse.routePlan.reduce( (acc, route) => { const feeAssetId = toAssetId({ @@ -208,65 +157,6 @@ export const getTradeRate = async ( {} as Record, ) - if (feeAccountInstruction && affiliateBps !== '0') { - const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) - - protocolFees[solAssetId] = { - requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) - .plus(PDA_ACCOUNT_CREATION_COST) - .toFixed(), - asset: solAsset, - } - } - - if (input.sendAddress) { - const { instruction: createWSOLTokenAccountInstruction } = - await adapter.createAssociatedTokenAccountInstruction({ - from: input.sendAddress, - // If we have a receive address, we use that as the receive address to verify the receive addy has an associated token account (ATA) or not, - // else we verify if our own addy has an ATA - to: receiveAddress ?? input.sendAddress, - tokenId: fromAssetId( - sellAsset.assetId === solAssetId ? wrappedSolAssetId : sellAsset.assetId, - ).assetReference, - }) - - if (createWSOLTokenAccountInstruction) { - const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) - - protocolFees[solAssetId] = { - requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) - .plus(PDA_ACCOUNT_CREATION_COST) - .toFixed(), - asset: solAsset, - } - } - - const { instruction: createBuyTokenAccountInstruction } = - await adapter.createAssociatedTokenAccountInstruction({ - from: input.sendAddress, - // If we have a receive address, we use that as the receive address to verify the receive addy has an associated token account (ATA) or not, - // else we verify if our own addy has an ATA - to: receiveAddress ?? input.sendAddress, - tokenId: fromAssetId(buyAsset.assetId === solAssetId ? wrappedSolAssetId : buyAsset.assetId) - .assetReference, - }) - - if (createBuyTokenAccountInstruction) { - const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) - - protocolFees[solAssetId] = { - requiresBalance: true, - amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount) - .plus(PDA_ACCOUNT_CREATION_COST) - .toFixed(), - asset: solAsset, - } - } - } - const rates: TradeRate[] = [] const feeData = await getFeeData() @@ -283,6 +173,29 @@ export const getTradeRate = async ( // e.g for 0.5% bps, Jupiter represents this as 50. 50/100 = 0.5, then we div by 100 again to honour our decimal format e.g 0.5/100 = 0.005 bn(priceResponse.slippageBps).div(100).div(100).toString() + const { instructions } = await createSwapInstructions({ + priceResponse, + sendAddress: input.sendAddress ?? SOLANA_RANDOM_ADDRESS, + receiveAddress, + affiliateBps, + buyAsset, + sellAsset, + adapter, + jupiterUrl, + }) + + const accountCreationFees = calculateAccountCreationCosts(instructions) + + if (accountCreationFees !== '0') { + const solProtocolFeeAmount = bnOrZero(protocolFees[solAssetId]?.amountCryptoBaseUnit) + + protocolFees[solAssetId] = { + requiresBalance: true, + amountCryptoBaseUnit: bnOrZero(solProtocolFeeAmount).plus(accountCreationFees).toFixed(), + asset: solAsset, + } + } + const tradeRate: TradeRate = { id: uuid(), rate: inputOutputRate, diff --git a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts index 308577f15a2..0c4993293d3 100644 --- a/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts +++ b/packages/swapper/src/swappers/JupiterSwapper/utils/helpers.ts @@ -1,16 +1,20 @@ +import { BorshInstructionCoder } from '@coral-xyz/anchor' import { ASSOCIATED_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@coral-xyz/anchor/dist/cjs/utils/token' -import type { QuoteResponse, SwapInstructionsResponse } from '@jup-ag/api' +import type { Instruction, QuoteResponse, SwapInstructionsResponse } from '@jup-ag/api' import type { ChainId } from '@shapeshiftoss/caip' -import { fromAssetId } from '@shapeshiftoss/caip' -import type { KnownChainIds } from '@shapeshiftoss/types' +import { fromAssetId, solAssetId, wrappedSolAssetId } from '@shapeshiftoss/caip' +import type { ChainAdapter } from '@shapeshiftoss/chain-adapters/src/solana' +import type { Asset, KnownChainIds } from '@shapeshiftoss/types' import { bnOrZero } from '@shapeshiftoss/utils' import type { Result } from '@sniptt/monads' import type { Connection } from '@solana/web3.js' import { PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js' -import type { AxiosResponse } from 'axios' +import type { AxiosError, AxiosResponse } from 'axios' import type { SwapErrorRight } from '../../../types' +import { referralIdl } from '../idls/referral' import { + JUPITER_AFFILIATE_CONTRACT_ADDRESS, JUPITER_REFERALL_FEE_PROJECT_ACCOUNT, jupiterSupportedChainIds, PDA_ACCOUNT_CREATION_COST, @@ -42,6 +46,17 @@ type GetJupiterSwapArgs = { feeAccount: string | undefined } +type CreateInstructionsParams = { + priceResponse: QuoteResponse + sendAddress: string + receiveAddress?: string + affiliateBps: string + buyAsset: Asset + sellAsset: any + adapter: ChainAdapter + jupiterUrl: string +} + export const getJupiterPrice = ({ apiUrl, sourceAsset, @@ -177,3 +192,122 @@ export const calculateAccountCreationCosts = (instructions: TransactionInstructi return totalCost.toString() } + +export const createSwapInstructions = async ({ + priceResponse, + sendAddress, + receiveAddress, + affiliateBps, + buyAsset, + sellAsset, + adapter, + jupiterUrl, +}: CreateInstructionsParams): Promise<{ + instructions: TransactionInstruction[] + addressLookupTableAddresses: string[] +}> => { + const isCrossAccountTrade = receiveAddress ? receiveAddress !== sendAddress : false + + const buyAssetAddress = + buyAsset.assetId === solAssetId + ? fromAssetId(wrappedSolAssetId).assetReference + : fromAssetId(buyAsset.assetId).assetReference + + const sellAssetAddress = + sellAsset.assetId === solAssetId + ? fromAssetId(wrappedSolAssetId).assetReference + : fromAssetId(sellAsset.assetId).assetReference + + const contractAddress = + buyAsset.assetId === solAssetId ? undefined : fromAssetId(buyAsset.assetId).assetReference + + const { instruction: createTokenAccountInstruction, destinationTokenAccount } = + contractAddress && isCrossAccountTrade + ? await adapter.createAssociatedTokenAccountInstruction({ + from: sendAddress, + to: receiveAddress!, + tokenId: contractAddress, + }) + : { instruction: undefined, destinationTokenAccount: undefined } + + const [buyAssetReferralPubKey] = PublicKey.findProgramAddressSync( + [ + Buffer.from('referral_ata'), + new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), + new PublicKey(buyAssetAddress).toBuffer(), + ], + new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + ) + + const [sellAssetReferralPubKey] = PublicKey.findProgramAddressSync( + [ + Buffer.from('referral_ata'), + new PublicKey(SHAPESHIFT_JUPITER_REFERRAL_KEY).toBuffer(), + new PublicKey(sellAssetAddress).toBuffer(), + ], + new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + ) + + const instructionData = new BorshInstructionCoder(referralIdl).encode( + 'initializeReferralTokenAccount', + {}, + ) + + const { instruction: feeAccountInstruction, tokenAccount } = + await getFeeTokenAccountAndInstruction({ + feePayerPubKey: new PublicKey(sendAddress), + buyAssetReferralPubKey, + sellAssetReferralPubKey, + programId: new PublicKey(JUPITER_AFFILIATE_CONTRACT_ADDRESS), + instructionData, + buyTokenId: buyAssetAddress, + sellTokenId: sellAssetAddress, + connection: adapter.getConnection(), + }) + + const maybeSwapResponse = await getJupiterSwapInstructions({ + apiUrl: jupiterUrl, + fromAddress: sendAddress, + toAddress: isCrossAccountTrade ? destinationTokenAccount?.toString() : undefined, + rawQuote: priceResponse, + useSharedAccounts: priceResponse.routePlan.length > 1 && isCrossAccountTrade ? true : false, + feeAccount: affiliateBps !== '0' ? tokenAccount?.toString() : undefined, + }) + + if (maybeSwapResponse.isErr()) { + const error = maybeSwapResponse.unwrapErr() + const cause = error.cause as AxiosError + throw Error(cause.response!.data.detail) + } + + const { data: swapResponse } = maybeSwapResponse.unwrap() + + const convertJupiterInstruction = (instruction: Instruction): TransactionInstruction => ({ + ...instruction, + keys: instruction.accounts.map(account => ({ + ...account, + pubkey: new PublicKey(account.pubkey), + })), + data: Buffer.from(instruction.data, 'base64'), + programId: new PublicKey(instruction.programId), + }) + + const instructions: TransactionInstruction[] = [ + ...swapResponse.setupInstructions.map(convertJupiterInstruction), + convertJupiterInstruction(swapResponse.swapInstruction), + ] + + if (feeAccountInstruction && affiliateBps !== '0') { + instructions.unshift(feeAccountInstruction) + } + + if (createTokenAccountInstruction) { + instructions.unshift(createTokenAccountInstruction) + } + + if (swapResponse.cleanupInstruction) { + instructions.push(convertJupiterInstruction(swapResponse.cleanupInstruction)) + } + + return { instructions, addressLookupTableAddresses: swapResponse.addressLookupTableAddresses } +}