@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana-program/token-wrap",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Javascript helpers for interacting with the Solana Token Wrap program",
5
5
  "author": "Anza Maintainers <maintainers@anza.xyz>",
6
6
  "license": "Apache-2.0",
@@ -18,7 +18,8 @@
18
18
  },
19
19
  "files": [
20
20
  "./dist/src",
21
- "./dist/types"
21
+ "./dist/types",
22
+ "./src/"
22
23
  ],
23
24
  "publishConfig": {
24
25
  "access": "public",
@@ -32,26 +33,27 @@
32
33
  "url": "https://github.com/solana-program/token-wrap/issues"
33
34
  },
34
35
  "peerDependencies": {
35
- "@solana/kit": "^6.0.0"
36
+ "@solana/kit": "^6.10.0"
36
37
  },
37
38
  "dependencies": {
38
- "@solana-program/system": "^0.11.0",
39
- "@solana-program/token": "^0.10.0",
40
- "@solana-program/token-2022": "^0.9.0"
39
+ "@solana-program/system": "^0.12.2",
40
+ "@solana-program/token": "^0.14.0",
41
+ "@solana-program/token-2022": "^0.12.0",
42
+ "@solana/program-client-core": "^6.10.0"
41
43
  },
42
44
  "devDependencies": {
43
- "@eslint/js": "^9.39.2",
44
- "@solana/kit": "^6.0.0",
45
+ "@eslint/js": "^10.0.1",
46
+ "@solana/kit": "^6.10.0",
45
47
  "@tsconfig/strictest": "^2.0.8",
46
- "@types/node": "^25.0.3",
47
- "eslint": "^9.39.2",
48
+ "@types/node": "^25.9.3",
49
+ "eslint": "^10.5.0",
48
50
  "eslint-config-prettier": "^10.1.8",
49
- "prettier": "^3.8.1",
51
+ "prettier": "^3.8.4",
50
52
  "tsup": "^8.5.1",
51
- "tsx": "^4.21.0",
52
- "typedoc": "^0.28.15",
53
- "typescript": "^5.9.3",
54
- "typescript-eslint": "^8.49.0"
53
+ "tsx": "^4.22.4",
54
+ "typedoc": "^0.28.19",
55
+ "typescript": "^6.0.3",
56
+ "typescript-eslint": "^8.61.1"
55
57
  },
56
58
  "scripts": {
57
59
  "build": "tsc && tsup",
@@ -60,6 +62,7 @@
60
62
  "lint:fix": "eslint --fix --ext js,ts,tsx src",
61
63
  "format": "prettier --write .",
62
64
  "format:check": "prettier --check .",
65
+ "test": "exit 0",
63
66
  "example:single-signer": "tsx src/examples/single-signer.ts",
64
67
  "example:multisig": "tsx src/examples/multisig.ts"
65
68
  }
@@ -0,0 +1,125 @@
1
+ import {
2
+ Address,
3
+ fetchEncodedAccount,
4
+ GetAccountInfoApi,
5
+ GetMinimumBalanceForRentExemptionApi,
6
+ Instruction,
7
+ KeyPairSigner,
8
+ Rpc,
9
+ } from '@solana/kit';
10
+ import { getMintSize, TOKEN_2022_PROGRAM_ADDRESS, extension } from '@solana-program/token-2022';
11
+ import { getTransferSolInstruction } from '@solana-program/system';
12
+ import { findBackpointerPda, findWrappedMintPda, getBackpointerSize, getCreateMintInstruction } from './generated';
13
+
14
+ export interface CreateMintArgs {
15
+ rpc: Rpc<GetAccountInfoApi & GetMinimumBalanceForRentExemptionApi>;
16
+ unwrappedMint: Address;
17
+ wrappedTokenProgram: Address;
18
+ payer: KeyPairSigner;
19
+ idempotent?: boolean;
20
+ }
21
+
22
+ export interface CreateMintResult {
23
+ wrappedMint: Address;
24
+ backpointer: Address;
25
+ fundedWrappedMintLamports: bigint;
26
+ fundedBackpointerLamports: bigint;
27
+ ixs: Instruction[];
28
+ }
29
+
30
+ // The on-chain program adds these two extensions by default. We must account for
31
+ // their size here. The `getMintSize` function from the library expects extension
32
+ // data objects, but since the size of these extensions is fixed, we can pass
33
+ // dummy/default values.
34
+ const DEFAULT_EXTENSIONS = [
35
+ extension('ConfidentialTransferMint', {
36
+ autoApproveNewAccounts: true,
37
+ authority: null,
38
+ auditorElgamalPubkey: null,
39
+ }),
40
+ extension('MetadataPointer', {
41
+ authority: null,
42
+ metadataAddress: null,
43
+ }),
44
+ ];
45
+
46
+ export async function createMint({
47
+ rpc,
48
+ unwrappedMint,
49
+ wrappedTokenProgram,
50
+ payer,
51
+ idempotent = false,
52
+ }: CreateMintArgs): Promise<CreateMintResult> {
53
+ const [wrappedMint] = await findWrappedMintPda({
54
+ unwrappedMint,
55
+ wrappedTokenProgram: wrappedTokenProgram,
56
+ });
57
+ const [backpointer] = await findBackpointerPda({ wrappedMint });
58
+
59
+ const instructions: Instruction[] = [];
60
+
61
+ // Fund wrapped mint account if needed
62
+ let fundedWrappedMintLamports = 0n;
63
+
64
+ let mintSize = BigInt(getMintSize());
65
+ if (wrappedTokenProgram === TOKEN_2022_PROGRAM_ADDRESS) {
66
+ mintSize = BigInt(getMintSize(DEFAULT_EXTENSIONS));
67
+ }
68
+
69
+ const [wrappedMintAccount, wrappedMintRent] = await Promise.all([
70
+ fetchEncodedAccount(rpc, wrappedMint),
71
+ rpc.getMinimumBalanceForRentExemption(mintSize).send(),
72
+ ]);
73
+
74
+ const wrappedMintLamports = wrappedMintAccount.exists ? wrappedMintAccount.lamports : 0n;
75
+ if (wrappedMintLamports < wrappedMintRent) {
76
+ fundedWrappedMintLamports = wrappedMintRent - wrappedMintLamports;
77
+ instructions.push(
78
+ getTransferSolInstruction({
79
+ source: payer,
80
+ destination: wrappedMint,
81
+ amount: fundedWrappedMintLamports,
82
+ }),
83
+ );
84
+ }
85
+
86
+ // Fund backpointer account if needed
87
+ let fundedBackpointerLamports = 0n;
88
+
89
+ const backpointerSize = BigInt(getBackpointerSize());
90
+ const [backpointerAccount, backpointerRent] = await Promise.all([
91
+ fetchEncodedAccount(rpc, backpointer),
92
+ rpc.getMinimumBalanceForRentExemption(backpointerSize).send(),
93
+ ]);
94
+
95
+ const backpointerLamports = backpointerAccount.exists ? backpointerAccount.lamports : 0n;
96
+ if (backpointerLamports < backpointerRent) {
97
+ fundedBackpointerLamports = backpointerRent - backpointerLamports;
98
+ instructions.push(
99
+ getTransferSolInstruction({
100
+ source: payer,
101
+ destination: backpointer,
102
+ amount: fundedBackpointerLamports,
103
+ }),
104
+ );
105
+ }
106
+
107
+ // Add create_mint instruction
108
+ instructions.push(
109
+ getCreateMintInstruction({
110
+ wrappedMint,
111
+ backpointer,
112
+ unwrappedMint,
113
+ wrappedTokenProgram,
114
+ idempotent,
115
+ }),
116
+ );
117
+
118
+ return {
119
+ wrappedMint,
120
+ backpointer,
121
+ ixs: instructions,
122
+ fundedWrappedMintLamports,
123
+ fundedBackpointerLamports,
124
+ };
125
+ }
@@ -0,0 +1,295 @@
1
+ import {
2
+ address,
3
+ appendTransactionMessageInstructions,
4
+ assertIsSendableTransaction,
5
+ assertIsTransactionWithBlockhashLifetime,
6
+ createKeyPairSignerFromBytes,
7
+ createNoopSigner,
8
+ createSolanaRpc,
9
+ createSolanaRpcSubscriptions,
10
+ createTransactionMessage,
11
+ getBase58Decoder,
12
+ getSignatureFromTransaction,
13
+ partiallySignTransactionMessageWithSigners,
14
+ pipe,
15
+ sendAndConfirmTransactionFactory,
16
+ setTransactionMessageFeePayerSigner,
17
+ setTransactionMessageLifetimeUsingBlockhash,
18
+ signTransactionMessageWithSigners,
19
+ } from '@solana/kit';
20
+ import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022';
21
+ import {
22
+ findWrappedMintAuthorityPda,
23
+ findWrappedMintPda,
24
+ combinedMultisigTx,
25
+ createMint,
26
+ multisigOfflineSignUnwrap,
27
+ createEscrowAccount,
28
+ multisigOfflineSignWrap,
29
+ } from '../index';
30
+ import { createTokenAccount, getOwnerFromAccount } from '../utilities';
31
+
32
+ // Replace these consts with your own
33
+ const PAYER_KEYPAIR_BYTES = new Uint8Array([
34
+ 242, 30, 38, 177, 152, 71, 235, 193, 93, 30, 119, 131, 42, 186, 202, 7, 45, 250, 126, 135, 107, 137, 38, 91, 202,
35
+ 212, 12, 8, 154, 213, 163, 200, 23, 237, 17, 163, 3, 135, 34, 126, 235, 146, 251, 18, 199, 101, 153, 249, 134, 88,
36
+ 219, 68, 167, 136, 234, 195, 12, 34, 184, 85, 234, 25, 125, 94,
37
+ ]);
38
+
39
+ // Create using CLI: spl-token create-multisig 2 $SIGNER_1_PUBKEY $SIGNER_2_PUBKEY
40
+ const MULTISIG_SPL_TOKEN = address('2XBevFsu4pnZpB9PewYKAJHNyx9dFQf3MaiGBszF5fm8');
41
+ const MULTISIG_SPL_TOKEN_2022 = address('BSdexGFqwmDGeXe4pBXVbQnqrEH5trmo9W3wqoXUQY5Y');
42
+ const SIGNER_A_KEYPAIR_BYTES = new Uint8Array([
43
+ 210, 190, 232, 169, 113, 107, 195, 87, 14, 9, 125, 106, 41, 174, 131, 9, 29, 144, 95, 134, 68, 123, 80, 215, 194,
44
+ 30, 170, 140, 33, 175, 69, 126, 201, 176, 240, 30, 173, 145, 185, 162, 231, 196, 71, 236, 233, 153, 42, 243, 146,
45
+ 82, 70, 153, 129, 194, 156, 110, 84, 18, 71, 143, 38, 244, 232, 58,
46
+ ]);
47
+ const SIGNER_B_KEYPAIR_BYTES = new Uint8Array([
48
+ 37, 161, 191, 225, 59, 192, 226, 154, 168, 4, 189, 155, 235, 240, 187, 210, 230, 176, 133, 163, 6, 132, 229, 129,
49
+ 10, 9, 67, 88, 215, 124, 195, 243, 189, 178, 12, 18, 216, 91, 154, 193, 75, 164, 71, 224, 106, 148, 225, 156, 124,
50
+ 241, 250, 51, 27, 8, 37, 111, 60, 187, 219, 161, 55, 42, 129, 236,
51
+ ]);
52
+
53
+ const UNWRAPPED_MINT_ADDRESS = address('F2qGWupzMUQnGfX8e25XZps8d9AGdVde8hLQT2pxsb4M');
54
+ const UNWRAPPED_TOKEN_ACCOUNT = address('94Y9pxekEm59b67PQQwvjb7wbwz689wDZ3dAwhCtJpPS'); // Must be owned by MULTISIG_SPL_TOKEN
55
+ const AMOUNT_TO_WRAP = 100n;
56
+
57
+ async function main() {
58
+ const rpc = createSolanaRpc('http://127.0.0.1:8899');
59
+ const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
60
+ const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
61
+
62
+ const payer = await createKeyPairSignerFromBytes(PAYER_KEYPAIR_BYTES);
63
+ const { value: blockhash } = await rpc.getLatestBlockhash().send();
64
+
65
+ // Initialize the wrapped mint
66
+ const createMintHelper = await createMint({
67
+ rpc,
68
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
69
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
70
+ payer,
71
+ idempotent: true,
72
+ });
73
+ const createMintTx = await pipe(
74
+ createTransactionMessage({ version: 0 }),
75
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
76
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
77
+ tx => appendTransactionMessageInstructions(createMintHelper.ixs, tx),
78
+ tx => signTransactionMessageWithSigners(tx),
79
+ );
80
+ assertIsSendableTransaction(createMintTx);
81
+ assertIsTransactionWithBlockhashLifetime(createMintTx);
82
+ await sendAndConfirm(createMintTx, { commitment: 'confirmed' });
83
+ const createMintSignature = getSignatureFromTransaction(createMintTx);
84
+
85
+ console.log('======== Create Mint Successful ========');
86
+ console.log('Wrapped Mint:', createMintHelper.wrappedMint);
87
+ console.log('Backpointer:', createMintHelper.backpointer);
88
+ console.log('Funded wrapped mint lamports:', createMintHelper.fundedWrappedMintLamports);
89
+ console.log('Funded backpointer lamports:', createMintHelper.fundedBackpointerLamports);
90
+ console.log('Signature:', createMintSignature);
91
+
92
+ // === Setup accounts needed for wrap ===
93
+
94
+ // Create escrow account that with hold unwrapped tokens
95
+ const createEscrowHelper = await createEscrowAccount({
96
+ rpc,
97
+ payer,
98
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
99
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
100
+ });
101
+ if (createEscrowHelper.kind === 'instructions_to_create') {
102
+ const createEscrowTx = await pipe(
103
+ createTransactionMessage({ version: 0 }),
104
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
105
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
106
+ tx => appendTransactionMessageInstructions(createEscrowHelper.ixs, tx),
107
+ tx => signTransactionMessageWithSigners(tx),
108
+ );
109
+ assertIsSendableTransaction(createEscrowTx);
110
+ assertIsTransactionWithBlockhashLifetime(createEscrowTx);
111
+ await sendAndConfirm(createEscrowTx, { commitment: 'confirmed' });
112
+ const createEscrowSignature = getSignatureFromTransaction(createEscrowTx);
113
+
114
+ console.log('======== Create Escrow Successful ========');
115
+ console.log('Escrow address:', createEscrowHelper.address);
116
+ console.log('Signature:', createEscrowSignature);
117
+ } else {
118
+ console.log('======== Escrow already exists, skipping creation ========');
119
+ }
120
+
121
+ // Create recipient account where wrapped tokens will be minted to
122
+ const [wrappedMint] = await findWrappedMintPda({
123
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
124
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
125
+ });
126
+ const recipientTokenAccountHelper = await createTokenAccount({
127
+ rpc,
128
+ payer,
129
+ mint: wrappedMint,
130
+ tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
131
+ owner: MULTISIG_SPL_TOKEN_2022,
132
+ });
133
+ const recipientTokenAccountTx = await pipe(
134
+ createTransactionMessage({ version: 0 }),
135
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
136
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
137
+ tx => appendTransactionMessageInstructions(recipientTokenAccountHelper.ixs, tx),
138
+ tx => signTransactionMessageWithSigners(tx),
139
+ );
140
+ assertIsSendableTransaction(recipientTokenAccountTx);
141
+ assertIsTransactionWithBlockhashLifetime(recipientTokenAccountTx);
142
+ await sendAndConfirm(recipientTokenAccountTx, { commitment: 'confirmed' });
143
+
144
+ const unwrappedTokenProgram = await getOwnerFromAccount(rpc, UNWRAPPED_TOKEN_ACCOUNT);
145
+ const [wrappedMintAuthority] = await findWrappedMintAuthorityPda({ wrappedMint });
146
+
147
+ const { value: wrapBlockhash } = await rpc.getLatestBlockhash().send();
148
+
149
+ const signerA = await createKeyPairSignerFromBytes(SIGNER_A_KEYPAIR_BYTES);
150
+ const signerB = await createKeyPairSignerFromBytes(SIGNER_B_KEYPAIR_BYTES);
151
+
152
+ // Two signers and the payer sign the transaction independently
153
+
154
+ const wrapTxA = await multisigOfflineSignWrap({
155
+ payer: createNoopSigner(payer.address),
156
+ unwrappedTokenAccount: UNWRAPPED_TOKEN_ACCOUNT,
157
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
158
+ amount: AMOUNT_TO_WRAP,
159
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
160
+ recipientWrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
161
+ transferAuthority: MULTISIG_SPL_TOKEN,
162
+ wrappedMint,
163
+ wrappedMintAuthority,
164
+ unwrappedTokenProgram,
165
+ multiSigners: [signerA, createNoopSigner(signerB.address)],
166
+ blockhash: wrapBlockhash,
167
+ });
168
+ const signedWrapTxA = await partiallySignTransactionMessageWithSigners(wrapTxA);
169
+ assertIsTransactionWithBlockhashLifetime(signedWrapTxA);
170
+
171
+ const wrapTxB = await multisigOfflineSignWrap({
172
+ payer: createNoopSigner(payer.address),
173
+ unwrappedTokenAccount: UNWRAPPED_TOKEN_ACCOUNT,
174
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
175
+ amount: AMOUNT_TO_WRAP,
176
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
177
+ recipientWrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
178
+ transferAuthority: MULTISIG_SPL_TOKEN,
179
+ wrappedMint,
180
+ wrappedMintAuthority,
181
+ unwrappedTokenProgram,
182
+ multiSigners: [createNoopSigner(signerA.address), signerB],
183
+ blockhash: wrapBlockhash,
184
+ });
185
+ const signedWrapTxB = await partiallySignTransactionMessageWithSigners(wrapTxB);
186
+ assertIsTransactionWithBlockhashLifetime(signedWrapTxB);
187
+
188
+ const wrapTxC = await multisigOfflineSignWrap({
189
+ payer,
190
+ unwrappedTokenAccount: UNWRAPPED_TOKEN_ACCOUNT,
191
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
192
+ amount: AMOUNT_TO_WRAP,
193
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
194
+ recipientWrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
195
+ transferAuthority: MULTISIG_SPL_TOKEN,
196
+ wrappedMint,
197
+ wrappedMintAuthority,
198
+ unwrappedTokenProgram,
199
+ multiSigners: [createNoopSigner(signerA.address), createNoopSigner(signerB.address)],
200
+ blockhash: wrapBlockhash,
201
+ });
202
+ const signedWrapTxC = await partiallySignTransactionMessageWithSigners(wrapTxC);
203
+ assertIsTransactionWithBlockhashLifetime(signedWrapTxC);
204
+
205
+ // Lastly, all signatures are combined together and broadcast
206
+
207
+ const combinedWrapTx = combinedMultisigTx({
208
+ signedTxs: [signedWrapTxA, signedWrapTxB, signedWrapTxC],
209
+ blockhash,
210
+ });
211
+ await sendAndConfirm(combinedWrapTx, { commitment: 'confirmed' });
212
+
213
+ console.log('======== Multisig Wrap Successful ========');
214
+ for (const [pubkey, signature] of Object.entries(combinedWrapTx.signatures)) {
215
+ if (signature) {
216
+ const base58Sig = getBase58Decoder().decode(signature);
217
+ console.log(`pubkey: ${pubkey}`);
218
+ console.log(`signature: ${base58Sig}`);
219
+ console.log('-----');
220
+ }
221
+ }
222
+
223
+ // Unwraps from the token account owned by MULTISIG_SPL_TOKEN_2022
224
+
225
+ const { value: unwrapBlockhash } = await rpc.getLatestBlockhash().send();
226
+
227
+ const unwrapTxA = await multisigOfflineSignUnwrap({
228
+ payer: createNoopSigner(payer.address),
229
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
230
+ amount: AMOUNT_TO_WRAP,
231
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
232
+ wrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
233
+ recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
234
+ transferAuthority: MULTISIG_SPL_TOKEN_2022,
235
+ wrappedMint,
236
+ wrappedMintAuthority,
237
+ unwrappedTokenProgram,
238
+ multiSigners: [signerA, createNoopSigner(signerB.address)],
239
+ blockhash: unwrapBlockhash,
240
+ });
241
+ const signedUnwrapTxA = await partiallySignTransactionMessageWithSigners(unwrapTxA);
242
+ assertIsTransactionWithBlockhashLifetime(signedUnwrapTxA);
243
+
244
+ const unwrapTxB = await multisigOfflineSignUnwrap({
245
+ payer: createNoopSigner(payer.address),
246
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
247
+ amount: AMOUNT_TO_WRAP,
248
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
249
+ wrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
250
+ recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
251
+ transferAuthority: MULTISIG_SPL_TOKEN_2022,
252
+ wrappedMint,
253
+ wrappedMintAuthority,
254
+ unwrappedTokenProgram,
255
+ multiSigners: [createNoopSigner(signerA.address), signerB],
256
+ blockhash: unwrapBlockhash,
257
+ });
258
+ const signedUnwrapTxB = await partiallySignTransactionMessageWithSigners(unwrapTxB);
259
+ assertIsTransactionWithBlockhashLifetime(signedUnwrapTxB);
260
+
261
+ const unwrapTxC = await multisigOfflineSignUnwrap({
262
+ payer: payer,
263
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
264
+ amount: AMOUNT_TO_WRAP,
265
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
266
+ wrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
267
+ recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
268
+ transferAuthority: MULTISIG_SPL_TOKEN_2022,
269
+ wrappedMint,
270
+ wrappedMintAuthority,
271
+ unwrappedTokenProgram,
272
+ multiSigners: [createNoopSigner(signerA.address), createNoopSigner(signerB.address)],
273
+ blockhash: unwrapBlockhash,
274
+ });
275
+ const signedUnwrapTxC = await partiallySignTransactionMessageWithSigners(unwrapTxC);
276
+ assertIsTransactionWithBlockhashLifetime(signedUnwrapTxC);
277
+
278
+ const combinedUnwrapTx = combinedMultisigTx({
279
+ signedTxs: [signedUnwrapTxA, signedUnwrapTxB, signedUnwrapTxC],
280
+ blockhash,
281
+ });
282
+ await sendAndConfirm(combinedUnwrapTx, { commitment: 'confirmed' });
283
+
284
+ console.log('======== Multisig Unwrap Successful ========');
285
+ for (const [pubkey, signature] of Object.entries(combinedUnwrapTx.signatures)) {
286
+ if (signature) {
287
+ const base58Sig = getBase58Decoder().decode(signature);
288
+ console.log(`pubkey: ${pubkey}`);
289
+ console.log(`signature: ${base58Sig}`);
290
+ console.log('-----');
291
+ }
292
+ }
293
+ }
294
+
295
+ void main();
@@ -0,0 +1,175 @@
1
+ import {
2
+ address,
3
+ appendTransactionMessageInstructions,
4
+ assertIsSendableTransaction,
5
+ assertIsTransactionWithBlockhashLifetime,
6
+ createKeyPairSignerFromBytes,
7
+ createSolanaRpc,
8
+ createSolanaRpcSubscriptions,
9
+ createTransactionMessage,
10
+ getSignatureFromTransaction,
11
+ pipe,
12
+ sendAndConfirmTransactionFactory,
13
+ setTransactionMessageFeePayerSigner,
14
+ setTransactionMessageLifetimeUsingBlockhash,
15
+ signTransactionMessageWithSigners,
16
+ } from '@solana/kit';
17
+ import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022';
18
+ import { createEscrowAccount, findWrappedMintPda, createMint, singleSignerUnwrap, singleSignerWrap } from '../index';
19
+ import { createTokenAccount } from '../utilities';
20
+
21
+ // Replace these consts with your own
22
+ const PRIVATE_KEY_PAIR = new Uint8Array([
23
+ 242, 30, 38, 177, 152, 71, 235, 193, 93, 30, 119, 131, 42, 186, 202, 7, 45, 250, 126, 135, 107, 137, 38, 91, 202,
24
+ 212, 12, 8, 154, 213, 163, 200, 23, 237, 17, 163, 3, 135, 34, 126, 235, 146, 251, 18, 199, 101, 153, 249, 134, 88,
25
+ 219, 68, 167, 136, 234, 195, 12, 34, 184, 85, 234, 25, 125, 94,
26
+ ]);
27
+ const UNWRAPPED_MINT_ADDRESS = address('FAbYm8kdDsyc6csvTXPMBwCJDjTVkZcvrnyVVTSF74hU');
28
+ const UNWRAPPED_TOKEN_ACCOUNT = address('4dSPDdFuTbKTuJDDtTd8SUdbH6QY42hpTPRi6RRzzsPF');
29
+ const AMOUNT_TO_WRAP = 100n;
30
+
31
+ async function main() {
32
+ const rpc = createSolanaRpc('http://127.0.0.1:8899');
33
+ const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
34
+ const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
35
+
36
+ const payer = await createKeyPairSignerFromBytes(PRIVATE_KEY_PAIR);
37
+ const { value: blockhash } = await rpc.getLatestBlockhash().send();
38
+
39
+ // Initialize the wrapped mint
40
+ const createMintHelper = await createMint({
41
+ rpc,
42
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
43
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
44
+ payer,
45
+ idempotent: true,
46
+ });
47
+ const createMintTx = await pipe(
48
+ createTransactionMessage({ version: 0 }),
49
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
50
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
51
+ tx => appendTransactionMessageInstructions(createMintHelper.ixs, tx),
52
+ tx => signTransactionMessageWithSigners(tx),
53
+ );
54
+ assertIsSendableTransaction(createMintTx);
55
+ assertIsTransactionWithBlockhashLifetime(createMintTx);
56
+ await sendAndConfirm(createMintTx, { commitment: 'confirmed' });
57
+ const createMintSignature = getSignatureFromTransaction(createMintTx);
58
+
59
+ console.log('======== Create Mint Successful ========');
60
+ console.log('Wrapped Mint:', createMintHelper.wrappedMint);
61
+ console.log('Backpointer:', createMintHelper.backpointer);
62
+ console.log('Funded wrapped mint lamports:', createMintHelper.fundedWrappedMintLamports);
63
+ console.log('Funded backpointer lamports:', createMintHelper.fundedBackpointerLamports);
64
+ console.log('Signature:', createMintSignature);
65
+
66
+ // === Setup accounts needed for wrap ===
67
+
68
+ // Create escrow account that with hold unwrapped tokens
69
+ const createEscrowHelper = await createEscrowAccount({
70
+ rpc,
71
+ payer,
72
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
73
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
74
+ });
75
+ if (createEscrowHelper.kind === 'instructions_to_create') {
76
+ const createEscrowTx = await pipe(
77
+ createTransactionMessage({ version: 0 }),
78
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
79
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
80
+ tx => appendTransactionMessageInstructions(createEscrowHelper.ixs, tx),
81
+ tx => signTransactionMessageWithSigners(tx),
82
+ );
83
+ assertIsSendableTransaction(createEscrowTx);
84
+ assertIsTransactionWithBlockhashLifetime(createEscrowTx);
85
+ await sendAndConfirm(createEscrowTx, { commitment: 'confirmed' });
86
+ const createEscrowSignature = getSignatureFromTransaction(createEscrowTx);
87
+
88
+ console.log('======== Create Escrow Successful ========');
89
+ console.log('Escrow address:', createEscrowHelper.address);
90
+ console.log('Signature:', createEscrowSignature);
91
+ } else {
92
+ console.log('======== Escrow already exists, skipping creation ========');
93
+ }
94
+
95
+ // Create recipient account where wrapped tokens will be minted to
96
+ const [wrappedMint] = await findWrappedMintPda({
97
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
98
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
99
+ });
100
+ const recipientTokenAccountHelper = await createTokenAccount({
101
+ rpc,
102
+ payer,
103
+ mint: wrappedMint,
104
+ tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
105
+ owner: payer.address,
106
+ });
107
+ const recipientTokenAccountTx = await pipe(
108
+ createTransactionMessage({ version: 0 }),
109
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
110
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
111
+ tx => appendTransactionMessageInstructions(recipientTokenAccountHelper.ixs, tx),
112
+ tx => signTransactionMessageWithSigners(tx),
113
+ );
114
+ assertIsSendableTransaction(recipientTokenAccountTx);
115
+ assertIsTransactionWithBlockhashLifetime(recipientTokenAccountTx);
116
+ await sendAndConfirm(recipientTokenAccountTx, { commitment: 'confirmed' });
117
+
118
+ // Execute wrap
119
+ const wrapHelper = await singleSignerWrap({
120
+ rpc,
121
+ payer,
122
+ unwrappedTokenAccount: UNWRAPPED_TOKEN_ACCOUNT,
123
+ wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
124
+ amount: AMOUNT_TO_WRAP,
125
+ unwrappedMint: UNWRAPPED_MINT_ADDRESS,
126
+ recipientWrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
127
+ });
128
+
129
+ const wrapTx = await pipe(
130
+ createTransactionMessage({ version: 0 }),
131
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
132
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
133
+ tx => appendTransactionMessageInstructions(wrapHelper.ixs, tx),
134
+ tx => signTransactionMessageWithSigners(tx),
135
+ );
136
+ assertIsSendableTransaction(wrapTx);
137
+ assertIsTransactionWithBlockhashLifetime(wrapTx);
138
+ await sendAndConfirm(wrapTx, { commitment: 'confirmed' });
139
+ const wrapSignature = getSignatureFromTransaction(wrapTx);
140
+
141
+ console.log('======== Wrap Successful ========');
142
+ console.log('Wrap amount:', wrapHelper.amount);
143
+ console.log('Recipient account:', wrapHelper.recipientWrappedTokenAccount);
144
+ console.log('Escrow Account:', wrapHelper.escrowAccount);
145
+ console.log('Signature:', wrapSignature);
146
+
147
+ // execute unwrap
148
+
149
+ const unwrapHelper = await singleSignerUnwrap({
150
+ rpc,
151
+ payer,
152
+ wrappedTokenAccount: recipientTokenAccountHelper.keyPair.address,
153
+ amount: AMOUNT_TO_WRAP,
154
+ recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
155
+ });
156
+
157
+ const unwrapTx = await pipe(
158
+ createTransactionMessage({ version: 0 }),
159
+ tx => setTransactionMessageFeePayerSigner(payer, tx),
160
+ tx => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
161
+ tx => appendTransactionMessageInstructions(unwrapHelper.ixs, tx),
162
+ tx => signTransactionMessageWithSigners(tx),
163
+ );
164
+ assertIsSendableTransaction(unwrapTx);
165
+ assertIsTransactionWithBlockhashLifetime(unwrapTx);
166
+ await sendAndConfirm(unwrapTx, { commitment: 'confirmed' });
167
+ const unwrapSignature = getSignatureFromTransaction(unwrapTx);
168
+
169
+ console.log('======== Unwrap Successful ========');
170
+ console.log('Unwrapped amount:', unwrapHelper.amount);
171
+ console.log('Recipient account:', unwrapHelper.recipientUnwrappedToken);
172
+ console.log('Signature:', unwrapSignature);
173
+ }
174
+
175
+ void main();