> ## Documentation Index
> Fetch the complete documentation index at: https://swig-prevent-subaccount-withdrawal.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 3. Signing with Swig

> "I came. I saw. I signed it with Swig." - Julius Caesar, probably

Now that we have our Swig account set up with proper authorities from the previous tutorials, let's explore how to sign transactions. If you haven't completed the previous tutorials, please go back and complete them first as we'll be building on those concepts.

Create a new file called `tutorial-3.ts` in your `examples/classic/transfer/tutorial` directory for the `classic` version or `examples/kit/transfer/tutorial` for `kit`:

```bash
touch tutorial/tutorial-3.ts
```

## Example

<Tabs>
  <Tab title="Classic">
    ```typescript
    import {
      Connection,
      Keypair,
      LAMPORTS_PER_SOL,
      PublicKey,
      Transaction,
      sendAndConfirmTransaction,
    } from "@solana/web3.js";

    import {
      Actions,
      createEd25519AuthorityInfo,
      fetchSwig,
      findSwigPda,
      getAddAuthorityInstructions,
      getCreateSwigInstruction,
      getSignInstructions,
    } from "@swig-wallet/classic";

    import {
      ASSOCIATED_TOKEN_PROGRAM_ID,
      TOKEN_PROGRAM_ID,
      createAssociatedTokenAccount,
      createMint,
      createTransferInstruction,
      getAccount,
      mintTo,
    } from "@solana/spl-token";

    import chalk from "chalk";

    async function createSwigAccount(connection: Connection, user: Keypair) {
      try {
        const id = new Uint8Array(32);
        crypto.getRandomValues(id);
        const swigAddress = findSwigPda(id);
        const rootAuthorityInfo = createEd25519AuthorityInfo(user.publicKey);
        const rootActions = Actions.set().manageAuthority().get();

        const createSwigIx = await getCreateSwigInstruction({
          payer: user.publicKey,
          id,
          actions: rootActions,
          authorityInfo: rootAuthorityInfo,
        });

        const transaction = new Transaction().add(createSwigIx);
        const signature = await sendAndConfirmTransaction(connection, transaction, [
          user,
        ]);

        console.log(
          chalk.green("✓ Swig account created at:"),
          chalk.cyan(swigAddress.toBase58()),
        );
        console.log(chalk.blue("Transaction signature:"), chalk.cyan(signature));
        return swigAddress;
      } catch (error) {
        console.error(
          chalk.red("✗ Error creating Swig account:"),
          chalk.red(error),
        );
        throw error;
      }
    }

    async function addNewAuthority(
      connection: Connection,
      rootUser: Keypair,
      newAuthority: Keypair,
      swigAddress: PublicKey,
      actions: any,
      description: string,
    ) {
      try {
        const swig = await fetchSwig(connection, swigAddress);
        const rootRole = swig.findRolesByEd25519SignerPk(rootUser.publicKey)[0];
        if (!rootRole) {
          throw new Error("Root role not found for authority");
        }

        const addAuthorityInstructions = await getAddAuthorityInstructions(
          swig,
          rootRole.id,
          createEd25519AuthorityInfo(newAuthority.publicKey),
          actions,
        );

        const transaction = new Transaction().add(...addAuthorityInstructions);
        await sendAndConfirmTransaction(connection, transaction, [rootUser]);
        console.log(
          chalk.green(`✓ New ${description} authority added:`),
          chalk.cyan(newAuthority.publicKey.toBase58()),
        );
      } catch (error) {
        console.error(
          chalk.red(`✗ Error adding ${description} authority:`),
          chalk.red(error),
        );
        throw error;
      }
    }

    async function displayTokenBalance(
      connection: Connection,
      tokenAccount: PublicKey,
      label: string,
    ) {
      const account = await getAccount(connection, tokenAccount);
      console.log(
        chalk.yellow(`${label} token balance:`),
        chalk.cyan(account.amount.toString()),
      );
    }

    (async () => {
      console.log(
        chalk.blue("🚀 Starting tutorial - Token Authority and Transfers"),
      );

      // Connect to local Solana network
      const connection = new Connection("http://localhost:8899", "confirmed");

      // Create and fund the root user
      const rootUser = Keypair.generate();
      console.log(
        chalk.green("👤 Root user public key:"),
        chalk.cyan(rootUser.publicKey.toBase58()),
      );

      let airdrop = await connection.requestAirdrop(
        rootUser.publicKey,
        100 * LAMPORTS_PER_SOL,
      );
      let blockhash = await connection.getLatestBlockhash();
      await connection.confirmTransaction({
        signature: airdrop,
        blockhash: blockhash.blockhash,
        lastValidBlockHeight: blockhash.lastValidBlockHeight,
      });

      // Create the Swig account
      console.log(chalk.yellow("\n📝 Creating Swig account..."));
      const swigAddress = await createSwigAccount(connection, rootUser);

      // Create token mint
      console.log(chalk.yellow("\n💎 Creating token mint..."));
      const mintAuthority = Keypair.generate();
      const tokenMint = await createMint(
        connection,
        rootUser,
        mintAuthority.publicKey,
        null,
        0,
      );
      console.log(
        chalk.green("✓ Token mint created:"),
        chalk.cyan(tokenMint.toBase58()),
      );

      // Create token accounts
      console.log(chalk.yellow("\n💰 Creating token accounts..."));
      const swigTokenAccount = await createAssociatedTokenAccount(
        connection,
        rootUser,
        tokenMint,
        swigAddress,
        {},
        TOKEN_PROGRAM_ID,
        ASSOCIATED_TOKEN_PROGRAM_ID,
        true,
      );
      console.log(
        chalk.green("✓ Swig token account created:"),
        chalk.cyan(swigTokenAccount.toBase58()),
      );

      const recipientKeypair = Keypair.generate();
      const recipientTokenAccount = await createAssociatedTokenAccount(
        connection,
        rootUser,
        tokenMint,
        recipientKeypair.publicKey,
      );
      console.log(
        chalk.green("✓ Recipient token account created:"),
        chalk.cyan(recipientTokenAccount.toBase58()),
      );

      // Mint initial tokens to Swig account
      console.log(chalk.yellow("\n🏦 Minting tokens to Swig account..."));
      await mintTo(
        connection,
        rootUser,
        tokenMint,
        swigTokenAccount,
        mintAuthority,
        10,
      );
      await displayTokenBalance(connection, swigTokenAccount, "Swig");
      await displayTokenBalance(connection, recipientTokenAccount, "Recipient");

      // Create token authority with permission to send exactly 10 tokens
      const tokenAuthority = Keypair.generate();
      console.log(
        chalk.green("\n👥 Token authority public key:"),
        chalk.cyan(tokenAuthority.publicKey.toBase58()),
      );
      await connection.requestAirdrop(
        tokenAuthority.publicKey,
        100 * LAMPORTS_PER_SOL,
      );
      console.log(chalk.yellow("\n🔑 Adding token authority..."));
      const tokenActions = Actions.set()
        .tokenLimit({ mint: tokenMint, amount: BigInt(10) })
        .get();
      await addNewAuthority(
        connection,
        rootUser,
        tokenAuthority,
        swigAddress,
        tokenActions,
        "token",
      );

      // Add some delay to ensure the authority is added
      await new Promise((resolve) => setTimeout(resolve, 2000));

      // First transfer - should succeed
      console.log(chalk.yellow("\n💸 Attempting first transfer of 10 tokens..."));
      try {
        const swig = await fetchSwig(connection, swigAddress);
        const tokenRole = swig.findRolesByEd25519SignerPk(
          tokenAuthority.publicKey,
        )[0];

        const transferIx = createTransferInstruction(
          swigTokenAccount,
          recipientTokenAccount,
          swigAddress,
          BigInt(10),
        );

        const signedTransfer = await signInstruction(
          tokenRole,
          tokenAuthority.publicKey,
          [transferIx],
        );

        const transaction = new Transaction().add(signedTransfer);
        await sendAndConfirmTransaction(connection, transaction, [tokenAuthority]);
        console.log(chalk.green("✓ First transfer successful!"));
        await displayTokenBalance(connection, swigTokenAccount, "Swig");
        await displayTokenBalance(connection, recipientTokenAccount, "Recipient");
      } catch (error) {
        console.error(chalk.red("✗ First transfer failed:"), chalk.red(error));
      }

      // Second transfer - should fail
      console.log(chalk.yellow("\n💸 Attempting second transfer (should fail)..."));
      try {
        const swig = await fetchSwig(connection, swigAddress);
        const tokenRole = swig.findRolesByEd25519SignerPk(
          tokenAuthority.publicKey,
        )[0];

        const transferIx = createTransferInstruction(
          swigTokenAccount,
          recipientTokenAccount,
          swigAddress,
          BigInt(10),
        );

        const signedTransfer = await signInstruction(
          tokenRole,
          tokenAuthority.publicKey,
          [transferIx],
        );

        const transaction = new Transaction().add(signedTransfer);
        await sendAndConfirmTransaction(connection, transaction, [tokenAuthority], {
          skipPreflight: true,
        });
        console.error(chalk.red("✗ Second transfer unexpectedly succeeded!"));
      } catch (error) {
        console.log(
          chalk.green("✓ Second transfer failed as expected:"),
          "Authority has no remaining token allowance",
        );
      }

      await displayTokenBalance(connection, swigTokenAccount, "Final Swig");
      await displayTokenBalance(
        connection,
        recipientTokenAccount,
        "Final Recipient",
      );

      console.log(chalk.green("\n✨ Tutorial completed successfully!"));
      console.log(
        chalk.yellow("🔍 Check out your transaction on Solana Explorer:"),
      );
      console.log(
        chalk.cyan(
          `https://explorer.solana.com/address/${swigAddress}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`,
        ),
      );
    })();
    ```
  </Tab>

  <Tab title="Kit">
    ```typescript
    import {
      createSolanaRpc,
      createSolanaRpcSubscriptions,
      generateKeyPairSigner,
      getSignatureFromTransaction,
      lamports,
      pipe,
      sendAndConfirmTransactionFactory,
      signTransactionMessageWithSigners,
      createTransactionMessage,
      setTransactionMessageFeePayerSigner,
      setTransactionMessageLifetimeUsingBlockhash,
      appendTransactionMessageInstructions,
      addSignersToTransactionMessage,
      type IInstruction,
      type KeyPairSigner,
    } from '@solana/kit';

    import {
      Actions,
      createEd25519AuthorityInfo,
      fetchSwig,
      findSwigPda,
      getAddAuthorityInstructions,
      getCreateSwigInstruction,
      getSignInstructions,
    } from '@swig-wallet/kit';

    import {
      TOKEN_2022_PROGRAM_ADDRESS,
      findAssociatedTokenPda,
      getCreateAssociatedTokenInstructionAsync,
      getInitializeMintInstruction,
      getMintSize,
      getMintToCheckedInstruction,
      getTransferCheckedInstruction,
    } from '@solana-program/token-2022';

    import { getCreateAccountInstruction } from '@solana-program/system';

    import chalk from 'chalk';
    import { sleepSync } from 'bun';

    function randomBytes(length: number): Uint8Array {
      const arr = new Uint8Array(length);
      crypto.getRandomValues(arr);
      return arr;
    }

    async function sendTransaction(
      instructions: IInstruction[],
      payer: KeyPairSigner,
      signers: KeyPairSigner[] = []
    ): Promise<string> {
      const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
      const txMsg = pipe(
        createTransactionMessage({ version: 0 }),
        (tx) => setTransactionMessageFeePayerSigner(payer, tx),
        (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
        (tx) => appendTransactionMessageInstructions(instructions, tx),
        (tx) => addSignersToTransactionMessage(signers, tx)
      );

      const signedTx = await signTransactionMessageWithSigners(txMsg);
      await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTx, {
        commitment: 'confirmed',
      });
      return getSignatureFromTransaction(signedTx).toString();
    }

    const rpc = createSolanaRpc('http://localhost:8899');
    const rpcSubscriptions = createSolanaRpcSubscriptions('ws://localhost:8900');

    (async () => {
      console.log(chalk.blue('🚀 Starting tutorial - Token Authority and Transfers (Kit Style)'));

      const rootUser = await generateKeyPairSigner();
      await rpc.requestAirdrop(rootUser.address, lamports(100n * 1_000_000_000n)).send();
      sleepSync(2000);
      console.log(chalk.green('👤 Root user public key:'), chalk.cyan(rootUser.address.toString()));

      const id = randomBytes(32);
      const swigAddress = await findSwigPda(id);
      const rootAuthorityInfo = createEd25519AuthorityInfo(rootUser.address);
      const rootActions = Actions.set().manageAuthority().get();

      const createSwigIx = await getCreateSwigInstruction({
        payer: rootUser.address,
        id,
        actions: rootActions,
        authorityInfo: rootAuthorityInfo,
      });

      await sendTransaction([createSwigIx], rootUser);
      console.log(chalk.green('✓ Swig account created at:'), chalk.cyan(swigAddress.toString()));

      await rpc.requestAirdrop(swigAddress, lamports(1n)).send();
      sleepSync(2000);
      console.log(chalk.green('✓ Airdropped 1 SOL to Swig account'));

      const mint = await generateKeyPairSigner();
      const mintAuthority = rootUser;
      const decimals = 0;

      const rent = await rpc.getMinimumBalanceForRentExemption(BigInt(getMintSize())).send();

      const createMintAccountIx = getCreateAccountInstruction({
        payer: mintAuthority,
        newAccount: mint,
        lamports: rent,
        space: BigInt(getMintSize()),
        programAddress: TOKEN_2022_PROGRAM_ADDRESS,
      });

      const initMintIx = getInitializeMintInstruction({
        mint: mint.address,
        decimals,
        mintAuthority: mintAuthority.address,
      });

      const [swigATA] = await findAssociatedTokenPda({
        mint: mint.address,
        owner: swigAddress,
        tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
      });

      const createSwigATAIx = await getCreateAssociatedTokenInstructionAsync({
        payer: mintAuthority,
        mint: mint.address,
        owner: swigAddress,
      });

      const mintToSwigIx = await getMintToCheckedInstruction({
        mint: mint.address,
        token: swigATA,
        mintAuthority,
        amount: 10n,
        decimals,
      });

      await sendTransaction(
        [createMintAccountIx, initMintIx, createSwigATAIx, mintToSwigIx],
        mintAuthority,
        [mint]
      );
      console.log(chalk.green('✓ Token mint and Swig ATA created and funded'));

      const recipient = await generateKeyPairSigner();
      const [recipientATA] = await findAssociatedTokenPda({
        mint: mint.address,
        owner: recipient.address,
        tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
      });

      const createRecipientATAIx = await getCreateAssociatedTokenInstructionAsync({
        payer: rootUser,
        mint: mint.address,
        owner: recipient.address,
      });

      await sendTransaction([createRecipientATAIx], rootUser);
      console.log(chalk.green('✓ Recipient ATA created'));

      const tokenAuthority = await generateKeyPairSigner();
      await rpc.requestAirdrop(tokenAuthority.address, lamports(1n)).send();
      sleepSync(2000);

      const tokenActions = Actions.set()
        .tokenLimit({ mint: mint.address, amount: 10n })
        .get();

      const swig = await fetchSwig(rpc, swigAddress);
      const rootRole = swig.findRolesByEd25519SignerPk(rootUser.address)[0];

      const addAuthorityIxs = await getAddAuthorityInstructions(
        swig,
        rootRole.id,
        createEd25519AuthorityInfo(tokenAuthority.address),
        tokenActions
      );

      await sendTransaction(addAuthorityIxs, rootUser);
      console.log(chalk.green('✓ Token authority added'));

      //check how much sol the swig has
      const swigBalance = await rpc.getBalance(swigAddress).send();
      console.log(chalk.green('✓ Swig account balance:'), chalk.cyan(swigBalance.value.toString()));

      // Add balance checks for Swig ATA and Recipient ATA
      const swigATABalance = await rpc.getTokenAccountBalance(swigATA).send();
      console.log(chalk.green('✓ Swig ATA balance:'), chalk.cyan(swigATABalance.value.amount));

      const recipientATABalance = await rpc.getTokenAccountBalance(recipientATA).send();
      console.log(chalk.green('✓ Recipient ATA balance:'), chalk.cyan(recipientATABalance.value.amount));

      // ❌ Second transfer (should fail)
      try {
        const swigLatest = await fetchSwig(rpc, swigAddress);
        const role = swigLatest.findRolesByEd25519SignerPk(tokenAuthority.address)[0];

        const transferAgain = getTransferCheckedInstruction({
          source: swigATA,
          destination: recipientATA,
          mint: mint.address,
          amount: 10n,
          decimals,
          authority: swigAddress,
        });

        const signed = await getSignInstructions(swigLatest, role.id, [transferAgain]);
        await sendTransaction(signed, tokenAuthority);
        console.error(chalk.red('✗ Second transfer unexpectedly succeeded!'));
      } catch {
        console.log(chalk.green('✓ Second transfer failed as expected (no allowance left)'));
      }

      console.log(chalk.green('\n✨ Tutorial completed successfully!'));
      console.log(
        chalk.yellow('🔍 View your Swig on Explorer:'),
        chalk.cyan(
          `https://explorer.solana.com/address/${swigAddress}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`
        )
      );
    })();
    ```
  </Tab>
</Tabs>

Let's break down how signing works with Swig.

## 1. Fetch the Swig Account

<Tabs>
  <Tab title="Classic">
    ```typescript
    const swig = await fetchSwig(connection, swigAddress);
    ```
  </Tab>

  <Tab title="Kit">
    ```typescript
    const swig = await fetchSwig(rpc, swigAddress);
    ```
  </Tab>
</Tabs>

First, we need to fetch the current state of our Swig account. This contains all the roles and their permissions that we set up in the previous tutorials.

## 2. Find the Appropriate Role

<Tabs>
  <Tab title="Classic">
    ```typescript
    const tokenRole = swig.findRolesByEd25519SignerPk(tokenAuthority.publicKey)[0];
    ```
  </Tab>

  <Tab title="Kit">
    ```typescript
    const role = swig.findRolesByEd25519SignerPk(tokenAuthority.address)[0];
    ```
  </Tab>
</Tabs>

We locate the role associated with our token authority. Remember from the previous tutorial that this role was created with specific token transfer limits.

> 💡 A single authority can have multiple roles. Always make sure you're using the correct role for your intended action.

## 3. Create the Instruction

<Tabs>
  <Tab title="Classic">
    ```typescript
    const transferIx = createTransferInstruction(
      swigTokenAccount,
      recipientTokenAccount,
      swigAddress,
      BigInt(10),
    );
    ```
  </Tab>

  <Tab title="Kit">
    ```typescript
    const transferAgain = getTransferCheckedInstruction({
      source: swigATA,
      destination: recipientATA,
      mint: mint.address,
      amount: 10n,
      decimals,
      authority: swigAddress,
    });
    ```
  </Tab>
</Tabs>

Create the instruction that needs to be signed. In this case, it's a token transfer instruction, but Swig can sign any type of instruction as long as the role has the appropriate permissions.

## 4. Sign the Instruction

<Tabs>
  <Tab title="Classic">
    ```typescript
    const signedTransferInstructions = await getSignInstructions(
      swig,
      tokenRole.id,
      [transferIx],
    );
    ```
  </Tab>

  <Tab title="Kit">
    ```typescript
    const signed = await getSignInstructions(swigLatest, role.id, [transferAgain]);
    ```
  </Tab>
</Tabs>

This is where the magic happens. The `getSignInstructions` function:

1. Verifies that the role has permission to sign this type of instruction
2. Creates the necessary signing instructions
3. Wraps the original instruction with Swig's signing logic

> 🔒 Swig validates both the authority type and action permissions at this stage

## 5. Build and Send the Transaction

<Tabs>
  <Tab title="Classic">
    ```typescript
    const transaction = new Transaction().add(...signedTransferInstructions);
    await sendAndConfirmTransaction(connection, transaction, [tokenAuthority]);
    ```
  </Tab>

  <Tab title="Kit">
    ```typescript
    await sendTransaction(signed, tokenAuthority);
    ```
  </Tab>
</Tabs>

Finally, we build the transaction with our signed instructions and send it to the network. Note that the transaction still needs to be signed by the authority's keypair because they're the one paying for the transaction fees.

## Understanding Signature Verification

When the transaction reaches the Solana network, the Swig program will:

1. Extract the original instruction
2. Verify the authority's signature
3. Check that the role's permissions allow this action
4. Execute the instruction if all checks pass

> 🧠 This multi-step verification ensures that only authorized actions are performed

## Ready to try it?

Make sure you have:

1. Completed the previous tutorials
2. Have a running validator (`bun start-validator`)
3. Created a Swig account with appropriate authorities

Then run:

```bash
bun run ./tutorial/tutorial-3.ts
```

The link at the end of the tutorial will take you to the swig on the Solana Explorer. You will see multiple transactions and there should be one failed which shows the negative case.
