@cowprotocol/sdk-trading-solana 0.1.0

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/README.md ADDED
@@ -0,0 +1,38 @@
1
+ <p align="center">
2
+ <img width="400" src="https://github.com/cowprotocol/cow-sdk/raw/main/docs/images/CoW.png" alt="CoW Protocol logo" />
3
+ </p>
4
+
5
+ # Solana Trading SDK
6
+
7
+ ## Test coverage
8
+
9
+ | Statements | Branches | Functions | Lines |
10
+ | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
11
+ | ![Statements](https://img.shields.io/badge/statements-pending-lightgrey.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-pending-lightgrey.svg?style=flat) | ![Functions](https://img.shields.io/badge/functions-pending-lightgrey.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-pending-lightgrey.svg?style=flat) |
12
+
13
+ CoW Protocol's Solana settlement support: Jupiter-sourced quotes and on-chain `CreateOrder`
14
+ posting against the CoW Protocol Solana settlement program.
15
+
16
+ **Experimental.** The Solana settlement program this package targets isn't deployed anywhere
17
+ reachable yet. See `docs/superpowers/specs/2026-09-01-sdk-trading-solana-extraction-design.md` in
18
+ the `cowswap` repo for background.
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ import { SolanaTradingSdk } from '@cowprotocol/sdk-trading-solana'
24
+
25
+ const sdk = new SolanaTradingSdk({ signAndSend })
26
+ const { quoteResults, postSwapOrderFromQuote } = await sdk.getQuote({
27
+ ownerAddress,
28
+ receiverAddress,
29
+ sellTokenAddress,
30
+ sellTokenDecimals,
31
+ buyTokenAddress,
32
+ buyTokenDecimals,
33
+ amount,
34
+ kind,
35
+ })
36
+
37
+ const result = await postSwapOrderFromQuote()
38
+ ```
@@ -0,0 +1,172 @@
1
+ import { PublicKey, PublicKeyInitData, TransactionInstruction } from '@solana/web3.js';
2
+ import { OrderKind } from '@cowprotocol/sdk-order-book';
3
+ import { CowEnv } from '@cowprotocol/sdk-config';
4
+ import { QuoteResults, SwapAdvancedSettings, SigningStepManager, OrderPostingResult, QuoteAndPost } from '@cowprotocol/sdk-trading';
5
+
6
+ interface JupiterOrderRequest {
7
+ inputMint: string;
8
+ outputMint: string;
9
+ amount: string;
10
+ swapMode: 'ExactIn' | 'ExactOut';
11
+ clientPlatform?: string;
12
+ }
13
+ /**
14
+ * Fields of Jupiter's `/order` response this SDK actually reads. Jupiter's own swap transaction/execute
15
+ * flow (`transaction`, `requestId`) is deliberately not modeled here — quotes are sourced from Jupiter,
16
+ * but orders are posted through the CoW Protocol settlement program, never through Jupiter's `/execute`.
17
+ */
18
+ interface JupiterOrderResponse {
19
+ inputMint: string;
20
+ outputMint: string;
21
+ inAmount: string;
22
+ outAmount: string;
23
+ swapMode: 'ExactIn' | 'ExactOut';
24
+ slippageBps: number;
25
+ }
26
+ /** Client for Jupiter's public quote API. Quote-only: never used to submit or execute a swap. */
27
+ declare class JupiterAPI {
28
+ getOrder(request: JupiterOrderRequest): Promise<JupiterOrderResponse>;
29
+ }
30
+
31
+ /**
32
+ * TS port of `cow-settlement-interface`'s `OrderIntent` (interface/src/data/intent.rs, v0.3.0).
33
+ * Every field here has a Rust counterpart with the same name; keep them in sync if the settlement
34
+ * program's wire format changes.
35
+ */
36
+ interface SolanaOrderIntent {
37
+ owner: PublicKey;
38
+ buyTokenAccount: PublicKey;
39
+ buyMint: PublicKey;
40
+ sellTokenAccount: PublicKey;
41
+ sellMint: PublicKey;
42
+ sellAmount: bigint;
43
+ buyAmount: bigint;
44
+ /** Unix timestamp seconds. */
45
+ validTo: number;
46
+ kind: OrderKind;
47
+ partiallyFillable: boolean;
48
+ /**
49
+ * Must be `true`: this is the flag the `CreateOrder` instruction authenticates against (the owner
50
+ * signs the transaction themselves). The alternative — an off-chain Ed25519-presigned order anyone can
51
+ * submit — is a different, unused authentication path.
52
+ */
53
+ createdOnChain: boolean;
54
+ /** Exactly 32 bytes, opaque to the settlement program. */
55
+ appData: Uint8Array;
56
+ }
57
+ /** Canonical byte size of an encoded `OrderIntent`, per `EncodedOrderIntent::SIZE` in the Rust source. */
58
+ declare const ENCODED_ORDER_INTENT_SIZE = 213;
59
+ declare function encodeOrderIntent(intent: SolanaOrderIntent): Uint8Array;
60
+ /**
61
+ * SHA-256 of the encoded intent bytes — doubles as the order UID and the middle seed of the order PDA
62
+ * (`OrderIntent::uid()` in the Rust source). Uses the Web Crypto API (available in both Node 20+ and
63
+ * browsers) rather than a new hashing dependency.
64
+ */
65
+ declare function hashOrderIntent(encoded: Uint8Array): Promise<Uint8Array>;
66
+ declare function toHex(bytes: Uint8Array): string;
67
+
68
+ interface SolanaQuoteParameters {
69
+ ownerAddress: PublicKeyInitData;
70
+ receiverAddress: PublicKeyInitData;
71
+ sellTokenAddress: PublicKeyInitData;
72
+ sellTokenDecimals: number;
73
+ buyTokenAddress: PublicKeyInitData;
74
+ buyTokenDecimals: number;
75
+ /** Sell-side amount for a SELL order, buy-side amount for a BUY order — same convention as Jupiter's `amount`. */
76
+ amount: bigint;
77
+ kind: OrderKind;
78
+ partiallyFillable?: boolean;
79
+ /** Order lifetime from now, in seconds. Defaults to 30 minutes. */
80
+ validForSeconds?: number;
81
+ /** Token program owning `sellMint`'s accounts (classic SPL Token vs Token-2022). Defaults to the
82
+ * classic SPL Token program — pass `TOKEN_2022_PROGRAM_ID` explicitly for Token-2022 mints, since the
83
+ * associated token account address differs by program. */
84
+ sellTokenProgramId?: PublicKeyInitData;
85
+ /** Same as `sellTokenProgramId`, for `buyMint`. */
86
+ buyTokenProgramId?: PublicKeyInitData;
87
+ }
88
+ interface SolanaQuote {
89
+ intent: SolanaOrderIntent;
90
+ intentBytes: Uint8Array;
91
+ /** SHA-256 of `intentBytes`; also the order's uid and the order PDA's seed. */
92
+ uid: Uint8Array;
93
+ orderPda: PublicKey;
94
+ programId: PublicKey;
95
+ /** The raw Jupiter response the quote was built from — real amounts/slippage for the caller to read. */
96
+ jupiterOrder: JupiterOrderResponse;
97
+ /** Token program owning `intent.buyMint`'s accounts, as resolved at quote time — needed to re-derive
98
+ * `buyTokenAccount`'s associated token address if `receiver` is overridden when posting. */
99
+ buyTokenProgramId?: PublicKey;
100
+ }
101
+ /** Signs and submits a `CreateOrder` instruction; supplied by the caller since this SDK has no bound
102
+ * Solana wallet/signer (unlike the EVM adapter). */
103
+ type SolanaSignAndSend = (instruction: TransactionInstruction) => Promise<{
104
+ signature: string;
105
+ }>;
106
+
107
+ /**
108
+ * Version-embedded seed shared by every settlement-program PDA (`SETTLEMENT_SEED` in
109
+ * cow-settlement-interface). Must be regenerated if `SOLANA_SETTLEMENT_PROGRAM_VERSION` changes.
110
+ */
111
+ declare const SETTLEMENT_SEED: Uint8Array<ArrayBufferLike>;
112
+ /** Trailing seed identifying order PDAs (`ORDER_SEED` in cow-settlement-interface). */
113
+ declare const ORDER_SEED: Uint8Array<ArrayBufferLike>;
114
+ /**
115
+ * Derives the canonical order PDA and bump for an order's `uid`, matching `find_order_pda` in
116
+ * cow-settlement-interface.
117
+ */
118
+ declare function findOrderPda(programId: PublicKey, uid: Uint8Array): [PublicKey, number];
119
+
120
+ interface CreateOrderInstructionParams {
121
+ programId: PublicKey;
122
+ /** Authenticates the order; must match `intent.owner` and sign the transaction. */
123
+ owner: PublicKey;
124
+ /** Funds the new order PDA's rent; must sign the transaction. May equal `owner`. */
125
+ createdBy: PublicKey;
126
+ /** The canonical PDA for `intent`'s uid — see `findOrderPda`. */
127
+ orderPda: PublicKey;
128
+ intent: SolanaOrderIntent;
129
+ }
130
+ /**
131
+ * Builds the `CreateOrder` instruction, matching `CreateOrder::into::<Instruction>()` in
132
+ * cow-settlement-interface: `data = [discriminator=2, ...213 intent bytes]`, accounts
133
+ * `[owner (readonly signer), created_by (writable signer), order_pda (writable), system_program]`.
134
+ */
135
+ declare function buildCreateOrderInstruction(params: CreateOrderInstructionParams): TransactionInstruction;
136
+
137
+ declare function getSolanaQuote(params: SolanaQuoteParameters, options?: {
138
+ env?: CowEnv;
139
+ }): Promise<{
140
+ quoteResults: QuoteResults;
141
+ solanaQuote: SolanaQuote;
142
+ }>;
143
+
144
+ /**
145
+ * Builds the real `CreateOrder` instruction for `quote` and has the caller sign and submit it. This is
146
+ * the Solana analogue of `postSwapOrderFromQuote` in `postSwapOrder.ts` — but where the EVM version signs
147
+ * order data and POSTs it to the CoW order-book, Solana orders are created entirely on-chain, so this
148
+ * builds a transaction instruction instead of a signed order body. `createdBy` is always `quote.intent.owner`:
149
+ * a single connected wallet both authenticates and funds the order's rent.
150
+ */
151
+ declare function postSolanaSwapOrderFromQuote({ quoteResults, solanaQuote }: {
152
+ quoteResults: QuoteResults;
153
+ solanaQuote: SolanaQuote;
154
+ }, signAndSend: SolanaSignAndSend, advancedSettings?: SwapAdvancedSettings, signingStepManager?: SigningStepManager): Promise<OrderPostingResult>;
155
+
156
+ interface SolanaTradingSdkOptions {
157
+ signAndSend: SolanaSignAndSend;
158
+ env?: CowEnv;
159
+ }
160
+ /**
161
+ * Solana counterpart to `TradingSdk`. Unlike the EVM SDK, which gets its signer implicitly from a
162
+ * global adapter set once at app startup, Solana has no such adapter — `signAndSend` is bound at
163
+ * construction instead, so callers get the same `sdk.getQuote(...)` → `.postSwapOrderFromQuote()`
164
+ * shape without threading a signer through every call.
165
+ */
166
+ declare class SolanaTradingSdk {
167
+ private readonly options;
168
+ constructor(options: SolanaTradingSdkOptions);
169
+ getQuote(params: SolanaQuoteParameters): Promise<QuoteAndPost>;
170
+ }
171
+
172
+ export { type CreateOrderInstructionParams, ENCODED_ORDER_INTENT_SIZE, JupiterAPI, type JupiterOrderRequest, type JupiterOrderResponse, ORDER_SEED, SETTLEMENT_SEED, type SolanaOrderIntent, type SolanaQuote, type SolanaQuoteParameters, type SolanaSignAndSend, SolanaTradingSdk, type SolanaTradingSdkOptions, buildCreateOrderInstruction, encodeOrderIntent, findOrderPda, getSolanaQuote, hashOrderIntent, postSolanaSwapOrderFromQuote, toHex };
@@ -0,0 +1,172 @@
1
+ import { PublicKey, PublicKeyInitData, TransactionInstruction } from '@solana/web3.js';
2
+ import { OrderKind } from '@cowprotocol/sdk-order-book';
3
+ import { CowEnv } from '@cowprotocol/sdk-config';
4
+ import { QuoteResults, SwapAdvancedSettings, SigningStepManager, OrderPostingResult, QuoteAndPost } from '@cowprotocol/sdk-trading';
5
+
6
+ interface JupiterOrderRequest {
7
+ inputMint: string;
8
+ outputMint: string;
9
+ amount: string;
10
+ swapMode: 'ExactIn' | 'ExactOut';
11
+ clientPlatform?: string;
12
+ }
13
+ /**
14
+ * Fields of Jupiter's `/order` response this SDK actually reads. Jupiter's own swap transaction/execute
15
+ * flow (`transaction`, `requestId`) is deliberately not modeled here — quotes are sourced from Jupiter,
16
+ * but orders are posted through the CoW Protocol settlement program, never through Jupiter's `/execute`.
17
+ */
18
+ interface JupiterOrderResponse {
19
+ inputMint: string;
20
+ outputMint: string;
21
+ inAmount: string;
22
+ outAmount: string;
23
+ swapMode: 'ExactIn' | 'ExactOut';
24
+ slippageBps: number;
25
+ }
26
+ /** Client for Jupiter's public quote API. Quote-only: never used to submit or execute a swap. */
27
+ declare class JupiterAPI {
28
+ getOrder(request: JupiterOrderRequest): Promise<JupiterOrderResponse>;
29
+ }
30
+
31
+ /**
32
+ * TS port of `cow-settlement-interface`'s `OrderIntent` (interface/src/data/intent.rs, v0.3.0).
33
+ * Every field here has a Rust counterpart with the same name; keep them in sync if the settlement
34
+ * program's wire format changes.
35
+ */
36
+ interface SolanaOrderIntent {
37
+ owner: PublicKey;
38
+ buyTokenAccount: PublicKey;
39
+ buyMint: PublicKey;
40
+ sellTokenAccount: PublicKey;
41
+ sellMint: PublicKey;
42
+ sellAmount: bigint;
43
+ buyAmount: bigint;
44
+ /** Unix timestamp seconds. */
45
+ validTo: number;
46
+ kind: OrderKind;
47
+ partiallyFillable: boolean;
48
+ /**
49
+ * Must be `true`: this is the flag the `CreateOrder` instruction authenticates against (the owner
50
+ * signs the transaction themselves). The alternative — an off-chain Ed25519-presigned order anyone can
51
+ * submit — is a different, unused authentication path.
52
+ */
53
+ createdOnChain: boolean;
54
+ /** Exactly 32 bytes, opaque to the settlement program. */
55
+ appData: Uint8Array;
56
+ }
57
+ /** Canonical byte size of an encoded `OrderIntent`, per `EncodedOrderIntent::SIZE` in the Rust source. */
58
+ declare const ENCODED_ORDER_INTENT_SIZE = 213;
59
+ declare function encodeOrderIntent(intent: SolanaOrderIntent): Uint8Array;
60
+ /**
61
+ * SHA-256 of the encoded intent bytes — doubles as the order UID and the middle seed of the order PDA
62
+ * (`OrderIntent::uid()` in the Rust source). Uses the Web Crypto API (available in both Node 20+ and
63
+ * browsers) rather than a new hashing dependency.
64
+ */
65
+ declare function hashOrderIntent(encoded: Uint8Array): Promise<Uint8Array>;
66
+ declare function toHex(bytes: Uint8Array): string;
67
+
68
+ interface SolanaQuoteParameters {
69
+ ownerAddress: PublicKeyInitData;
70
+ receiverAddress: PublicKeyInitData;
71
+ sellTokenAddress: PublicKeyInitData;
72
+ sellTokenDecimals: number;
73
+ buyTokenAddress: PublicKeyInitData;
74
+ buyTokenDecimals: number;
75
+ /** Sell-side amount for a SELL order, buy-side amount for a BUY order — same convention as Jupiter's `amount`. */
76
+ amount: bigint;
77
+ kind: OrderKind;
78
+ partiallyFillable?: boolean;
79
+ /** Order lifetime from now, in seconds. Defaults to 30 minutes. */
80
+ validForSeconds?: number;
81
+ /** Token program owning `sellMint`'s accounts (classic SPL Token vs Token-2022). Defaults to the
82
+ * classic SPL Token program — pass `TOKEN_2022_PROGRAM_ID` explicitly for Token-2022 mints, since the
83
+ * associated token account address differs by program. */
84
+ sellTokenProgramId?: PublicKeyInitData;
85
+ /** Same as `sellTokenProgramId`, for `buyMint`. */
86
+ buyTokenProgramId?: PublicKeyInitData;
87
+ }
88
+ interface SolanaQuote {
89
+ intent: SolanaOrderIntent;
90
+ intentBytes: Uint8Array;
91
+ /** SHA-256 of `intentBytes`; also the order's uid and the order PDA's seed. */
92
+ uid: Uint8Array;
93
+ orderPda: PublicKey;
94
+ programId: PublicKey;
95
+ /** The raw Jupiter response the quote was built from — real amounts/slippage for the caller to read. */
96
+ jupiterOrder: JupiterOrderResponse;
97
+ /** Token program owning `intent.buyMint`'s accounts, as resolved at quote time — needed to re-derive
98
+ * `buyTokenAccount`'s associated token address if `receiver` is overridden when posting. */
99
+ buyTokenProgramId?: PublicKey;
100
+ }
101
+ /** Signs and submits a `CreateOrder` instruction; supplied by the caller since this SDK has no bound
102
+ * Solana wallet/signer (unlike the EVM adapter). */
103
+ type SolanaSignAndSend = (instruction: TransactionInstruction) => Promise<{
104
+ signature: string;
105
+ }>;
106
+
107
+ /**
108
+ * Version-embedded seed shared by every settlement-program PDA (`SETTLEMENT_SEED` in
109
+ * cow-settlement-interface). Must be regenerated if `SOLANA_SETTLEMENT_PROGRAM_VERSION` changes.
110
+ */
111
+ declare const SETTLEMENT_SEED: Uint8Array<ArrayBufferLike>;
112
+ /** Trailing seed identifying order PDAs (`ORDER_SEED` in cow-settlement-interface). */
113
+ declare const ORDER_SEED: Uint8Array<ArrayBufferLike>;
114
+ /**
115
+ * Derives the canonical order PDA and bump for an order's `uid`, matching `find_order_pda` in
116
+ * cow-settlement-interface.
117
+ */
118
+ declare function findOrderPda(programId: PublicKey, uid: Uint8Array): [PublicKey, number];
119
+
120
+ interface CreateOrderInstructionParams {
121
+ programId: PublicKey;
122
+ /** Authenticates the order; must match `intent.owner` and sign the transaction. */
123
+ owner: PublicKey;
124
+ /** Funds the new order PDA's rent; must sign the transaction. May equal `owner`. */
125
+ createdBy: PublicKey;
126
+ /** The canonical PDA for `intent`'s uid — see `findOrderPda`. */
127
+ orderPda: PublicKey;
128
+ intent: SolanaOrderIntent;
129
+ }
130
+ /**
131
+ * Builds the `CreateOrder` instruction, matching `CreateOrder::into::<Instruction>()` in
132
+ * cow-settlement-interface: `data = [discriminator=2, ...213 intent bytes]`, accounts
133
+ * `[owner (readonly signer), created_by (writable signer), order_pda (writable), system_program]`.
134
+ */
135
+ declare function buildCreateOrderInstruction(params: CreateOrderInstructionParams): TransactionInstruction;
136
+
137
+ declare function getSolanaQuote(params: SolanaQuoteParameters, options?: {
138
+ env?: CowEnv;
139
+ }): Promise<{
140
+ quoteResults: QuoteResults;
141
+ solanaQuote: SolanaQuote;
142
+ }>;
143
+
144
+ /**
145
+ * Builds the real `CreateOrder` instruction for `quote` and has the caller sign and submit it. This is
146
+ * the Solana analogue of `postSwapOrderFromQuote` in `postSwapOrder.ts` — but where the EVM version signs
147
+ * order data and POSTs it to the CoW order-book, Solana orders are created entirely on-chain, so this
148
+ * builds a transaction instruction instead of a signed order body. `createdBy` is always `quote.intent.owner`:
149
+ * a single connected wallet both authenticates and funds the order's rent.
150
+ */
151
+ declare function postSolanaSwapOrderFromQuote({ quoteResults, solanaQuote }: {
152
+ quoteResults: QuoteResults;
153
+ solanaQuote: SolanaQuote;
154
+ }, signAndSend: SolanaSignAndSend, advancedSettings?: SwapAdvancedSettings, signingStepManager?: SigningStepManager): Promise<OrderPostingResult>;
155
+
156
+ interface SolanaTradingSdkOptions {
157
+ signAndSend: SolanaSignAndSend;
158
+ env?: CowEnv;
159
+ }
160
+ /**
161
+ * Solana counterpart to `TradingSdk`. Unlike the EVM SDK, which gets its signer implicitly from a
162
+ * global adapter set once at app startup, Solana has no such adapter — `signAndSend` is bound at
163
+ * construction instead, so callers get the same `sdk.getQuote(...)` → `.postSwapOrderFromQuote()`
164
+ * shape without threading a signer through every call.
165
+ */
166
+ declare class SolanaTradingSdk {
167
+ private readonly options;
168
+ constructor(options: SolanaTradingSdkOptions);
169
+ getQuote(params: SolanaQuoteParameters): Promise<QuoteAndPost>;
170
+ }
171
+
172
+ export { type CreateOrderInstructionParams, ENCODED_ORDER_INTENT_SIZE, JupiterAPI, type JupiterOrderRequest, type JupiterOrderResponse, ORDER_SEED, SETTLEMENT_SEED, type SolanaOrderIntent, type SolanaQuote, type SolanaQuoteParameters, type SolanaSignAndSend, SolanaTradingSdk, type SolanaTradingSdkOptions, buildCreateOrderInstruction, encodeOrderIntent, findOrderPda, getSolanaQuote, hashOrderIntent, postSolanaSwapOrderFromQuote, toHex };
package/dist/index.js ADDED
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ ENCODED_ORDER_INTENT_SIZE: () => ENCODED_ORDER_INTENT_SIZE,
24
+ JupiterAPI: () => JupiterAPI,
25
+ ORDER_SEED: () => ORDER_SEED,
26
+ SETTLEMENT_SEED: () => SETTLEMENT_SEED,
27
+ SolanaTradingSdk: () => SolanaTradingSdk,
28
+ buildCreateOrderInstruction: () => buildCreateOrderInstruction,
29
+ encodeOrderIntent: () => encodeOrderIntent,
30
+ findOrderPda: () => findOrderPda,
31
+ getSolanaQuote: () => getSolanaQuote,
32
+ hashOrderIntent: () => hashOrderIntent,
33
+ postSolanaSwapOrderFromQuote: () => postSolanaSwapOrderFromQuote,
34
+ toHex: () => toHex
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/orderIntent.ts
39
+ var import_sdk_order_book = require("@cowprotocol/sdk-order-book");
40
+ var ENCODED_ORDER_INTENT_SIZE = 213;
41
+ var FLAG_CREATED_ON_CHAIN = 1 << 0;
42
+ var FLAG_KIND_BUY = 1 << 1;
43
+ var FLAG_PARTIALLY_FILLABLE = 1 << 2;
44
+ var MAX_VALID_TO = 4294967295;
45
+ function encodeOrderIntent(intent) {
46
+ if (intent.appData.length !== 32) {
47
+ throw new Error("appData must be exactly 32 bytes");
48
+ }
49
+ if (!Number.isInteger(intent.validTo) || intent.validTo < 0 || intent.validTo > MAX_VALID_TO) {
50
+ throw new Error(`validTo must be an integer between 0 and ${MAX_VALID_TO}`);
51
+ }
52
+ const bytes = new Uint8Array(ENCODED_ORDER_INTENT_SIZE);
53
+ const view = new DataView(bytes.buffer);
54
+ let offset = 0;
55
+ const writePubkey = (pubkey) => {
56
+ bytes.set(pubkey.toBytes(), offset);
57
+ offset += 32;
58
+ };
59
+ const writeU64LE = (value) => {
60
+ view.setBigUint64(offset, value, true);
61
+ offset += 8;
62
+ };
63
+ writePubkey(intent.owner);
64
+ writePubkey(intent.buyTokenAccount);
65
+ writePubkey(intent.buyMint);
66
+ writePubkey(intent.sellTokenAccount);
67
+ writePubkey(intent.sellMint);
68
+ writeU64LE(intent.sellAmount);
69
+ writeU64LE(intent.buyAmount);
70
+ view.setUint32(offset, intent.validTo, true);
71
+ offset += 4;
72
+ let flags = 0;
73
+ if (intent.createdOnChain)
74
+ flags |= FLAG_CREATED_ON_CHAIN;
75
+ if (intent.kind === import_sdk_order_book.OrderKind.BUY)
76
+ flags |= FLAG_KIND_BUY;
77
+ if (intent.partiallyFillable)
78
+ flags |= FLAG_PARTIALLY_FILLABLE;
79
+ bytes[offset] = flags;
80
+ offset += 1;
81
+ bytes.set(intent.appData, offset);
82
+ return bytes;
83
+ }
84
+ async function hashOrderIntent(encoded) {
85
+ const digest = await crypto.subtle.digest("SHA-256", encoded);
86
+ return new Uint8Array(digest);
87
+ }
88
+ function toHex(bytes) {
89
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
90
+ }
91
+
92
+ // src/orderPda.ts
93
+ var import_web3 = require("@solana/web3.js");
94
+ var import_sdk_config = require("@cowprotocol/sdk-config");
95
+ var SETTLEMENT_SEED_PREFIX = "settlement v";
96
+ var SETTLEMENT_SEED_VERSION_LEN = 7;
97
+ var SETTLEMENT_SEED = new TextEncoder().encode(
98
+ SETTLEMENT_SEED_PREFIX + import_sdk_config.SOLANA_SETTLEMENT_PROGRAM_VERSION.padEnd(SETTLEMENT_SEED_VERSION_LEN, " ")
99
+ );
100
+ var ORDER_SEED = new TextEncoder().encode("order");
101
+ function findOrderPda(programId, uid) {
102
+ return import_web3.PublicKey.findProgramAddressSync([SETTLEMENT_SEED, uid, ORDER_SEED], programId);
103
+ }
104
+
105
+ // src/createOrderInstruction.ts
106
+ var import_web32 = require("@solana/web3.js");
107
+ var CREATE_ORDER_DISCRIMINATOR = 2;
108
+ function buildCreateOrderInstruction(params) {
109
+ const intentBytes = encodeOrderIntent(params.intent);
110
+ const data = Buffer.alloc(1 + intentBytes.length);
111
+ data[0] = CREATE_ORDER_DISCRIMINATOR;
112
+ data.set(intentBytes, 1);
113
+ return new import_web32.TransactionInstruction({
114
+ programId: params.programId,
115
+ keys: [
116
+ { pubkey: params.owner, isSigner: true, isWritable: false },
117
+ { pubkey: params.createdBy, isSigner: true, isWritable: true },
118
+ { pubkey: params.orderPda, isSigner: false, isWritable: true },
119
+ { pubkey: import_web32.SystemProgram.programId, isSigner: false, isWritable: false }
120
+ ],
121
+ data
122
+ });
123
+ }
124
+
125
+ // src/jupiterApi.ts
126
+ var JUPITER_ORDER_ENDPOINT = "https://ultra-api.jup.ag/order";
127
+ var DEFAULT_CLIENT_PLATFORM = "cowswap";
128
+ var QUOTE_TIMEOUT_MS = 1e4;
129
+ var UNSIGNED_INTEGER_PATTERN = /^\d+$/;
130
+ var JupiterAPI = class {
131
+ async getOrder(request) {
132
+ const params = new URLSearchParams({
133
+ inputMint: request.inputMint,
134
+ outputMint: request.outputMint,
135
+ amount: request.amount,
136
+ swapMode: request.swapMode,
137
+ clientPlatform: request.clientPlatform ?? DEFAULT_CLIENT_PLATFORM
138
+ });
139
+ const controller = new AbortController();
140
+ const timeoutId = setTimeout(() => controller.abort(), QUOTE_TIMEOUT_MS);
141
+ try {
142
+ const response = await fetch(`${JUPITER_ORDER_ENDPOINT}?${params.toString()}`, { signal: controller.signal });
143
+ let body;
144
+ try {
145
+ body = await response.json();
146
+ } catch {
147
+ throw new Error(`Jupiter quote request failed (${response.status})`);
148
+ }
149
+ if (!response.ok) {
150
+ const message = isJupiterErrorResponse(body) ? body.error : `Jupiter quote request failed (${response.status})`;
151
+ throw new Error(message);
152
+ }
153
+ if (!isJupiterOrderResponse(body, request)) {
154
+ throw new Error("Jupiter quote response is malformed or does not match the requested pair");
155
+ }
156
+ return body;
157
+ } finally {
158
+ clearTimeout(timeoutId);
159
+ }
160
+ }
161
+ };
162
+ function isJupiterErrorResponse(body) {
163
+ return typeof body === "object" && body !== null && typeof body.error === "string";
164
+ }
165
+ function isJupiterOrderResponse(body, request) {
166
+ if (typeof body !== "object" || body === null)
167
+ return false;
168
+ const candidate = body;
169
+ return candidate.inputMint === request.inputMint && candidate.outputMint === request.outputMint && candidate.swapMode === request.swapMode && typeof candidate.inAmount === "string" && UNSIGNED_INTEGER_PATTERN.test(candidate.inAmount) && typeof candidate.outAmount === "string" && UNSIGNED_INTEGER_PATTERN.test(candidate.outAmount) && typeof candidate.slippageBps === "number" && Number.isFinite(candidate.slippageBps);
170
+ }
171
+
172
+ // src/getSolanaQuote.ts
173
+ var import_web33 = require("@solana/web3.js");
174
+ var import_spl_token = require("@solana/spl-token");
175
+ var import_sdk_config2 = require("@cowprotocol/sdk-config");
176
+ var import_sdk_order_book2 = require("@cowprotocol/sdk-order-book");
177
+ var DEFAULT_VALID_FOR_SECONDS = 30 * 60;
178
+ var ZERO_APP_DATA = new Uint8Array(32);
179
+ var jupiterApi = new JupiterAPI();
180
+ async function getSolanaQuote(params, options = {}) {
181
+ const {
182
+ ownerAddress,
183
+ receiverAddress,
184
+ sellTokenDecimals,
185
+ buyTokenDecimals,
186
+ amount,
187
+ kind,
188
+ partiallyFillable = false,
189
+ validForSeconds = DEFAULT_VALID_FOR_SECONDS,
190
+ sellTokenProgramId,
191
+ buyTokenProgramId
192
+ } = params;
193
+ if (!Number.isFinite(validForSeconds) || validForSeconds <= 0) {
194
+ throw new Error("validForSeconds must be a finite number greater than zero");
195
+ }
196
+ const owner = new import_web33.PublicKey(ownerAddress);
197
+ const receiver = new import_web33.PublicKey(receiverAddress);
198
+ const sellMint = new import_web33.PublicKey(params.sellTokenAddress);
199
+ const buyMint = new import_web33.PublicKey(params.buyTokenAddress);
200
+ const sellTokenProgram = sellTokenProgramId ? new import_web33.PublicKey(sellTokenProgramId) : void 0;
201
+ const buyTokenProgram = buyTokenProgramId ? new import_web33.PublicKey(buyTokenProgramId) : void 0;
202
+ const sellTokenAddress = sellMint.toBase58();
203
+ const buyTokenAddress = buyMint.toBase58();
204
+ const jupiterOrder = await jupiterApi.getOrder({
205
+ inputMint: sellTokenAddress,
206
+ outputMint: buyTokenAddress,
207
+ amount: amount.toString(),
208
+ swapMode: kind === import_sdk_order_book2.OrderKind.SELL ? "ExactIn" : "ExactOut"
209
+ });
210
+ const validTo = Math.floor(Date.now() / 1e3) + validForSeconds;
211
+ const orderParams = {
212
+ sellToken: sellTokenAddress,
213
+ buyToken: buyTokenAddress,
214
+ receiver: receiver.toBase58(),
215
+ sellAmount: jupiterOrder.inAmount,
216
+ buyAmount: jupiterOrder.outAmount,
217
+ validTo,
218
+ // TODO: fill appData when we know the format
219
+ appData: "{}",
220
+ // TODO: implement fees
221
+ feeAmount: "0",
222
+ gasAmount: "0",
223
+ gasPrice: "0",
224
+ sellTokenPrice: "0",
225
+ kind,
226
+ partiallyFillable
227
+ };
228
+ const amountsAndCosts = (0, import_sdk_order_book2.getQuoteAmountsAndCosts)({
229
+ orderParams,
230
+ slippagePercentBps: jupiterOrder.slippageBps,
231
+ // TODO: implement fees
232
+ partnerFeeBps: 0,
233
+ protocolFeeBps: 0
234
+ });
235
+ const intent = {
236
+ owner,
237
+ buyTokenAccount: (0, import_spl_token.getAssociatedTokenAddressSync)(buyMint, receiver, false, buyTokenProgram),
238
+ buyMint,
239
+ sellTokenAccount: (0, import_spl_token.getAssociatedTokenAddressSync)(sellMint, owner, false, sellTokenProgram),
240
+ sellMint,
241
+ sellAmount: amountsAndCosts.amountsToSign.sellAmount,
242
+ buyAmount: amountsAndCosts.amountsToSign.buyAmount,
243
+ validTo,
244
+ kind,
245
+ partiallyFillable,
246
+ createdOnChain: true,
247
+ appData: ZERO_APP_DATA
248
+ };
249
+ const intentBytes = encodeOrderIntent(intent);
250
+ const uid = await hashOrderIntent(intentBytes);
251
+ const programId = new import_web33.PublicKey(
252
+ options.env === "staging" ? import_sdk_config2.SOLANA_SETTLEMENT_PROGRAM_ID_STAGING : import_sdk_config2.SOLANA_SETTLEMENT_PROGRAM_ID
253
+ );
254
+ const [orderPda] = findOrderPda(programId, uid);
255
+ const solanaQuote = {
256
+ intent,
257
+ intentBytes,
258
+ uid,
259
+ orderPda,
260
+ programId,
261
+ jupiterOrder,
262
+ buyTokenProgramId: buyTokenProgram
263
+ };
264
+ const quoteResponse = {
265
+ quote: orderParams,
266
+ from: owner.toBase58(),
267
+ expiration: new Date(intent.validTo * 1e3).toISOString(),
268
+ verified: false
269
+ };
270
+ const tradeParameters = {
271
+ kind,
272
+ owner: owner.toBase58(),
273
+ sellToken: sellTokenAddress,
274
+ sellTokenDecimals,
275
+ buyToken: buyTokenAddress,
276
+ buyTokenDecimals,
277
+ amount: amount.toString(),
278
+ receiver: receiver.toBase58(),
279
+ validFor: validForSeconds,
280
+ partiallyFillable: orderParams.partiallyFillable
281
+ };
282
+ const quoteResults = {
283
+ quoteResponse,
284
+ amountsAndCosts,
285
+ suggestedSlippageBps: jupiterOrder.slippageBps,
286
+ tradeParameters,
287
+ orderToSign: {},
288
+ appDataInfo: {},
289
+ orderTypedData: {}
290
+ };
291
+ return { quoteResults, solanaQuote };
292
+ }
293
+
294
+ // src/postSwapOrderFromQuote.ts
295
+ var import_spl_token2 = require("@solana/spl-token");
296
+ var import_sdk_order_book3 = require("@cowprotocol/sdk-order-book");
297
+ var import_web34 = require("@solana/web3.js");
298
+ async function postSolanaSwapOrderFromQuote({ quoteResults, solanaQuote }, signAndSend, advancedSettings, signingStepManager) {
299
+ const intent = { ...solanaQuote.intent };
300
+ let uid = solanaQuote.uid;
301
+ let orderPda = solanaQuote.orderPda;
302
+ if (advancedSettings?.quoteRequest) {
303
+ const { validTo, receiver } = advancedSettings.quoteRequest;
304
+ if (receiver) {
305
+ intent.buyTokenAccount = (0, import_spl_token2.getAssociatedTokenAddressSync)(
306
+ intent.buyMint,
307
+ new import_web34.PublicKey(receiver),
308
+ false,
309
+ solanaQuote.buyTokenProgramId
310
+ );
311
+ }
312
+ if (validTo)
313
+ intent.validTo = validTo;
314
+ if (receiver || validTo) {
315
+ const intentBytes = encodeOrderIntent(intent);
316
+ uid = await hashOrderIntent(intentBytes);
317
+ [orderPda] = findOrderPda(solanaQuote.programId, uid);
318
+ }
319
+ }
320
+ const instruction = buildCreateOrderInstruction({
321
+ programId: solanaQuote.programId,
322
+ owner: intent.owner,
323
+ createdBy: intent.owner,
324
+ orderPda,
325
+ intent
326
+ });
327
+ await signingStepManager?.beforeOrderSign?.();
328
+ const { signature } = await signAndSend(instruction);
329
+ await signingStepManager?.afterOrderSign?.();
330
+ const orderToSign = quoteResults.orderToSign;
331
+ return {
332
+ orderId: toHex(uid),
333
+ txHash: signature,
334
+ signature,
335
+ signingScheme: import_sdk_order_book3.SigningScheme.PRESIGN,
336
+ orderToSign
337
+ };
338
+ }
339
+
340
+ // src/solanaTradingSdk.ts
341
+ var SolanaTradingSdk = class {
342
+ constructor(options) {
343
+ this.options = options;
344
+ }
345
+ async getQuote(params) {
346
+ const quote = await getSolanaQuote(params, { env: this.options.env });
347
+ return {
348
+ quoteResults: quote.quoteResults,
349
+ postSwapOrderFromQuote: (advancedSettings, signingStepManager) => postSolanaSwapOrderFromQuote(quote, this.options.signAndSend, advancedSettings, signingStepManager)
350
+ };
351
+ }
352
+ };
353
+ // Annotate the CommonJS export names for ESM import in node:
354
+ 0 && (module.exports = {
355
+ ENCODED_ORDER_INTENT_SIZE,
356
+ JupiterAPI,
357
+ ORDER_SEED,
358
+ SETTLEMENT_SEED,
359
+ SolanaTradingSdk,
360
+ buildCreateOrderInstruction,
361
+ encodeOrderIntent,
362
+ findOrderPda,
363
+ getSolanaQuote,
364
+ hashOrderIntent,
365
+ postSolanaSwapOrderFromQuote,
366
+ toHex
367
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,329 @@
1
+ // src/orderIntent.ts
2
+ import { OrderKind } from "@cowprotocol/sdk-order-book";
3
+ var ENCODED_ORDER_INTENT_SIZE = 213;
4
+ var FLAG_CREATED_ON_CHAIN = 1 << 0;
5
+ var FLAG_KIND_BUY = 1 << 1;
6
+ var FLAG_PARTIALLY_FILLABLE = 1 << 2;
7
+ var MAX_VALID_TO = 4294967295;
8
+ function encodeOrderIntent(intent) {
9
+ if (intent.appData.length !== 32) {
10
+ throw new Error("appData must be exactly 32 bytes");
11
+ }
12
+ if (!Number.isInteger(intent.validTo) || intent.validTo < 0 || intent.validTo > MAX_VALID_TO) {
13
+ throw new Error(`validTo must be an integer between 0 and ${MAX_VALID_TO}`);
14
+ }
15
+ const bytes = new Uint8Array(ENCODED_ORDER_INTENT_SIZE);
16
+ const view = new DataView(bytes.buffer);
17
+ let offset = 0;
18
+ const writePubkey = (pubkey) => {
19
+ bytes.set(pubkey.toBytes(), offset);
20
+ offset += 32;
21
+ };
22
+ const writeU64LE = (value) => {
23
+ view.setBigUint64(offset, value, true);
24
+ offset += 8;
25
+ };
26
+ writePubkey(intent.owner);
27
+ writePubkey(intent.buyTokenAccount);
28
+ writePubkey(intent.buyMint);
29
+ writePubkey(intent.sellTokenAccount);
30
+ writePubkey(intent.sellMint);
31
+ writeU64LE(intent.sellAmount);
32
+ writeU64LE(intent.buyAmount);
33
+ view.setUint32(offset, intent.validTo, true);
34
+ offset += 4;
35
+ let flags = 0;
36
+ if (intent.createdOnChain)
37
+ flags |= FLAG_CREATED_ON_CHAIN;
38
+ if (intent.kind === OrderKind.BUY)
39
+ flags |= FLAG_KIND_BUY;
40
+ if (intent.partiallyFillable)
41
+ flags |= FLAG_PARTIALLY_FILLABLE;
42
+ bytes[offset] = flags;
43
+ offset += 1;
44
+ bytes.set(intent.appData, offset);
45
+ return bytes;
46
+ }
47
+ async function hashOrderIntent(encoded) {
48
+ const digest = await crypto.subtle.digest("SHA-256", encoded);
49
+ return new Uint8Array(digest);
50
+ }
51
+ function toHex(bytes) {
52
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
53
+ }
54
+
55
+ // src/orderPda.ts
56
+ import { PublicKey } from "@solana/web3.js";
57
+ import { SOLANA_SETTLEMENT_PROGRAM_VERSION } from "@cowprotocol/sdk-config";
58
+ var SETTLEMENT_SEED_PREFIX = "settlement v";
59
+ var SETTLEMENT_SEED_VERSION_LEN = 7;
60
+ var SETTLEMENT_SEED = new TextEncoder().encode(
61
+ SETTLEMENT_SEED_PREFIX + SOLANA_SETTLEMENT_PROGRAM_VERSION.padEnd(SETTLEMENT_SEED_VERSION_LEN, " ")
62
+ );
63
+ var ORDER_SEED = new TextEncoder().encode("order");
64
+ function findOrderPda(programId, uid) {
65
+ return PublicKey.findProgramAddressSync([SETTLEMENT_SEED, uid, ORDER_SEED], programId);
66
+ }
67
+
68
+ // src/createOrderInstruction.ts
69
+ import { SystemProgram, TransactionInstruction } from "@solana/web3.js";
70
+ var CREATE_ORDER_DISCRIMINATOR = 2;
71
+ function buildCreateOrderInstruction(params) {
72
+ const intentBytes = encodeOrderIntent(params.intent);
73
+ const data = Buffer.alloc(1 + intentBytes.length);
74
+ data[0] = CREATE_ORDER_DISCRIMINATOR;
75
+ data.set(intentBytes, 1);
76
+ return new TransactionInstruction({
77
+ programId: params.programId,
78
+ keys: [
79
+ { pubkey: params.owner, isSigner: true, isWritable: false },
80
+ { pubkey: params.createdBy, isSigner: true, isWritable: true },
81
+ { pubkey: params.orderPda, isSigner: false, isWritable: true },
82
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
83
+ ],
84
+ data
85
+ });
86
+ }
87
+
88
+ // src/jupiterApi.ts
89
+ var JUPITER_ORDER_ENDPOINT = "https://ultra-api.jup.ag/order";
90
+ var DEFAULT_CLIENT_PLATFORM = "cowswap";
91
+ var QUOTE_TIMEOUT_MS = 1e4;
92
+ var UNSIGNED_INTEGER_PATTERN = /^\d+$/;
93
+ var JupiterAPI = class {
94
+ async getOrder(request) {
95
+ const params = new URLSearchParams({
96
+ inputMint: request.inputMint,
97
+ outputMint: request.outputMint,
98
+ amount: request.amount,
99
+ swapMode: request.swapMode,
100
+ clientPlatform: request.clientPlatform ?? DEFAULT_CLIENT_PLATFORM
101
+ });
102
+ const controller = new AbortController();
103
+ const timeoutId = setTimeout(() => controller.abort(), QUOTE_TIMEOUT_MS);
104
+ try {
105
+ const response = await fetch(`${JUPITER_ORDER_ENDPOINT}?${params.toString()}`, { signal: controller.signal });
106
+ let body;
107
+ try {
108
+ body = await response.json();
109
+ } catch {
110
+ throw new Error(`Jupiter quote request failed (${response.status})`);
111
+ }
112
+ if (!response.ok) {
113
+ const message = isJupiterErrorResponse(body) ? body.error : `Jupiter quote request failed (${response.status})`;
114
+ throw new Error(message);
115
+ }
116
+ if (!isJupiterOrderResponse(body, request)) {
117
+ throw new Error("Jupiter quote response is malformed or does not match the requested pair");
118
+ }
119
+ return body;
120
+ } finally {
121
+ clearTimeout(timeoutId);
122
+ }
123
+ }
124
+ };
125
+ function isJupiterErrorResponse(body) {
126
+ return typeof body === "object" && body !== null && typeof body.error === "string";
127
+ }
128
+ function isJupiterOrderResponse(body, request) {
129
+ if (typeof body !== "object" || body === null)
130
+ return false;
131
+ const candidate = body;
132
+ return candidate.inputMint === request.inputMint && candidate.outputMint === request.outputMint && candidate.swapMode === request.swapMode && typeof candidate.inAmount === "string" && UNSIGNED_INTEGER_PATTERN.test(candidate.inAmount) && typeof candidate.outAmount === "string" && UNSIGNED_INTEGER_PATTERN.test(candidate.outAmount) && typeof candidate.slippageBps === "number" && Number.isFinite(candidate.slippageBps);
133
+ }
134
+
135
+ // src/getSolanaQuote.ts
136
+ import { PublicKey as PublicKey3 } from "@solana/web3.js";
137
+ import { getAssociatedTokenAddressSync } from "@solana/spl-token";
138
+ import { SOLANA_SETTLEMENT_PROGRAM_ID, SOLANA_SETTLEMENT_PROGRAM_ID_STAGING } from "@cowprotocol/sdk-config";
139
+ import { getQuoteAmountsAndCosts, OrderKind as OrderKind2 } from "@cowprotocol/sdk-order-book";
140
+ var DEFAULT_VALID_FOR_SECONDS = 30 * 60;
141
+ var ZERO_APP_DATA = new Uint8Array(32);
142
+ var jupiterApi = new JupiterAPI();
143
+ async function getSolanaQuote(params, options = {}) {
144
+ const {
145
+ ownerAddress,
146
+ receiverAddress,
147
+ sellTokenDecimals,
148
+ buyTokenDecimals,
149
+ amount,
150
+ kind,
151
+ partiallyFillable = false,
152
+ validForSeconds = DEFAULT_VALID_FOR_SECONDS,
153
+ sellTokenProgramId,
154
+ buyTokenProgramId
155
+ } = params;
156
+ if (!Number.isFinite(validForSeconds) || validForSeconds <= 0) {
157
+ throw new Error("validForSeconds must be a finite number greater than zero");
158
+ }
159
+ const owner = new PublicKey3(ownerAddress);
160
+ const receiver = new PublicKey3(receiverAddress);
161
+ const sellMint = new PublicKey3(params.sellTokenAddress);
162
+ const buyMint = new PublicKey3(params.buyTokenAddress);
163
+ const sellTokenProgram = sellTokenProgramId ? new PublicKey3(sellTokenProgramId) : void 0;
164
+ const buyTokenProgram = buyTokenProgramId ? new PublicKey3(buyTokenProgramId) : void 0;
165
+ const sellTokenAddress = sellMint.toBase58();
166
+ const buyTokenAddress = buyMint.toBase58();
167
+ const jupiterOrder = await jupiterApi.getOrder({
168
+ inputMint: sellTokenAddress,
169
+ outputMint: buyTokenAddress,
170
+ amount: amount.toString(),
171
+ swapMode: kind === OrderKind2.SELL ? "ExactIn" : "ExactOut"
172
+ });
173
+ const validTo = Math.floor(Date.now() / 1e3) + validForSeconds;
174
+ const orderParams = {
175
+ sellToken: sellTokenAddress,
176
+ buyToken: buyTokenAddress,
177
+ receiver: receiver.toBase58(),
178
+ sellAmount: jupiterOrder.inAmount,
179
+ buyAmount: jupiterOrder.outAmount,
180
+ validTo,
181
+ // TODO: fill appData when we know the format
182
+ appData: "{}",
183
+ // TODO: implement fees
184
+ feeAmount: "0",
185
+ gasAmount: "0",
186
+ gasPrice: "0",
187
+ sellTokenPrice: "0",
188
+ kind,
189
+ partiallyFillable
190
+ };
191
+ const amountsAndCosts = getQuoteAmountsAndCosts({
192
+ orderParams,
193
+ slippagePercentBps: jupiterOrder.slippageBps,
194
+ // TODO: implement fees
195
+ partnerFeeBps: 0,
196
+ protocolFeeBps: 0
197
+ });
198
+ const intent = {
199
+ owner,
200
+ buyTokenAccount: getAssociatedTokenAddressSync(buyMint, receiver, false, buyTokenProgram),
201
+ buyMint,
202
+ sellTokenAccount: getAssociatedTokenAddressSync(sellMint, owner, false, sellTokenProgram),
203
+ sellMint,
204
+ sellAmount: amountsAndCosts.amountsToSign.sellAmount,
205
+ buyAmount: amountsAndCosts.amountsToSign.buyAmount,
206
+ validTo,
207
+ kind,
208
+ partiallyFillable,
209
+ createdOnChain: true,
210
+ appData: ZERO_APP_DATA
211
+ };
212
+ const intentBytes = encodeOrderIntent(intent);
213
+ const uid = await hashOrderIntent(intentBytes);
214
+ const programId = new PublicKey3(
215
+ options.env === "staging" ? SOLANA_SETTLEMENT_PROGRAM_ID_STAGING : SOLANA_SETTLEMENT_PROGRAM_ID
216
+ );
217
+ const [orderPda] = findOrderPda(programId, uid);
218
+ const solanaQuote = {
219
+ intent,
220
+ intentBytes,
221
+ uid,
222
+ orderPda,
223
+ programId,
224
+ jupiterOrder,
225
+ buyTokenProgramId: buyTokenProgram
226
+ };
227
+ const quoteResponse = {
228
+ quote: orderParams,
229
+ from: owner.toBase58(),
230
+ expiration: new Date(intent.validTo * 1e3).toISOString(),
231
+ verified: false
232
+ };
233
+ const tradeParameters = {
234
+ kind,
235
+ owner: owner.toBase58(),
236
+ sellToken: sellTokenAddress,
237
+ sellTokenDecimals,
238
+ buyToken: buyTokenAddress,
239
+ buyTokenDecimals,
240
+ amount: amount.toString(),
241
+ receiver: receiver.toBase58(),
242
+ validFor: validForSeconds,
243
+ partiallyFillable: orderParams.partiallyFillable
244
+ };
245
+ const quoteResults = {
246
+ quoteResponse,
247
+ amountsAndCosts,
248
+ suggestedSlippageBps: jupiterOrder.slippageBps,
249
+ tradeParameters,
250
+ orderToSign: {},
251
+ appDataInfo: {},
252
+ orderTypedData: {}
253
+ };
254
+ return { quoteResults, solanaQuote };
255
+ }
256
+
257
+ // src/postSwapOrderFromQuote.ts
258
+ import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token";
259
+ import { SigningScheme } from "@cowprotocol/sdk-order-book";
260
+ import { PublicKey as PublicKey4 } from "@solana/web3.js";
261
+ async function postSolanaSwapOrderFromQuote({ quoteResults, solanaQuote }, signAndSend, advancedSettings, signingStepManager) {
262
+ const intent = { ...solanaQuote.intent };
263
+ let uid = solanaQuote.uid;
264
+ let orderPda = solanaQuote.orderPda;
265
+ if (advancedSettings?.quoteRequest) {
266
+ const { validTo, receiver } = advancedSettings.quoteRequest;
267
+ if (receiver) {
268
+ intent.buyTokenAccount = getAssociatedTokenAddressSync2(
269
+ intent.buyMint,
270
+ new PublicKey4(receiver),
271
+ false,
272
+ solanaQuote.buyTokenProgramId
273
+ );
274
+ }
275
+ if (validTo)
276
+ intent.validTo = validTo;
277
+ if (receiver || validTo) {
278
+ const intentBytes = encodeOrderIntent(intent);
279
+ uid = await hashOrderIntent(intentBytes);
280
+ [orderPda] = findOrderPda(solanaQuote.programId, uid);
281
+ }
282
+ }
283
+ const instruction = buildCreateOrderInstruction({
284
+ programId: solanaQuote.programId,
285
+ owner: intent.owner,
286
+ createdBy: intent.owner,
287
+ orderPda,
288
+ intent
289
+ });
290
+ await signingStepManager?.beforeOrderSign?.();
291
+ const { signature } = await signAndSend(instruction);
292
+ await signingStepManager?.afterOrderSign?.();
293
+ const orderToSign = quoteResults.orderToSign;
294
+ return {
295
+ orderId: toHex(uid),
296
+ txHash: signature,
297
+ signature,
298
+ signingScheme: SigningScheme.PRESIGN,
299
+ orderToSign
300
+ };
301
+ }
302
+
303
+ // src/solanaTradingSdk.ts
304
+ var SolanaTradingSdk = class {
305
+ constructor(options) {
306
+ this.options = options;
307
+ }
308
+ async getQuote(params) {
309
+ const quote = await getSolanaQuote(params, { env: this.options.env });
310
+ return {
311
+ quoteResults: quote.quoteResults,
312
+ postSwapOrderFromQuote: (advancedSettings, signingStepManager) => postSolanaSwapOrderFromQuote(quote, this.options.signAndSend, advancedSettings, signingStepManager)
313
+ };
314
+ }
315
+ };
316
+ export {
317
+ ENCODED_ORDER_INTENT_SIZE,
318
+ JupiterAPI,
319
+ ORDER_SEED,
320
+ SETTLEMENT_SEED,
321
+ SolanaTradingSdk,
322
+ buildCreateOrderInstruction,
323
+ encodeOrderIntent,
324
+ findOrderPda,
325
+ getSolanaQuote,
326
+ hashOrderIntent,
327
+ postSolanaSwapOrderFromQuote,
328
+ toHex
329
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@cowprotocol/sdk-trading-solana",
3
+ "version": "0.1.0",
4
+ "description": "CowProtocol Solana trading: Jupiter-sourced quotes and on-chain CreateOrder posting against the Solana settlement program",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/cowprotocol/cow-sdk.git",
8
+ "directory": "packages/sdk-trading-solana"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "require": "./dist/index.js",
14
+ "import": "./dist/index.mjs",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.mjs",
20
+ "types": "./dist/index.d.ts",
21
+ "sideEffects": false,
22
+ "license": "MIT",
23
+ "files": [
24
+ "dist/**"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "devDependencies": {
30
+ "@types/jest": "^29.5.12",
31
+ "@types/node": "^20.17.31",
32
+ "tsup": "^7.2.0",
33
+ "typescript": "^5.2.2",
34
+ "jest": "^29.7.0",
35
+ "jest-fetch-mock": "^3.0.3",
36
+ "ts-jest": "^29.0.0",
37
+ "@cow-sdk/typescript-config": "0.0.0-beta.0"
38
+ },
39
+ "dependencies": {
40
+ "@solana/spl-token": "0.4.14",
41
+ "@solana/web3.js": "1.98.4",
42
+ "@cowprotocol/sdk-config": "2.4.0",
43
+ "@cowprotocol/sdk-order-book": "4.0.2",
44
+ "@cowprotocol/sdk-trading": "2.3.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup src/index.ts --format esm,cjs --dts",
48
+ "lint": "eslint src/**/*.ts",
49
+ "test": "jest",
50
+ "test:watch": "jest --watch",
51
+ "test:coverage": "jest --coverage",
52
+ "coverage:badges": "istanbul-badges-readme --exitCode=1",
53
+ "test:coverage:html": "jest --silent=false --coverage --coverageReporters html",
54
+ "typecheck": "tsc --noEmit",
55
+ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
56
+ }
57
+ }