@solana-program/token-wrap 2.4.0 → 2.5.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.
Files changed (56) hide show
  1. package/package.json +18 -15
  2. package/src/create-mint.ts +125 -0
  3. package/src/examples/multisig.ts +295 -0
  4. package/src/examples/single-signer.ts +175 -0
  5. package/src/examples/sync-spl-to-token2022.ts +96 -0
  6. package/src/examples/sync-token2022-to-spl.ts +101 -0
  7. package/src/generated/accounts/backpointer.ts +125 -0
  8. package/{dist/types/src/generated/accounts/index.d.ts → src/generated/accounts/index.ts} +1 -0
  9. package/{dist/types/src/generated/errors/index.d.ts → src/generated/errors/index.ts} +1 -0
  10. package/src/generated/errors/tokenWrap.ts +89 -0
  11. package/{dist/types/src/generated/index.d.ts → src/generated/index.ts} +1 -0
  12. package/src/generated/instructions/closeStuckEscrow.ts +235 -0
  13. package/src/generated/instructions/createMint.ts +250 -0
  14. package/{dist/types/src/generated/instructions/index.d.ts → src/generated/instructions/index.ts} +1 -0
  15. package/src/generated/instructions/syncMetadataToSplToken.ts +305 -0
  16. package/src/generated/instructions/syncMetadataToToken2022.ts +253 -0
  17. package/src/generated/instructions/unwrap.ts +326 -0
  18. package/src/generated/instructions/wrap.ts +326 -0
  19. package/src/generated/pdas/backpointer.ts +32 -0
  20. package/{dist/types/src/generated/pdas/index.d.ts → src/generated/pdas/index.ts} +1 -0
  21. package/src/generated/pdas/wrappedMint.ts +37 -0
  22. package/src/generated/pdas/wrappedMintAuthority.ts +32 -0
  23. package/{dist/types/src/generated/programs/index.d.ts → src/generated/programs/index.ts} +1 -0
  24. package/src/generated/programs/tokenWrap.ts +228 -0
  25. package/src/global.d.ts +8 -0
  26. package/src/index.ts +23 -0
  27. package/src/unwrap.ts +208 -0
  28. package/src/utilities.ts +234 -0
  29. package/src/wrap.ts +211 -0
  30. package/dist/src/index.js +0 -1222
  31. package/dist/src/index.js.map +0 -1
  32. package/dist/src/index.mjs +0 -1135
  33. package/dist/src/index.mjs.map +0 -1
  34. package/dist/types/src/create-mint.d.ts +0 -16
  35. package/dist/types/src/examples/multisig.d.ts +0 -1
  36. package/dist/types/src/examples/single-signer.d.ts +0 -1
  37. package/dist/types/src/examples/sync-spl-to-token2022.d.ts +0 -1
  38. package/dist/types/src/examples/sync-token2022-to-spl.d.ts +0 -1
  39. package/dist/types/src/generated/accounts/backpointer.d.ts +0 -33
  40. package/dist/types/src/generated/errors/tokenWrap.d.ts +0 -35
  41. package/dist/types/src/generated/instructions/closeStuckEscrow.d.ts +0 -63
  42. package/dist/types/src/generated/instructions/createMint.d.ts +0 -76
  43. package/dist/types/src/generated/instructions/syncMetadataToSplToken.d.ts +0 -92
  44. package/dist/types/src/generated/instructions/syncMetadataToToken2022.d.ts +0 -75
  45. package/dist/types/src/generated/instructions/unwrap.d.ts +0 -103
  46. package/dist/types/src/generated/instructions/wrap.d.ts +0 -103
  47. package/dist/types/src/generated/pdas/backpointer.d.ts +0 -14
  48. package/dist/types/src/generated/pdas/wrappedMint.d.ts +0 -15
  49. package/dist/types/src/generated/pdas/wrappedMintAuthority.d.ts +0 -14
  50. package/dist/types/src/generated/programs/tokenWrap.d.ts +0 -38
  51. package/dist/types/src/generated/shared/index.d.ts +0 -49
  52. package/dist/types/src/index.d.ts +0 -5
  53. package/dist/types/src/unwrap.d.ts +0 -44
  54. package/dist/types/src/utilities.d.ts +0 -37
  55. package/dist/types/src/wrap.d.ts +0 -41
  56. package/dist/types/tsup.config.d.ts +0 -3
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ export * from './generated';
2
+
3
+ export { createMint, type CreateMintArgs, type CreateMintResult } from './create-mint';
4
+ export {
5
+ singleSignerWrap,
6
+ type SingleSignerWrapArgs,
7
+ type SingleSignerWrapResult,
8
+ multisigOfflineSignWrap,
9
+ type MultiSignerWrapIxBuilderArgs,
10
+ } from './wrap';
11
+ export {
12
+ singleSignerUnwrap,
13
+ type SingleSignerUnwrapArgs,
14
+ type SingleSignerUnwrapResult,
15
+ multisigOfflineSignUnwrap,
16
+ } from './unwrap';
17
+ export {
18
+ createEscrowAccount,
19
+ type CreateEscrowAccountArgs,
20
+ type CreateEscrowAccountResult,
21
+ combinedMultisigTx,
22
+ type MultiSigCombineArgs,
23
+ } from './utilities';
package/src/unwrap.ts ADDED
@@ -0,0 +1,208 @@
1
+ import { findAssociatedTokenPda, getTokenDecoder } from '@solana-program/token-2022';
2
+ import {
3
+ Address,
4
+ appendTransactionMessageInstructions,
5
+ Blockhash,
6
+ createTransactionMessage,
7
+ fetchEncodedAccount,
8
+ GetAccountInfoApi,
9
+ Instruction,
10
+ pipe,
11
+ Rpc,
12
+ setTransactionMessageFeePayerSigner,
13
+ setTransactionMessageLifetimeUsingBlockhash,
14
+ TransactionMessage,
15
+ TransactionMessageWithBlockhashLifetime,
16
+ TransactionMessageWithFeePayerSigner,
17
+ TransactionSigner,
18
+ } from '@solana/kit';
19
+ import { findWrappedMintAuthorityPda, getUnwrapInstruction, UnwrapInput } from './generated';
20
+ import { getMintFromTokenAccount, getOwnerFromAccount } from './utilities';
21
+
22
+ export interface SingleSignerUnwrapArgs {
23
+ rpc: Rpc<GetAccountInfoApi>;
24
+ payer: TransactionSigner; // Fee payer and default transfer authority
25
+ wrappedTokenAccount: Address;
26
+ amount: bigint | number;
27
+ recipientUnwrappedToken: Address;
28
+ // Optional arguments below (will be derived/defaulted if not provided)
29
+ transferAuthority?: Address | TransactionSigner; // Defaults to payer
30
+ unwrappedMint?: Address; // Will derive from unwrappedEscrow if not provided
31
+ wrappedTokenProgram?: Address; // Will derive from wrappedTokenAccount if not provided
32
+ unwrappedTokenProgram?: Address; // Will derive from unwrappedEscrow if not provided
33
+ }
34
+
35
+ async function resolveUnwrapAddrs({
36
+ rpc,
37
+ payer,
38
+ wrappedTokenAccount,
39
+ recipientUnwrappedToken,
40
+ inputUnwrappedMint,
41
+ inputTransferAuthority,
42
+ inputWrappedTokenProgram,
43
+ inputUnwrappedTokenProgram,
44
+ }: {
45
+ rpc: Rpc<GetAccountInfoApi>;
46
+ payer: TransactionSigner;
47
+ wrappedTokenAccount: Address;
48
+ recipientUnwrappedToken: Address;
49
+ inputUnwrappedMint?: Address;
50
+ inputTransferAuthority?: Address | TransactionSigner;
51
+ inputWrappedTokenProgram?: Address;
52
+ inputUnwrappedTokenProgram?: Address;
53
+ }) {
54
+ const wrappedTokenProgram = inputWrappedTokenProgram ?? (await getOwnerFromAccount(rpc, wrappedTokenAccount));
55
+ const unwrappedTokenProgram =
56
+ inputUnwrappedTokenProgram ?? (await getOwnerFromAccount(rpc, recipientUnwrappedToken));
57
+ const unwrappedMint = inputUnwrappedMint ?? (await getMintFromTokenAccount(rpc, recipientUnwrappedToken));
58
+
59
+ // Get wrapped mint from the token account being burned
60
+ const wrappedAccountInfo = await fetchEncodedAccount(rpc, wrappedTokenAccount);
61
+ if (!wrappedAccountInfo.exists) {
62
+ throw new Error(`Wrapped token account ${wrappedTokenAccount} not found.`);
63
+ }
64
+ const wrappedMint = getTokenDecoder().decode(wrappedAccountInfo.data).mint;
65
+
66
+ const [wrappedMintAuthority] = await findWrappedMintAuthorityPda({ wrappedMint });
67
+
68
+ // Default transfer authority to payer if not provided
69
+ const transferAuthority = inputTransferAuthority ?? payer;
70
+
71
+ return {
72
+ unwrappedMint,
73
+ wrappedMint,
74
+ wrappedMintAuthority,
75
+ transferAuthority,
76
+ wrappedTokenProgram,
77
+ unwrappedTokenProgram,
78
+ };
79
+ }
80
+
81
+ interface UnwrapTxBuilderArgs {
82
+ wrappedTokenAccount: Address;
83
+ amount: bigint | number;
84
+ wrappedMint: Address;
85
+ wrappedMintAuthority: Address;
86
+ unwrappedMint: Address;
87
+ recipientUnwrappedToken: Address;
88
+ unwrappedTokenProgram: Address;
89
+ wrappedTokenProgram: Address;
90
+ transferAuthority: Address | TransactionSigner;
91
+ multiSigners?: TransactionSigner[];
92
+ }
93
+
94
+ async function buildUnwrapTransaction({
95
+ recipientUnwrappedToken,
96
+ wrappedMintAuthority,
97
+ unwrappedMint,
98
+ wrappedTokenProgram,
99
+ unwrappedTokenProgram,
100
+ wrappedTokenAccount,
101
+ wrappedMint,
102
+ transferAuthority,
103
+ amount,
104
+ multiSigners = [],
105
+ }: UnwrapTxBuilderArgs): Promise<Instruction> {
106
+ const [unwrappedEscrow] = await findAssociatedTokenPda({
107
+ owner: wrappedMintAuthority,
108
+ mint: unwrappedMint,
109
+ tokenProgram: unwrappedTokenProgram,
110
+ });
111
+
112
+ const unwrapInstructionInput: UnwrapInput = {
113
+ unwrappedEscrow,
114
+ recipientUnwrappedToken,
115
+ wrappedMintAuthority,
116
+ unwrappedMint,
117
+ wrappedTokenProgram,
118
+ unwrappedTokenProgram,
119
+ wrappedTokenAccount,
120
+ wrappedMint,
121
+ transferAuthority,
122
+ amount: BigInt(amount),
123
+ multiSigners,
124
+ };
125
+
126
+ return getUnwrapInstruction(unwrapInstructionInput);
127
+ }
128
+
129
+ export interface SingleSignerUnwrapResult {
130
+ ixs: Instruction[];
131
+ recipientUnwrappedToken: Address;
132
+ amount: bigint;
133
+ }
134
+
135
+ /**
136
+ * Creates, signs (single signer or default authority), and sends an unwrap transaction.
137
+ * Derives necessary PDAs and default accounts if not provided.
138
+ */
139
+ export async function singleSignerUnwrap({
140
+ rpc,
141
+ payer,
142
+ wrappedTokenAccount,
143
+ amount,
144
+ recipientUnwrappedToken,
145
+ transferAuthority: inputTransferAuthority,
146
+ unwrappedMint: inputUnwrappedMint,
147
+ wrappedTokenProgram: inputWrappedTokenProgram,
148
+ unwrappedTokenProgram: inputUnwrappedTokenProgram,
149
+ }: SingleSignerUnwrapArgs): Promise<SingleSignerUnwrapResult> {
150
+ const {
151
+ wrappedMint,
152
+ wrappedMintAuthority,
153
+ transferAuthority,
154
+ unwrappedTokenProgram,
155
+ unwrappedMint,
156
+ wrappedTokenProgram,
157
+ } = await resolveUnwrapAddrs({
158
+ rpc,
159
+ payer,
160
+ wrappedTokenAccount,
161
+ recipientUnwrappedToken,
162
+ inputUnwrappedMint,
163
+ inputTransferAuthority,
164
+ inputWrappedTokenProgram,
165
+ inputUnwrappedTokenProgram,
166
+ });
167
+
168
+ const ix = await buildUnwrapTransaction({
169
+ recipientUnwrappedToken,
170
+ wrappedMintAuthority,
171
+ unwrappedMint,
172
+ wrappedTokenProgram,
173
+ unwrappedTokenProgram,
174
+ wrappedTokenAccount,
175
+ wrappedMint,
176
+ transferAuthority,
177
+ amount,
178
+ });
179
+
180
+ return {
181
+ recipientUnwrappedToken,
182
+ amount: BigInt(amount),
183
+ ixs: [ix],
184
+ };
185
+ }
186
+
187
+ export interface MultiSignerUnWrapTxBuilderArgs extends UnwrapTxBuilderArgs {
188
+ payer: TransactionSigner;
189
+ blockhash: {
190
+ blockhash: Blockhash;
191
+ lastValidBlockHeight: bigint;
192
+ };
193
+ multiSigners: TransactionSigner[];
194
+ }
195
+
196
+ // Used to collect signatures
197
+ export async function multisigOfflineSignUnwrap(
198
+ args: MultiSignerUnWrapTxBuilderArgs,
199
+ ): Promise<TransactionMessage & TransactionMessageWithBlockhashLifetime & TransactionMessageWithFeePayerSigner> {
200
+ const unwrapIx = await buildUnwrapTransaction(args);
201
+
202
+ return pipe(
203
+ createTransactionMessage({ version: 0 }),
204
+ tx => setTransactionMessageFeePayerSigner(args.payer, tx),
205
+ tx => setTransactionMessageLifetimeUsingBlockhash(args.blockhash, tx),
206
+ tx => appendTransactionMessageInstructions([unwrapIx], tx),
207
+ );
208
+ }
@@ -0,0 +1,234 @@
1
+ import { getCreateAccountInstruction } from '@solana-program/system';
2
+ import { getInitializeAccountInstruction as initializeToken, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
3
+ import {
4
+ fetchMaybeToken,
5
+ findAssociatedTokenPda,
6
+ getCreateAssociatedTokenInstruction,
7
+ getTokenDecoder,
8
+ InitializeAccountInput,
9
+ getInitializeAccountInstruction as initializeToken2022,
10
+ Token,
11
+ TOKEN_2022_PROGRAM_ADDRESS,
12
+ } from '@solana-program/token-2022';
13
+ import {
14
+ Account,
15
+ Address,
16
+ assertIsFullySignedTransaction,
17
+ assertIsSendableTransaction,
18
+ Blockhash,
19
+ containsBytes,
20
+ fetchEncodedAccount,
21
+ FullySignedTransaction,
22
+ generateKeyPairSigner,
23
+ GetAccountInfoApi,
24
+ GetMinimumBalanceForRentExemptionApi,
25
+ Instruction,
26
+ KeyPairSigner,
27
+ Rpc,
28
+ SignatureBytes,
29
+ Transaction,
30
+ TransactionWithBlockhashLifetime,
31
+ TransactionWithinSizeLimit,
32
+ } from '@solana/kit';
33
+ import { findWrappedMintAuthorityPda, findWrappedMintPda } from './generated';
34
+
35
+ function getInitializeTokenFn(tokenProgram: Address): (input: InitializeAccountInput) => Instruction {
36
+ if (tokenProgram === TOKEN_PROGRAM_ADDRESS) return initializeToken;
37
+ if (tokenProgram === TOKEN_2022_PROGRAM_ADDRESS) return initializeToken2022;
38
+ throw new Error(`${tokenProgram} is not a valid token program.`);
39
+ }
40
+
41
+ export async function createTokenAccount({
42
+ rpc,
43
+ payer,
44
+ mint,
45
+ owner,
46
+ tokenProgram,
47
+ }: {
48
+ rpc: Rpc<GetMinimumBalanceForRentExemptionApi>;
49
+ payer: KeyPairSigner;
50
+ mint: Address;
51
+ owner: Address;
52
+ tokenProgram: Address;
53
+ }): Promise<{ ixs: Instruction[]; keyPair: KeyPairSigner }> {
54
+ const [keyPair, lamports] = await Promise.all([
55
+ generateKeyPairSigner(),
56
+ rpc.getMinimumBalanceForRentExemption(165n).send(),
57
+ ]);
58
+
59
+ const createAccountIx = getCreateAccountInstruction({
60
+ payer,
61
+ newAccount: keyPair,
62
+ lamports,
63
+ space: 165,
64
+ programAddress: tokenProgram,
65
+ });
66
+
67
+ const initializeAccountIx = getInitializeTokenFn(tokenProgram)({
68
+ account: keyPair.address,
69
+ mint,
70
+ owner,
71
+ });
72
+
73
+ return {
74
+ ixs: [createAccountIx, initializeAccountIx],
75
+ keyPair,
76
+ };
77
+ }
78
+
79
+ export interface CreateEscrowAccountArgs {
80
+ rpc: Rpc<GetAccountInfoApi & GetMinimumBalanceForRentExemptionApi>;
81
+ payer: KeyPairSigner;
82
+ unwrappedMint: Address;
83
+ wrappedTokenProgram: Address;
84
+ }
85
+
86
+ export type CreateEscrowAccountResult =
87
+ | { kind: 'already_exists'; account: Account<Token> }
88
+ | {
89
+ kind: 'instructions_to_create';
90
+ address: Address;
91
+ ixs: Instruction[];
92
+ };
93
+
94
+ export async function createEscrowAccount({
95
+ rpc,
96
+ payer,
97
+ unwrappedMint,
98
+ wrappedTokenProgram,
99
+ }: CreateEscrowAccountArgs): Promise<CreateEscrowAccountResult> {
100
+ const [wrappedMint] = await findWrappedMintPda({ unwrappedMint, wrappedTokenProgram });
101
+ const [wrappedMintAuthority] = await findWrappedMintAuthorityPda({ wrappedMint });
102
+ const unwrappedTokenProgram = await getOwnerFromAccount(rpc, unwrappedMint);
103
+
104
+ const [escrowAta] = await findAssociatedTokenPda({
105
+ owner: wrappedMintAuthority,
106
+ mint: unwrappedMint,
107
+ tokenProgram: unwrappedTokenProgram,
108
+ });
109
+
110
+ const escrowResult = await fetchMaybeToken(rpc, escrowAta);
111
+ if (escrowResult.exists) {
112
+ return { kind: 'already_exists', account: escrowResult };
113
+ }
114
+
115
+ const ix = getCreateAssociatedTokenInstruction({
116
+ payer,
117
+ owner: wrappedMintAuthority,
118
+ mint: unwrappedMint,
119
+ ata: escrowAta,
120
+ tokenProgram: unwrappedTokenProgram,
121
+ }) as Instruction;
122
+
123
+ return { address: escrowAta, ixs: [ix], kind: 'instructions_to_create' };
124
+ }
125
+
126
+ export async function getOwnerFromAccount(rpc: Rpc<GetAccountInfoApi>, accountAddress: Address): Promise<Address> {
127
+ const accountInfo = await rpc.getAccountInfo(accountAddress, { encoding: 'base64' }).send();
128
+ if (!accountInfo.value) {
129
+ throw new Error(`Account ${accountAddress} not found.`);
130
+ }
131
+ return accountInfo.value.owner;
132
+ }
133
+
134
+ export async function getMintFromTokenAccount(
135
+ rpc: Rpc<GetAccountInfoApi>,
136
+ tokenAccountAddress: Address,
137
+ ): Promise<Address> {
138
+ const account = await fetchEncodedAccount(rpc, tokenAccountAddress);
139
+ if (!account.exists) {
140
+ throw new Error(`Unwrapped token account ${tokenAccountAddress} not found.`);
141
+ }
142
+ return getTokenDecoder().decode(account.data).mint;
143
+ }
144
+
145
+ function messageBytesEqual(results: (Transaction & TransactionWithBlockhashLifetime)[]): boolean {
146
+ // If array has only one element, return true
147
+ if (results.length === 1) {
148
+ return true;
149
+ }
150
+
151
+ // Use the first result as reference
152
+ const reference = results[0];
153
+ if (!reference) throw new Error('No transactions in input');
154
+
155
+ // Compare each result with the reference
156
+ return results.every(
157
+ c =>
158
+ reference.messageBytes.length === c.messageBytes.length &&
159
+ containsBytes(reference.messageBytes, c.messageBytes, 0),
160
+ );
161
+ }
162
+
163
+ function combineSignatures(
164
+ signedTxs: (Transaction & TransactionWithBlockhashLifetime)[],
165
+ ): Record<string, SignatureBytes> {
166
+ // Step 1: Determine the canonical signer order from the first signed transaction.
167
+ // Insertion order is the way to re-create this. Without it, verification will fail.
168
+ const firstSignedTx = signedTxs[0];
169
+ if (!firstSignedTx) {
170
+ throw new Error('No signed transactions provided');
171
+ }
172
+
173
+ const allSignatures: Record<string, SignatureBytes | null> = {};
174
+
175
+ // Step 1: Insert a null signature for each signer, maintaining the order of the signatures from the first signed transaction
176
+ for (const pubkey of Object.keys(firstSignedTx.signatures)) {
177
+ allSignatures[pubkey] = null;
178
+ }
179
+
180
+ // Step 2: Gather all signatures from all transactions
181
+ for (const signedTx of signedTxs) {
182
+ for (const [address, signature] of Object.entries(signedTx.signatures)) {
183
+ if (signature) {
184
+ // only store non-null signers
185
+ allSignatures[address] = signature;
186
+ }
187
+ }
188
+ }
189
+
190
+ // Step 3: Assert all signatures are set
191
+ const missingSigners: string[] = [];
192
+ for (const [pubkey, signature] of Object.entries(allSignatures)) {
193
+ if (signature === null) {
194
+ missingSigners.push(pubkey);
195
+ }
196
+ }
197
+ if (missingSigners.length > 0) {
198
+ throw new Error(`Missing signatures for: ${missingSigners.join(', ')}`);
199
+ }
200
+
201
+ return allSignatures as Record<string, SignatureBytes>;
202
+ }
203
+
204
+ export interface MultiSigCombineArgs {
205
+ signedTxs: (Transaction & TransactionWithBlockhashLifetime)[];
206
+ blockhash: {
207
+ blockhash: Blockhash;
208
+ lastValidBlockHeight: bigint;
209
+ };
210
+ }
211
+
212
+ // Combines, validates, and broadcasts outputs of multisig offline partially signed txs
213
+ export function combinedMultisigTx({
214
+ signedTxs,
215
+ blockhash,
216
+ }: MultiSigCombineArgs): Transaction &
217
+ FullySignedTransaction &
218
+ TransactionWithBlockhashLifetime &
219
+ TransactionWithinSizeLimit {
220
+ const messagesEqual = messageBytesEqual(signedTxs);
221
+ if (!messagesEqual) throw new Error('Messages are not all the same');
222
+ if (!signedTxs[0]) throw new Error('No signed transactions provided');
223
+
224
+ const tx = {
225
+ messageBytes: signedTxs[0].messageBytes,
226
+ signatures: combineSignatures(signedTxs),
227
+ lifetimeConstraint: blockhash,
228
+ };
229
+
230
+ assertIsFullySignedTransaction(tx);
231
+ assertIsSendableTransaction(tx);
232
+
233
+ return tx;
234
+ }
package/src/wrap.ts ADDED
@@ -0,0 +1,211 @@
1
+ import { findAssociatedTokenPda } from '@solana-program/token-2022';
2
+ import {
3
+ Address,
4
+ appendTransactionMessageInstructions,
5
+ Blockhash,
6
+ createTransactionMessage,
7
+ GetAccountInfoApi,
8
+ Instruction,
9
+ pipe,
10
+ Rpc,
11
+ setTransactionMessageFeePayerSigner,
12
+ setTransactionMessageLifetimeUsingBlockhash,
13
+ TransactionMessage,
14
+ TransactionMessageWithBlockhashLifetime,
15
+ TransactionMessageWithFeePayerSigner,
16
+ TransactionSigner,
17
+ } from '@solana/kit';
18
+ import { findWrappedMintAuthorityPda, findWrappedMintPda, getWrapInstruction, WrapInput } from './generated';
19
+ import { getMintFromTokenAccount, getOwnerFromAccount } from './utilities';
20
+
21
+ interface IxBuilderArgs {
22
+ unwrappedTokenAccount: Address;
23
+ wrappedTokenProgram: Address;
24
+ amount: bigint | number;
25
+ wrappedMint: Address;
26
+ wrappedMintAuthority: Address;
27
+ transferAuthority: Address | TransactionSigner;
28
+ unwrappedMint: Address;
29
+ recipientWrappedTokenAccount: Address;
30
+ unwrappedTokenProgram: Address;
31
+ multiSigners?: TransactionSigner[];
32
+ }
33
+
34
+ export interface MultiSignerWrapIxBuilderArgs extends IxBuilderArgs {
35
+ payer: TransactionSigner;
36
+ blockhash: {
37
+ blockhash: Blockhash;
38
+ lastValidBlockHeight: bigint;
39
+ };
40
+ multiSigners: TransactionSigner[];
41
+ }
42
+
43
+ // Used to collect signatures
44
+ export async function multisigOfflineSignWrap(
45
+ args: MultiSignerWrapIxBuilderArgs,
46
+ ): Promise<TransactionMessage & TransactionMessageWithBlockhashLifetime & TransactionMessageWithFeePayerSigner> {
47
+ const wrapIx = await buildWrapIx(args);
48
+
49
+ return pipe(
50
+ createTransactionMessage({ version: 0 }),
51
+ tx => setTransactionMessageFeePayerSigner(args.payer, tx),
52
+ tx => setTransactionMessageLifetimeUsingBlockhash(args.blockhash, tx),
53
+ tx => appendTransactionMessageInstructions([wrapIx], tx),
54
+ );
55
+ }
56
+
57
+ export interface SingleSignerWrapArgs {
58
+ rpc: Rpc<GetAccountInfoApi>;
59
+ payer: TransactionSigner; // Fee payer and default transfer authority
60
+ unwrappedTokenAccount: Address;
61
+ wrappedTokenProgram: Address;
62
+ amount: bigint | number;
63
+ transferAuthority?: Address | TransactionSigner; // Defaults to payer if not provided
64
+ unwrappedMint?: Address; // Will fetch from unwrappedTokenAccount if not provided
65
+ recipientWrappedTokenAccount?: Address; // Defaults to payer's ATA if not provided
66
+ unwrappedTokenProgram?: Address; // Will fetch from unwrappedTokenAccount owner if not provided
67
+ }
68
+
69
+ export interface SingleSignerWrapResult {
70
+ ixs: Instruction[];
71
+ recipientWrappedTokenAccount: Address;
72
+ escrowAccount: Address;
73
+ amount: bigint;
74
+ }
75
+
76
+ export async function singleSignerWrap({
77
+ rpc,
78
+ payer,
79
+ unwrappedTokenAccount,
80
+ wrappedTokenProgram,
81
+ amount,
82
+ transferAuthority: inputTransferAuthority,
83
+ unwrappedMint: inputUnwrappedMint,
84
+ recipientWrappedTokenAccount: inputRecipientTokenAccount,
85
+ unwrappedTokenProgram: inputUnwrappedTokenProgram,
86
+ }: SingleSignerWrapArgs): Promise<SingleSignerWrapResult> {
87
+ const {
88
+ unwrappedMint,
89
+ unwrappedTokenProgram,
90
+ wrappedMint,
91
+ wrappedMintAuthority,
92
+ recipientWrappedTokenAccount,
93
+ transferAuthority,
94
+ unwrappedEscrow,
95
+ } = await resolveAddrs({
96
+ rpc,
97
+ payer,
98
+ inputTransferAuthority,
99
+ inputUnwrappedMint,
100
+ unwrappedTokenAccount,
101
+ inputUnwrappedTokenProgram,
102
+ wrappedTokenProgram,
103
+ inputRecipientTokenAccount,
104
+ });
105
+
106
+ const ix = await buildWrapIx({
107
+ unwrappedTokenAccount,
108
+ wrappedTokenProgram,
109
+ amount,
110
+ transferAuthority,
111
+ unwrappedMint,
112
+ wrappedMint,
113
+ wrappedMintAuthority,
114
+ recipientWrappedTokenAccount,
115
+ unwrappedTokenProgram,
116
+ });
117
+
118
+ return {
119
+ ixs: [ix],
120
+ recipientWrappedTokenAccount,
121
+ escrowAccount: unwrappedEscrow,
122
+ amount: BigInt(amount),
123
+ };
124
+ }
125
+
126
+ // Meant to handle all of the potential default values
127
+ async function resolveAddrs({
128
+ rpc,
129
+ payer,
130
+ unwrappedTokenAccount,
131
+ wrappedTokenProgram,
132
+ inputTransferAuthority,
133
+ inputUnwrappedMint,
134
+ inputRecipientTokenAccount,
135
+ inputUnwrappedTokenProgram,
136
+ }: {
137
+ rpc: Rpc<GetAccountInfoApi>;
138
+ payer: TransactionSigner;
139
+ unwrappedTokenAccount: Address;
140
+ wrappedTokenProgram: Address;
141
+ inputTransferAuthority?: Address | TransactionSigner;
142
+ inputUnwrappedMint?: Address;
143
+ inputRecipientTokenAccount?: Address;
144
+ inputUnwrappedTokenProgram?: Address;
145
+ }) {
146
+ const unwrappedMint = inputUnwrappedMint ?? (await getMintFromTokenAccount(rpc, unwrappedTokenAccount));
147
+ const unwrappedTokenProgram = inputUnwrappedTokenProgram ?? (await getOwnerFromAccount(rpc, unwrappedTokenAccount));
148
+ const [wrappedMint] = await findWrappedMintPda({ unwrappedMint, wrappedTokenProgram });
149
+ const [wrappedMintAuthority] = await findWrappedMintAuthorityPda({ wrappedMint });
150
+ const recipientWrappedTokenAccount =
151
+ inputRecipientTokenAccount ??
152
+ (
153
+ await findAssociatedTokenPda({
154
+ owner: payer.address,
155
+ mint: wrappedMint,
156
+ tokenProgram: wrappedTokenProgram,
157
+ })
158
+ )[0];
159
+ const [unwrappedEscrow] = await findAssociatedTokenPda({
160
+ owner: wrappedMintAuthority,
161
+ mint: unwrappedMint,
162
+ tokenProgram: unwrappedTokenProgram,
163
+ });
164
+
165
+ const transferAuthority = inputTransferAuthority ?? payer;
166
+
167
+ return {
168
+ unwrappedEscrow,
169
+ transferAuthority,
170
+ unwrappedMint,
171
+ unwrappedTokenProgram,
172
+ wrappedMint,
173
+ wrappedMintAuthority,
174
+ recipientWrappedTokenAccount,
175
+ };
176
+ }
177
+
178
+ async function buildWrapIx({
179
+ unwrappedTokenAccount,
180
+ wrappedTokenProgram,
181
+ amount,
182
+ transferAuthority,
183
+ unwrappedMint,
184
+ recipientWrappedTokenAccount,
185
+ unwrappedTokenProgram,
186
+ wrappedMint,
187
+ wrappedMintAuthority,
188
+ multiSigners = [],
189
+ }: IxBuilderArgs): Promise<Instruction> {
190
+ const [unwrappedEscrow] = await findAssociatedTokenPda({
191
+ owner: wrappedMintAuthority,
192
+ mint: unwrappedMint,
193
+ tokenProgram: unwrappedTokenProgram,
194
+ });
195
+
196
+ const wrapInstructionInput: WrapInput = {
197
+ recipientWrappedTokenAccount,
198
+ wrappedMint,
199
+ wrappedMintAuthority,
200
+ unwrappedTokenProgram,
201
+ wrappedTokenProgram,
202
+ unwrappedTokenAccount,
203
+ unwrappedMint,
204
+ unwrappedEscrow,
205
+ transferAuthority,
206
+ amount: BigInt(amount),
207
+ multiSigners,
208
+ };
209
+
210
+ return getWrapInstruction(wrapInstructionInput);
211
+ }