Signing
Unlock the wallet first, then pass an exact wallet session and chain reference to the signing method. The snippets below keep progress events visible so your UI can show what the signer is doing.
Prerequisites
You need a configured SeamsWebProvider, a wallet session, and the account or chain identity that matches the operation. See wallet setup and authentication if the wallet is still locked.
Unlock before signing
Use the unlock result to obtain the wallet session and the identity for the selected chain.
ts
import type { LoginAndCreateSessionResult, UnlockFlowEvent } from '@seams/sdk';
import type { SeamsContextType } from '@seams/sdk/react';
type UnlockWallet = SeamsContextType['unlock'];
function assertNever(value: never): never {
throw new Error(`Unhandled unlock result: ${String(value)}`);
}
function logUnlockEvent(event: UnlockFlowEvent): void {
console.log(event.phase, event.status, event.message);
}
export async function unlockWallet(
unlock: UnlockWallet,
walletId: string,
): Promise<LoginAndCreateSessionResult> {
const result = await unlock(walletId, { onEvent: logUnlockEvent });
if (!result.success) {
throw new Error(result.error);
}
switch (result.kind) {
case 'near_wallet_unlocked':
console.log('NEAR account ready', result.nearAccountId);
return result;
case 'ecdsa_wallet_unlocked':
console.log('EVM-family wallet ready', result.walletId);
return result;
default:
return assertNever(result);
}
}Send a NEAR transaction
The example sends a set_greeting function call to a NEAR testnet account and waits for EXECUTED_OPTIMISTIC.
tsx
import { ActionType, TxExecutionStatus, useSeams } from '@seams/sdk/react';
import type { FunctionCallAction } from '@seams/sdk/react';
import type { SeamsWeb, SigningFlowEvent } from '@seams/sdk';
import { nearAccountRefFromAccountId, walletSessionRefFromSession } from '@seams/sdk/advanced';
function logSigningEvent(event: SigningFlowEvent): void {
console.log(event.phase, event.status, event.message);
}
export async function signGreeting(
seams: SeamsWeb,
walletId: string,
nearAccountId: string,
): Promise<void> {
const action: FunctionCallAction = {
type: ActionType.FunctionCall,
methodName: 'set_greeting',
args: { greeting: 'Hello from Seams' },
gas: '30000000000000',
deposit: '0',
};
await seams.near.signAndSendTransaction({
walletSession: walletSessionRefFromSession({
walletId,
walletSessionUserId: walletId,
}),
nearAccount: nearAccountRefFromAccountId(nearAccountId),
receiverId: 'guest-book.testnet',
actions: [action],
options: {
waitUntil: TxExecutionStatus.EXECUTED_OPTIMISTIC,
onEvent: logSigningEvent,
},
});
}
export function SetGreetingButton() {
const { seams, loginState } = useSeams();
const onSign = async (): Promise<void> => {
if (!loginState.isLoggedIn || !loginState.walletId || !loginState.nearAccountId) {
throw new Error('Unlock a wallet with a NEAR account before signing');
}
await signGreeting(seams, loginState.walletId, loginState.nearAccountId);
};
return <button onClick={() => void onSign()}>Sign transaction</button>;
}signGreeting resolves after the configured execution status. The button example checks that the wallet is logged in and that a NEAR account is ready before it starts.
Sign a NEP-413 message
Use NEP-413 for an off-chain, domain-bound message such as a checkout approval.
ts
import type { SeamsWeb, SignNEP413MessageResult, SigningFlowEvent } from '@seams/sdk';
import { nearAccountRefFromAccountId, walletSessionRefFromSession } from '@seams/sdk/advanced';
function logSigningEvent(event: SigningFlowEvent): void {
console.log(event.phase, event.status, event.message);
}
export async function signCheckoutMessage(
seams: SeamsWeb,
walletId: string,
nearAccountId: string,
): Promise<SignNEP413MessageResult> {
const result = await seams.near.signNEP413Message({
walletSession: walletSessionRefFromSession({
walletId,
walletSessionUserId: walletId,
}),
nearAccount: nearAccountRefFromAccountId(nearAccountId),
params: {
message: 'Approve checkout quote #quote_123',
recipient: 'merchant.example',
state: 'quote_123',
},
options: { onEvent: logSigningEvent },
});
if (!result.success) {
throw new Error(result.error);
}
return result;
}The successful result contains the signed message data. The helper throws only after the SDK returns success: false, so callers can replace the throw with an inline error state when needed.
Execute an EVM-family transaction
Create a typed EIP-1559 request and provide the chain target for the network you support.
ts
import type { SeamsWeb, SigningFlowEvent } from '@seams/sdk';
import {
thresholdEcdsaChainTargetFromConfig,
walletSessionRefFromSession,
} from '@seams/sdk/advanced';
function logSigningEvent(event: SigningFlowEvent): void {
console.log(event.phase, event.status, event.message);
}
export async function executeEvmTransaction(seams: SeamsWeb, walletId: string): Promise<string> {
const walletSession = walletSessionRefFromSession({
walletId,
walletSessionUserId: walletId,
});
const chainTarget = thresholdEcdsaChainTargetFromConfig({
network: 'tempo-testnet',
rpcUrl: 'https://rpc.moderato.tempo.xyz',
explorerUrl: 'https://explore.testnet.tempo.xyz',
chainId: 42431,
});
const request = {
chain: 'evm',
kind: 'eip1559',
senderSignatureAlgorithm: 'secp256k1',
tx: {
chainId: 42431,
maxPriorityFeePerGas: 1n,
maxFeePerGas: 1n,
gasLimit: 21_000n,
to: '0x1234567890abcdef1234567890abcdef12345678',
value: 0n,
data: '0x',
},
} satisfies Extract<
Parameters<SeamsWeb['tempo']['executeEvmFamilyTransaction']>[0]['request'],
{ chain: 'evm' }
>;
const execution = await seams.tempo.executeEvmFamilyTransaction({
walletSession,
chainTarget,
request,
options: { onEvent: logSigningEvent },
});
console.log('transaction hash', execution.txHash);
return execution.txHash;
}The sample targets Tempo testnet and returns the transaction hash. Replace the recipient, fees, and chain target with values from your app's transaction builder before sending a real transaction.
Expected result
- NEAR signing resolves after the requested transaction execution status.
- NEP-413 returns a successful signed-message result.
- EVM-family execution returns
txHashafter the transaction is submitted.
Progress callbacks receive SigningFlowEvent values. Use them for a status indicator and keep the final operation identity for reconciliation.
Recoverable failures
- A cancelled approval or policy denial ends the current request. Preserve the draft intent so the person can review and retry it.
- An expired or exhausted wallet session needs a fresh unlock before retrying.
- A signing or RPC failure should remain attached to the operation being attempted; avoid submitting a second transaction until the first hash or nonce state is reconciled.
- Validate chain ids, recipients, fees, and account references in your app before calling the SDK.
Read next: advanced wallet operations, events and progress, or results and errors.