Types
TypeScript interfaces, constants, error classes, and the SpendingLimits wrapper of @lazorkit/wallet.
The public types of @lazorkit/wallet 3.4.0, grouped by what you're doing. Each block says
whether the type is exported (import type { … } from '@lazorkit/wallet') or a
shape only: the SDK uses it internally without exporting it, so derive it from an
exported type when you need it in your code:
import type { WalletHookInterface } from '@lazorkit/wallet';
type SendTxPayload = Parameters<WalletHookInterface['signAndSendTransaction']>[0];
type CreateSessionOptions = NonNullable<Parameters<WalletHookInterface['createSession']>[0]>;Wallet state
WalletInfo
Exported from @lazorkit/wallet.
interface WalletInfo {
readonly credentialId: string; // WebAuthn credential ID (base64)
readonly passkeyPubkey: number[]; // 33-byte compressed secp256r1 pubkey
readonly expo: string; // 'web' for the React SDK
readonly platform: string;
/** Wallet PDA — internal account. Never send funds here. */
readonly smartWallet: string;
/** Vault PDA — where funds live; what users see and fund. base58. */
readonly vaultPda?: string;
readonly walletDevice: string; // this passkey's authority PDA
readonly accountName?: string;
/** 1 = made before LazorKit v2, 2 = since. Absent on records saved by 2.x (= v1). */
readonly protocolVersion?: 1 | 2;
}smartWallet ≠ vaultPda
smartWallet is the internal wallet PDA. Funds live at vaultPda
(useWallet().vaultPubkey). connect fills vaultPda in for records saved by releases
that did not store it; do not derive it with findVaultPda.
Provider props
Shape only: not exported.
interface LazorkitProviderProps {
children: ReactNode;
rpcUrl?: string;
portalUrl?: string;
paymasterConfig?: PaymasterConfig; // the relayer for v2 wallets
v1PaymasterConfig?: PaymasterConfig; // the relayer for v1 wallets (3.0.0+)
cluster?: 'mainnet' | 'devnet'; // when rpcUrl does not say (3.0.0+)
onConfirmWallet?: OnConfirmWallet; // default 'builtin'
trustedAuthorities?: string[]; // your own Ed25519 keys, base58
watchMints?: string[]; // SPL Token mints your app receives, base58
keyStorage?: 'auto' | 'memory'; // where generated keys are kept; default 'auto' (3.3.0+)
}
interface PaymasterConfig {
paymasterUrl: string; // Kora JSON-RPC endpoint
apiKey?: string; // sent as x-api-key; not a secret
}Use React.ComponentProps<typeof LazorkitProvider> in your code. The same settings,
without children, are the exported WalletConfig (the stored config) and
LazorkitAdapterConfig (the wallet adapter's, which also takes clusterSimulation).
Wallet confirmation
Everything here is exported, from 3.0.0. How it is used: Wallet Confirmation.
ConnectHookOptions
interface ConnectHookOptions {
feeMode?: 'paymaster' | 'user'; // accepted and ignored by the web SDK
confirmWallet?: string; // vault (or wallet PDA) the user chose
onConfirmWallet?: OnConfirmWallet; // overrides the provider's for this call
onSuccess?: (wallet: WalletInfo) => void; // once isConnecting is false
onFail?: (error: Error) => void;
}WalletChoice
A wallet the passkey is proven to hold, offered for the user to recognise.
interface WalletChoice {
wallet: string; // wallet PDA — an internal account
vault: string; // vault PDA — where the funds are. Show this one.
version: 1 | 2; // 1 = made before LazorKit v2, not migrated yet
lamports: number; // vault balance
signatureCount: number; // 0 = not used with this passkey yet
vaultIsSystemAccount: boolean; // false: vault handed to another program
otherAuthorities: { // every authority besides this passkey
address: string; // the authority account
type: 'passkey' | 'key'; // passkey = secp256r1, key = Ed25519
role: 'owner' | 'admin' | 'spender' | 'unknown'; // 'spender' = Delegate
key?: string; // 'key' only: the Ed25519 public key
sameRelyingParty?: boolean; // 'passkey' only: same portal as this passkey
trusted: boolean; // listed in trustedAuthorities; a passkey never is
}[];
liveSessions: { // session keys that can still sign
address: string;
sessionKey: string | null; // null when unreadable
approxExpiresAt: number | null; // ms since the epoch (400 ms a slot)
trusted: boolean;
}[];
pendingDeferred: { // authorized, not yet executed. Never trusted.
address: string;
approxExpiresAt: number | null;
}[];
tokenGrants: { // rights over the vault's token accounts
tokenAccount: string;
tokenProgram: string;
mint: string | null;
kind: 'delegate' | 'closeAuthority' | 'owner' | 'unreadable';
grantee: string | null;
trusted: boolean;
}[];
}ConfirmWalletRequest
interface ConfirmWalletRequest {
credentialId: string; // the passkey's credential id (base64)
candidates: WalletChoice[]; // in the order found — not a recommendation
}ConfirmWalletHandler
Your own chooser: resolve with the chosen vault (or wallet), or null when the user
recognises none. A throw is passed on as connect's error.
type ConfirmWalletHandler = (
request: ConfirmWalletRequest,
) => Promise<{ wallet: string } | null> | { wallet: string } | null;OnConfirmWallet
type OnConfirmWallet = ConfirmWalletHandler | 'builtin' | 'throw';Errors
All exported as classes. When each is thrown and what to do: Errors.
// Connecting
class WalletNeedsConfirmationError extends Error { // onConfirmWallet: 'throw'
readonly credentialId: string;
readonly candidates: WalletChoice[];
}
class WalletConfirmationDeclinedError extends Error {} // the user chose none; nothing saved
class PortalCancelledError extends Error {} // portal closed, or disconnect mid-connect
// Sending (3.1.0+)
class TransactionFailedError extends Error { // landed and failed
readonly signature: string;
readonly transactionError: TransactionError;
readonly slot: number;
readonly logs?: string[];
}
class TransactionExpiredError extends Error { readonly signature: string } // never landed
class TransactionOutcomeUnknownError extends Error { // check before resending
readonly signature: string | undefined;
}
class ConfirmationTimeoutError extends TransactionOutcomeUnknownError {
readonly signature: string;
readonly waitedMs: number;
}
class PreviousTransactionPendingError extends Error { readonly pendingSignature: string | undefined }
class SignatureReusedError extends Error { readonly code: 3006 }
class PaymasterError extends Error {
readonly code?: number; // JSON-RPC error code
readonly data?: unknown; // JSON-RPC error data
readonly httpStatus?: number;
readonly signature?: string;
maybeSent: boolean;
}
// Deferred execution (3.2.0+)
class DeferredExpiredError extends Error {
readonly code: 3014;
readonly deferredExecPda: PublicKey;
readonly authorizeSignature: string | undefined;
readonly expiresAtSlot: bigint | undefined;
}
interface DeferredFailureContext { // on every TX2 error (3.2.1+)
deferredExecPda?: PublicKey;
authorizeSignature?: string;
expiresAtSlot?: bigint;
}
// v1 wallets (3.0.0+)
class V1WalletRetiredError extends Error { readonly code: 4018 }
class V1WalletMigratedError extends Error {}
// Kept session and authority keys (3.3.0+)
class KeyWalletMismatchError extends Error {
readonly code: 'KEY_WALLET_MISMATCH';
readonly slot: 'session' | 'authority';
readonly reason: KeyWalletMismatchReason;
readonly keyWallet: string | undefined; // the wallet PDA the key signs for; undefined when unbound
readonly connectedWallet: string | undefined; // the connected wallet's PDA; undefined when none
}
// Exported type. 'disconnected' (3.4.0): the key's wallet is connected again, but a
// disconnect ran during this send, which is not finished. Send again.
type KeyWalletMismatchReason = 'no-wallet' | 'other-wallet' | 'unbound' | 'disconnected';
// A session or Delegate send moving an asset its policy does not name (3.4.0+), from the
// v2 program release that adds errors 3037 and 3038. Nothing in it ran.
type PolicySigner = 'session' | 'authority'; // exported type
class UnlistedSolOutflowError extends Error { // "This session is not allowed to spend SOL"
readonly code: 3037; // ActionUnlistedSolOutflow
readonly signer: PolicySigner; // 'authority': a Delegate ("This key …")
readonly cause?: unknown; // the failure as it came
}
class UnlistedTokenOutflowError extends Error { // "This session is not allowed to spend this token"
readonly code: 3038; // ActionUnlistedTokenOutflow
readonly signer: PolicySigner;
readonly cause?: unknown;
}Predicates: isDeferredExpiredError, isSignatureReusedError, isRetiredDeploymentError,
isKeyWalletMismatchError, and since 3.4.0 isUnlistedSolOutflowError and
isUnlistedTokenOutflowError. Each is true for its class from any copy of the package,
also wrapped in cause or in a wallet-adapter WalletError
(Errors › Telling errors apart). Since 3.3.0: in 3.2.1
isSignatureReusedError and isRetiredDeploymentError read only the error's text and
missed the SDK's own errors.
Transaction payloads
Shape only: not exported.
interface TransactionOptions {
addressLookupTableAccounts?: AddressLookupTableAccount[]; // v0 only
txVersion?: 'legacy' | 'v0'; // default 'v0'
clusterSimulation?: 'devnet' | 'mainnet'; // the portal's preview
feeToken?: string; // ignored by the web SDK
computeUnitLimit?: number; // ignored by the web SDK
}
// Every send-tx method on the hook.
interface SendTxPayload {
instructions: TransactionInstruction[];
transactionOptions?: TransactionOptions;
}DeferredTxPayload
Exported from @lazorkit/wallet.
authorizeAndExecute and authorizeDeferred:
interface DeferredTxPayload extends SendTxPayload {
expiryOffset?: number; // slots after TX1 that TX2 is accepted, 10–9000; default 1500
}executeDeferred takes { deferredPayload: string; transactionOptions? }.
Callbacks
Exported from @lazorkit/wallet (3.3.0+). Every action of useWallet() and the store
takes them; how they run:
Sending transactions › Callbacks.
interface ActionCallbacks<T> {
readonly onSuccess?: (result: T) => void;
readonly onFail?: (error: Error) => void;
}
interface DisconnectOptions {
readonly keepSessionKeys?: boolean; // keep the session key; default false (deleted)
readonly onSuccess?: () => void;
readonly onFail?: (error: Error) => void;
}
interface RemoveAuthorityOptions {
readonly onSuccess?: () => void;
readonly onFail?: (error: Error) => void;
}
type SignMessageOptions = ActionCallbacks<SignMessageResult>;LazorkitAdapterDisconnectOptions
Exported from @lazorkit/wallet (3.3.1+). LazorkitWalletAdapter.disconnect's argument;
wallet-adapter UIs call disconnect() without it, which deletes the session key the SDK
keeps. Since 3.4.0 the adapter's disconnect() also disconnects the store, with or without
it: keepSessionKeys decides only whether the session key is deleted.
type LazorkitAdapterDisconnectOptions = Pick<DisconnectOptions, 'keepSessionKeys'>;Signed messages
Exported from @lazorkit/wallet (3.3.1+). How they are used:
useWallet › Messages.
const SIGNED_MESSAGE_DOMAIN = 'LazorKit signed message v1'; // the challenge's tag
const OWNERSHIP_PROOF_DOMAIN = 'LazorKit ownership proof v1'; // connect's proofs
type SignedMessageInput = string | Uint8Array; // a string is signed as its UTF-8 bytes
// What signMessage resolves with: the passkey's assertion over signedMessageChallenge(message).
interface SignMessageResult {
readonly signature: string; // P-256, 64 bytes (r || s, low-S), base64
readonly signedPayload: string; // authenticatorData || SHA-256(clientDataJSON), base64
readonly clientDataJsonBase64: string; // its challenge is base64url(signedMessageChallenge(message))
readonly authenticatorDataBase64: string;
}
interface VerifySignedMessageParams {
readonly message: SignedMessageInput; // the same string, or the same bytes
readonly signature: string | Uint8Array; // 64 bytes, or their base64
readonly clientDataJsonBase64: string;
readonly authenticatorDataBase64: string;
readonly signedPayload?: string; // checked when given
readonly publicKey: string | Uint8Array | ArrayLike<number>; // P-256: 33, 65 or 64 bytes, or base64. Read it from chain.
readonly rpId?: string; // the authenticatorData's rpIdHash must be its SHA-256
readonly origin?: string; // clientDataJSON's origin must equal it
}
interface VerifyWalletMessageParams extends Omit<VerifySignedMessageParams, 'publicKey' | 'rpId'> {
readonly connection: Connection; // the cluster the wallet lives on
readonly wallet: string | PublicKey; // the claimed wallet: vault or wallet PDA
readonly credentialId: string; // the passkey's credential id, base64
readonly rpId: string; // the passkey's relying party, e.g. 'portal.lazor.sh'
readonly cluster?: 'mainnet' | 'devnet'; // for an RPC URL that does not say
}
function signedMessageChallenge(message: SignedMessageInput): Uint8Array; // tag || SHA-256(tag || message), 58 bytes
function verifySignedMessage(params: VerifySignedMessageParams): boolean; // offline; never throws
function verifyWalletMessage(params: VerifyWalletMessageParams): Promise<boolean>; // key read from chain
function createOwnershipChallenge(): Uint8Array; // OWNERSHIP_PROOF_DOMAIN || 32 random bytes, 59 bytesverifySignatureBrowser (and useWallet().verifyMessage, which calls it) is deprecated
since 3.3.1: it checks only that signature is over signedPayload, not which message was
signed. Never use it to authenticate.
Deferred execution payloads
DeferredPayload
Exported from @lazorkit/wallet.
What TX1 registered on chain, as authorizeDeferred returns it (serialized). The SDK
converts to and from its wire form with serializeDeferredPayload /
deserializeDeferredPayload, both exported.
interface DeferredPayload {
walletPda: PublicKey;
deferredExecPda: PublicKey;
compactInstructions: {
programIdIndex: number;
accountIndexes: number[];
data: Uint8Array;
}[];
remainingAccounts: {
pubkey: PublicKey;
isSigner: boolean;
isWritable: boolean;
}[];
executor?: PublicKey; // who the accounts hash expects to send TX2
refundDestination?: PublicKey; // the Authorize payer: the only refund destination the program accepts
}The serialized form carries version: 2; a payload from the other protocol version is
refused. Re-authorize rather than replaying one across the boundary.
Session payloads
Shape only: not exported.
interface CreateSessionPayload {
expiresInSlots?: bigint; // default 50,000 slots
spendingLimits?: SpendingLimits; // required unless unrestricted; checked before the prompt
unrestricted?: boolean; // a session with no limits, explicitly
sessionKey?: PublicKey | string; // register an external key; nothing generated or stored.
// One that already has a session: resolves with that
// session as it was made, whatever the limits (3.4.0:
// still checked first). Revoke it to change them.
}
interface RevokeSessionPayload {
sessionPda?: PublicKey | string; // default: the session whose key the SDK keeps (the connected wallet's)
}SpendingLimits
Exported from @lazorkit/wallet.
High-level wrapper around the on-chain action catalogue. The SDK translates
SpendingLimits into SessionAction[] before submission (spendingLimitsToActions). At
least one limit is required unless you pass unrestricted: true.
interface SpendingLimits {
/** Lifetime SOL cap (lamports) — session dies once spent. */
solLifetimeCap?: bigint;
/** Maximum lamports per single execute. */
solPerTxMax?: bigint;
/** SOL cap that resets every `windowSlots` slots. */
solRecurring?: {
limit: bigint;
windowSlots: bigint;
};
/** The tokens the session may spend, one entry per mint (3.4.0+). */
tokens?: readonly TokenSpendingLimit[];
}
/** Exported (3.4.0+). The limits on one token, in the mint's base units; at least one. */
interface TokenSpendingLimit {
mint: PublicKey | string; // the token's mint
lifetimeCap?: bigint; // exhausted for this token once spent
perTxMax?: bigint; // max per single execute
recurring?: { // cap that resets every windowSlots slots
limit: bigint;
windowSlots: bigint;
};
}An asset the limits do not name cannot leave the wallet, from the v2 program release that
adds errors 3037 and 3038 (not deployed yet; until then it is not bounded at all): with no
SOL limit the session spends no SOL, rent the vault pays for a new account included, and
it spends a token only with a tokens entry for its mint. wSOL is a mint of its own. See
Session Keys › What a policy bounds.
createSession checks the limits before anything is read or the passkey is asked, and
throws on a tokens entry with no limit, a mint named twice, an amount outside a u64, a
window of 0 slots, more than 16 actions, or more than 244 bytes of actions. A SOL limit
takes 19 bytes (solRecurring 43), a token's lifetimeCap or perTxMax 51 and its
recurring 75.
// The actions a SpendingLimits stands for, checked the same way (3.4.0+). Also a
// Delegate's policy: serializeActions(spendingLimitsToActions(limits)).
function spendingLimitsToActions(limits: SpendingLimits | undefined): SessionAction[];Need program limits?
SpendingLimits covers SOL and tokens. For program whitelisting or per-action expiries,
use the raw SessionAction catalogue with
LazorKitClient.createSession.
Common windows
windowSlots | at 400 ms a slot |
|---|---|
9_000n | 1 hour |
54_000n | 6 hours |
216_000n | 1 day |
1_512_000n | 7 days |
A slot's length varies by cluster and load (devnet ran near 230 ms in September 2026), so these are approximations.
Authority payloads
Shape only: not exported.
interface AddAuthorityPayload {
role: number; // required, no default: ROLE_SPENDER (2, a Delegate) | ROLE_ADMIN (1) | ROLE_OWNER (0, v1 only)
policy?: Uint8Array; // required for ROLE_SPENDER on v2: serializeActions([...]).
// Names every asset the key may spend; within 244 bytes (not checked)
unrestricted?: boolean; // required to add a key to a v1 wallet
}removeAuthority takes a positional targetAuthorityPda: string and optional
RemoveAuthorityOptions.
Changed in 3.3.0
Breaking: role is required. In 3.2.1 it was optional and defaulted to ROLE_ADMIN.
See useWallet › addAuthority.
Ranks & auth types
export const ROLE_OWNER = 0; // manages every authority, including other Owners
export const ROLE_ADMIN = 1; // manages Delegates and sessions
export const ROLE_SPENDER = 2; // a Delegate: manages nothing, spends within its policy
export const AUTH_TYPE_ED25519 = 0;
export const AUTH_TYPE_SECP256R1 = 1;See Ranks & policies.
Low-level SessionAction
The raw action catalogue, for sessions created with LazorKitClient.createSession and for
Delegate policies (serializeActions). Actions, SessionActionType, SessionAction
and serializeActions are exported.
enum SessionActionType {
SolLimit = 1,
SolRecurringLimit = 2,
SolMaxPerTx = 3,
TokenLimit = 4,
TokenRecurringLimit = 5,
TokenMaxPerTx = 6,
ProgramWhitelist = 10,
ProgramBlacklist = 11,
}import { Actions, serializeActions } from '@lazorkit/wallet';
import { PublicKey } from '@solana/web3.js';
const mint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
const policy = serializeActions([
Actions.solLimit(1_000_000_000n),
Actions.solRecurringLimit({ limit: 100_000_000n, window: 216_000n }),
Actions.solMaxPerTx(50_000_000n),
Actions.tokenLimit({ mint, remaining: 100_000_000n }),
Actions.tokenRecurringLimit({ mint, limit: 10_000_000n, window: 216_000n }),
Actions.tokenMaxPerTx({ mint, max: 5_000_000n }),
Actions.programWhitelist(new PublicKey('11111111111111111111111111111111')),
]);Every builder takes an optional expiresAt slot as its last argument (inside the object
for the object forms).
Constants
| Export | Value |
|---|---|
DEFAULTS.PORTAL_URL | https://portal.lazor.sh |
DEFAULTS.PAYMASTER_URL | https://kora.devnet.lazorkit.com |
DEFAULTS.RPC_ENDPOINT | https://api.devnet.solana.com |
DEFAULTS.SESSION_EXPIRY_SLOTS | 50000n |
DEFAULTS.DEFERRED_EXPIRY_SLOTS | 1500 |
MIN_DEFERRED_EXPIRY_SLOTS / MAX_DEFERRED_EXPIRY_SLOTS | 10 / 9000 |
SIGNATURE_REUSED_CODE | 3006 |
DEFERRED_EXPIRED_CODE | 3014 |
RETIRED_DEPLOYMENT_CODE | 4018 |
UNLISTED_SOL_OUTFLOW_CODE / UNLISTED_TOKEN_OUTFLOW_CODE | 3037 / 3038 (3.4.0+) |
PROGRAM_ID / PROGRAM_ADDRESS | the v2 mainnet id |
PROGRAM_ID_MAINNET, PROGRAM_ID_DEVNET, PROGRAM_ID_MAINNET_V1, PROGRAM_ID_DEVNET_V1 | see Networks & versions |
SIGNED_MESSAGE_DOMAIN | 'LazorKit signed message v1' (3.3.1+) |
OWNERSHIP_PROOF_DOMAIN | 'LazorKit ownership proof v1' (3.3.1+) |
Other exported helpers: forgetStoredKeys() (deletes the session and authority keys the SDK
keeps; 3.3.0+), registerCluster(rpcUrl, cluster) (what the cluster prop does),
getCredentialHash(credentialIdBase64), signedMessageChallenge, verifySignedMessage
and verifyWalletMessage (above), createOwnershipChallenge (since
3.3.1 the domain-separated 59-byte challenge: the bytes of @lazorkit/sdk-legacy 1.4.0's
createTaggedOwnershipChallenge, not of its createOwnershipChallenge, which stays 32 bare
random bytes), verifyOwnershipProof, pickOwnWallet, selectWalletByAddress,
readAuthorityCounter, readAuthorityPubkey, extractErrorCode, errorFromCode,
ERROR_NAMES (3036–3038 and 4018 named since 3.4.0), spendingLimitsToActions
(above), and the account readers AuthorityAccount / SessionAccount.
Paymaster (exported) takes { protocolVersion?, beforeAttempt? } as its second argument.
beforeAttempt (3.4.0) runs right before each attempt to send, retries included, of every
send that paymaster makes, and what it throws stops the send. The SDK uses it to check a
kept key before each attempt.
Hook interface
WalletHookInterface (exported) is the full return type of useWallet:
interface WalletHookInterface {
// State
smartWalletPubkey: PublicKey | null; // the wallet PDA (internal)
vaultPubkey: PublicKey | null; // the vault
wallet: WalletInfo | null;
protocolVersion: 1 | 2 | null;
isConnected: boolean;
isLoading: boolean;
isConnecting: boolean;
isSigning: boolean;
error: Error | null;
// Core. Every action takes onSuccess / onFail (ActionCallbacks), in its payload or options.
connect: (options?: ConnectHookOptions) => Promise<WalletInfo>;
disconnect: (options?: DisconnectOptions) => Promise<void>;
signAndSendTransaction: (payload: SendTxPayload & ActionCallbacks<string>) => Promise<string>;
signMessage: (message: string, options?: SignMessageOptions) => Promise<SignMessageResult>;
/** @deprecated Never use it to authenticate: use verifyWalletMessage. */
verifyMessage: (args: {
signedPayload: Uint8Array;
signature: Uint8Array;
publicKey: Uint8Array;
}) => Promise<boolean>;
// Session
createSession: (payload?: {
expiresInSlots?: bigint;
spendingLimits?: SpendingLimits;
sessionKey?: PublicKey | string;
unrestricted?: boolean;
onSuccess?: (sessionPda: string, sessionPublicKey: string) => void;
onFail?: (error: Error) => void;
}) => Promise<{ sessionPda: string; sessionPublicKey: string }>;
revokeSession: (payload?: {
sessionPda?: PublicKey | string;
onSuccess?: () => void;
onFail?: (error: Error) => void;
}) => Promise<void>;
signAndSendWithSession: (payload: SendTxPayload & ActionCallbacks<string>) => Promise<string>;
// Authority
addAuthority: (payload: {
role: number;
policy?: Uint8Array;
unrestricted?: boolean;
onSuccess?: (authorityPda: string, authorityPublicKey: string) => void;
onFail?: (error: Error) => void;
}) => Promise<{ authorityPda: string; authorityPublicKey: string }>;
removeAuthority: (targetAuthorityPda: string, options?: RemoveAuthorityOptions) => Promise<void>;
signAndSendWithAuthority: (payload: SendTxPayload & ActionCallbacks<string>) => Promise<string>;
// Deferred execution
authorizeAndExecute: (payload: DeferredTxPayload & ActionCallbacks<string>) => Promise<string>;
authorizeDeferred: (
payload: DeferredTxPayload & ActionCallbacks<{ signature: string; deferredPayload: string }>,
) => Promise<{ signature: string; deferredPayload: string }>;
executeDeferred: (payload: {
deferredPayload: string;
transactionOptions?: TransactionOptions;
} & ActionCallbacks<string>) => Promise<string>;
}