@exodus/solana-lib 3.19.3 → 3.20.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/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [3.20.0](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.19.3...@exodus/solana-lib@3.20.0) (2026-02-02)
7
+
8
+
9
+ ### Features
10
+
11
+
12
+ * feat(solana): reintroduce solana SPL delegation (#7353)
13
+
14
+
15
+
6
16
  ## [3.19.3](https://github.com/ExodusMovement/assets/compare/@exodus/solana-lib@3.19.2...@exodus/solana-lib@3.19.3) (2026-01-27)
7
17
 
8
18
  **Note:** Version bump only for package @exodus/solana-lib
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-lib",
3
- "version": "3.19.3",
3
+ "version": "3.20.0",
4
4
  "description": "Solana utils, such as for cryptography, address encoding/decoding, transaction building, etc.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -48,5 +48,5 @@
48
48
  "type": "git",
49
49
  "url": "git+https://github.com/ExodusMovement/assets.git"
50
50
  },
51
- "gitHead": "577dcb8f798b57216309a798dbd2ff7350449892"
51
+ "gitHead": "3cea3bc89ff5c4aaa461ac148e384ea24dbe2d38"
52
52
  }
@@ -1,11 +1,9 @@
1
1
  import { TOKEN_PROGRAM_ID } from '../constants.js'
2
2
  import { findAssociatedTokenAddress } from '../encode.js'
3
3
  import { PublicKey, Transaction as SolanaWeb3Transaction } from '../vendor/index.js'
4
- import { Token, U64 } from './spl-token.js'
4
+ import { createApproveInstruction } from './token-delegation.js'
5
5
  import { createAssociatedTokenAccount } from './tokenTransfer.js'
6
6
 
7
- const MAX_U64 = new U64('ffffffffffffffff', 'hex')
8
-
9
7
  export const createAgentTokenInitTx = ({
10
8
  agentWalletAddress,
11
9
  feePayerWalletAddress,
@@ -13,27 +11,27 @@ export const createAgentTokenInitTx = ({
13
11
  recentBlockhash,
14
12
  delegateAccount,
15
13
  }) => {
14
+ const tokenProgram = TOKEN_PROGRAM_ID.toBase58()
15
+
16
16
  const createATAInstruction = createAssociatedTokenAccount(
17
17
  feePayerWalletAddress,
18
18
  tokenMintAddress,
19
19
  agentWalletAddress,
20
- TOKEN_PROGRAM_ID.toBase58()
20
+ tokenProgram
21
21
  )
22
22
 
23
23
  const agentTokenAccount = findAssociatedTokenAddress(
24
24
  agentWalletAddress,
25
25
  tokenMintAddress,
26
- TOKEN_PROGRAM_ID.toBase58()
26
+ tokenProgram
27
27
  )
28
28
 
29
- const approveInstruction = Token.createApproveInstruction(
30
- TOKEN_PROGRAM_ID,
31
- new PublicKey(agentTokenAccount),
32
- new PublicKey(delegateAccount),
33
- new PublicKey(agentWalletAddress),
34
- [],
35
- MAX_U64
36
- )
29
+ const approveInstruction = createApproveInstruction({
30
+ tokenAccountAddress: agentTokenAccount,
31
+ ownerAddress: agentWalletAddress,
32
+ delegateAddress: delegateAccount,
33
+ tokenProgram,
34
+ })
37
35
 
38
36
  const transaction = new SolanaWeb3Transaction({
39
37
  recentBlockhash,
@@ -435,6 +435,62 @@ export const Token = {
435
435
  })
436
436
  },
437
437
 
438
+ /**
439
+ * Construct a Revoke instruction
440
+ *
441
+ * @param programId SPL Token program account
442
+ * @param account Public key of the token account
443
+ * @param owner Owner of the token account
444
+ * @param multiSigners Signing accounts if `owner` is a multiSig
445
+ */
446
+ createRevokeInstruction(programId, account, owner, multiSigners) {
447
+ const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')])
448
+ const data = Buffer.alloc(dataLayout.span)
449
+
450
+ dataLayout.encode(
451
+ {
452
+ instruction: 5, // Revoke instruction
453
+ },
454
+ data
455
+ )
456
+
457
+ const keys = [
458
+ {
459
+ pubkey: account,
460
+ isSigner: false,
461
+ isWritable: true,
462
+ },
463
+ ]
464
+
465
+ if (multiSigners.length === 0) {
466
+ keys.push({
467
+ pubkey: owner,
468
+ isSigner: true,
469
+ isWritable: false,
470
+ })
471
+ } else {
472
+ keys.push({
473
+ pubkey: owner,
474
+ isSigner: false,
475
+ isWritable: false,
476
+ })
477
+
478
+ multiSigners.forEach((signer) =>
479
+ keys.push({
480
+ pubkey: signer.publicKey,
481
+ isSigner: true,
482
+ isWritable: false,
483
+ })
484
+ )
485
+ }
486
+
487
+ return new TransactionInstruction({
488
+ keys,
489
+ programId,
490
+ data,
491
+ })
492
+ },
493
+
438
494
  /**
439
495
  * Construct a SyncNative instruction
440
496
  *
@@ -0,0 +1,87 @@
1
+ import { TOKEN_PROGRAM_ID } from '../constants.js'
2
+ import { findAssociatedTokenAddress } from '../encode.js'
3
+ import { PublicKey, Transaction as SolanaWeb3Transaction } from '../vendor/index.js'
4
+ import { Token, U64 } from './spl-token.js'
5
+
6
+ const MAX_U64 = new U64('ffffffffffffffff', 'hex')
7
+
8
+ export const createApproveInstruction = ({
9
+ tokenAccountAddress,
10
+ ownerAddress,
11
+ delegateAddress,
12
+ amount,
13
+ tokenProgram = TOKEN_PROGRAM_ID.toBase58(),
14
+ }) => {
15
+ const programId = new PublicKey(tokenProgram)
16
+
17
+ return Token.createApproveInstruction(
18
+ programId,
19
+ new PublicKey(tokenAccountAddress),
20
+ new PublicKey(delegateAddress),
21
+ new PublicKey(ownerAddress),
22
+ [],
23
+ amount ? new U64(amount) : MAX_U64
24
+ )
25
+ }
26
+
27
+ export const createApproveDelegationTx = ({
28
+ ownerAddress,
29
+ tokenMintAddress,
30
+ delegateAddress,
31
+ amount,
32
+ recentBlockhash,
33
+ tokenProgram = TOKEN_PROGRAM_ID.toBase58(),
34
+ }) => {
35
+ const tokenAccountAddress = findAssociatedTokenAddress(
36
+ ownerAddress,
37
+ tokenMintAddress,
38
+ tokenProgram
39
+ )
40
+
41
+ const approveInstruction = createApproveInstruction({
42
+ tokenAccountAddress,
43
+ ownerAddress,
44
+ delegateAddress,
45
+ amount,
46
+ tokenProgram,
47
+ })
48
+
49
+ const transaction = new SolanaWeb3Transaction({
50
+ recentBlockhash,
51
+ feePayer: new PublicKey(ownerAddress),
52
+ })
53
+
54
+ transaction.add(approveInstruction)
55
+
56
+ return transaction
57
+ }
58
+
59
+ export const createRevokeDelegationTx = ({
60
+ ownerAddress,
61
+ tokenMintAddress,
62
+ recentBlockhash,
63
+ tokenProgram = TOKEN_PROGRAM_ID.toBase58(),
64
+ }) => {
65
+ const programId = new PublicKey(tokenProgram)
66
+ const tokenAccountAddress = findAssociatedTokenAddress(
67
+ ownerAddress,
68
+ tokenMintAddress,
69
+ tokenProgram
70
+ )
71
+
72
+ const revokeInstruction = Token.createRevokeInstruction(
73
+ programId,
74
+ new PublicKey(tokenAccountAddress),
75
+ new PublicKey(ownerAddress),
76
+ []
77
+ )
78
+
79
+ const transaction = new SolanaWeb3Transaction({
80
+ recentBlockhash,
81
+ feePayer: new PublicKey(ownerAddress),
82
+ })
83
+
84
+ transaction.add(revokeInstruction)
85
+
86
+ return transaction
87
+ }
package/src/index.js CHANGED
@@ -16,3 +16,8 @@ export { default as Transaction } from './transaction.js'
16
16
  export { U64, Token } from './helpers/spl-token.js'
17
17
  export { createGetKeyIdentifier, getSupportedPurposes } from './key-identifier.js'
18
18
  export { createAgentTokenInitTx } from './helpers/create-agent-token-init-tx.js'
19
+ export {
20
+ createApproveInstruction,
21
+ createApproveDelegationTx,
22
+ createRevokeDelegationTx,
23
+ } from './helpers/token-delegation.js'
@@ -172,8 +172,14 @@ class Tx {
172
172
 
173
173
  let amountLeft = new BN(amount)
174
174
  let amountToSend
175
- let isNotEnoughBalance = false
176
- for (let { mintAddress, tokenAccountAddress, balance, decimals } of fromTokenAddresses) {
175
+ for (let {
176
+ mintAddress,
177
+ tokenAccountAddress,
178
+ balance,
179
+ decimals,
180
+ isDelegated,
181
+ delegatedAmount,
182
+ } of fromTokenAddresses) {
177
183
  // need to add more of this instruction until we reach the desired balance (amount) to send
178
184
  assert(mintAddress === tokenMintAddress, `Got unexpected mintAddress ${mintAddress}`)
179
185
 
@@ -181,12 +187,18 @@ class Tx {
181
187
  if (amountLeft.isZero()) break
182
188
 
183
189
  balance = new BN(balance)
190
+
191
+ if (isDelegated && delegatedAmount) {
192
+ const maxDelegated = new BN(delegatedAmount)
193
+ if (balance.gt(maxDelegated)) {
194
+ balance = maxDelegated
195
+ }
196
+ }
197
+
184
198
  if (balance.gte(amountLeft)) {
185
199
  amountToSend = amountLeft
186
200
  amountLeft = new BN(0)
187
201
  } else {
188
- // Not enough balance case.
189
- isNotEnoughBalance = true
190
202
  amountToSend = balance
191
203
  amountLeft = amountLeft.sub(amountToSend)
192
204
  }
@@ -234,7 +246,7 @@ class Tx {
234
246
  this.transaction.add(tokenTransferInstruction)
235
247
  }
236
248
 
237
- assert(isNotEnoughBalance === false, `Not enough balance to send ${amount} ${tokenMintAddress}`)
249
+ assert(amountLeft.isZero(), `Not enough balance to send ${amount} ${tokenMintAddress}`)
238
250
  }
239
251
 
240
252
  static createStakeAccountTransaction({ address, amount, seed = SEED, pool, recentBlockhash }) {