New: AI Agent SDK is live — integrate Claude-powered wallet actions into your app. Read the docs
SDK Error Reference

SDK Error Reference

The Veil SDK and the underlying Soroban wallet contract produce typed errors. This page enumerates every error class and contract error code, when each is thrown, and how to recover.


TypeScript SDK errors

These classes are thrown by methods in invisible-wallet-sdk. They extend the native Error class, so you can catch them with a standard try/catch and narrow by instanceof or by the .name property.

NoGuardianSet

PropertyValue
name"NoGuardianSet"
Message"No guardian has been set for this wallet"

When thrown: You called initiateRecovery() or completeRecovery() on a wallet that was never configured with a guardian key via setGuardian().

Recovery: Register a guardian before initiating recovery. If you are already locked out and no guardian was ever set, there is no programmatic recovery path — the wallet contract is inaccessible.

import { NoGuardianSet } from 'invisible-wallet-sdk'
 
try {
  await wallet.initiateRecovery(guardianKeypair)
} catch (err) {
  if (err instanceof NoGuardianSet) {
    // prompt user to set a guardian before this flow is reachable
  }
}

RecoveryTimelockActive

PropertyValue
name"RecoveryTimelockActive"
Constructornew RecoveryTimelockActive(expiresAt: number)
Message"Recovery timelock is active until <ISO 8601 date>"

When thrown: You called completeRecovery() before the on-chain timelock has expired. The expiresAt Unix timestamp (seconds) is the earliest time the recovery can be completed.

Recovery: Wait until expiresAt and retry. Display the expiry time to the user so they know when to return.

import { RecoveryTimelockActive } from 'invisible-wallet-sdk'
 
try {
  await wallet.completeRecovery(newPublicKey, guardianKeypair)
} catch (err) {
  if (err instanceof RecoveryTimelockActive) {
    const unlockDate = new Date(err.message.split('until ')[1])
    console.log(`Try again after ${unlockDate.toLocaleString()}`)
  }
}

RecoveryNotPending

PropertyValue
name"RecoveryNotPending"
Message"No recovery is currently pending"

When thrown: You called completeRecovery() but no recovery was ever initiated for this wallet, or a previous recovery was already completed or cancelled.

Recovery: Call initiateRecovery() first to start a new recovery window, then wait for the timelock to expire before calling completeRecovery().


Contract error codes (WalletError)

The WalletError enum is defined in contracts/invisible_wallet/src/lib.rs. When a Soroban invocation fails with one of these codes the SDK wraps it in a standard Error whose message contains the numeric code returned by the RPC node.

CodeNameWhen thrownRecovery
1AlreadyInitializedinit() called on a wallet that already has a registered signerDo not call init() more than once per contract instance
2InvalidSignatureFormatThe WebAuthn signature bytes could not be parsed as DER-encoded ECDSAEnsure derToRawSignature in utils.ts ran without error before submitting
3SignerNotAuthorizedThe supplied public key is not in the registered-signers listRegister the key via add_signer() using an already-authorized signer
4InvalidPublicKeyThe supplied public key bytes are not a valid uncompressed P-256 point (must be 65 bytes, prefix 0x04)Re-extract the key with extractP256PublicKey
5InvalidSignatureThe signature bytes are not valid (wrong length, malformed r/s)Re-run derToRawSignature; check the raw byte length is 64
6SignatureVerificationFailedP-256 ECDSA verification failed — signature does not match the payload under the supplied keyThe WebAuthn assertion was for a different transaction; re-request a fresh assertion
7InvalidChallengechallenge is absent from clientDataJSON or does not match base64url(signature_payload)Ensure the challenge passed to navigator.credentials.get() is base64url(signature_payload) with no padding
8RpIdMismatchrpIdHash in authData does not match SHA-256(expected_rp_id) stored in the contractThe credential was registered for a different domain; register a new credential on the correct origin
9OriginMismatchorigin in clientDataJSON does not match the expected originThe assertion came from the wrong website; ensure your app URL matches the registered RP ID
10CannotRemoveLastSignerremove_signer() would leave the wallet with zero signersAdd a replacement signer before removing the last one
11SignerNotFoundThe signer index supplied to remove_signer() does not existQuery the current signer list before removing
12NoGuardianSetinitiate_recovery() called but no guardian was registered via set_guardian()Register a guardian first
13RecoveryAlreadyPendinginitiate_recovery() called while a recovery is already in progressCancel the existing recovery or wait for it to complete
14RecoveryNotPendingcomplete_recovery() called with no active recoveryCall initiate_recovery() first
15RecoveryTimelockActivecomplete_recovery() called before the timelock has expiredWait for the on-chain ledger timestamp to exceed the stored expiry
16NonceMismatchThe transaction nonce is out of sequence (replay or ordering protection)Fetch the current on-chain nonce and retry with the correct value
17InsufficientAllowanceA session-key transfer exceeds its approved spending limitRequest a new session key with a higher allowance, or use the primary passkey signer
18AllowanceExpiredThe session-key allowance period has endedIssue a new session key
19SessionKeyExpiredThe session key’s validity window has passedIssue a new session key
20SessionKeyAclViolationThe session key attempted an operation not permitted by its ACLUse a session key with broader permissions, or use the primary passkey signer

Mapping contract codes to SDK errors

When the Soroban RPC returns a simulation or transaction failure the error object contains a diagnosticEvents array. The contract code is embedded as a u32 in the event data. The SDK does not currently re-wrap these into typed classes; callers must inspect the raw RPC error. A future SDK release will expose a helper:

// Planned (not yet released)
import { parseContractError } from 'invisible-wallet-sdk'
 
try {
  await wallet.send(destination, amount, signerKeypair)
} catch (err) {
  const code = parseContractError(err) // returns WalletError code or null
  if (code === 6) { /* SignatureVerificationFailed — re-request assertion */ }
}

Until parseContractError ships, inspect err.message for the string "Error(Contract, #N)" where N is the numeric code from the table above.