Skip to content

Partial Signing Examples

Examples demonstrating how to build transactions where the backend partially signs a transaction and the user (fee payer) signs later.

Backend: Create and Partially Sign Transaction

ts
// Create an instruction that requires the backend signer
import { 
address
} from '@nosana/kit';
const
transferInstruction
=
getTransferSolInstruction
({
source
:
backendSigner
,
destination
:
address
('user-address'),
amount
:
lamports
(1000n),
}); // Build the transaction with the USER's address as fee payer (not their signer!) // This sets up the transaction so the user will pay fees but sign later const
transactionMessage
= await
client
.
solana
.
buildTransaction
([
transferInstruction
], {
feePayer
:
address
('user-address'), // Just the address, not the signer
}); // Partially sign the transaction with embedded signers (backend's signer) // The fee payer (user) has NOT signed yet const
partiallySignedTx
= await
client
.
solana
.
partiallySignTransaction
(
transactionMessage
);
// Serialize for transmission to the user const
serializedTx
=
client
.
solana
.
serializeTransaction
(
partiallySignedTx
);
console
.
log
('Serialized transaction:',
serializedTx
);

User: Receive, Verify, and Sign Transaction

ts
// Deserialize the received transaction
const 
receivedTx
= await
client
.
solana
.
deserializeTransaction
(
serializedTx
);
// (Optional) Decompile to inspect the transaction before signing const
decompiled
=
client
.
solana
.
decompileTransaction
(
receivedTx
);
console
.
log
('Decompiled transaction for inspection:');
console
.
log
(' - Fee payer:',
decompiled
.
feePayer
);
console
.
log
(' - Number of instructions:',
decompiled
.
instructions
.
length
);
// Verify the fee payer is the user's address if (
decompiled
.
feePayer
.
address
!==
userSigner
.
address
) {
throw new
Error
('Fee payer mismatch!');
} // Sign with the user's signer (the fee payer) const
fullySignedTx
= await
client
.
solana
.
signTransactionWithSigners
(
receivedTx
, [
userSigner
]);
// Send to the network import type {
Signature
} from '@solana/kit';
const
signature
:
Signature
= await
client
.
solana
.
sendTransaction
(
fullySignedTx
);
console
.
log
('Transaction sent:',
signature
);

Multiple Backend Signers

ts
// Create instructions that require multiple backend signers
import { 
address
} from '@nosana/kit';
const
instruction1
=
getTransferSolInstruction
({
source
:
backendSigner1
,
destination
:
address
('user-address'),
amount
:
lamports
(500n),
}); const
instruction2
=
getTransferSolInstruction
({
source
:
backendSigner2
,
destination
:
address
('user-address'),
amount
:
lamports
(500n),
}); // Build with user as fee payer const
transactionMessage
= await
client
.
solana
.
buildTransaction
([
instruction1
,
instruction2
], {
feePayer
:
address
('user-address'),
}); // Partially sign - both backend signers will sign const
partiallySignedTx
= await
client
.
solana
.
partiallySignTransaction
(
transactionMessage
);
// Serialize and send to user const
serializedTx
=
client
.
solana
.
serializeTransaction
(
partiallySignedTx
);

Complete Flow Example

This example shows the complete flow from backend to user:

ts
// ============================================================
// BACKEND SIDE: Create and partially sign a transaction
// ============================================================

import { 
address
} from '@nosana/kit';
const
transferInstruction
=
getTransferSolInstruction
({
source
:
backendSigner
,
destination
:
userSigner
.
address
,
amount
:
lamports
(1000n),
}); // Build with user's address as fee payer (not their signer!) const
transactionMessage
= await
client
.
solana
.
buildTransaction
([
transferInstruction
], {
feePayer
:
userSigner
.
address
,
}); // Partially sign with backend's signer const
partiallySignedTx
:
Transaction
&
TransactionWithBlockhashLifetime
=
await
client
.
solana
.
partiallySignTransaction
(
transactionMessage
);
// Serialize for transmission const
serializedTx
=
client
.
solana
.
serializeTransaction
(
partiallySignedTx
);
// ============================================================ // USER SIDE: Receive, verify, sign, and send // ============================================================ // Deserialize the received transaction const
receivedTx
= await
client
.
solana
.
deserializeTransaction
(
serializedTx
);
// (Optional) Inspect before signing const
decompiled
=
client
.
solana
.
decompileTransaction
(
receivedTx
);
// Verify fee payer if (
decompiled
.
feePayer
.
address
!==
userSigner
.
address
) {
throw new
Error
('Fee payer mismatch!');
} // Sign with user's signer const
fullySignedTx
= await
client
.
solana
.
signTransactionWithSigners
(
receivedTx
, [
userSigner
]);
// Send to network import type {
Signature
} from '@solana/kit';
const
signature
:
Signature
= await
client
.
solana
.
sendTransaction
(
fullySignedTx
);
console
.
log
('Transaction confirmed:',
signature
);

TIP

When to use partial signing:

  • Backend needs to sign instructions (e.g., transferring from backend-controlled accounts)
  • User should pay transaction fees
  • You want to separate backend and user signing responsibilities
  • You need to serialize/transmit transactions between services

WARNING

Always verify the transaction contents (using decompileTransaction) before signing on the user side to ensure the transaction matches expectations.