@solana-program/token-wrap 2.4.0 → 2.5.1
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/package.json +18 -15
- package/src/create-mint.ts +125 -0
- package/src/examples/multisig.ts +295 -0
- package/src/examples/single-signer.ts +175 -0
- package/src/examples/sync-spl-to-token2022.ts +96 -0
- package/src/examples/sync-token2022-to-spl.ts +101 -0
- package/src/generated/accounts/backpointer.ts +125 -0
- package/{dist/types/src/generated/accounts/index.d.ts → src/generated/accounts/index.ts} +1 -0
- package/{dist/types/src/generated/errors/index.d.ts → src/generated/errors/index.ts} +1 -0
- package/src/generated/errors/tokenWrap.ts +89 -0
- package/{dist/types/src/generated/index.d.ts → src/generated/index.ts} +1 -0
- package/src/generated/instructions/closeStuckEscrow.ts +235 -0
- package/src/generated/instructions/createMint.ts +250 -0
- package/{dist/types/src/generated/instructions/index.d.ts → src/generated/instructions/index.ts} +1 -0
- package/src/generated/instructions/syncMetadataToSplToken.ts +305 -0
- package/src/generated/instructions/syncMetadataToToken2022.ts +253 -0
- package/src/generated/instructions/unwrap.ts +326 -0
- package/src/generated/instructions/wrap.ts +326 -0
- package/src/generated/pdas/backpointer.ts +32 -0
- package/{dist/types/src/generated/pdas/index.d.ts → src/generated/pdas/index.ts} +1 -0
- package/src/generated/pdas/wrappedMint.ts +37 -0
- package/src/generated/pdas/wrappedMintAuthority.ts +32 -0
- package/{dist/types/src/generated/programs/index.d.ts → src/generated/programs/index.ts} +1 -0
- package/src/generated/programs/tokenWrap.ts +228 -0
- package/src/global.d.ts +8 -0
- package/src/index.ts +23 -0
- package/src/unwrap.ts +208 -0
- package/src/utilities.ts +234 -0
- package/src/wrap.ts +211 -0
- package/dist/src/index.js +0 -1222
- package/dist/src/index.js.map +0 -1
- package/dist/src/index.mjs +0 -1135
- package/dist/src/index.mjs.map +0 -1
- package/dist/types/src/create-mint.d.ts +0 -16
- package/dist/types/src/examples/multisig.d.ts +0 -1
- package/dist/types/src/examples/single-signer.d.ts +0 -1
- package/dist/types/src/examples/sync-spl-to-token2022.d.ts +0 -1
- package/dist/types/src/examples/sync-token2022-to-spl.d.ts +0 -1
- package/dist/types/src/generated/accounts/backpointer.d.ts +0 -33
- package/dist/types/src/generated/errors/tokenWrap.d.ts +0 -35
- package/dist/types/src/generated/instructions/closeStuckEscrow.d.ts +0 -63
- package/dist/types/src/generated/instructions/createMint.d.ts +0 -76
- package/dist/types/src/generated/instructions/syncMetadataToSplToken.d.ts +0 -92
- package/dist/types/src/generated/instructions/syncMetadataToToken2022.d.ts +0 -75
- package/dist/types/src/generated/instructions/unwrap.d.ts +0 -103
- package/dist/types/src/generated/instructions/wrap.d.ts +0 -103
- package/dist/types/src/generated/pdas/backpointer.d.ts +0 -14
- package/dist/types/src/generated/pdas/wrappedMint.d.ts +0 -15
- package/dist/types/src/generated/pdas/wrappedMintAuthority.d.ts +0 -14
- package/dist/types/src/generated/programs/tokenWrap.d.ts +0 -38
- package/dist/types/src/generated/shared/index.d.ts +0 -49
- package/dist/types/src/index.d.ts +0 -5
- package/dist/types/src/unwrap.d.ts +0 -44
- package/dist/types/src/utilities.d.ts +0 -37
- package/dist/types/src/wrap.d.ts +0 -41
- 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.
|
|
3
|
+
"version": "2.5.1",
|
|
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.
|
|
36
|
+
"@solana/kit": "^6.10.0"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@solana-program/system": "^0.
|
|
39
|
-
"@solana-program/token": "^0.
|
|
40
|
-
"@solana-program/token-2022": "^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": "^
|
|
44
|
-
"@solana/kit": "^6.
|
|
45
|
+
"@eslint/js": "^10.0.1",
|
|
46
|
+
"@solana/kit": "^6.10.0",
|
|
45
47
|
"@tsconfig/strictest": "^2.0.8",
|
|
46
|
-
"@types/node": "^25.
|
|
47
|
-
"eslint": "^
|
|
48
|
+
"@types/node": "^25.9.3",
|
|
49
|
+
"eslint": "^10.5.0",
|
|
48
50
|
"eslint-config-prettier": "^10.1.8",
|
|
49
|
-
"prettier": "^3.8.
|
|
51
|
+
"prettier": "^3.8.4",
|
|
50
52
|
"tsup": "^8.5.1",
|
|
51
|
-
"tsx": "^4.
|
|
52
|
-
"typedoc": "^0.28.
|
|
53
|
-
"typescript": "^
|
|
54
|
-
"typescript-eslint": "^8.
|
|
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();
|