Skip to content

Signing

The wallet signs with the user's keys. Keys never leave the wallet. Your dApp receives only signatures or transaction results.

signMessage()

ts
signMessage(message: string): Promise<UltraResponse<SignMessageResult>>

Asks the user to sign an arbitrary message. Signing a message sends nothing to the chain and costs no resources.

ts
const { data } = await wallet.signMessage('message: I agree to the terms of example.com');
data.signature; // "SIG_K1_K8r…"

The message must start with one of these prefixes. The extension rejects a message without one immediately, with -32000 (invalid input). The Web Wallet rejects it with -32003 after the user confirms.

PrefixUse for
message:Human-readable text. The user sees the text in the prompt.
0xHex-encoded data.
UOSxUltra-specific payloads.

The extension trims leading and trailing whitespace from the message before signing. Avoid surrounding whitespace in messages and challenges, so the signed text is exactly what your server expects.

Verifying a message signature

The wallet computes a K1 signature over SHA-256(UTF-8 bytes of the full message string, prefix included). The 0x form is not hex-decoded first; the whole string is signed as text. To verify, recover the public key and check it on chain, as described in Login with a signed nonce:

ts
import { Bytes, Signature } from '@wharfkit/antelope';

const key = Signature.from(signature).recoverMessage(Bytes.from(message, 'utf8'));
// Then confirm with get_accounts_by_authorizers that `key` belongs to the expected account.

The extension signs with the selected account's key. The Web Wallet signs with the key of the user's EBA account.

signTransaction()

ts
signTransaction(
    transaction: BlockchainTransaction | BlockchainTransaction[],
    options?: SignTransactionOptions,
): Promise<UltraResponse<SignTransactionResult>>

Shows the user a transaction to approve. On approval, the wallet signs it and broadcasts it to the network the wallet is on.

Transaction format

The SDK uses a simplified action format. The wallet adds the chain's TAPoS fields, the expiration and the signatures for you.

FieldTypeRequiredDescription
contractstringThe account the contract is deployed to, for example eosio.token.
actionstringThe action name, for example transfer.
dataobjectThe action arguments as JSON. The wallet serializes them using the contract's ABI.
authorization{ actor, permission }[]Who authorizes the action. Defaults to the connected account with active.
authorizationsstring[]Deprecated. The same, as "account@permission" strings.
ts
const { data } = await wallet.signTransaction({
    contract: 'eosio.token',
    action: 'transfer',
    data: {
        from: 'aa1aa2aa3aa4',
        to: 'bb1bb2bb3bb4',
        quantity: '1.00000000 UOS',
        memo: 'Thanks!',
    },
});

data.transactionHash; // "51c6d324522a0ee05baeee2a8857b016e47481207850074ee83f914e6adc45ae"

Token quantities must use the token's exact precision. UOS has 8 decimals ('1.00000000 UOS').

Authorizations

In the common case, where the connected user authorizes with active, leave the authorization out. Both wallets default to the connected account with active.

To use a different permission, or several signers:

ts
await wallet.signTransaction({
    contract: 'mygame.dapp',
    action: 'claim',
    data: { player: 'aa1aa2aa3aa4' },
    authorization: [{ actor: 'aa1aa2aa3aa4', permission: 'gameplay' }],
});
  • A legacy string without a permission ('aa1aa2aa3aa4') is treated as aa1aa2aa3aa4@active.
  • Names must be valid Antelope names. If you pass authorizations and none of them are valid, current Web Wallet releases reject the request with -32000 instead of signing as the connected account.
  • The extension merges authorizations and authorization and removes duplicates.
  • Call getAvailableAuthorizations() first to see which account@permission pairs the extension can actually sign for.

Web Wallet releases before September 2026

Web Wallet releases before September 2026 read only the legacy authorizations string array, and sign as the connected account with active when it is missing. Current releases read both fields and show the exact signers on the approval screen. If you need a non-default authorization and want to support older releases, also pass authorizations: ['account@permission'].

Multiple actions in one transaction

Pass an array to put several actions into one atomic transaction. It needs one approval and has one transaction ID, and if any action fails, the whole transaction fails:

ts
const { data } = await wallet.signTransaction([
    {
        contract: 'eosio.token',
        action: 'transfer',
        data: { from: 'aa1aa2aa3aa4', to: 'mygame.dapp', quantity: '5.00000000 UOS', memo: 'deposit' },
    },
    {
        contract: 'mygame.dapp',
        action: 'enter',
        data: { player: 'aa1aa2aa3aa4', round: 42 },
    },
]);

The broadcast result

When the transaction is broadcast, data contains the chain's push_transaction response, plus transactionHash:

FieldDescription
transactionHashThe transaction ID. Look it up on an explorer or with the history API.
processedThe execution trace: block_num, block_time, receipt (CPU/NET usage), action_traces (including inline actions and console output), except.

The chain has executed the transaction when signTransaction() resolves. It is not yet irreversible. If your app needs finality, wait for the block to become irreversible before you treat the result as final.

Sign without broadcasting

Pass { signOnly: true } to get the signed transaction back without sending it. Use this when your backend or another party broadcasts it, or when the transaction needs several signatures (multisig):

ts
const { data } = await wallet.signTransaction(actions, { signOnly: true });
// {
//   expiration: '2026-09-23T10:01:00',
//   ref_block_num: 12345,
//   ref_block_prefix: 987654321,
//   max_net_usage_words: 0,
//   max_cpu_usage_ms: 0,
//   delay_sec: 0,
//   context_free_actions: [],
//   actions: [ /* serialized actions */ ],
//   transaction_extensions: [],
//   signatures: ['SIG_K1_…'],
//   …
// }
  • Extension: a sign-only request is a partial-signing request. The approval screen makes the user tick an explicit consent checkbox before approving. The wallet signs with every key it holds for the requested authorizations. data.unsignedAuth lists, as "account@permission" strings, the authorizations the wallet believes it holds no key for. It is best-effort and may be missing when the wallet could not resolve the authorizations, so check the signatures before relying on the result. The result also contains transaction_id: '', transactionHash: '' and processed: null.
  • Web Wallet: signs with the keys it holds, skips actors it has no key for, and returns the signed transaction. It does not return unsignedAuth.

The signed transaction is only valid until its expiration time. Broadcast it before then.

Limits

  • The extension queues at most 10 pending requests per origin. More requests reject with -32005 (limit exceeded).
  • The Web Wallet handles one request at a time per SDK instance. A second call while a request is pending rejects with 32002 (Requested resource not available).
  • If the chain rejects the transaction (an assertion fails, for example), the promise rejects with -32003 (transaction rejected). The chain's error is in the error's data: a string with the extension, the node's JSON error with the Web Wallet.