⚙️Transaction Factory

The TransactionFactory component of the SDK is outlined in the interface found here. This component allows the user to craft executable solana transactions.

Instantiating an TransactionFactory

Mainnet

import { clusterApiUrl, Connection } from "@solana/web3.js";
import { TransactionFactoryImpl } from "@seabed-labs/pre-authorized-debit";

// You can use any connection object you'd like, this is just an example
const connection = new Connection(clusterApiUrl("mainnet-beta"));

const txFactory = TransactionFactoryImpl.mainnet(connection);

Devnet

import { clusterApiUrl, Connection } from "@solana/web3.js";
import { TransactionFactoryImpl } from "@seabed-labs/pre-authorized-debit";

// You can use any connection object you'd like, this is just an example
const connection = new Connection(clusterApiUrl("devnet"));

const txFactory = TransactionFactoryImpl.devnet(connection);

Custom

Point the instruction factory to a custom deployment on any cluster:

import { clusterApiUrl, Connection } from "@solana/web3.js";
import { TransactionFactoryImpl } from "@seabed-labs/pre-authorized-debit";

const connection = new Connection(/* your connection args */);
const CUSTOM_PAD_PROGRAM_ID = /* your custom program ID */;

const txFactory = TransactionFactoryImpl.custom(
  connection,
  CUSTOM_PAD_PROGRAM_ID,
  // optionally, pass in a custom read client
  //   (if not provided, our read client will be pointed to the custom program),
  // optionally, pass in a custom ix factory
  //   (if not provided, our ix factory will be pointed to the custom program),
);

Supported Methods

The transaction factory supports the following methods. These methods are 1:1 identical TX counterparts to the IX factory methods in the Instruction Factory. The main difference is that they return a different type TransactionWithMetadata which will be explored below.

⚠️ NOTE

Not all these transactions have instructions that map 1:1 to the instructions in the program. For example, pause and unpause are modeled as 2 methods here but utilize the same instruction with different parameters in the program.

Approve smart delegate has an instruction that is sent to the SPL token (or token 2022) program and not the pre-authorized-debit program.

These abstractions will hopefully make the SDK slightly easier to use.

All the methods listed above return the following generic type. This type will contain the instruction in the IX factory counterpart but might also contain additional setup/cleanup instructions such as SOL -> wSOL wrapping (or vice versa).

// Return Type
type TransactionWithMetadata<T> = {
  setupInstructions: TransactionInstruction[];
  coreInstructions: TransactionInstruction[];
  cleanupInstructions: TransactionInstruction[];
  expectedSigners: ExpectedSigner[];
  meta: T;
  buildVersionedTransaction(
    params: BuildVersionedTransactionParams,
  ): Promise<VersionedTransaction>;
  simulate(
    params: SimulateParams,
  ): Promise<TransactionSimulationResultWithMeta<T>>;
  execute(params: ExecuteParams): Promise<TransactionResultWithMeta<T>>;
}

// Inner Types
type ExpectedSigner = {
  publicKey: PublicKey; // this pubkey needs to sign for IX to be successful
  reason: string; // human-readable reason for why this pubkey needs to sign
};

type TransactionSimulationResultWithMeta<T> = {
  result: RpcResponseAndContext<SimulatedTransactionResponse>;
  meta: T;
};

type TransactionResultWithMeta<T> = {
  signature: string;
  meta: T;
};

/**
 * Provide either signers[0] or txFeesPayer at minimum, else method will throw.
 */
type SignerAndTxFeePayerParams =
  | {
      signers: Signer[];
      /**
       * If this is undefined, signers[0] is used
       */
      txFeesPayer?: PublicKey;
    }
  | { txFeesPayer: PublicKey };

type BuildVersionedTransactionParams = SignerAndTxFeePayerParams;

type SimulateParams = SignerAndTxFeePayerParams & {
  simulateConfig?: SimulateTransactionConfig;
};

type ExecuteParams = SignerAndTxFeePayerParams & {
  sendOptions?: SendOptions;
};

You can simulate and/or execute the transaction by using the utility method attached to the return type.

You can also extract the TX from it and execute it yourself outside the SDK (for example, in a browser environment where you don't have the keypair directly and only the injected wallet/provider).

Both examples are shown below using buildDebitTx (this applies to all methods).

  1. Direct execution example (same applies to simulate):

import {
  Connection,
  SendOptions,
} from "@solana/web3.js";
import { TransactionFactoryImpl } from "@seabed-labs/pre-authorized-debit";

// You can use any connection object you'd like, this is just an example
const connection = new Connection(clusterApiUrl("mainnet-beta"));
const txFactory = TransactionFactoryImpl.mainnet(connection);

const debitAuthorityKeypair = // load it however;
const txPayerKeypair = // load it however (can be same as above also);

const txSendOptions: SendOptions | undefined = // ...;

const txResult = await txFactory.buildDebitTx({
  preAuthorization: // the pre-authorization account's pubkey,
  amount: // bigint, amount to debit,
  destinationTokenAccount: // destination token account pubkey to send "amount" to,
  checkSmartDelegateEnabled: // optional boolean, checks delegate is smart delegate and fails otherwise,
  
  // optional, unwrap params if mint is NATIVE_MINT
  unwrapNativeMintParams: {
    lamportsDestinationAccount: // any pubkey ,
  },
}).execute({
  sendOptions: txSendOptions,
  signers: [txPayerKeypair, debitAuthorityKeypair],
  txFeesPayer: txPayerKeypair.publicKey,
});

const {
  signature, // tx signature
  meta, // undefined in this case since there's no metadata
} = txResult;
  1. Indirect execution example (same applies to simulate):

// ... (same code as example above)

const tx = await txFactory.buildDebitTx({
  preAuthorization: // the pre-authorization account's pubkey,
  amount: // bigint, amount to debit,
  destinationTokenAccount: // destination token account pubkey to send "amount" to,
  checkSmartDelegateEnabled: // optional boolean, checks delegate is smart delegate and fails otherwise,
  
  // optional, unwrap params if mint is NATIVE_MINT
  unwrapNativeMintParams: {
    lamportsDestinationAccount: // any pubkey ,
  },
});

const versionedTx = await tx.buildVersionedTransaction({
  txFeesPayer: txPayerKeypair.publicKey // tx fees payer
});
// alternatively, use a provider to sign and send the TX
versionedTx.sign(...);
const signature = await connection.sendTransaction(versionedTx);

Build InitSmartDelegate transaction

Builds the init_smart_delegate instruction and wrap it in an executable TX. While this is available to SDK users, this instruction only needs to be run once and it will likely be run by us for any instances of the program we deploy.

This transaction will initialize the singleton smart_delegate account.

const tx = await txFactory.buildInitSmartDelegateTx({
  payer: // the payer pubkey that'll pay for smart_delegate account creation,
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  meta: { smartDelegate: smartDelegatePubkey },
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build InitOneTimePreAuthorization transaction

Build the init_pre_authorization instruction with one-time configuration and wrap it in an executable TX.

The TX can also include IXs to wrap SOL into the token account if mint is SOL.

This transaction will initialize a pre_authorization account that represents a one-time pre-authorization.

const tx = await txFactory.buildInitOneTimePreAuthorizationTx({
  // pre-auth generic
  payer: // the pubkey paying for pre_authorization account creation,
  tokenAccount: // the token account pubkey,
  debitAuthority: // the debit authority pubkey,
  activation: // a Date instance representing the pre-auth's activation time
  
  // one-time specific
  amountAuthorized: // a bigint that represents the one-time pre-auth's amount
  expiry: // an optional Date instance representing the pre-auth's expiry time
  
  // optionally, wrap SOL if mint is NATIVE_MINT
  wrapNativeMintParams: {
    lamportsSourceAccount: // source of the lamports to wrap (has to sign),
    wrapLamportsAmount: // bigint, amount of lamports to wrap
  };
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  meta: { preAuthorization: preAuthorizationPubkey },
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build InitRecurringPreAuthorization transaction

Build the init_pre_authorization instruction with recurring configuration and wrap it in an executable TX.

The TX can also include IXs to wrap SOL into the token account if mint is SOL.

This transaction will initialize a pre_authorization account that represents a recurring pre-authorization.

const tx = await txFactory.buildInitOneTimePreAuthorizationTx({
  // pre-auth generic
  payer: // the pubkey paying for pre_authorization account creation,
  tokenAccount: // the token account pubkey,
  debitAuthority: // the debit authority pubkey,
  activation: // a Date instance representing the pre-auth's activation time
  
  // recurring specific
  repeatFrequencySeconds: // the recurring frequency in seconds (type: bigint),
  recurringAmountAuthorized: // amount authorized each cycle (type: bigint),
  numCycles: // optional, total cycles this pre-auth will be active (type: bigint),
  resetEveryCycle: // if false, the unused "recurringAmountAuthorized" will accrue across cycles (type: boolean)
  
  // optionally, wrap SOL if mint is NATIVE_MINT
  wrapNativeMintParams: {
    lamportsSourceAccount: // source of the lamports to wrap (has to sign),
    wrapLamportsAmount: // bigint, amount of lamports to wrap
  };
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  meta: { preAuthorization: preAuthorizationPubkey },
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build PausePreAuthorization transaction

Build the update_pause_pre_authorization instruction with pause = true configuration and wrap it in an executable TX.

This transaction will pause a pre_authorization account.

const tx = await txFactory.buildPausePreAuthorizationTx({
  preAuthorization: // the pre-authorization account's pubkey
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  // no metadata for this
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build UnpausePreAuthorization transaction

Build the update_pause_pre_authorization instruction with pause = false configuration and wrap it in an executable TX.

This transaction will un-pause a pre_authorization account.

const tx = await txFactory.buildUnpausePreAuthorizationTx({
  preAuthorization: // the pre-authorization account's pubkey
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  // no metadata for this
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build ClosePreAuthorizationAsOwner transaction

Build the close_pre_authorization instruction with authority as pre-authorization's token account's owner and wrap it in an executable TX.

The TX can also include IXs to unwrap SOL from the token account if mint is SOL.

This transaction will close a pre_authorization account.

const tx = await txFactory.buildClosePreAuthorizationAsOwnerTx({
  preAuthorization: // the pre-authorization account's pubkey,
  rentReceiver: // optional, the account pubkey that'll receive the lamports in the pre-auth account
  
  // optional unwrap params if mint is NATIVE_MINT
  unwrapNativeMintParams: {
    lamportsDestinationAccount: // optionally, the destination for lamports
  };
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  // no metadata for this
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build ClosePreAuthorizationAsDebitAuthority transaction

Build the close_pre_authorization instruction with authority as pre-authorization's debit authority and wrap it in an executable TX.

This transaction will close a pre_authorization account.

const tx = await txFactory.buildClosePreAuthorizationAsDebitAuthorityTx({
  preAuthorization: // the pre-authorization account's pubkey,
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  // no metadata for this
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build Debit transaction

Build the debit instruction and wrap it in an executable TX.

The TX can also include IXs to unwrap SOL from the destination token account if mint is SOL.

This transaction will debit the token account via the pre-authorization if it is valid.

const tx = await txFactory.buildDebitTx({
  preAuthorization: // the pre-authorization account's pubkey,
  amount: // bigint, amount to debit,
  destinationTokenAccount: // destination token account pubkey to send "amount" to,
  checkSmartDelegateEnabled: // optional boolean, checks delegate is smart delegate and fails otherwise
  
  // optional unwrap params if mint is NATIVE_MINT
  unwrapNativeMintParams: {
    lamportsDestinationAccount: // optionally, the destination for lamports
  };
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  // no metadata for this
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Build ApproveSmartDelegate transaction

Build the approve instruction (on SPL Token Program or on SPL Token2022 Program) for a given token account to set its delegate to the smart_delegate account and delegated amount to u64::MAX and wrap it around an executable TX.

When a pre-authorization account is created, the delegate of the token account is already configured as the smart_delegate and the delegated amount is set to u64::MAX. This instruction can be used to "reset" this delegation so that the token account's associated pre-authorizations are still valid if it was unset by another Dapp. Alternatively, if the delegated amount isn't sufficient anymore, this same method can be used to reset it back to u64::MAX.

⚠️ NOTE

This instruction does not call our pre-authorized-debit-v1 program, it calls the SPL Token/Token2022 program directly.

const tx = await txFactory.buildApproveSmartDelegateTx({
  tokenAccount: // token account pubkey,
});

const {
  // data
  setupInstructions,
  coreInstructions,
  cleanupInstructions,
  expectedSigners,
  // no metadata for this
  
  // methods
  buildVersionedTransaction,
  simulate,
  execute,
} = tx;

Last updated