@cowprotocol/sdk-trading-solana 0.7.0 → 0.8.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 +202 -21
- package/dist/index.d.mts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.js +67 -19
- package/dist/index.mjs +57 -19
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -10,51 +10,232 @@
|
|
|
10
10
|
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
11
11
|
|  |  |  |  |
|
|
12
12
|
|
|
13
|
-
CoW Protocol's Solana settlement
|
|
14
|
-
|
|
13
|
+
`@cowprotocol/sdk-trading-solana` is CoW Protocol's Solana settlement client: it turns a quote
|
|
14
|
+
into an on-chain `CreateOrder` instruction against the CoW Protocol Solana
|
|
15
|
+
settlement program.
|
|
15
16
|
|
|
16
|
-
**Experimental.** The
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
**Experimental.** The settlement program (`cow-settlement-interface` / `solana-programs`) is a young,
|
|
18
|
+
actively evolving deployment — expect breaking changes to the intent wire format, PDA seeds and this
|
|
19
|
+
package's API across minor versions. The currently deployed program id is exported as
|
|
20
|
+
[`SOLANA_SETTLEMENT_PROGRAM_ID`](https://github.com/cowprotocol/cow-sdk/blob/main/packages/config/src/chains/const/contracts.ts)
|
|
21
|
+
from `@cowprotocol/sdk-config` — see it on
|
|
22
|
+
[Solana Explorer](https://explorer.solana.com/address/FYp8R5K4B3B1Kfr7QuWzMz4TwoT7wptjYtxgCrY5sRXb).
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
```
|
|
25
|
+
npm install @cowprotocol/sdk-trading-solana
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A runnable end-to-end example lives in [`examples/nodejs/solana`](../../examples/nodejs/solana).
|
|
29
|
+
|
|
30
|
+
## Making a trade
|
|
31
|
+
|
|
32
|
+
### Step 0 — approve the settlement program as SPL delegate
|
|
33
|
+
|
|
34
|
+
The settlement program moves funds out of the seller's token account via a CPI, which SPL Token
|
|
35
|
+
only allows for an approved `delegate`. Before any order on a given sell-token account can settle,
|
|
36
|
+
that account's owner must approve the settlement **state PDA** (not the program id itself) as
|
|
37
|
+
delegate, for at least the amount being sold. `approveCowProtocol` builds that instruction without
|
|
38
|
+
sending it, deriving the sell-token account and delegate for you:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const sdk = new SolanaTradingSdk() // same env as the SDK instance used for the quote below
|
|
42
|
+
|
|
43
|
+
const approveInstruction = sdk.approveCowProtocol({
|
|
44
|
+
ownerAddress: owner,
|
|
45
|
+
sellTokenAddress: sellMint,
|
|
46
|
+
approveAmount, // at least the order's sellAmount
|
|
47
|
+
})
|
|
48
|
+
```
|
|
21
49
|
|
|
22
|
-
|
|
50
|
+
This approval is **not one-time**: like any SPL delegate, each settled order decrements the
|
|
51
|
+
delegate's remaining allowance by the amount actually transferred, so approving once for exactly one
|
|
52
|
+
order's `sellAmount` leaves nothing for the next one. Check the sell-token account's current
|
|
53
|
+
`delegate`/`delegatedAmount` before building an order, and reapprove (topping the allowance back up)
|
|
54
|
+
whenever it's insufficient — approving a new amount replaces the previous allowance rather than
|
|
55
|
+
adding to it. Separately, because the delegate PDA is derived from the settlement program's
|
|
56
|
+
major/minor version (`getSettlementSeed`), a program upgrade that bumps that version changes the
|
|
57
|
+
delegate address entirely, requiring a fresh approval regardless of remaining allowance. This step
|
|
58
|
+
can be sent as its own transaction ahead of time, or bundled with the `CreateOrder` instruction in
|
|
59
|
+
the same transaction — see [Step 2](#step-2--create-the-order) below.
|
|
60
|
+
|
|
61
|
+
### Step 1 — get a quote
|
|
62
|
+
|
|
63
|
+
Quoting needs no signer, just the addresses and amount:
|
|
23
64
|
|
|
24
65
|
```ts
|
|
66
|
+
import { OrderKind } from '@cowprotocol/sdk-order-book'
|
|
25
67
|
import { SolanaTradingSdk } from '@cowprotocol/sdk-trading-solana'
|
|
26
68
|
|
|
27
|
-
const sdk = new SolanaTradingSdk()
|
|
69
|
+
const sdk = new SolanaTradingSdk() // pass `{ env: 'staging' }` to target staging
|
|
70
|
+
|
|
28
71
|
const { quoteResults, solanaQuote, buildOrder, postSwapOrderFromQuote } = await sdk.getQuote({
|
|
29
|
-
ownerAddress,
|
|
30
|
-
receiverAddress,
|
|
72
|
+
ownerAddress, // the seller's wallet — signs the CreateOrder transaction
|
|
31
73
|
sellTokenAddress,
|
|
32
74
|
sellTokenDecimals,
|
|
33
75
|
buyTokenAddress,
|
|
34
76
|
buyTokenDecimals,
|
|
35
|
-
amount,
|
|
36
|
-
kind,
|
|
77
|
+
amount, // bigint — sell-side amount for a SELL order, buy-side amount for a BUY order
|
|
78
|
+
kind: OrderKind.SELL,
|
|
37
79
|
})
|
|
38
80
|
```
|
|
39
81
|
|
|
40
|
-
|
|
82
|
+
- `quoteResults` mirrors the EVM SDK's [`QuoteResults`](../trading/src/types.ts) (trade parameters, suggested slippage, amounts
|
|
83
|
+
and costs) so UI code can share formatting logic across chains.
|
|
84
|
+
- `solanaQuote` is the Solana-specific quote: the order **intent** that was built from the quote
|
|
85
|
+
response, its encoded bytes, its `uid`, the order PDA it will live at, and the raw upstream quote
|
|
86
|
+
(real amounts/slippage) the intent was derived from.
|
|
87
|
+
- Native SOL is accepted as the sentinel address; it's substituted with wrapped SOL (WSOL) before
|
|
88
|
+
quoting and building the intent, since Solana has no native-token mint to reference on-chain.
|
|
89
|
+
- Passing `sellTokenProgramId` / `buyTokenProgramId` (e.g. `TOKEN_2022_PROGRAM_ID`) is required for
|
|
90
|
+
Token-2022 mints, since the associated token account address differs by program.
|
|
41
91
|
|
|
42
|
-
|
|
92
|
+
### Step 2 — create the order
|
|
93
|
+
|
|
94
|
+
Two ways to get the order on-chain, depending on whether you need to bundle it with other
|
|
95
|
+
instructions:
|
|
96
|
+
|
|
97
|
+
**Let the SDK submit it as its own transaction** — pass a signing/sending function at the point of
|
|
98
|
+
signing:
|
|
43
99
|
|
|
44
100
|
```ts
|
|
45
101
|
const result = await postSwapOrderFromQuote(signAndSend)
|
|
102
|
+
// result.orderId, result.txHash / result.signature
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`signAndSend` has the shape `(instruction: TransactionInstruction) => Promise<{ signature: string }>`
|
|
106
|
+
— build, sign and send the transaction however fits your app (a `Connection` + `Keypair`, a wallet
|
|
107
|
+
adapter, a Squads/Safe-style multisig flow, etc).
|
|
108
|
+
|
|
109
|
+
**Bundle the order with your own instructions** — a Solana order is created by a single instruction,
|
|
110
|
+
so it can share one transaction with, say, the Step 0 delegate approval, a native-SOL wrap, or
|
|
111
|
+
anything else. `buildOrder` returns that instruction without sending it:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const { instruction, orderId, orderPda, uid } = await buildOrder()
|
|
115
|
+
|
|
116
|
+
await sendMyTransaction([approveInstruction, instruction])
|
|
46
117
|
```
|
|
47
118
|
|
|
48
|
-
|
|
119
|
+
This is the recommended path whenever the sell-token account's delegate allowance needs
|
|
120
|
+
topping up for this trade, since it lets the delegate approval and the order creation land
|
|
121
|
+
atomically in one transaction.
|
|
49
122
|
|
|
50
|
-
|
|
51
|
-
|
|
123
|
+
Both paths accept the same optional `advancedSettings` (currently `quoteRequest.receiver` and
|
|
124
|
+
`quoteRequest.validTo`) and apply them identically — overriding `receiver` or `validTo` re-derives
|
|
125
|
+
the order's `uid` and PDA so they still match the intent actually being created:
|
|
52
126
|
|
|
53
127
|
```ts
|
|
54
|
-
|
|
128
|
+
await postSwapOrderFromQuote(signAndSend, { quoteRequest: { receiver: otherWallet.toBase58() } })
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Step 3 — read back the result
|
|
132
|
+
|
|
133
|
+
Both `postSwapOrderFromQuote` and `buildOrder` return an `orderId` — the `uid` as the `0x`-prefixed
|
|
134
|
+
hex string the CoW order-book API uses for EVM orders too, so it can be handled uniformly by shared
|
|
135
|
+
UI/state code. `solanaQuote.orderPda` is the on-chain account the order lives at once the
|
|
136
|
+
`CreateOrder` instruction lands.
|
|
55
137
|
|
|
56
|
-
|
|
138
|
+
## The intent model
|
|
139
|
+
|
|
140
|
+
Every Solana order is, at its core, a `SolanaOrderIntent` — a fixed-layout struct that is the direct
|
|
141
|
+
TypeScript counterpart of `OrderIntent` in `cow-settlement-interface` (`interface/src/data/intent.rs`).
|
|
142
|
+
Unlike an EVM order (an EIP-712 message the owner signs off-chain and the order-book stores), a
|
|
143
|
+
Solana intent is never signed as data — it's written directly into an on-chain account by an
|
|
144
|
+
instruction the owner's wallet signs.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
interface SolanaOrderIntent {
|
|
148
|
+
owner: PublicKey // signs the CreateOrder transaction
|
|
149
|
+
buyTokenAccount: PublicKey // receives the buy-side proceeds — implicitly encodes the receiver
|
|
150
|
+
buyMint: PublicKey
|
|
151
|
+
sellTokenAccount: PublicKey // funds pulled from here — must be owned by `owner`; implicitly encodes the spender
|
|
152
|
+
sellMint: PublicKey
|
|
153
|
+
sellAmount: bigint
|
|
154
|
+
buyAmount: bigint
|
|
155
|
+
validTo: number // unix timestamp, seconds
|
|
156
|
+
kind: OrderKind // SELL | BUY
|
|
157
|
+
partiallyFillable: boolean
|
|
158
|
+
createdOnChain: boolean // must be true — see "Authentication" below
|
|
159
|
+
appData: Uint8Array // exactly 32 bytes, opaque to the settlement program; no convention defined yet, sent as zeroes
|
|
160
|
+
}
|
|
57
161
|
```
|
|
58
162
|
|
|
59
|
-
|
|
60
|
-
|
|
163
|
+
### Encoding
|
|
164
|
+
|
|
165
|
+
`encodeOrderIntent` packs the intent into the exact 213-byte (`ENCODED_ORDER_INTENT_SIZE`) little-endian
|
|
166
|
+
layout the settlement program reads (`EncodedOrderIntent::from(&OrderIntent)`):
|
|
167
|
+
|
|
168
|
+
| Bytes | Field |
|
|
169
|
+
| ----- | --------------------------------- |
|
|
170
|
+
| 32 | `owner` |
|
|
171
|
+
| 32 | `buyTokenAccount` |
|
|
172
|
+
| 32 | `buyMint` |
|
|
173
|
+
| 32 | `sellTokenAccount` |
|
|
174
|
+
| 32 | `sellMint` |
|
|
175
|
+
| 8 | `sellAmount` (u64 LE) |
|
|
176
|
+
| 8 | `buyAmount` (u64 LE) |
|
|
177
|
+
| 4 | `validTo` (u32 LE) |
|
|
178
|
+
| 1 | flags (bit 0 `createdOnChain`, bit 1 `kind === BUY`, bit 2 `partiallyFillable`) |
|
|
179
|
+
| 32 | `appData` |
|
|
180
|
+
|
|
181
|
+
### Identity: uid and the order PDA
|
|
182
|
+
|
|
183
|
+
`hashOrderIntent` takes the SHA-256 digest of the encoded intent bytes. That 32-byte digest is both:
|
|
184
|
+
|
|
185
|
+
- the order's **uid** (`toOrderId` formats it as the `0x`-prefixed id used across the SDK), and
|
|
186
|
+
- the middle seed of the order's **program-derived address** (`findOrderPda`), alongside a
|
|
187
|
+
version-embedded settlement seed (`getSettlementSeed`, `"settlement v" + version`, padded to a
|
|
188
|
+
fixed width so seeds from different versions can't collide) and a trailing `"order"` seed.
|
|
189
|
+
|
|
190
|
+
Because the uid is a hash of the intent's content, any change to the intent — including an
|
|
191
|
+
`advancedSettings` override to `receiver` or `validTo` — changes the uid and therefore the PDA the
|
|
192
|
+
order will be created at. `buildSolanaSwapOrder` re-derives both whenever `advancedSettings`
|
|
193
|
+
actually changes the intent, so the instruction it returns always targets the PDA matching the
|
|
194
|
+
intent it encodes.
|
|
195
|
+
|
|
196
|
+
### The `CreateOrder` instruction
|
|
197
|
+
|
|
198
|
+
`buildCreateOrderInstruction` builds the instruction that writes the intent on-chain, matching
|
|
199
|
+
`CreateOrder::into::<Instruction>()`:
|
|
200
|
+
|
|
201
|
+
- **data**: `[discriminator = 2, ...213 intent bytes]`
|
|
202
|
+
- **accounts**: `owner` (readonly signer), `createdBy` (writable signer — funds the new order PDA's
|
|
203
|
+
rent; may equal `owner`), `orderPda` (writable), the System Program
|
|
204
|
+
|
|
205
|
+
### Authentication: why `createdOnChain` must be `true`
|
|
206
|
+
|
|
207
|
+
`cow-settlement-interface` supports two ways an intent can be authenticated: created on-chain (the
|
|
208
|
+
owner signs the `CreateOrder` transaction themselves) or via an off-chain Ed25519-presigned order
|
|
209
|
+
that anyone can submit on the owner's behalf. This SDK only implements the former —
|
|
210
|
+
`encodeOrderIntent` always sets the on-chain flag, and the settlement program authenticates the
|
|
211
|
+
order against the transaction's own signature rather than a separate signed payload. This is the
|
|
212
|
+
Solana counterpart of an EVM `PRESIGN` order (`buildSolanaSwapOrder` reports
|
|
213
|
+
`signingScheme: SigningScheme.PRESIGN` for exactly this reason) rather than an EIP-712 `EIP712` one.
|
|
214
|
+
|
|
215
|
+
## Example app
|
|
216
|
+
|
|
217
|
+
[`examples/nodejs/solana`](../../examples/nodejs/solana) is a runnable, end-to-end demo: it gets a
|
|
218
|
+
quote, approves the settlement program's delegate, builds the `CreateOrder` instruction, bundles
|
|
219
|
+
both into one transaction and submits it. **It targets mainnet-beta by default and trades real
|
|
220
|
+
funds** — see that example's own README for setup, including how to point it at devnet instead.
|
|
221
|
+
|
|
222
|
+
## Why Solana trading looks different from EVM trading
|
|
223
|
+
|
|
224
|
+
On EVM chains, `TradingSdk` gets a quote from the CoW order-book API, has the caller sign an
|
|
225
|
+
EIP-712 typed order, and POSTs the signed order to the order-book, which later settles it. Solana
|
|
226
|
+
has neither an order-book API to post to nor an implicit global signer:
|
|
227
|
+
|
|
228
|
+
- **Quotes don't come from the CoW order-book (yet).** `SolanaTradingSdk.getQuote` currently sources
|
|
229
|
+
pricing and route discovery from an external quoting API, then builds a CoW Protocol order
|
|
230
|
+
**intent** from the result. That API is used for quoting only — orders are never submitted through
|
|
231
|
+
it, only ever created via the CoW settlement program. This is expected to move onto the CoW
|
|
232
|
+
order-book API directly as Solana support matures.
|
|
233
|
+
- **Orders are created entirely on-chain.** There is no signed order body to POST. Creating an order
|
|
234
|
+
means submitting a single `CreateOrder` instruction that writes the order intent into a new
|
|
235
|
+
program-derived account (the order PDA). The transaction signature that lands on-chain *is* the
|
|
236
|
+
order's authentication.
|
|
237
|
+
- **No bound signer.** The EVM SDK gets its signer from a global adapter set once at app startup.
|
|
238
|
+
This package has no such adapter: quoting needs no signer at all, and a signing/sending function
|
|
239
|
+
(`SolanaSignAndSend`) is passed only at the point of actually submitting a transaction. That also
|
|
240
|
+
means a quote can be turned into a raw `TransactionInstruction` (via `buildOrder`) without ever
|
|
241
|
+
involving a signer, so it can be composed into a larger transaction built and sent by the caller.
|
package/dist/index.d.mts
CHANGED
|
@@ -29,16 +29,16 @@ declare class JupiterAPI {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* TS port of `cow-settlement-interface`'s `OrderIntent` (interface/src/data/intent.rs, v0.
|
|
32
|
+
* TS port of `cow-settlement-interface`'s `OrderIntent` (interface/src/data/intent.rs, v0.4.0).
|
|
33
33
|
* Every field here has a Rust counterpart with the same name; keep them in sync if the settlement
|
|
34
34
|
* program's wire format changes.
|
|
35
35
|
*/
|
|
36
36
|
interface SolanaOrderIntent {
|
|
37
37
|
owner: PublicKey;
|
|
38
|
-
buyTokenAccount: PublicKey;
|
|
39
|
-
buyMint: PublicKey;
|
|
40
38
|
sellTokenAccount: PublicKey;
|
|
41
39
|
sellMint: PublicKey;
|
|
40
|
+
buyTokenAccount: PublicKey;
|
|
41
|
+
buyMint: PublicKey;
|
|
42
42
|
sellAmount: bigint;
|
|
43
43
|
buyAmount: bigint;
|
|
44
44
|
/** Unix timestamp seconds. */
|
|
@@ -73,7 +73,6 @@ declare function toOrderId(uid: Uint8Array): string;
|
|
|
73
73
|
|
|
74
74
|
interface SolanaQuoteParameters {
|
|
75
75
|
ownerAddress: PublicKeyInitData;
|
|
76
|
-
receiverAddress: PublicKeyInitData;
|
|
77
76
|
sellTokenAddress: PublicKeyInitData;
|
|
78
77
|
sellTokenDecimals: number;
|
|
79
78
|
buyTokenAddress: PublicKeyInitData;
|
|
@@ -81,6 +80,7 @@ interface SolanaQuoteParameters {
|
|
|
81
80
|
/** Sell-side amount for a SELL order, buy-side amount for a BUY order — same convention as Jupiter's `amount`. */
|
|
82
81
|
amount: bigint;
|
|
83
82
|
kind: OrderKind;
|
|
83
|
+
receiverAddress?: PublicKeyInitData;
|
|
84
84
|
partiallyFillable?: boolean;
|
|
85
85
|
/** Order lifetime from now, in seconds. Defaults to 30 minutes. */
|
|
86
86
|
validForSeconds?: number;
|
|
@@ -217,6 +217,15 @@ declare function postSolanaSwapOrderFromQuote(quote: SolanaSwapOrderQuote, signA
|
|
|
217
217
|
interface SolanaTradingSdkOptions {
|
|
218
218
|
env?: CowEnv;
|
|
219
219
|
}
|
|
220
|
+
interface ApproveCowProtocolParams {
|
|
221
|
+
ownerAddress: PublicKeyInitData;
|
|
222
|
+
sellTokenAddress: PublicKeyInitData;
|
|
223
|
+
/** Amount to approve, at least the order's `sellAmount`. */
|
|
224
|
+
approveAmount: bigint;
|
|
225
|
+
/** Token program owning the sell mint's accounts (classic SPL Token vs Token-2022). Defaults to the
|
|
226
|
+
* classic SPL Token program — pass `TOKEN_2022_PROGRAM_ID` explicitly for Token-2022 mints. */
|
|
227
|
+
sellTokenProgramId?: PublicKeyInitData;
|
|
228
|
+
}
|
|
220
229
|
/**
|
|
221
230
|
* Solana counterpart to `QuoteAndPost`. It additionally exposes `solanaQuote` and `buildOrder`, because a
|
|
222
231
|
* Solana order is a plain instruction: callers may want to bundle it with a wrap or a token delegation and
|
|
@@ -239,7 +248,14 @@ interface SolanaQuoteAndPost {
|
|
|
239
248
|
declare class SolanaTradingSdk {
|
|
240
249
|
private readonly options;
|
|
241
250
|
constructor(options?: SolanaTradingSdkOptions);
|
|
251
|
+
/**
|
|
252
|
+
* Builds the SPL `approve` instruction that delegates `sellAmount` of `sellTokenAddress` to the
|
|
253
|
+
* settlement program, without sending it — see "Step 0" in the README for why this is required
|
|
254
|
+
* before an order on a given sell-token account can settle. Bundle it with `buildOrder`'s
|
|
255
|
+
* instruction in the same transaction the first time a wallet trades a given token.
|
|
256
|
+
*/
|
|
257
|
+
approveCowProtocol(params: ApproveCowProtocolParams): TransactionInstruction;
|
|
242
258
|
getQuote(params: SolanaQuoteParameters): Promise<SolanaQuoteAndPost>;
|
|
243
259
|
}
|
|
244
260
|
|
|
245
|
-
export { type CreateOrderInstructionParams, ENCODED_ORDER_INTENT_SIZE, JupiterAPI, type JupiterOrderRequest, type JupiterOrderResponse, ORDER_SEED, type SolanaOrderIntent, type SolanaQuote, type SolanaQuoteAndPost, type SolanaQuoteParameters, type SolanaSignAndSend, type SolanaSwapOrder, type SolanaSwapOrderQuote, SolanaTradingSdk, type SolanaTradingSdkOptions, buildCreateOrderInstruction, buildSolanaSwapOrder, encodeOrderIntent, findOrderPda, findSettlementStatePda, getSettlementSeed, getSolanaDelegateAuthority, getSolanaQuote, getSolanaSettlementProgramId, hashOrderIntent, postSolanaSwapOrderFromQuote, toHex, toOrderId };
|
|
261
|
+
export { type ApproveCowProtocolParams, type CreateOrderInstructionParams, ENCODED_ORDER_INTENT_SIZE, JupiterAPI, type JupiterOrderRequest, type JupiterOrderResponse, ORDER_SEED, type SolanaOrderIntent, type SolanaQuote, type SolanaQuoteAndPost, type SolanaQuoteParameters, type SolanaSignAndSend, type SolanaSwapOrder, type SolanaSwapOrderQuote, SolanaTradingSdk, type SolanaTradingSdkOptions, buildCreateOrderInstruction, buildSolanaSwapOrder, encodeOrderIntent, findOrderPda, findSettlementStatePda, getSettlementSeed, getSolanaDelegateAuthority, getSolanaQuote, getSolanaSettlementProgramId, hashOrderIntent, postSolanaSwapOrderFromQuote, toHex, toOrderId };
|
package/dist/index.d.ts
CHANGED
|
@@ -29,16 +29,16 @@ declare class JupiterAPI {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* TS port of `cow-settlement-interface`'s `OrderIntent` (interface/src/data/intent.rs, v0.
|
|
32
|
+
* TS port of `cow-settlement-interface`'s `OrderIntent` (interface/src/data/intent.rs, v0.4.0).
|
|
33
33
|
* Every field here has a Rust counterpart with the same name; keep them in sync if the settlement
|
|
34
34
|
* program's wire format changes.
|
|
35
35
|
*/
|
|
36
36
|
interface SolanaOrderIntent {
|
|
37
37
|
owner: PublicKey;
|
|
38
|
-
buyTokenAccount: PublicKey;
|
|
39
|
-
buyMint: PublicKey;
|
|
40
38
|
sellTokenAccount: PublicKey;
|
|
41
39
|
sellMint: PublicKey;
|
|
40
|
+
buyTokenAccount: PublicKey;
|
|
41
|
+
buyMint: PublicKey;
|
|
42
42
|
sellAmount: bigint;
|
|
43
43
|
buyAmount: bigint;
|
|
44
44
|
/** Unix timestamp seconds. */
|
|
@@ -73,7 +73,6 @@ declare function toOrderId(uid: Uint8Array): string;
|
|
|
73
73
|
|
|
74
74
|
interface SolanaQuoteParameters {
|
|
75
75
|
ownerAddress: PublicKeyInitData;
|
|
76
|
-
receiverAddress: PublicKeyInitData;
|
|
77
76
|
sellTokenAddress: PublicKeyInitData;
|
|
78
77
|
sellTokenDecimals: number;
|
|
79
78
|
buyTokenAddress: PublicKeyInitData;
|
|
@@ -81,6 +80,7 @@ interface SolanaQuoteParameters {
|
|
|
81
80
|
/** Sell-side amount for a SELL order, buy-side amount for a BUY order — same convention as Jupiter's `amount`. */
|
|
82
81
|
amount: bigint;
|
|
83
82
|
kind: OrderKind;
|
|
83
|
+
receiverAddress?: PublicKeyInitData;
|
|
84
84
|
partiallyFillable?: boolean;
|
|
85
85
|
/** Order lifetime from now, in seconds. Defaults to 30 minutes. */
|
|
86
86
|
validForSeconds?: number;
|
|
@@ -217,6 +217,15 @@ declare function postSolanaSwapOrderFromQuote(quote: SolanaSwapOrderQuote, signA
|
|
|
217
217
|
interface SolanaTradingSdkOptions {
|
|
218
218
|
env?: CowEnv;
|
|
219
219
|
}
|
|
220
|
+
interface ApproveCowProtocolParams {
|
|
221
|
+
ownerAddress: PublicKeyInitData;
|
|
222
|
+
sellTokenAddress: PublicKeyInitData;
|
|
223
|
+
/** Amount to approve, at least the order's `sellAmount`. */
|
|
224
|
+
approveAmount: bigint;
|
|
225
|
+
/** Token program owning the sell mint's accounts (classic SPL Token vs Token-2022). Defaults to the
|
|
226
|
+
* classic SPL Token program — pass `TOKEN_2022_PROGRAM_ID` explicitly for Token-2022 mints. */
|
|
227
|
+
sellTokenProgramId?: PublicKeyInitData;
|
|
228
|
+
}
|
|
220
229
|
/**
|
|
221
230
|
* Solana counterpart to `QuoteAndPost`. It additionally exposes `solanaQuote` and `buildOrder`, because a
|
|
222
231
|
* Solana order is a plain instruction: callers may want to bundle it with a wrap or a token delegation and
|
|
@@ -239,7 +248,14 @@ interface SolanaQuoteAndPost {
|
|
|
239
248
|
declare class SolanaTradingSdk {
|
|
240
249
|
private readonly options;
|
|
241
250
|
constructor(options?: SolanaTradingSdkOptions);
|
|
251
|
+
/**
|
|
252
|
+
* Builds the SPL `approve` instruction that delegates `sellAmount` of `sellTokenAddress` to the
|
|
253
|
+
* settlement program, without sending it — see "Step 0" in the README for why this is required
|
|
254
|
+
* before an order on a given sell-token account can settle. Bundle it with `buildOrder`'s
|
|
255
|
+
* instruction in the same transaction the first time a wallet trades a given token.
|
|
256
|
+
*/
|
|
257
|
+
approveCowProtocol(params: ApproveCowProtocolParams): TransactionInstruction;
|
|
242
258
|
getQuote(params: SolanaQuoteParameters): Promise<SolanaQuoteAndPost>;
|
|
243
259
|
}
|
|
244
260
|
|
|
245
|
-
export { type CreateOrderInstructionParams, ENCODED_ORDER_INTENT_SIZE, JupiterAPI, type JupiterOrderRequest, type JupiterOrderResponse, ORDER_SEED, type SolanaOrderIntent, type SolanaQuote, type SolanaQuoteAndPost, type SolanaQuoteParameters, type SolanaSignAndSend, type SolanaSwapOrder, type SolanaSwapOrderQuote, SolanaTradingSdk, type SolanaTradingSdkOptions, buildCreateOrderInstruction, buildSolanaSwapOrder, encodeOrderIntent, findOrderPda, findSettlementStatePda, getSettlementSeed, getSolanaDelegateAuthority, getSolanaQuote, getSolanaSettlementProgramId, hashOrderIntent, postSolanaSwapOrderFromQuote, toHex, toOrderId };
|
|
261
|
+
export { type ApproveCowProtocolParams, type CreateOrderInstructionParams, ENCODED_ORDER_INTENT_SIZE, JupiterAPI, type JupiterOrderRequest, type JupiterOrderResponse, ORDER_SEED, type SolanaOrderIntent, type SolanaQuote, type SolanaQuoteAndPost, type SolanaQuoteParameters, type SolanaSignAndSend, type SolanaSwapOrder, type SolanaSwapOrderQuote, SolanaTradingSdk, type SolanaTradingSdkOptions, buildCreateOrderInstruction, buildSolanaSwapOrder, encodeOrderIntent, findOrderPda, findSettlementStatePda, getSettlementSeed, getSolanaDelegateAuthority, getSolanaQuote, getSolanaSettlementProgramId, hashOrderIntent, postSolanaSwapOrderFromQuote, toHex, toOrderId };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -66,10 +76,10 @@ function encodeOrderIntent(intent) {
|
|
|
66
76
|
offset += 8;
|
|
67
77
|
};
|
|
68
78
|
writePubkey(intent.owner);
|
|
69
|
-
writePubkey(intent.buyTokenAccount);
|
|
70
|
-
writePubkey(intent.buyMint);
|
|
71
79
|
writePubkey(intent.sellTokenAccount);
|
|
72
80
|
writePubkey(intent.sellMint);
|
|
81
|
+
writePubkey(intent.buyTokenAccount);
|
|
82
|
+
writePubkey(intent.buyMint);
|
|
73
83
|
writeU64LE(intent.sellAmount);
|
|
74
84
|
writeU64LE(intent.buyAmount);
|
|
75
85
|
view.setUint32(offset, intent.validTo, true);
|
|
@@ -228,7 +238,7 @@ async function getSolanaQuote(params, options = {}) {
|
|
|
228
238
|
const {
|
|
229
239
|
slippageBps: slippageBpsOverride,
|
|
230
240
|
ownerAddress,
|
|
231
|
-
receiverAddress,
|
|
241
|
+
receiverAddress = ownerAddress,
|
|
232
242
|
sellTokenDecimals,
|
|
233
243
|
buyTokenDecimals,
|
|
234
244
|
amount,
|
|
@@ -352,27 +362,49 @@ async function getSolanaQuote(params, options = {}) {
|
|
|
352
362
|
var import_sdk_order_book3 = require("@cowprotocol/sdk-order-book");
|
|
353
363
|
var import_spl_token2 = require("@solana/spl-token");
|
|
354
364
|
var import_web36 = require("@solana/web3.js");
|
|
365
|
+
|
|
366
|
+
// src/appData.ts
|
|
367
|
+
var import_sdk_app_data = require("@cowprotocol/sdk-app-data");
|
|
368
|
+
var import_deepmerge = __toESM(require("deepmerge"));
|
|
369
|
+
async function mergeAppData(doc, override) {
|
|
370
|
+
const clearedDoc = {
|
|
371
|
+
...doc,
|
|
372
|
+
metadata: {
|
|
373
|
+
...doc.metadata,
|
|
374
|
+
...override.metadata?.hooks ? { hooks: {} } : {},
|
|
375
|
+
...override.metadata?.userConsents ? { userConsents: [] } : {}
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
const merged = (0, import_deepmerge.default)(clearedDoc, override);
|
|
379
|
+
const fullAppData = await (0, import_sdk_app_data.stringifyDeterministic)(merged);
|
|
380
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(fullAppData));
|
|
381
|
+
return new Uint8Array(digest);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/buildSwapOrder.ts
|
|
355
385
|
async function buildSolanaSwapOrder({ quoteResults, solanaQuote }, advancedSettings) {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (receiver) {
|
|
362
|
-
intent.buyTokenAccount = (0, import_spl_token2.getAssociatedTokenAddressSync)(
|
|
363
|
-
intent.buyMint,
|
|
386
|
+
const { validTo, receiver } = advancedSettings?.quoteRequest ?? {};
|
|
387
|
+
const overrides = {
|
|
388
|
+
...receiver && {
|
|
389
|
+
buyTokenAccount: (0, import_spl_token2.getAssociatedTokenAddressSync)(
|
|
390
|
+
solanaQuote.intent.buyMint,
|
|
364
391
|
new import_web36.PublicKey(receiver),
|
|
365
392
|
false,
|
|
366
393
|
solanaQuote.buyTokenProgramId
|
|
367
|
-
)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const intentBytes = encodeOrderIntent(intent);
|
|
373
|
-
uid = await hashOrderIntent(intentBytes);
|
|
374
|
-
[orderPda] = findOrderPda(solanaQuote.programId, uid);
|
|
394
|
+
)
|
|
395
|
+
},
|
|
396
|
+
...validTo && { validTo },
|
|
397
|
+
...advancedSettings?.appData && {
|
|
398
|
+
appData: await mergeAppData(quoteResults.appDataInfo.doc ?? {}, advancedSettings.appData)
|
|
375
399
|
}
|
|
400
|
+
};
|
|
401
|
+
const intent = { ...solanaQuote.intent, ...overrides };
|
|
402
|
+
let uid = solanaQuote.uid;
|
|
403
|
+
let orderPda = solanaQuote.orderPda;
|
|
404
|
+
if (Object.keys(overrides).length > 0) {
|
|
405
|
+
const intentBytes = encodeOrderIntent(intent);
|
|
406
|
+
uid = await hashOrderIntent(intentBytes);
|
|
407
|
+
[orderPda] = findOrderPda(solanaQuote.programId, uid);
|
|
376
408
|
}
|
|
377
409
|
const instruction = buildCreateOrderInstruction({
|
|
378
410
|
programId: solanaQuote.programId,
|
|
@@ -408,10 +440,26 @@ async function postSolanaSwapOrderFromQuote(quote, signAndSend, advancedSettings
|
|
|
408
440
|
}
|
|
409
441
|
|
|
410
442
|
// src/solanaTradingSdk.ts
|
|
443
|
+
var import_spl_token3 = require("@solana/spl-token");
|
|
444
|
+
var import_web37 = require("@solana/web3.js");
|
|
411
445
|
var SolanaTradingSdk = class {
|
|
412
446
|
constructor(options = {}) {
|
|
413
447
|
this.options = options;
|
|
414
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Builds the SPL `approve` instruction that delegates `sellAmount` of `sellTokenAddress` to the
|
|
451
|
+
* settlement program, without sending it — see "Step 0" in the README for why this is required
|
|
452
|
+
* before an order on a given sell-token account can settle. Bundle it with `buildOrder`'s
|
|
453
|
+
* instruction in the same transaction the first time a wallet trades a given token.
|
|
454
|
+
*/
|
|
455
|
+
approveCowProtocol(params) {
|
|
456
|
+
const owner = new import_web37.PublicKey(params.ownerAddress);
|
|
457
|
+
const sellMint = new import_web37.PublicKey(params.sellTokenAddress);
|
|
458
|
+
const tokenProgramId = params.sellTokenProgramId ? new import_web37.PublicKey(params.sellTokenProgramId) : void 0;
|
|
459
|
+
const sellTokenAccount = (0, import_spl_token3.getAssociatedTokenAddressSync)(sellMint, owner, false, tokenProgramId);
|
|
460
|
+
const delegate = getSolanaDelegateAuthority(this.options.env);
|
|
461
|
+
return (0, import_spl_token3.createApproveInstruction)(sellTokenAccount, delegate, owner, params.approveAmount, void 0, tokenProgramId);
|
|
462
|
+
}
|
|
415
463
|
async getQuote(params) {
|
|
416
464
|
const quote = await getSolanaQuote(params, { env: this.options.env });
|
|
417
465
|
return {
|
package/dist/index.mjs
CHANGED
|
@@ -24,10 +24,10 @@ function encodeOrderIntent(intent) {
|
|
|
24
24
|
offset += 8;
|
|
25
25
|
};
|
|
26
26
|
writePubkey(intent.owner);
|
|
27
|
-
writePubkey(intent.buyTokenAccount);
|
|
28
|
-
writePubkey(intent.buyMint);
|
|
29
27
|
writePubkey(intent.sellTokenAccount);
|
|
30
28
|
writePubkey(intent.sellMint);
|
|
29
|
+
writePubkey(intent.buyTokenAccount);
|
|
30
|
+
writePubkey(intent.buyMint);
|
|
31
31
|
writeU64LE(intent.sellAmount);
|
|
32
32
|
writeU64LE(intent.buyAmount);
|
|
33
33
|
view.setUint32(offset, intent.validTo, true);
|
|
@@ -189,7 +189,7 @@ async function getSolanaQuote(params, options = {}) {
|
|
|
189
189
|
const {
|
|
190
190
|
slippageBps: slippageBpsOverride,
|
|
191
191
|
ownerAddress,
|
|
192
|
-
receiverAddress,
|
|
192
|
+
receiverAddress = ownerAddress,
|
|
193
193
|
sellTokenDecimals,
|
|
194
194
|
buyTokenDecimals,
|
|
195
195
|
amount,
|
|
@@ -313,27 +313,49 @@ async function getSolanaQuote(params, options = {}) {
|
|
|
313
313
|
import { SigningScheme } from "@cowprotocol/sdk-order-book";
|
|
314
314
|
import { getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2 } from "@solana/spl-token";
|
|
315
315
|
import { PublicKey as PublicKey6 } from "@solana/web3.js";
|
|
316
|
+
|
|
317
|
+
// src/appData.ts
|
|
318
|
+
import { stringifyDeterministic } from "@cowprotocol/sdk-app-data";
|
|
319
|
+
import deepmerge from "deepmerge";
|
|
320
|
+
async function mergeAppData(doc, override) {
|
|
321
|
+
const clearedDoc = {
|
|
322
|
+
...doc,
|
|
323
|
+
metadata: {
|
|
324
|
+
...doc.metadata,
|
|
325
|
+
...override.metadata?.hooks ? { hooks: {} } : {},
|
|
326
|
+
...override.metadata?.userConsents ? { userConsents: [] } : {}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const merged = deepmerge(clearedDoc, override);
|
|
330
|
+
const fullAppData = await stringifyDeterministic(merged);
|
|
331
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(fullAppData));
|
|
332
|
+
return new Uint8Array(digest);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/buildSwapOrder.ts
|
|
316
336
|
async function buildSolanaSwapOrder({ quoteResults, solanaQuote }, advancedSettings) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (receiver) {
|
|
323
|
-
intent.buyTokenAccount = getAssociatedTokenAddressSync2(
|
|
324
|
-
intent.buyMint,
|
|
337
|
+
const { validTo, receiver } = advancedSettings?.quoteRequest ?? {};
|
|
338
|
+
const overrides = {
|
|
339
|
+
...receiver && {
|
|
340
|
+
buyTokenAccount: getAssociatedTokenAddressSync2(
|
|
341
|
+
solanaQuote.intent.buyMint,
|
|
325
342
|
new PublicKey6(receiver),
|
|
326
343
|
false,
|
|
327
344
|
solanaQuote.buyTokenProgramId
|
|
328
|
-
)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const intentBytes = encodeOrderIntent(intent);
|
|
334
|
-
uid = await hashOrderIntent(intentBytes);
|
|
335
|
-
[orderPda] = findOrderPda(solanaQuote.programId, uid);
|
|
345
|
+
)
|
|
346
|
+
},
|
|
347
|
+
...validTo && { validTo },
|
|
348
|
+
...advancedSettings?.appData && {
|
|
349
|
+
appData: await mergeAppData(quoteResults.appDataInfo.doc ?? {}, advancedSettings.appData)
|
|
336
350
|
}
|
|
351
|
+
};
|
|
352
|
+
const intent = { ...solanaQuote.intent, ...overrides };
|
|
353
|
+
let uid = solanaQuote.uid;
|
|
354
|
+
let orderPda = solanaQuote.orderPda;
|
|
355
|
+
if (Object.keys(overrides).length > 0) {
|
|
356
|
+
const intentBytes = encodeOrderIntent(intent);
|
|
357
|
+
uid = await hashOrderIntent(intentBytes);
|
|
358
|
+
[orderPda] = findOrderPda(solanaQuote.programId, uid);
|
|
337
359
|
}
|
|
338
360
|
const instruction = buildCreateOrderInstruction({
|
|
339
361
|
programId: solanaQuote.programId,
|
|
@@ -369,10 +391,26 @@ async function postSolanaSwapOrderFromQuote(quote, signAndSend, advancedSettings
|
|
|
369
391
|
}
|
|
370
392
|
|
|
371
393
|
// src/solanaTradingSdk.ts
|
|
394
|
+
import { createApproveInstruction, getAssociatedTokenAddressSync as getAssociatedTokenAddressSync3 } from "@solana/spl-token";
|
|
395
|
+
import { PublicKey as PublicKey7 } from "@solana/web3.js";
|
|
372
396
|
var SolanaTradingSdk = class {
|
|
373
397
|
constructor(options = {}) {
|
|
374
398
|
this.options = options;
|
|
375
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Builds the SPL `approve` instruction that delegates `sellAmount` of `sellTokenAddress` to the
|
|
402
|
+
* settlement program, without sending it — see "Step 0" in the README for why this is required
|
|
403
|
+
* before an order on a given sell-token account can settle. Bundle it with `buildOrder`'s
|
|
404
|
+
* instruction in the same transaction the first time a wallet trades a given token.
|
|
405
|
+
*/
|
|
406
|
+
approveCowProtocol(params) {
|
|
407
|
+
const owner = new PublicKey7(params.ownerAddress);
|
|
408
|
+
const sellMint = new PublicKey7(params.sellTokenAddress);
|
|
409
|
+
const tokenProgramId = params.sellTokenProgramId ? new PublicKey7(params.sellTokenProgramId) : void 0;
|
|
410
|
+
const sellTokenAccount = getAssociatedTokenAddressSync3(sellMint, owner, false, tokenProgramId);
|
|
411
|
+
const delegate = getSolanaDelegateAuthority(this.options.env);
|
|
412
|
+
return createApproveInstruction(sellTokenAccount, delegate, owner, params.approveAmount, void 0, tokenProgramId);
|
|
413
|
+
}
|
|
376
414
|
async getQuote(params) {
|
|
377
415
|
const quote = await getSolanaQuote(params, { env: this.options.env });
|
|
378
416
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cowprotocol/sdk-trading-solana",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "CowProtocol Solana trading: Jupiter-sourced quotes and on-chain CreateOrder posting against the Solana settlement program",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,11 +37,13 @@
|
|
|
37
37
|
"ts-jest": "^29.0.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@cowprotocol/sdk-
|
|
41
|
-
"@cowprotocol/sdk-
|
|
42
|
-
"@cowprotocol/sdk-
|
|
40
|
+
"@cowprotocol/sdk-app-data": "6.0.5",
|
|
41
|
+
"@cowprotocol/sdk-config": "2.7.0",
|
|
42
|
+
"@cowprotocol/sdk-order-book": "4.0.6",
|
|
43
|
+
"@cowprotocol/sdk-trading": "2.6.0",
|
|
43
44
|
"@solana/spl-token": "0.4.14",
|
|
44
|
-
"@solana/web3.js": "1.98.4"
|
|
45
|
+
"@solana/web3.js": "1.98.4",
|
|
46
|
+
"deepmerge": "^4.3.1"
|
|
45
47
|
},
|
|
46
48
|
"scripts": {
|
|
47
49
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|