In the case of Subxt, you have a couple of options:
1. Get the signer payload, sign it and hand back to Subxt
At the end of the day, the bytes that needs signing is a signer payload which contains the transaction bytes plus some additional/extra details (or possibly a hashed version of this).
In Subxt, you can do something like this to get the bytes needed to sign some transaction and then hand back a signature and submit it:
// 1. Create a "partial" transaction:
let partial_tx = client.tx().create_partial_signed(
&payload,
Default::default()
).await?;
// 2. Fetch the bytes that need to be signed:
let signer_payload = partial_tx.signer_payload();
// 3. go off and sign them
let signature: &[u8] = {/* ... */}
// 4. Hand signature back to Subxt and submit the tx.
// At this point you need to provide the "from" address
// that corresponds to the key used to sign it.
let tx = partial_tx.sign_with_address_and_signature(
&address,
&signature
);
// 5. Now you can `tx.submit_and_watch()` or whatever to
// submit the signed tx.
See https://docs.rs/subxt/latest/subxt/book/usage/transactions/index.html for more information on signing transactions.
If you end up with a bad signature here, it can be hard to tell why. The address and signature need to match (ie the address is derived from the public key of the signer you used), and the signature algorithm needs to be suitable for the given chain (ie for Polkadot we'd expect an sr25519 signature).
2. Gather details needed for wallets like Talisman and have it sign.
Wallets like Talisman can't be simply given the signer payload bytes to be signed, and instead must be given a JSON object containing various details.
This payload has the shape:
export interface SignerPayloadSON {
/* The ss-58 encoded address */
address: string;
/* The checkpoint hash of the block, in hex */
blockHash: string;
/* The checkpoint block number, in hex */
blockNumber: string;
/* The era for this transaction, in hex */
era: string;
/* The genesis hash of the chain, in hex */
genesisHash: string;
/* The encoded method (with arguments) in hex */
method: string;
/* The nonce for this transaction, in hex */
nonce: string;
/* The current spec version for the runtime */
specVersion: string;
/* The tip for this transaction, in hex */
tip: string;
/* The current transaction version for the runtime */
transactionVersion: string;
/* The applicable signed extensions for this runtime */
signedExtensions: string[];
/* The version of the extrinsic we are dealing with */
version: number;
}
(see https://github.com/polkadot-js/api/blob/52e36534cfde94d4f032e88fee9ba0d24ccb4fb1/packages/types/src/types/extrinsic.ts#L32)
The wallet itself then takes these details and constructs a signer payload and then signs it using the selected account, handing back the signature.
We have an example in Subxt wherein we build this type in a Subxt WASM app and submit it to a browser wallet for signing; see https://github.com/paritytech/subxt/blob/ab84228af45a7d5ba5e2494172554e000da05898/examples/wasm-example/src/services.rs#L125, and more generally check out the example to learn more.
In the case of using polkadot.js itself, I think it accepts a JSON object similar to the above to sign transactions, but I'm not sure offhand!