@oobe-protocol-labs/sap-mcp-server 0.9.38 → 0.9.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/env.d.ts +19 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +9 -2
- package/dist/config/env.js.map +1 -1
- package/dist/payments/monetization-gate.d.ts +5 -0
- package/dist/payments/monetization-gate.d.ts.map +1 -1
- package/dist/payments/monetization-gate.js +39 -2
- package/dist/payments/monetization-gate.js.map +1 -1
- package/dist/payments/prepaid-credit-store.d.ts +111 -0
- package/dist/payments/prepaid-credit-store.d.ts.map +1 -0
- package/dist/payments/prepaid-credit-store.js +230 -0
- package/dist/payments/prepaid-credit-store.js.map +1 -0
- package/dist/payments/pricing.d.ts.map +1 -1
- package/dist/payments/pricing.js +1 -0
- package/dist/payments/pricing.js.map +1 -1
- package/dist/payments/schema-validation.d.ts +52 -0
- package/dist/payments/schema-validation.d.ts.map +1 -0
- package/dist/payments/schema-validation.js +112 -0
- package/dist/payments/schema-validation.js.map +1 -0
- package/dist/payments/x402-paid-call.d.ts +21 -0
- package/dist/payments/x402-paid-call.d.ts.map +1 -1
- package/dist/payments/x402-paid-call.js +108 -3
- package/dist/payments/x402-paid-call.js.map +1 -1
- package/dist/perps/adrena/adrena-builder-commodity.d.ts +63 -0
- package/dist/perps/adrena/adrena-builder-commodity.d.ts.map +1 -0
- package/dist/perps/adrena/adrena-builder-commodity.js +200 -0
- package/dist/perps/adrena/adrena-builder-commodity.js.map +1 -0
- package/dist/perps/adrena/adrena-builder-core.d.ts +319 -0
- package/dist/perps/adrena/adrena-builder-core.d.ts.map +1 -0
- package/dist/perps/adrena/adrena-builder-core.js +517 -0
- package/dist/perps/adrena/adrena-builder-core.js.map +1 -0
- package/dist/perps/adrena/adrena-builder-liquidity.d.ts +50 -0
- package/dist/perps/adrena/adrena-builder-liquidity.d.ts.map +1 -0
- package/dist/perps/adrena/adrena-builder-liquidity.js +196 -0
- package/dist/perps/adrena/adrena-builder-liquidity.js.map +1 -0
- package/dist/perps/adrena/adrena-builder-staking.d.ts +57 -0
- package/dist/perps/adrena/adrena-builder-staking.d.ts.map +1 -0
- package/dist/perps/adrena/adrena-builder-staking.js +368 -0
- package/dist/perps/adrena/adrena-builder-staking.js.map +1 -0
- package/dist/perps/adrena/adrena-builder-trading.d.ts +160 -0
- package/dist/perps/adrena/adrena-builder-trading.d.ts.map +1 -0
- package/dist/perps/adrena/adrena-builder-trading.js +835 -0
- package/dist/perps/adrena/adrena-builder-trading.js.map +1 -0
- package/dist/perps/adrena/adrena-builder.d.ts +14 -293
- package/dist/perps/adrena/adrena-builder.d.ts.map +1 -1
- package/dist/perps/adrena/adrena-builder.js +14 -1140
- package/dist/perps/adrena/adrena-builder.js.map +1 -1
- package/dist/perps/adrena/adrena-constants.d.ts +33 -5
- package/dist/perps/adrena/adrena-constants.d.ts.map +1 -1
- package/dist/perps/adrena/adrena-constants.js +29 -7
- package/dist/perps/adrena/adrena-constants.js.map +1 -1
- package/dist/perps/adrena/adrena-idl.d.ts +15084 -0
- package/dist/perps/adrena/adrena-idl.d.ts.map +1 -0
- package/dist/perps/adrena/adrena-idl.js +29903 -0
- package/dist/perps/adrena/adrena-idl.js.map +1 -0
- package/dist/perps/adrena/adrena-pda.d.ts +5 -4
- package/dist/perps/adrena/adrena-pda.d.ts.map +1 -1
- package/dist/perps/adrena/adrena-pda.js +6 -10
- package/dist/perps/adrena/adrena-pda.js.map +1 -1
- package/dist/perps/adrena/index.d.ts +1 -0
- package/dist/perps/adrena/index.d.ts.map +1 -1
- package/dist/perps/adrena/index.js +1 -0
- package/dist/perps/adrena/index.js.map +1 -1
- package/dist/perps/chart-indicators.d.ts +27 -0
- package/dist/perps/chart-indicators.d.ts.map +1 -0
- package/dist/perps/chart-indicators.js +345 -0
- package/dist/perps/chart-indicators.js.map +1 -0
- package/dist/perps/chart-tools.d.ts +45 -0
- package/dist/perps/chart-tools.d.ts.map +1 -0
- package/dist/perps/chart-tools.js +341 -0
- package/dist/perps/chart-tools.js.map +1 -0
- package/dist/perps/perp-analytics-tools.d.ts +20 -0
- package/dist/perps/perp-analytics-tools.d.ts.map +1 -0
- package/dist/perps/perp-analytics-tools.js +868 -0
- package/dist/perps/perp-analytics-tools.js.map +1 -0
- package/dist/perps/perp-constants.d.ts +362 -0
- package/dist/perps/perp-constants.d.ts.map +1 -0
- package/dist/perps/perp-constants.js +210 -0
- package/dist/perps/perp-constants.js.map +1 -0
- package/dist/perps/perp-decoders.d.ts +47 -0
- package/dist/perps/perp-decoders.d.ts.map +1 -0
- package/dist/perps/perp-decoders.js +332 -0
- package/dist/perps/perp-decoders.js.map +1 -0
- package/dist/policy/policy-engine.d.ts +42 -0
- package/dist/policy/policy-engine.d.ts.map +1 -1
- package/dist/policy/policy-engine.js +43 -0
- package/dist/policy/policy-engine.js.map +1 -1
- package/dist/premium/activation-manager.d.ts.map +1 -1
- package/dist/premium/activation-manager.js +20 -2
- package/dist/premium/activation-manager.js.map +1 -1
- package/dist/prompts/context/sap-agent-context.prompt.js +15 -1
- package/dist/prompts/context/sap-agent-context.prompt.js.map +1 -1
- package/dist/server/server-metadata.d.ts +13 -0
- package/dist/server/server-metadata.d.ts.map +1 -1
- package/dist/server/server-metadata.js +15 -2
- package/dist/server/server-metadata.js.map +1 -1
- package/dist/solana/ata-utils.d.ts +39 -0
- package/dist/solana/ata-utils.d.ts.map +1 -0
- package/dist/solana/ata-utils.js +49 -0
- package/dist/solana/ata-utils.js.map +1 -0
- package/dist/strategies/trade-journal.d.ts +78 -0
- package/dist/strategies/trade-journal.d.ts.map +1 -0
- package/dist/strategies/trade-journal.js +92 -0
- package/dist/strategies/trade-journal.js.map +1 -0
- package/dist/tools/adrena/adrena-commodity-tools.d.ts +15 -0
- package/dist/tools/adrena/adrena-commodity-tools.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-commodity-tools.js +137 -0
- package/dist/tools/adrena/adrena-commodity-tools.js.map +1 -0
- package/dist/tools/adrena/adrena-data-tools.d.ts +23 -0
- package/dist/tools/adrena/adrena-data-tools.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-data-tools.js +437 -0
- package/dist/tools/adrena/adrena-data-tools.js.map +1 -0
- package/dist/tools/adrena/adrena-helpers.d.ts +46 -0
- package/dist/tools/adrena/adrena-helpers.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-helpers.js +41 -0
- package/dist/tools/adrena/adrena-helpers.js.map +1 -0
- package/dist/tools/adrena/adrena-index.d.ts +20 -0
- package/dist/tools/adrena/adrena-index.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-index.js +75 -0
- package/dist/tools/adrena/adrena-index.js.map +1 -0
- package/dist/tools/adrena/adrena-limit-order-tools.d.ts +22 -0
- package/dist/tools/adrena/adrena-limit-order-tools.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-limit-order-tools.js +91 -0
- package/dist/tools/adrena/adrena-limit-order-tools.js.map +1 -0
- package/dist/tools/adrena/adrena-liquidity-tools.d.ts +15 -0
- package/dist/tools/adrena/adrena-liquidity-tools.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-liquidity-tools.js +108 -0
- package/dist/tools/adrena/adrena-liquidity-tools.js.map +1 -0
- package/dist/tools/adrena/adrena-snapshot-tools.d.ts +42 -0
- package/dist/tools/adrena/adrena-snapshot-tools.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-snapshot-tools.js +124 -0
- package/dist/tools/adrena/adrena-snapshot-tools.js.map +1 -0
- package/dist/tools/adrena/adrena-staking-tools.d.ts +15 -0
- package/dist/tools/adrena/adrena-staking-tools.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-staking-tools.js +139 -0
- package/dist/tools/adrena/adrena-staking-tools.js.map +1 -0
- package/dist/tools/adrena/adrena-trading-tools.d.ts +78 -0
- package/dist/tools/adrena/adrena-trading-tools.d.ts.map +1 -0
- package/dist/tools/adrena/adrena-trading-tools.js +556 -0
- package/dist/tools/adrena/adrena-trading-tools.js.map +1 -0
- package/dist/tools/adrena-tools.d.ts +1 -66
- package/dist/tools/adrena-tools.d.ts.map +1 -1
- package/dist/tools/adrena-tools.js +1 -1061
- package/dist/tools/adrena-tools.js.map +1 -1
- package/dist/tools/client-sdk-tools.js +1 -1
- package/dist/tools/client-sdk-tools.js.map +1 -1
- package/dist/tools/memory-tools.d.ts.map +1 -1
- package/dist/tools/memory-tools.js +170 -0
- package/dist/tools/memory-tools.js.map +1 -1
- package/dist/tools/perp-tools.d.ts +6 -174
- package/dist/tools/perp-tools.d.ts.map +1 -1
- package/dist/tools/perp-tools.js +6 -1716
- package/dist/tools/perp-tools.js.map +1 -1
- package/dist/tools/premium-tools.d.ts.map +1 -1
- package/dist/tools/premium-tools.js +123 -5
- package/dist/tools/premium-tools.js.map +1 -1
- package/dist/tools/transaction-tools.d.ts +21 -1
- package/dist/tools/transaction-tools.d.ts.map +1 -1
- package/dist/tools/transaction-tools.js +114 -30
- package/dist/tools/transaction-tools.js.map +1 -1
- package/dist/tools/x402-paid-call-tool.d.ts.map +1 -1
- package/dist/tools/x402-paid-call-tool.js +288 -0
- package/dist/tools/x402-paid-call-tool.js.map +1 -1
- package/package.json +3 -2
- package/server.json +3 -3
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @name perps/adrena/adrena-builder-trading
|
|
3
|
+
* @description Trading position builders for the Adrena perps protocol.
|
|
4
|
+
*
|
|
5
|
+
* Contains builders for opening/closing long and short positions, stop loss,
|
|
6
|
+
* take profit, and limit order operations.
|
|
7
|
+
*
|
|
8
|
+
* @module perps/adrena/adrena-builder-trading
|
|
9
|
+
*/
|
|
10
|
+
import { PublicKey, Transaction, } from '@solana/web3.js';
|
|
11
|
+
import { ADRENA_CUSTODIES, ADRENA_PROGRAM_ID, TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, } from './adrena-constants.js';
|
|
12
|
+
import { deriveCortexPda, deriveOraclePda, deriveTransferAuthorityPda, deriveUserProfilePda, derivePositionPda, deriveLimitOrderBookPda, deriveCollateralEscrowPda, deriveAta, } from './adrena-pda.js';
|
|
13
|
+
import { toBN, toBNOrNull, getPoolPublicKey, getCustodyPublicKey, getMintPublicKey, fetchOraclePrice, readCustodyTokenAccount, readCustodyMetadata, validateLeverage, getWalletTokenBalances, checkSufficientBalance, ensureUserProfileInstructions, ensureAtaInstructions, createAdrenaProgram, buildInstruction, serializeUnsignedTx, buildResult, } from './adrena-builder-core.js';
|
|
14
|
+
// ─── Trading Builders ─────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* @name buildOpenPositionLong
|
|
17
|
+
* @description Build an unsigned transaction to open a long position on Adrena.
|
|
18
|
+
*
|
|
19
|
+
* @param connection — Solana RPC connection.
|
|
20
|
+
* @param owner — Position owner and fee payer.
|
|
21
|
+
* @param principalToken — Asset to trade (e.g. "JITOSOL").
|
|
22
|
+
* @param collateralToken — Collateral token (must match principal for longs).
|
|
23
|
+
* @param collateralAmount — Collateral amount in human-readable units (e.g. 10 = 10 JITOSOL).
|
|
24
|
+
* @param leverage — Leverage multiplier.
|
|
25
|
+
* @param price — Price in USD (scaled by 10^10), or null for market order.
|
|
26
|
+
* @returns Unsigned transaction result.
|
|
27
|
+
*/
|
|
28
|
+
export async function buildSimulatePosition(connection, owner, principalToken, collateralToken, collateralAmount, leverage, side, poolName, price = null) {
|
|
29
|
+
const program = createAdrenaProgram(connection);
|
|
30
|
+
const pool = getPoolPublicKey(poolName);
|
|
31
|
+
const custody = getCustodyPublicKey(principalToken, poolName);
|
|
32
|
+
const collateralCustody = getCustodyPublicKey(collateralToken, poolName);
|
|
33
|
+
const cortex = deriveCortexPda();
|
|
34
|
+
const oracle = deriveOraclePda();
|
|
35
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
36
|
+
const userProfile = deriveUserProfilePda(owner);
|
|
37
|
+
const position = derivePositionPda(owner, pool, custody, side);
|
|
38
|
+
const collateralCustodyTokenAccount = await readCustodyTokenAccount(connection, collateralCustody);
|
|
39
|
+
const fundingAccount = deriveAta(owner, getMintPublicKey(collateralToken));
|
|
40
|
+
const referrerProfile = null;
|
|
41
|
+
const collateralRaw = BigInt(Math.floor(collateralAmount * Math.pow(10, ADRENA_CUSTODIES[collateralToken.toUpperCase()].decimals)));
|
|
42
|
+
const priceRaw = price ?? await fetchOraclePrice(principalToken, side);
|
|
43
|
+
// Ensure the funding ATA exists before the Adrena instruction.
|
|
44
|
+
const collateralMint = getMintPublicKey(collateralToken);
|
|
45
|
+
const preInstructions = await ensureAtaInstructions(connection, owner, collateralMint, owner);
|
|
46
|
+
// Ensure user profile exists — Adrena requires it before opening positions.
|
|
47
|
+
const profileInstructions = await ensureUserProfileInstructions(connection, owner);
|
|
48
|
+
const allPreInstructions = [...preInstructions, ...profileInstructions];
|
|
49
|
+
// Leverage is passed as BPS (basis points) to Adrena: 3x = 30000 BPS.
|
|
50
|
+
const leverageBps = Math.floor(leverage * 10000);
|
|
51
|
+
const ixName = side === 'long' ? 'openOrIncreasePositionLong' : 'openOrIncreasePositionShort';
|
|
52
|
+
const ix = await buildInstruction(program, ixName, [
|
|
53
|
+
{
|
|
54
|
+
price: toBN(priceRaw),
|
|
55
|
+
collateral: toBN(collateralRaw),
|
|
56
|
+
leverage: leverageBps,
|
|
57
|
+
oraclePrices: null,
|
|
58
|
+
multiOraclePrices: null,
|
|
59
|
+
},
|
|
60
|
+
], {
|
|
61
|
+
owner,
|
|
62
|
+
payer: owner,
|
|
63
|
+
fundingAccount,
|
|
64
|
+
oracle,
|
|
65
|
+
custody,
|
|
66
|
+
collateralCustody,
|
|
67
|
+
collateralCustodyTokenAccount,
|
|
68
|
+
transferAuthority,
|
|
69
|
+
cortex,
|
|
70
|
+
pool,
|
|
71
|
+
position,
|
|
72
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
73
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
74
|
+
adrenaProgram: new PublicKey(ADRENA_PROGRAM_ID),
|
|
75
|
+
userProfile,
|
|
76
|
+
referrerProfile,
|
|
77
|
+
});
|
|
78
|
+
// Build the transaction for simulation — no serialization needed.
|
|
79
|
+
const blockhash = await connection.getLatestBlockhash();
|
|
80
|
+
const tx = new Transaction({
|
|
81
|
+
recentBlockhash: blockhash.blockhash,
|
|
82
|
+
feePayer: owner,
|
|
83
|
+
});
|
|
84
|
+
tx.add(...allPreInstructions, ix);
|
|
85
|
+
// Simulate the transaction to extract program logs and compute units.
|
|
86
|
+
const simulation = await connection.simulateTransaction(tx);
|
|
87
|
+
const simulationLogs = simulation.value.logs ?? [];
|
|
88
|
+
const simulationError = simulation.value.err
|
|
89
|
+
? (typeof simulation.value.err === 'string'
|
|
90
|
+
? simulation.value.err
|
|
91
|
+
: JSON.stringify(simulation.value.err))
|
|
92
|
+
: undefined;
|
|
93
|
+
const unitsConsumed = simulation.value.unitsConsumed;
|
|
94
|
+
const wouldSucceed = simulation.value.err === null;
|
|
95
|
+
// Pre-flight balance check.
|
|
96
|
+
const balanceCheck = await checkSufficientBalance(connection, owner, collateralToken, collateralAmount);
|
|
97
|
+
return {
|
|
98
|
+
simulationLogs,
|
|
99
|
+
...(simulationError ? { simulationError } : {}),
|
|
100
|
+
...(unitsConsumed !== undefined ? { unitsConsumed } : {}),
|
|
101
|
+
wouldSucceed,
|
|
102
|
+
balanceCheck,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export async function buildOpenPositionLong(connection, owner, principalToken, collateralToken, collateralAmount, leverage, price) {
|
|
106
|
+
const program = createAdrenaProgram(connection);
|
|
107
|
+
const pool = getPoolPublicKey('main-pool');
|
|
108
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
109
|
+
const collateralCustody = getCustodyPublicKey(collateralToken, 'main-pool');
|
|
110
|
+
const cortex = deriveCortexPda();
|
|
111
|
+
const oracle = deriveOraclePda();
|
|
112
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
113
|
+
const userProfile = deriveUserProfilePda(owner);
|
|
114
|
+
const position = derivePositionPda(owner, pool, custody, 'long');
|
|
115
|
+
const collateralCustodyTokenAccount = await readCustodyTokenAccount(connection, collateralCustody);
|
|
116
|
+
const fundingAccount = deriveAta(owner, getMintPublicKey(collateralToken));
|
|
117
|
+
const referrerProfile = null;
|
|
118
|
+
// Read custody metadata for leverage pre-validation and poolMetadata.
|
|
119
|
+
const poolMetadata = await readCustodyMetadata(connection, custody);
|
|
120
|
+
validateLeverage(leverage, Math.floor(poolMetadata.maxInitialLeverage * 10000), principalToken);
|
|
121
|
+
const collateralRaw = BigInt(Math.floor(collateralAmount * Math.pow(10, ADRENA_CUSTODIES[collateralToken.toUpperCase()].decimals)));
|
|
122
|
+
const priceRaw = price ?? await fetchOraclePrice(principalToken, 'long');
|
|
123
|
+
// Ensure the funding ATA exists before the Adrena instruction.
|
|
124
|
+
const collateralMint = getMintPublicKey(collateralToken);
|
|
125
|
+
const preInstructions = await ensureAtaInstructions(connection, owner, collateralMint, owner);
|
|
126
|
+
// Ensure user profile exists — Adrena requires it before opening positions.
|
|
127
|
+
const profileInstructions = await ensureUserProfileInstructions(connection, owner);
|
|
128
|
+
const allPreInstructions = [...preInstructions, ...profileInstructions];
|
|
129
|
+
// Leverage is passed as BPS (basis points) to Adrena: 3x = 30000 BPS.
|
|
130
|
+
// The SDK does: Math.floor(normalLeverage * BPS) where BPS = 10000.
|
|
131
|
+
const leverageBps = Math.floor(leverage * 10000);
|
|
132
|
+
const ix = await buildInstruction(program, 'openOrIncreasePositionLong', [
|
|
133
|
+
{
|
|
134
|
+
price: toBN(priceRaw),
|
|
135
|
+
collateral: toBN(collateralRaw),
|
|
136
|
+
leverage: leverageBps,
|
|
137
|
+
oraclePrices: null,
|
|
138
|
+
multiOraclePrices: null,
|
|
139
|
+
},
|
|
140
|
+
], {
|
|
141
|
+
owner,
|
|
142
|
+
payer: owner,
|
|
143
|
+
fundingAccount,
|
|
144
|
+
oracle,
|
|
145
|
+
custody,
|
|
146
|
+
collateralCustody,
|
|
147
|
+
collateralCustodyTokenAccount,
|
|
148
|
+
transferAuthority,
|
|
149
|
+
cortex,
|
|
150
|
+
pool,
|
|
151
|
+
position,
|
|
152
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
153
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
154
|
+
adrenaProgram: new PublicKey(ADRENA_PROGRAM_ID),
|
|
155
|
+
userProfile,
|
|
156
|
+
referrerProfile,
|
|
157
|
+
});
|
|
158
|
+
// Pre-flight balance check.
|
|
159
|
+
const balanceCheck = await checkSufficientBalance(connection, owner, collateralToken, collateralAmount);
|
|
160
|
+
const warning = !balanceCheck.sufficient
|
|
161
|
+
? `Insufficient ${collateralToken.toUpperCase()} balance: need ${collateralAmount}, have ${balanceCheck.availableBalance} (shortfall: ${balanceCheck.shortfall}). The transaction will fail on-chain.`
|
|
162
|
+
: !balanceCheck.solSufficientForFees
|
|
163
|
+
? `Insufficient SOL for transaction fees: have ${balanceCheck.solBalance} SOL, need ~0.005 SOL.`
|
|
164
|
+
: undefined;
|
|
165
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [...allPreInstructions, ix]);
|
|
166
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
167
|
+
return buildResult(transactionBase64, owner, ['openOrIncreasePositionLong'], position, balanceCheck, warning, poolMetadata);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* @name buildOpenPositionShort
|
|
171
|
+
* @description Build an unsigned transaction to open a short position on Adrena.
|
|
172
|
+
*
|
|
173
|
+
* @param connection — Solana RPC connection.
|
|
174
|
+
* @param owner — Position owner and fee payer.
|
|
175
|
+
* @param principalToken — Asset to short (e.g. "JITOSOL").
|
|
176
|
+
* @param collateralToken — Collateral token (must be USDC for shorts).
|
|
177
|
+
* @param collateralAmount — Collateral amount in human-readable units.
|
|
178
|
+
* @param leverage — Leverage multiplier.
|
|
179
|
+
* @param price — Price in USD (scaled by 10^10), or null for market order.
|
|
180
|
+
* @returns Unsigned transaction result.
|
|
181
|
+
*/
|
|
182
|
+
export async function buildOpenPositionShort(connection, owner, principalToken, collateralToken, collateralAmount, leverage, price) {
|
|
183
|
+
const program = createAdrenaProgram(connection);
|
|
184
|
+
const pool = getPoolPublicKey('main-pool');
|
|
185
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
186
|
+
const collateralCustody = getCustodyPublicKey(collateralToken, 'main-pool');
|
|
187
|
+
const cortex = deriveCortexPda();
|
|
188
|
+
const oracle = deriveOraclePda();
|
|
189
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
190
|
+
const userProfile = deriveUserProfilePda(owner);
|
|
191
|
+
const position = derivePositionPda(owner, pool, custody, 'short');
|
|
192
|
+
const collateralCustodyTokenAccount = await readCustodyTokenAccount(connection, collateralCustody);
|
|
193
|
+
const fundingAccount = deriveAta(owner, getMintPublicKey(collateralToken));
|
|
194
|
+
const referrerProfile = null;
|
|
195
|
+
// Read custody metadata for leverage pre-validation and poolMetadata.
|
|
196
|
+
const poolMetadata = await readCustodyMetadata(connection, custody);
|
|
197
|
+
validateLeverage(leverage, Math.floor(poolMetadata.maxInitialLeverage * 10000), principalToken);
|
|
198
|
+
const collateralRaw = BigInt(Math.floor(collateralAmount * Math.pow(10, ADRENA_CUSTODIES[collateralToken.toUpperCase()].decimals)));
|
|
199
|
+
const priceRaw = price ?? await fetchOraclePrice(principalToken, 'short');
|
|
200
|
+
// Ensure the funding ATA exists before the Adrena instruction.
|
|
201
|
+
const collateralMint = getMintPublicKey(collateralToken);
|
|
202
|
+
const preInstructions = await ensureAtaInstructions(connection, owner, collateralMint, owner);
|
|
203
|
+
// Ensure user profile exists — Adrena requires it before opening positions.
|
|
204
|
+
const profileInstructions = await ensureUserProfileInstructions(connection, owner);
|
|
205
|
+
const allPreInstructions = [...preInstructions, ...profileInstructions];
|
|
206
|
+
// Leverage is passed as BPS (basis points) to Adrena: 3x = 30000 BPS.
|
|
207
|
+
const leverageBps = Math.floor(leverage * 10000);
|
|
208
|
+
const ix = await buildInstruction(program, 'openOrIncreasePositionShort', [
|
|
209
|
+
{
|
|
210
|
+
price: toBN(priceRaw),
|
|
211
|
+
collateral: toBN(collateralRaw),
|
|
212
|
+
leverage: leverageBps,
|
|
213
|
+
oraclePrices: null,
|
|
214
|
+
multiOraclePrices: null,
|
|
215
|
+
},
|
|
216
|
+
], {
|
|
217
|
+
owner,
|
|
218
|
+
payer: owner,
|
|
219
|
+
fundingAccount,
|
|
220
|
+
oracle,
|
|
221
|
+
custody,
|
|
222
|
+
collateralCustody,
|
|
223
|
+
collateralCustodyTokenAccount,
|
|
224
|
+
transferAuthority,
|
|
225
|
+
cortex,
|
|
226
|
+
pool,
|
|
227
|
+
position,
|
|
228
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
229
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
230
|
+
adrenaProgram: new PublicKey(ADRENA_PROGRAM_ID),
|
|
231
|
+
userProfile,
|
|
232
|
+
referrerProfile,
|
|
233
|
+
});
|
|
234
|
+
// Pre-flight balance check.
|
|
235
|
+
const balanceCheck = await checkSufficientBalance(connection, owner, collateralToken, collateralAmount);
|
|
236
|
+
const warning = !balanceCheck.sufficient
|
|
237
|
+
? `Insufficient ${collateralToken.toUpperCase()} balance: need ${collateralAmount}, have ${balanceCheck.availableBalance} (shortfall: ${balanceCheck.shortfall}). The transaction will fail on-chain.`
|
|
238
|
+
: !balanceCheck.solSufficientForFees
|
|
239
|
+
? `Insufficient SOL for transaction fees: have ${balanceCheck.solBalance} SOL, need ~0.005 SOL.`
|
|
240
|
+
: undefined;
|
|
241
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [...allPreInstructions, ix]);
|
|
242
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
243
|
+
return buildResult(transactionBase64, owner, ['openOrIncreasePositionShort'], position, balanceCheck, warning, poolMetadata);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* @name buildClosePositionLong
|
|
247
|
+
* @description Build an unsigned transaction to close a long position on Adrena.
|
|
248
|
+
*
|
|
249
|
+
* @param connection — Solana RPC connection.
|
|
250
|
+
* @param owner — Position owner and fee payer.
|
|
251
|
+
* @param principalToken — Asset of the position.
|
|
252
|
+
* @param price — Optional close price (scaled by 10^10), null for market close.
|
|
253
|
+
* @param percentage — Percentage to close (1_000_000 = 100%).
|
|
254
|
+
* @returns Unsigned transaction result.
|
|
255
|
+
*/
|
|
256
|
+
export async function buildClosePositionLong(connection, owner, principalToken, price, percentage = 1000000n) {
|
|
257
|
+
const program = createAdrenaProgram(connection);
|
|
258
|
+
const pool = getPoolPublicKey('main-pool');
|
|
259
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
260
|
+
const collateralCustody = custody;
|
|
261
|
+
const cortex = deriveCortexPda();
|
|
262
|
+
const oracle = deriveOraclePda();
|
|
263
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
264
|
+
const userProfile = deriveUserProfilePda(owner);
|
|
265
|
+
const position = derivePositionPda(owner, pool, custody, 'long');
|
|
266
|
+
const collateralCustodyTokenAccount = await readCustodyTokenAccount(connection, collateralCustody);
|
|
267
|
+
const receivingAccount = deriveAta(owner, getMintPublicKey(principalToken));
|
|
268
|
+
const referrerProfile = null;
|
|
269
|
+
// Ensure the receiving ATA exists before closing.
|
|
270
|
+
const receivingMint = getMintPublicKey(principalToken);
|
|
271
|
+
const preInstructions = await ensureAtaInstructions(connection, owner, receivingMint, owner);
|
|
272
|
+
// Ensure user profile exists — Adrena requires it for position operations.
|
|
273
|
+
const profileInstructions = await ensureUserProfileInstructions(connection, owner);
|
|
274
|
+
const allPreInstructions = [...preInstructions, ...profileInstructions];
|
|
275
|
+
const ix = await buildInstruction(program, 'closePositionLong', [
|
|
276
|
+
{
|
|
277
|
+
price: toBNOrNull(price),
|
|
278
|
+
oraclePrices: null,
|
|
279
|
+
multiOraclePrices: null,
|
|
280
|
+
percentage: toBN(percentage),
|
|
281
|
+
},
|
|
282
|
+
], {
|
|
283
|
+
caller: owner,
|
|
284
|
+
owner,
|
|
285
|
+
receivingAccount,
|
|
286
|
+
transferAuthority,
|
|
287
|
+
cortex,
|
|
288
|
+
pool,
|
|
289
|
+
position,
|
|
290
|
+
custody,
|
|
291
|
+
oracle,
|
|
292
|
+
collateralCustody,
|
|
293
|
+
collateralCustodyTokenAccount,
|
|
294
|
+
userProfile,
|
|
295
|
+
referrerProfile,
|
|
296
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
297
|
+
adrenaProgram: new PublicKey(ADRENA_PROGRAM_ID),
|
|
298
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
299
|
+
});
|
|
300
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [...allPreInstructions, ix]);
|
|
301
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
302
|
+
// Pre-flight: show balances and check SOL for fees.
|
|
303
|
+
const balances = await getWalletTokenBalances(connection, owner);
|
|
304
|
+
const solBalance = balances.find(b => b.symbol === 'SOL')?.balance ?? 0;
|
|
305
|
+
const warning = solBalance < 0.005
|
|
306
|
+
? `Insufficient SOL for transaction fees: have ${solBalance} SOL, need ~0.005 SOL.`
|
|
307
|
+
: undefined;
|
|
308
|
+
const balanceCheck = {
|
|
309
|
+
wallet: owner.toBase58(),
|
|
310
|
+
balances,
|
|
311
|
+
requiredToken: 'NONE',
|
|
312
|
+
requiredAmount: 0,
|
|
313
|
+
availableBalance: 0,
|
|
314
|
+
sufficient: true,
|
|
315
|
+
shortfall: 0,
|
|
316
|
+
solBalance,
|
|
317
|
+
solSufficientForFees: solBalance >= 0.005,
|
|
318
|
+
};
|
|
319
|
+
return buildResult(transactionBase64, owner, ['closePositionLong'], position, balanceCheck, warning);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* @name buildClosePositionShort
|
|
323
|
+
* @description Build an unsigned transaction to close a short position on Adrena.
|
|
324
|
+
*
|
|
325
|
+
* @param connection — Solana RPC connection.
|
|
326
|
+
* @param owner — Position owner and fee payer.
|
|
327
|
+
* @param principalToken — Asset of the position.
|
|
328
|
+
* @param collateralToken — Collateral token (USDC for shorts).
|
|
329
|
+
* @param price — Optional close price (scaled by 10^10), null for market close.
|
|
330
|
+
* @param percentage — Percentage to close (1_000_000 = 100%).
|
|
331
|
+
* @returns Unsigned transaction result.
|
|
332
|
+
*/
|
|
333
|
+
export async function buildClosePositionShort(connection, owner, principalToken, collateralToken, price, percentage = 1000000n) {
|
|
334
|
+
const program = createAdrenaProgram(connection);
|
|
335
|
+
const pool = getPoolPublicKey('main-pool');
|
|
336
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
337
|
+
const collateralCustody = getCustodyPublicKey(collateralToken, 'main-pool');
|
|
338
|
+
const cortex = deriveCortexPda();
|
|
339
|
+
const oracle = deriveOraclePda();
|
|
340
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
341
|
+
const userProfile = deriveUserProfilePda(owner);
|
|
342
|
+
const position = derivePositionPda(owner, pool, custody, 'short');
|
|
343
|
+
const collateralCustodyTokenAccount = await readCustodyTokenAccount(connection, collateralCustody);
|
|
344
|
+
const receivingAccount = deriveAta(owner, getMintPublicKey(collateralToken));
|
|
345
|
+
const referrerProfile = null;
|
|
346
|
+
// Ensure the receiving ATA exists before closing.
|
|
347
|
+
const receivingMint = getMintPublicKey(collateralToken);
|
|
348
|
+
const preInstructions = await ensureAtaInstructions(connection, owner, receivingMint, owner);
|
|
349
|
+
// Ensure user profile exists — Adrena requires it for position operations.
|
|
350
|
+
const profileInstructions = await ensureUserProfileInstructions(connection, owner);
|
|
351
|
+
const allPreInstructions = [...preInstructions, ...profileInstructions];
|
|
352
|
+
const ix = await buildInstruction(program, 'closePositionShort', [
|
|
353
|
+
{
|
|
354
|
+
price: toBNOrNull(price),
|
|
355
|
+
oraclePrices: null,
|
|
356
|
+
multiOraclePrices: null,
|
|
357
|
+
percentage: toBN(percentage),
|
|
358
|
+
},
|
|
359
|
+
], {
|
|
360
|
+
caller: owner,
|
|
361
|
+
owner,
|
|
362
|
+
receivingAccount,
|
|
363
|
+
transferAuthority,
|
|
364
|
+
cortex,
|
|
365
|
+
pool,
|
|
366
|
+
position,
|
|
367
|
+
custody,
|
|
368
|
+
oracle,
|
|
369
|
+
collateralCustody,
|
|
370
|
+
collateralCustodyTokenAccount,
|
|
371
|
+
userProfile,
|
|
372
|
+
referrerProfile,
|
|
373
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
374
|
+
adrenaProgram: new PublicKey(ADRENA_PROGRAM_ID),
|
|
375
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
376
|
+
});
|
|
377
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [...allPreInstructions, ix]);
|
|
378
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
379
|
+
// Pre-flight: show balances and check SOL for fees.
|
|
380
|
+
const balances = await getWalletTokenBalances(connection, owner);
|
|
381
|
+
const solBalance = balances.find(b => b.symbol === 'SOL')?.balance ?? 0;
|
|
382
|
+
const warning = solBalance < 0.005
|
|
383
|
+
? `Insufficient SOL for transaction fees: have ${solBalance} SOL, need ~0.005 SOL.`
|
|
384
|
+
: undefined;
|
|
385
|
+
const balanceCheck = {
|
|
386
|
+
wallet: owner.toBase58(),
|
|
387
|
+
balances,
|
|
388
|
+
requiredToken: 'NONE',
|
|
389
|
+
requiredAmount: 0,
|
|
390
|
+
availableBalance: 0,
|
|
391
|
+
sufficient: true,
|
|
392
|
+
shortfall: 0,
|
|
393
|
+
solBalance,
|
|
394
|
+
solSufficientForFees: solBalance >= 0.005,
|
|
395
|
+
};
|
|
396
|
+
return buildResult(transactionBase64, owner, ['closePositionShort'], position, balanceCheck, warning);
|
|
397
|
+
}
|
|
398
|
+
// ─── SL / TP Builders ──────────────────────────────────────────────────────────
|
|
399
|
+
/**
|
|
400
|
+
* @name buildSetStopLoss
|
|
401
|
+
* @description Build an unsigned transaction to set stop loss on a position.
|
|
402
|
+
*
|
|
403
|
+
* @param connection — Solana RPC connection.
|
|
404
|
+
* @param owner — Position owner and fee payer.
|
|
405
|
+
* @param principalToken — Asset of the position.
|
|
406
|
+
* @param side — Position side.
|
|
407
|
+
* @param stopLossLimitPrice — Stop loss trigger price (scaled by 10^10).
|
|
408
|
+
* @param closePositionPrice — Optional close position price (scaled by 10^10).
|
|
409
|
+
* @returns Unsigned transaction result.
|
|
410
|
+
*/
|
|
411
|
+
export async function buildSetStopLoss(connection, owner, principalToken, side, stopLossLimitPrice, closePositionPrice) {
|
|
412
|
+
const program = createAdrenaProgram(connection);
|
|
413
|
+
const pool = getPoolPublicKey('main-pool');
|
|
414
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
415
|
+
const cortex = deriveCortexPda();
|
|
416
|
+
const position = derivePositionPda(owner, pool, custody, side);
|
|
417
|
+
const ixName = side === 'long' ? 'setStopLossLong' : 'setStopLossShort';
|
|
418
|
+
const ix = await buildInstruction(program, ixName, [
|
|
419
|
+
{
|
|
420
|
+
stopLossLimitPrice: toBN(stopLossLimitPrice),
|
|
421
|
+
closePositionPrice: toBNOrNull(closePositionPrice),
|
|
422
|
+
},
|
|
423
|
+
], {
|
|
424
|
+
owner,
|
|
425
|
+
cortex,
|
|
426
|
+
pool,
|
|
427
|
+
position,
|
|
428
|
+
custody,
|
|
429
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
430
|
+
});
|
|
431
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [ix]);
|
|
432
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
433
|
+
// Pre-flight: check SOL for fees.
|
|
434
|
+
const balances = await getWalletTokenBalances(connection, owner);
|
|
435
|
+
const solBalance = balances.find(b => b.symbol === 'SOL')?.balance ?? 0;
|
|
436
|
+
const warning = solBalance < 0.005
|
|
437
|
+
? `Insufficient SOL for transaction fees: have ${solBalance} SOL, need ~0.005 SOL.`
|
|
438
|
+
: undefined;
|
|
439
|
+
const balanceCheck = {
|
|
440
|
+
wallet: owner.toBase58(),
|
|
441
|
+
balances,
|
|
442
|
+
requiredToken: 'NONE',
|
|
443
|
+
requiredAmount: 0,
|
|
444
|
+
availableBalance: 0,
|
|
445
|
+
sufficient: true,
|
|
446
|
+
shortfall: 0,
|
|
447
|
+
solBalance,
|
|
448
|
+
solSufficientForFees: solBalance >= 0.005,
|
|
449
|
+
};
|
|
450
|
+
return buildResult(transactionBase64, owner, [ixName], position, balanceCheck, warning);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* @name buildSetTakeProfit
|
|
454
|
+
* @description Build an unsigned transaction to set take profit on a position.
|
|
455
|
+
*
|
|
456
|
+
* @param connection — Solana RPC connection.
|
|
457
|
+
* @param owner — Position owner and fee payer.
|
|
458
|
+
* @param principalToken — Asset of the position.
|
|
459
|
+
* @param side — Position side.
|
|
460
|
+
* @param takeProfitLimitPrice — Take profit trigger price (scaled by 10^10).
|
|
461
|
+
* @returns Unsigned transaction result.
|
|
462
|
+
*/
|
|
463
|
+
export async function buildSetTakeProfit(connection, owner, principalToken, side, takeProfitLimitPrice) {
|
|
464
|
+
const program = createAdrenaProgram(connection);
|
|
465
|
+
const pool = getPoolPublicKey('main-pool');
|
|
466
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
467
|
+
const cortex = deriveCortexPda();
|
|
468
|
+
const position = derivePositionPda(owner, pool, custody, side);
|
|
469
|
+
const ixName = side === 'long' ? 'setTakeProfitLong' : 'setTakeProfitShort';
|
|
470
|
+
const ix = await buildInstruction(program, ixName, [
|
|
471
|
+
{
|
|
472
|
+
takeProfitLimitPrice: toBN(takeProfitLimitPrice),
|
|
473
|
+
},
|
|
474
|
+
], {
|
|
475
|
+
owner,
|
|
476
|
+
cortex,
|
|
477
|
+
pool,
|
|
478
|
+
position,
|
|
479
|
+
custody,
|
|
480
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
481
|
+
});
|
|
482
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [ix]);
|
|
483
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
484
|
+
// Pre-flight: check SOL for fees.
|
|
485
|
+
const balances = await getWalletTokenBalances(connection, owner);
|
|
486
|
+
const solBalance = balances.find(b => b.symbol === 'SOL')?.balance ?? 0;
|
|
487
|
+
const warning = solBalance < 0.005
|
|
488
|
+
? `Insufficient SOL for transaction fees: have ${solBalance} SOL, need ~0.005 SOL.`
|
|
489
|
+
: undefined;
|
|
490
|
+
const balanceCheck = {
|
|
491
|
+
wallet: owner.toBase58(),
|
|
492
|
+
balances,
|
|
493
|
+
requiredToken: 'NONE',
|
|
494
|
+
requiredAmount: 0,
|
|
495
|
+
availableBalance: 0,
|
|
496
|
+
sufficient: true,
|
|
497
|
+
shortfall: 0,
|
|
498
|
+
solBalance,
|
|
499
|
+
solSufficientForFees: solBalance >= 0.005,
|
|
500
|
+
};
|
|
501
|
+
return buildResult(transactionBase64, owner, [ixName], position, balanceCheck, warning);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* @name buildCancelStopLoss
|
|
505
|
+
* @description Build an unsigned transaction to cancel stop loss on a position.
|
|
506
|
+
* @param connection — Solana RPC connection.
|
|
507
|
+
* @param owner — Position owner and fee payer.
|
|
508
|
+
* @param principalToken — Asset of the position.
|
|
509
|
+
* @param side — Position side.
|
|
510
|
+
* @returns Unsigned transaction result.
|
|
511
|
+
*/
|
|
512
|
+
export async function buildCancelStopLoss(connection, owner, principalToken, side) {
|
|
513
|
+
const program = createAdrenaProgram(connection);
|
|
514
|
+
const pool = getPoolPublicKey('main-pool');
|
|
515
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
516
|
+
const cortex = deriveCortexPda();
|
|
517
|
+
const position = derivePositionPda(owner, pool, custody, side);
|
|
518
|
+
const ix = await buildInstruction(program, 'cancelStopLoss', [], {
|
|
519
|
+
owner,
|
|
520
|
+
cortex,
|
|
521
|
+
pool,
|
|
522
|
+
position,
|
|
523
|
+
custody,
|
|
524
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
525
|
+
});
|
|
526
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [ix]);
|
|
527
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
528
|
+
// Pre-flight: check SOL for fees.
|
|
529
|
+
const balances = await getWalletTokenBalances(connection, owner);
|
|
530
|
+
const solBalance = balances.find(b => b.symbol === 'SOL')?.balance ?? 0;
|
|
531
|
+
const warning = solBalance < 0.005
|
|
532
|
+
? `Insufficient SOL for transaction fees: have ${solBalance} SOL, need ~0.005 SOL.`
|
|
533
|
+
: undefined;
|
|
534
|
+
const balanceCheck = {
|
|
535
|
+
wallet: owner.toBase58(),
|
|
536
|
+
balances,
|
|
537
|
+
requiredToken: 'NONE',
|
|
538
|
+
requiredAmount: 0,
|
|
539
|
+
availableBalance: 0,
|
|
540
|
+
sufficient: true,
|
|
541
|
+
shortfall: 0,
|
|
542
|
+
solBalance,
|
|
543
|
+
solSufficientForFees: solBalance >= 0.005,
|
|
544
|
+
};
|
|
545
|
+
return buildResult(transactionBase64, owner, ['cancelStopLoss'], position, balanceCheck, warning);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* @name buildCancelTakeProfit
|
|
549
|
+
* @description Build an unsigned transaction to cancel take profit on a position.
|
|
550
|
+
* @param connection — Solana RPC connection.
|
|
551
|
+
* @param owner — Position owner and fee payer.
|
|
552
|
+
* @param principalToken — Asset of the position.
|
|
553
|
+
* @param side — Position side.
|
|
554
|
+
* @returns Unsigned transaction result.
|
|
555
|
+
*/
|
|
556
|
+
export async function buildCancelTakeProfit(connection, owner, principalToken, side) {
|
|
557
|
+
const program = createAdrenaProgram(connection);
|
|
558
|
+
const pool = getPoolPublicKey('main-pool');
|
|
559
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
560
|
+
const cortex = deriveCortexPda();
|
|
561
|
+
const position = derivePositionPda(owner, pool, custody, side);
|
|
562
|
+
const ix = await buildInstruction(program, 'cancelTakeProfit', [], {
|
|
563
|
+
owner,
|
|
564
|
+
cortex,
|
|
565
|
+
pool,
|
|
566
|
+
position,
|
|
567
|
+
custody,
|
|
568
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
569
|
+
});
|
|
570
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [ix]);
|
|
571
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
572
|
+
// Pre-flight: check SOL for fees.
|
|
573
|
+
const balances = await getWalletTokenBalances(connection, owner);
|
|
574
|
+
const solBalance = balances.find(b => b.symbol === 'SOL')?.balance ?? 0;
|
|
575
|
+
const warning = solBalance < 0.005
|
|
576
|
+
? `Insufficient SOL for transaction fees: have ${solBalance} SOL, need ~0.005 SOL.`
|
|
577
|
+
: undefined;
|
|
578
|
+
const balanceCheck = {
|
|
579
|
+
wallet: owner.toBase58(),
|
|
580
|
+
balances,
|
|
581
|
+
requiredToken: 'NONE',
|
|
582
|
+
requiredAmount: 0,
|
|
583
|
+
availableBalance: 0,
|
|
584
|
+
sufficient: true,
|
|
585
|
+
shortfall: 0,
|
|
586
|
+
solBalance,
|
|
587
|
+
solSufficientForFees: solBalance >= 0.005,
|
|
588
|
+
};
|
|
589
|
+
return buildResult(transactionBase64, owner, ['cancelTakeProfit'], position, balanceCheck, warning);
|
|
590
|
+
}
|
|
591
|
+
// ─── Limit Order Builders ───────────────────────────────────────────────────────
|
|
592
|
+
/**
|
|
593
|
+
* @name buildAddLimitOrder
|
|
594
|
+
* @description Build an unsigned transaction to place a limit order on Adrena.
|
|
595
|
+
*
|
|
596
|
+
* @param connection — Solana RPC connection.
|
|
597
|
+
* @param owner — Order owner and fee payer.
|
|
598
|
+
* @param principalToken — Asset to trade.
|
|
599
|
+
* @param collateralToken — Collateral token.
|
|
600
|
+
* @param collateralAmount — Collateral amount in human-readable units.
|
|
601
|
+
* @param leverage — Leverage multiplier.
|
|
602
|
+
* @param side — Order side: long or short.
|
|
603
|
+
* @param triggerPrice — Trigger price (scaled by 10^10).
|
|
604
|
+
* @param limitPrice — Optional limit price (scaled by 10^10), null for market at fill.
|
|
605
|
+
* @returns Unsigned transaction result.
|
|
606
|
+
*/
|
|
607
|
+
export async function buildAddLimitOrder(connection, owner, principalToken, collateralToken, collateralAmount, leverage, side, triggerPrice, limitPrice) {
|
|
608
|
+
const program = createAdrenaProgram(connection);
|
|
609
|
+
const pool = getPoolPublicKey('main-pool');
|
|
610
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
611
|
+
const collateralCustody = getCustodyPublicKey(collateralToken, 'main-pool');
|
|
612
|
+
const cortex = deriveCortexPda();
|
|
613
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
614
|
+
const limitOrderBook = deriveLimitOrderBookPda(owner, pool);
|
|
615
|
+
const collateralEscrow = deriveCollateralEscrowPda(owner, pool, collateralCustody);
|
|
616
|
+
const collateralCustodyMint = getMintPublicKey(collateralToken);
|
|
617
|
+
const fundingAccount = deriveAta(owner, collateralCustodyMint);
|
|
618
|
+
const amountRaw = BigInt(Math.floor(collateralAmount * Math.pow(10, ADRENA_CUSTODIES[collateralToken.toUpperCase()].decimals)));
|
|
619
|
+
const ix = await buildInstruction(program, 'addLimitOrder', [
|
|
620
|
+
{
|
|
621
|
+
triggerPrice: toBN(triggerPrice),
|
|
622
|
+
limitPrice: toBNOrNull(limitPrice),
|
|
623
|
+
side: side === 'long' ? 0 : 1,
|
|
624
|
+
amount: toBN(amountRaw),
|
|
625
|
+
leverage,
|
|
626
|
+
},
|
|
627
|
+
], {
|
|
628
|
+
owner,
|
|
629
|
+
fundingAccount,
|
|
630
|
+
transferAuthority,
|
|
631
|
+
cortex,
|
|
632
|
+
pool,
|
|
633
|
+
limitOrderBook,
|
|
634
|
+
collateralEscrow,
|
|
635
|
+
collateralCustodyMint,
|
|
636
|
+
custody,
|
|
637
|
+
collateralCustody,
|
|
638
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
639
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
640
|
+
associatedTokenProgram: new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID),
|
|
641
|
+
});
|
|
642
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [ix]);
|
|
643
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
644
|
+
// Pre-flight balance check.
|
|
645
|
+
const balanceCheck = await checkSufficientBalance(connection, owner, collateralToken, collateralAmount);
|
|
646
|
+
const warning = !balanceCheck.sufficient
|
|
647
|
+
? `Insufficient ${collateralToken.toUpperCase()} balance: need ${collateralAmount}, have ${balanceCheck.availableBalance} (shortfall: ${balanceCheck.shortfall}). The transaction will fail on-chain.`
|
|
648
|
+
: !balanceCheck.solSufficientForFees
|
|
649
|
+
? `Insufficient SOL for transaction fees: have ${balanceCheck.solBalance} SOL, need ~0.005 SOL.`
|
|
650
|
+
: undefined;
|
|
651
|
+
return buildResult(transactionBase64, owner, ['addLimitOrder'], undefined, balanceCheck, warning);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* @name buildCancelLimitOrder
|
|
655
|
+
* @description Build an unsigned transaction to cancel a limit order on Adrena.
|
|
656
|
+
*
|
|
657
|
+
* @param connection — Solana RPC connection.
|
|
658
|
+
* @param owner — Order owner and fee payer.
|
|
659
|
+
* @param collateralToken — Collateral token.
|
|
660
|
+
* @param orderId — Limit order ID.
|
|
661
|
+
* @returns Unsigned transaction result.
|
|
662
|
+
*/
|
|
663
|
+
export async function buildCancelLimitOrder(connection, owner, collateralToken, orderId) {
|
|
664
|
+
const program = createAdrenaProgram(connection);
|
|
665
|
+
const pool = getPoolPublicKey('main-pool');
|
|
666
|
+
const collateralCustody = getCustodyPublicKey(collateralToken, 'main-pool');
|
|
667
|
+
const cortex = deriveCortexPda();
|
|
668
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
669
|
+
const limitOrderBook = deriveLimitOrderBookPda(owner, pool);
|
|
670
|
+
const collateralEscrow = deriveCollateralEscrowPda(owner, pool, collateralCustody);
|
|
671
|
+
const collateralCustodyMint = getMintPublicKey(collateralToken);
|
|
672
|
+
const receivingAccount = deriveAta(owner, collateralCustodyMint);
|
|
673
|
+
const ix = await buildInstruction(program, 'cancelLimitOrder', [
|
|
674
|
+
{
|
|
675
|
+
id: toBN(orderId),
|
|
676
|
+
},
|
|
677
|
+
], {
|
|
678
|
+
owner,
|
|
679
|
+
receivingAccount,
|
|
680
|
+
transferAuthority,
|
|
681
|
+
cortex,
|
|
682
|
+
pool,
|
|
683
|
+
limitOrderBook,
|
|
684
|
+
collateralEscrow,
|
|
685
|
+
collateralCustodyMint,
|
|
686
|
+
collateralCustody,
|
|
687
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
688
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
689
|
+
associatedTokenProgram: new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID),
|
|
690
|
+
});
|
|
691
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, [ix]);
|
|
692
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
693
|
+
// Pre-flight: check SOL for fees.
|
|
694
|
+
const balances = await getWalletTokenBalances(connection, owner);
|
|
695
|
+
const solBalance = balances.find(b => b.symbol === 'SOL')?.balance ?? 0;
|
|
696
|
+
const warning = solBalance < 0.005
|
|
697
|
+
? `Insufficient SOL for transaction fees: have ${solBalance} SOL, need ~0.005 SOL.`
|
|
698
|
+
: undefined;
|
|
699
|
+
const balanceCheck = {
|
|
700
|
+
wallet: owner.toBase58(),
|
|
701
|
+
balances,
|
|
702
|
+
requiredToken: 'NONE',
|
|
703
|
+
requiredAmount: 0,
|
|
704
|
+
availableBalance: 0,
|
|
705
|
+
sufficient: true,
|
|
706
|
+
shortfall: 0,
|
|
707
|
+
solBalance,
|
|
708
|
+
solSufficientForFees: solBalance >= 0.005,
|
|
709
|
+
};
|
|
710
|
+
return buildResult(transactionBase64, owner, ['cancelLimitOrder'], undefined, balanceCheck, warning);
|
|
711
|
+
}
|
|
712
|
+
// ─── Batch Position Package Builder ────────────────────────────────────────────
|
|
713
|
+
/**
|
|
714
|
+
* @name buildPositionPackage
|
|
715
|
+
* @description Build a single unsigned transaction that atomically opens a
|
|
716
|
+
* position AND sets stop loss AND take profit. This combines 3 separate
|
|
717
|
+
* builder calls into 1 transaction — 1 payment, 1 signing, 1 submit.
|
|
718
|
+
*
|
|
719
|
+
* If stopLossPriceUsd or takeProfitPriceUsd is null, that instruction is
|
|
720
|
+
* omitted (e.g. you can open + SL without TP).
|
|
721
|
+
*
|
|
722
|
+
* @param connection — Solana RPC connection.
|
|
723
|
+
* @param owner — Position owner and fee payer.
|
|
724
|
+
* @param principalToken — Asset to trade (e.g. "BONK").
|
|
725
|
+
* @param collateralToken — Collateral token (USDC for shorts, match principal for longs).
|
|
726
|
+
* @param collateralAmount — Collateral amount in human-readable units.
|
|
727
|
+
* @param leverage — Leverage multiplier (e.g. 3 = 3x).
|
|
728
|
+
* @param side — Position side: 'long' or 'short'.
|
|
729
|
+
* @param stopLossPriceUsd — Stop loss trigger price in USD, or null to skip.
|
|
730
|
+
* @param takeProfitPriceUsd — Take profit trigger price in USD, or null to skip.
|
|
731
|
+
* @param price — Optional limit price in USD (scaled by 10^10), null for market.
|
|
732
|
+
* @returns Unsigned transaction result with all instructions combined.
|
|
733
|
+
*/
|
|
734
|
+
export async function buildPositionPackage(connection, owner, principalToken, collateralToken, collateralAmount, leverage, side, stopLossPriceUsd, takeProfitPriceUsd, price = null) {
|
|
735
|
+
const program = createAdrenaProgram(connection);
|
|
736
|
+
const pool = getPoolPublicKey('main-pool');
|
|
737
|
+
const custody = getCustodyPublicKey(principalToken, 'main-pool');
|
|
738
|
+
const collateralCustody = getCustodyPublicKey(collateralToken, 'main-pool');
|
|
739
|
+
const cortex = deriveCortexPda();
|
|
740
|
+
const oracle = deriveOraclePda();
|
|
741
|
+
const transferAuthority = deriveTransferAuthorityPda();
|
|
742
|
+
const userProfile = deriveUserProfilePda(owner);
|
|
743
|
+
const position = derivePositionPda(owner, pool, custody, side);
|
|
744
|
+
const collateralCustodyTokenAccount = await readCustodyTokenAccount(connection, collateralCustody);
|
|
745
|
+
const fundingAccount = deriveAta(owner, getMintPublicKey(collateralToken));
|
|
746
|
+
const referrerProfile = null;
|
|
747
|
+
const collateralRaw = BigInt(Math.floor(collateralAmount * Math.pow(10, ADRENA_CUSTODIES[collateralToken.toUpperCase()].decimals)));
|
|
748
|
+
const priceRaw = price ?? await fetchOraclePrice(principalToken, side);
|
|
749
|
+
const leverageBps = Math.floor(leverage * 10000);
|
|
750
|
+
// Pre-instructions: ATA + user profile
|
|
751
|
+
const collateralMint = getMintPublicKey(collateralToken);
|
|
752
|
+
const preInstructions = await ensureAtaInstructions(connection, owner, collateralMint, owner);
|
|
753
|
+
const profileInstructions = await ensureUserProfileInstructions(connection, owner);
|
|
754
|
+
const allPreInstructions = [...preInstructions, ...profileInstructions];
|
|
755
|
+
// 1. Open position instruction
|
|
756
|
+
const ixName = side === 'long' ? 'openOrIncreasePositionLong' : 'openOrIncreasePositionShort';
|
|
757
|
+
const openIx = await buildInstruction(program, ixName, [
|
|
758
|
+
{
|
|
759
|
+
price: toBN(priceRaw),
|
|
760
|
+
collateral: toBN(collateralRaw),
|
|
761
|
+
leverage: leverageBps,
|
|
762
|
+
oraclePrices: null,
|
|
763
|
+
multiOraclePrices: null,
|
|
764
|
+
},
|
|
765
|
+
], {
|
|
766
|
+
owner,
|
|
767
|
+
payer: owner,
|
|
768
|
+
fundingAccount,
|
|
769
|
+
oracle,
|
|
770
|
+
custody,
|
|
771
|
+
collateralCustody,
|
|
772
|
+
collateralCustodyTokenAccount,
|
|
773
|
+
transferAuthority,
|
|
774
|
+
cortex,
|
|
775
|
+
pool,
|
|
776
|
+
position,
|
|
777
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
778
|
+
tokenProgram: new PublicKey(TOKEN_PROGRAM_ID),
|
|
779
|
+
adrenaProgram: new PublicKey(ADRENA_PROGRAM_ID),
|
|
780
|
+
userProfile,
|
|
781
|
+
referrerProfile,
|
|
782
|
+
});
|
|
783
|
+
const instructions = [...allPreInstructions, openIx];
|
|
784
|
+
const instructionNames = [ixName];
|
|
785
|
+
// 2. Stop loss instruction (optional)
|
|
786
|
+
if (stopLossPriceUsd !== null) {
|
|
787
|
+
const slLimitPrice = BigInt(Math.floor(stopLossPriceUsd * Math.pow(10, 10)));
|
|
788
|
+
const slIxName = side === 'long' ? 'setStopLossLong' : 'setStopLossShort';
|
|
789
|
+
const slIx = await buildInstruction(program, slIxName, [
|
|
790
|
+
{
|
|
791
|
+
stopLossLimitPrice: toBN(slLimitPrice),
|
|
792
|
+
closePositionPrice: toBNOrNull(null),
|
|
793
|
+
},
|
|
794
|
+
], {
|
|
795
|
+
owner,
|
|
796
|
+
cortex,
|
|
797
|
+
pool,
|
|
798
|
+
position,
|
|
799
|
+
custody,
|
|
800
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
801
|
+
});
|
|
802
|
+
instructions.push(slIx);
|
|
803
|
+
instructionNames.push(slIxName);
|
|
804
|
+
}
|
|
805
|
+
// 3. Take profit instruction (optional)
|
|
806
|
+
if (takeProfitPriceUsd !== null) {
|
|
807
|
+
const tpLimitPrice = BigInt(Math.floor(takeProfitPriceUsd * Math.pow(10, 10)));
|
|
808
|
+
const tpIxName = side === 'long' ? 'setTakeProfitLong' : 'setTakeProfitShort';
|
|
809
|
+
const tpIx = await buildInstruction(program, tpIxName, [
|
|
810
|
+
{
|
|
811
|
+
takeProfitLimitPrice: toBN(tpLimitPrice),
|
|
812
|
+
},
|
|
813
|
+
], {
|
|
814
|
+
owner,
|
|
815
|
+
cortex,
|
|
816
|
+
pool,
|
|
817
|
+
position,
|
|
818
|
+
custody,
|
|
819
|
+
systemProgram: new PublicKey(SYSTEM_PROGRAM_ID),
|
|
820
|
+
});
|
|
821
|
+
instructions.push(tpIx);
|
|
822
|
+
instructionNames.push(tpIxName);
|
|
823
|
+
}
|
|
824
|
+
// Balance check
|
|
825
|
+
const balanceCheck = await checkSufficientBalance(connection, owner, collateralToken, collateralAmount);
|
|
826
|
+
const warning = !balanceCheck.sufficient
|
|
827
|
+
? `Insufficient ${collateralToken.toUpperCase()} balance: need ${collateralAmount}, have ${balanceCheck.availableBalance} (shortfall: ${balanceCheck.shortfall}). The transaction will fail on-chain.`
|
|
828
|
+
: !balanceCheck.solSufficientForFees
|
|
829
|
+
? `Insufficient SOL for transaction fees: have ${balanceCheck.solBalance} SOL, need ~0.005 SOL.`
|
|
830
|
+
: undefined;
|
|
831
|
+
const _serializeResult = await serializeUnsignedTx(connection, owner, instructions);
|
|
832
|
+
const transactionBase64 = _serializeResult.transactionBase64;
|
|
833
|
+
return buildResult(transactionBase64, owner, instructionNames, position, balanceCheck, warning);
|
|
834
|
+
}
|
|
835
|
+
//# sourceMappingURL=adrena-builder-trading.js.map
|