Signer cookbook: any wallet signs a Crank transaction¶
Crank has no wallet plugins because it needs none. Every value-bearing tool returns a base64-encoded, unsigned Solana transaction. Anything that can sign a Solana transaction — a browser wallet, a hardware wallet, a multisig, a TEE signer, an embedded wallet — can sign it. Provider-agnostic is not a feature matrix here; it falls out of the unsigned-transaction flow itself.
Every recipe below is the same three steps:
- Deserialize the
transactionfield from the tool'sokenvelope. - Sign it with your provider of choice.
- Broadcast it yourself, or pass it back via the tool's
signed_transactionargument and let Crank relay it (post-trade verification runs either way).
The snippets are reference shapes, not pinned integrations — check each
provider's current documentation for exact package names and versions. In all
of them, unsignedB64 is the transaction string returned by a Crank tool,
and the key never touches Crank.
Common setup (TypeScript)¶
import { Connection, PublicKey, VersionedTransaction } from "@solana/web3.js";
const connection = new Connection("https://api.mainnet-beta.solana.com");
const tx = VersionedTransaction.deserialize(
Buffer.from(unsignedB64, "base64"),
);
Phantom (browser)¶
Phantom injects a provider that signs in the extension — the key never enters your page. The same shape works for any wallet-adapter wallet (Solflare, Backpack, et al.), which is what the Crank web app itself uses.
const provider = window.phantom?.solana; // or wallet-adapter's signTransaction
await provider.connect();
const signed = await provider.signTransaction(tx);
const signature = await connection.sendRawTransaction(signed.serialize());
Or sign-and-send in one call and let Phantom broadcast:
Ledger (hardware)¶
A hardware wallet keeps the key in the device's secure element; signing requires a physical confirmation on the device. The simplest route in a browser is wallet-adapter's Ledger support. Directly, via Ledger's transport libraries:
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
import Solana from "@ledgerhq/hw-app-solana";
const transport = await TransportWebUSB.create();
const ledger = new Solana(transport);
const { address } = await ledger.getAddress("44'/501'/0'");
const ledgerPublicKey = new PublicKey(address);
const { signature } = await ledger.signTransaction(
"44'/501'/0'", // BIP-44 derivation path
Buffer.from(tx.message.serialize()),
);
tx.addSignature(ledgerPublicKey, signature);
await connection.sendRawTransaction(tx.serialize());
For an autonomous agent a hardware wallet is a supervision choice, not an autonomy one: every transaction waits for a human press. It pairs naturally with paper mode and with reviewing an agent's proposed actions one by one.
Squads (multisig)¶
A Squads multisig does not counter-sign an existing
transaction — it wraps the transaction's message in an on-chain proposal
that vault members approve, then executes it as the vault. This is the pattern
for treasuries and teams where no single key should move funds. Ask Crank for
the transaction with the vault address as wallet_address, then:
import * as multisig from "@sqds/multisig";
// 1. Propose: wrap the unsigned transaction's message for the vault.
const ix = multisig.instructions.vaultTransactionCreate({
multisigPda,
transactionIndex,
creator: member.publicKey,
vaultIndex: 0,
transactionMessage: tx.message, // the Crank-built message
});
// 2. Members approve (each approval is its own small transaction).
// 3. Execute: the vault signs and the transaction lands on-chain.
Threshold, membership, and time locks are Squads-side configuration; Crank sees only the vault's public key and the eventual on-chain result.
Turnkey (TEE)¶
Turnkey signs inside AWS Nitro Enclaves — the same infrastructure class Crank uses for its own TEE-managed signing posture. Running your own Turnkey organization means the key lives in your enclave, under your policies, and signs Crank's transactions like any other signer:
import { Turnkey } from "@turnkey/sdk-server";
import { TurnkeySigner } from "@turnkey/solana";
const turnkey = new Turnkey({
apiBaseUrl: "https://api.turnkey.com",
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!,
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!,
defaultOrganizationId: process.env.TURNKEY_ORG_ID!,
});
const signer = new TurnkeySigner({
organizationId: process.env.TURNKEY_ORG_ID!,
client: turnkey.apiClient(),
});
await signer.addSignature(tx, walletPublicKeyBase58);
await connection.sendRawTransaction(tx.serialize());
Alternatively, skip running it yourself: omit wallet_address when calling
create_agent_wallet and Crank provisions TEE-managed signing in a dedicated
sub-organization for that wallet — see
Agent wallet provisioning.
Privy (embedded)¶
Privy embeds a wallet in your own app behind your users' existing login. The wallet object exposes standard Solana signing, so the flow is identical to any browser wallet:
import { useSignTransaction } from "@privy-io/react-auth/solana";
const { signTransaction } = useSignTransaction();
const signed = await signTransaction({
transaction: tx,
connection,
});
await connection.sendRawTransaction(signed.serialize());
Privy's server-side wallet API follows the same shape for headless agents:
deserialize, sign with the wallet's signTransaction, broadcast.
Delegated session signers (any provider)¶
Everything above also applies to a delegated session signer: generate the session keypair wherever you like — a local keypair file, a Ledger, your own Turnkey org, a Privy wallet — register its public key with Crank, and have the agent sign session-scoped transactions with it. The scope and revocation guarantees are enforced server-side regardless of which provider holds the key. That is the point: the security model does not depend on the signer, so the signer is entirely your choice.
Next¶
- The session-signing security model — what a session credential can and cannot do, TEE isolation, kill and unwind.
- How AI agents trade non-custodially — the unsigned-transaction flow and the policy gate stack.
- Quickstart — call your first tool in a few minutes.
- Code examples — the same signing rule across every agent runtime.