Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 97 additions & 17 deletions clients/js/src/examples/multisig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import {
} from '@solana/kit';
import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022';
import { findWrappedMintAuthorityPda, findWrappedMintPda } from '../generated';
import { combinedMultisigWrapTx, multisigOfflineSignWrapTx } from '../wrap';
import { createEscrowAccountTx, createTokenAccountTx, getOwnerFromAccount } from '../utilities';
import { multisigOfflineSignWrapTx } from '../wrap';
import {
combinedMultisigTx,
createEscrowAccountTx,
createTokenAccountTx,
getOwnerFromAccount,
} from '../utilities';
import { createMintTx } from '../create-mint';
import { multisigOfflineSignUnwrap } from '../unwrap';

// Replace these consts with your own
const PAYER_KEYPAIR_BYTES = new Uint8Array([
Expand All @@ -25,7 +31,8 @@ const PAYER_KEYPAIR_BYTES = new Uint8Array([
]);

// Create using CLI: spl-token create-multisig 2 $SIGNER_1_PUBKEY $SIGNER_2_PUBKEY
const MULTISIG_PUBKEY = address('2XBevFsu4pnZpB9PewYKAJHNyx9dFQf3MaiGBszF5fm8');
const MULTISIG_SPL_TOKEN = address('2XBevFsu4pnZpB9PewYKAJHNyx9dFQf3MaiGBszF5fm8');
const MULTISIG_SPL_TOKEN_2022 = address('BSdexGFqwmDGeXe4pBXVbQnqrEH5trmo9W3wqoXUQY5Y');
const SIGNER_A_KEYPAIR_BYTES = new Uint8Array([
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, 30, 170, 140, 33, 175, 69, 126, 201, 176, 240, 30, 173, 145, 185, 162, 231,
Expand All @@ -40,10 +47,10 @@ const SIGNER_B_KEYPAIR_BYTES = new Uint8Array([
]);

const UNWRAPPED_MINT_ADDRESS = address('F2qGWupzMUQnGfX8e25XZps8d9AGdVde8hLQT2pxsb4M');
const UNWRAPPED_TOKEN_ACCOUNT = address('94Y9pxekEm59b67PQQwvjb7wbwz689wDZ3dAwhCtJpPS'); // Must be owned by multisig account
const UNWRAPPED_TOKEN_ACCOUNT = address('94Y9pxekEm59b67PQQwvjb7wbwz689wDZ3dAwhCtJpPS'); // Must be owned by MULTISIG_SPL_TOKEN
const AMOUNT_TO_WRAP = 100n;

const main = async () => {
async function main() {
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
Expand Down Expand Up @@ -95,7 +102,7 @@ const main = async () => {
payer,
mint: wrappedMint,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
owner: payer.address,
owner: MULTISIG_SPL_TOKEN_2022,
});
const signedRecipientAccountTx = await signTransactionMessageWithSigners(
recipientTokenAccountMessage.tx,
Expand All @@ -105,6 +112,8 @@ const main = async () => {
const unwrappedTokenProgram = await getOwnerFromAccount(rpc, UNWRAPPED_TOKEN_ACCOUNT);
const [wrappedMintAuthority] = await findWrappedMintAuthorityPda({ wrappedMint });

const { value: wrapBlockhash } = await rpc.getLatestBlockhash().send();

const signerA = await createKeyPairSignerFromBytes(SIGNER_A_KEYPAIR_BYTES);
const signerB = await createKeyPairSignerFromBytes(SIGNER_B_KEYPAIR_BYTES);

Expand All @@ -118,12 +127,12 @@ const main = async () => {
amount: AMOUNT_TO_WRAP,
unwrappedMint: UNWRAPPED_MINT_ADDRESS,
recipientWrappedTokenAccount: recipientTokenAccountMessage.keyPair.address,
transferAuthority: MULTISIG_PUBKEY,
transferAuthority: MULTISIG_SPL_TOKEN,
wrappedMint,
wrappedMintAuthority,
unwrappedTokenProgram,
multiSigners: [signerA, createNoopSigner(signerB.address)],
blockhash,
blockhash: wrapBlockhash,
});
const signedWrapTxA = await partiallySignTransactionMessageWithSigners(wrapTxA);

Expand All @@ -135,12 +144,12 @@ const main = async () => {
amount: AMOUNT_TO_WRAP,
unwrappedMint: UNWRAPPED_MINT_ADDRESS,
recipientWrappedTokenAccount: recipientTokenAccountMessage.keyPair.address,
transferAuthority: MULTISIG_PUBKEY,
transferAuthority: MULTISIG_SPL_TOKEN,
wrappedMint,
wrappedMintAuthority,
unwrappedTokenProgram,
multiSigners: [createNoopSigner(signerA.address), signerB],
blockhash,
blockhash: wrapBlockhash,
});
const signedWrapTxB = await partiallySignTransactionMessageWithSigners(wrapTxB);

Expand All @@ -152,32 +161,103 @@ const main = async () => {
amount: AMOUNT_TO_WRAP,
unwrappedMint: UNWRAPPED_MINT_ADDRESS,
recipientWrappedTokenAccount: recipientTokenAccountMessage.keyPair.address,
transferAuthority: MULTISIG_PUBKEY,
transferAuthority: MULTISIG_SPL_TOKEN,
wrappedMint,
wrappedMintAuthority,
unwrappedTokenProgram,
multiSigners: [createNoopSigner(signerA.address), createNoopSigner(signerB.address)],
blockhash,
blockhash: wrapBlockhash,
});
const signedWrapTxC = await partiallySignTransactionMessageWithSigners(wrapTxC);

// Lastly, all signatures are combined together and broadcast

const combinedTx = combinedMultisigWrapTx({
const combinedWrapTx = combinedMultisigTx({
signedTxs: [signedWrapTxA, signedWrapTxB, signedWrapTxC],
blockhash,
});
await sendAndConfirm(combinedTx, { commitment: 'confirmed' });
await sendAndConfirm(combinedWrapTx, { commitment: 'confirmed' });

console.log('======== Multisig Wrap Successful ========');
for (const [pubkey, signature] of Object.entries(combinedWrapTx.signatures)) {
if (signature) {
const base58Sig = getBase58Decoder().decode(signature);
console.log(`pubkey: ${pubkey}`);
console.log(`signature: ${base58Sig}`);
console.log('-----');
}
}

// Unwraps from the token account owned by MULTISIG_SPL_TOKEN_2022

const { value: unwrapBlockhash } = await rpc.getLatestBlockhash().send();

const unwrapTxA = multisigOfflineSignUnwrap({
payer: createNoopSigner(payer.address),
unwrappedEscrow: createEscrowMessage.keyPair.address,
wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
amount: AMOUNT_TO_WRAP,
unwrappedMint: UNWRAPPED_MINT_ADDRESS,
wrappedTokenAccount: recipientTokenAccountMessage.keyPair.address,
recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
transferAuthority: MULTISIG_SPL_TOKEN_2022,
wrappedMint,
wrappedMintAuthority,
unwrappedTokenProgram,
multiSigners: [signerA, createNoopSigner(signerB.address)],
blockhash: unwrapBlockhash,
});
const signedUnwrapTxA = await partiallySignTransactionMessageWithSigners(unwrapTxA);

const unwrapTxB = multisigOfflineSignUnwrap({
payer: createNoopSigner(payer.address),
unwrappedEscrow: createEscrowMessage.keyPair.address,
wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
amount: AMOUNT_TO_WRAP,
unwrappedMint: UNWRAPPED_MINT_ADDRESS,
wrappedTokenAccount: recipientTokenAccountMessage.keyPair.address,
recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
transferAuthority: MULTISIG_SPL_TOKEN_2022,
wrappedMint,
wrappedMintAuthority,
unwrappedTokenProgram,
multiSigners: [createNoopSigner(signerA.address), signerB],
blockhash: unwrapBlockhash,
});
const signedUnwrapTxB = await partiallySignTransactionMessageWithSigners(unwrapTxB);

const unwrapTxC = multisigOfflineSignUnwrap({
payer: payer,
unwrappedEscrow: createEscrowMessage.keyPair.address,
wrappedTokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
amount: AMOUNT_TO_WRAP,
unwrappedMint: UNWRAPPED_MINT_ADDRESS,
wrappedTokenAccount: recipientTokenAccountMessage.keyPair.address,
recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
transferAuthority: MULTISIG_SPL_TOKEN_2022,
wrappedMint,
wrappedMintAuthority,
unwrappedTokenProgram,
multiSigners: [createNoopSigner(signerA.address), createNoopSigner(signerB.address)],
blockhash: unwrapBlockhash,
});
const signedUnwrapTxC = await partiallySignTransactionMessageWithSigners(unwrapTxC);

const combinedUnwrapTx = combinedMultisigTx({
signedTxs: [signedUnwrapTxA, signedUnwrapTxB, signedUnwrapTxC],
blockhash,
});
await sendAndConfirm(combinedUnwrapTx, { commitment: 'confirmed' });

console.log('======== Confirmed Multisig Tx ✅ ========');
for (const [pubkey, signature] of Object.entries(combinedTx.signatures)) {
console.log('======== Multisig Unwrap Successful ========');
for (const [pubkey, signature] of Object.entries(combinedUnwrapTx.signatures)) {
if (signature) {
const base58Sig = getBase58Decoder().decode(signature);
console.log(`pubkey: ${pubkey}`);
console.log(`signature: ${base58Sig}`);
console.log('-----');
}
}
};
}

void main();
28 changes: 24 additions & 4 deletions clients/js/src/examples/single-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { singleSignerWrapTx } from '../wrap';

import { createEscrowAccountTx, createTokenAccountTx } from '../utilities';
import { createMintTx } from '../create-mint';
import { singleSignerUnwrapTx } from '../unwrap';

// Replace these consts with your own
const PRIVATE_KEY_PAIR = new Uint8Array([
Expand All @@ -25,7 +26,7 @@ const UNWRAPPED_MINT_ADDRESS = address('FAbYm8kdDsyc6csvTXPMBwCJDjTVkZcvrnyVVTSF
const UNWRAPPED_TOKEN_ACCOUNT = address('4dSPDdFuTbKTuJDDtTd8SUdbH6QY42hpTPRi6RRzzsPF');
const AMOUNT_TO_WRAP = 100n;

const main = async () => {
async function main() {
const rpc = createSolanaRpc('http://127.0.0.1:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900');
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
Expand Down Expand Up @@ -99,13 +100,32 @@ const main = async () => {

const signedWrapTx = await signTransactionMessageWithSigners(wrapMessage.tx);
await sendAndConfirm(signedWrapTx, { commitment: 'confirmed' });
const signature = getSignatureFromTransaction(signedWrapTx);
const wrapSignature = getSignatureFromTransaction(signedWrapTx);

console.log('======== Wrap Successful ========');
console.log('Wrap amount:', wrapMessage.amount);
console.log('Recipient account:', wrapMessage.recipientWrappedTokenAccount);
console.log('Escrow Account:', wrapMessage.escrowAccount);
console.log('Signature:', signature);
};
console.log('Signature:', wrapSignature);

const unwrapMessage = await singleSignerUnwrapTx({
rpc,
blockhash,
payer,
wrappedTokenAccount: recipientTokenAccountMessage.keyPair.address,
unwrappedEscrow: createEscrowMessage.keyPair.address,
amount: AMOUNT_TO_WRAP,
recipientUnwrappedToken: UNWRAPPED_TOKEN_ACCOUNT,
});

const signedUnwrapTx = await signTransactionMessageWithSigners(unwrapMessage.tx);
await sendAndConfirm(signedUnwrapTx, { commitment: 'confirmed' });
const unwrapSignature = getSignatureFromTransaction(signedUnwrapTx);

console.log('======== Unwrap Successful ========');
console.log('Unwrapped amount:', unwrapMessage.amount);
console.log('Recipient account:', unwrapMessage.recipientUnwrappedToken);
console.log('Signature:', unwrapSignature);
}

void main();
12 changes: 9 additions & 3 deletions clients/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ export {
type SingleSignerWrapArgs,
type SingleSignerWrapResult,
multisigOfflineSignWrapTx,
type TxBuilderArgsWithMultiSigners,
combinedMultisigWrapTx,
type MultiSigBroadcastArgs,
type MultiSignerWrapTxBuilderArgs,
} from './wrap';
export {
singleSignerUnwrapTx,
type SingleSignerUnwrapArgs,
type SingleSignerUnwrapResult,
multisigOfflineSignUnwrap,
} from './unwrap';
export {
createEscrowAccountTx,
type CreateEscrowAccountTxArgs,
type CreateEscrowAccountTxResult,
combinedMultisigTx,
type MultiSigCombineArgs,
} from './utilities';
Loading