Skip to main content

Register account

Call ensureRegistered() once before the first mutating operation. The SDK caches the registration attempt per client instance.
  • Browser: posts the user’s public registration payload to your backend. The default route is /api/unlink/register.
  • Server or custodial: pass a register callback when constructing the user client. The callback usually calls admin.users.register(payload).
For direct backend code, call admin.users.register(payload) when you receive a wire payload from a browser register route:

Get address

Get the Bech32m Unlink address for this account.

Account constructors

Every high-level client is bound to a signing-capable account. The constructor you use decides which operations the account can perform. execute() needs a seed-backed account, so derive with fromMnemonic, fromSeed, fromEthereumSignature, or fromWallet. fromKeys can transfer and withdraw but cannot execute. account.fromMetaMask remains available as a deprecated alias for account.fromWallet.

Derive account from a wallet signature

The SDK can derive an Unlink account from an EOA personal_sign signature instead of a mnemonic. The Quickstart shows the one-shot account.fromWallet wrapper. This section is the lower-level reference.

Message format

The buildDeriveSeedMessage helper returns the exact string a wallet must sign:
Rules:
  • The message is the single source of truth. Bumping its format is a breaking change for every existing account.
  • appId is embedded verbatim. The SDK rejects empty strings, LF/CR, or anything over 64 bytes UTF-8 but does not canonicalise casing or whitespace. Use a stable app identifier.
  • chainId must be a positive integer.
  • The message uses the literal label Tenant: for address compatibility, even though the parameter is named appId. Do not change the message text, or existing accounts derive differently.

Sign and derive

If you control the signing path yourself, build the message, sign it, and pass the signature into account.fromEthereumSignature with the same appId and chainId.
The signature is canonicalised internally and expanded with HKDF-SHA256 into the 64-byte seed consumed by account.fromSeed. appId and chainId are bound into the HKDF salt. Any mismatch derives a fresh, empty account instead of a silent account swap. Stability across wallets relies on RFC-6979 deterministic ECDSA, which every mainstream wallet uses today. For long-term recovery, prefer the keystore export (account.export(keys)) over re-deriving from a wallet signature each session.

Get public key

Most integrations do not need the raw account public key. Use this only for custom account storage.

User storage

userStorage stores up to two opaque base64 payloads for a generic application userId. Engine does not interpret the payload. If mode is "encrypted", encrypt and decrypt client-side before calling Engine. Create the client with the application user id. Dynamic, your own auth, or any other session provider should sit behind your app’s requireUser() boundary and only supply this generic id to Engine:
For encrypted objects, store a base64-encoded envelope. The wrapping key is derived locally by an unlock adapter, such as a passphrase adapter today or a WebAuthn PRF adapter when available:
Object keys and userId values may contain letters, numbers, ., _, and -, but cannot be exactly . or ... Each payload is capped at 16,384 base64 characters.
Never derive encryption keys from Dynamic JWTs, Dynamic user ids, email, wallet addresses, Dynamic signatures, or any other identity-provider value. For WebAuthn PRF, derive a client-side KEK from the authenticator PRF output and decrypt locally; Engine must never receive PRF output, KEKs, plaintext seeds, or decrypted object contents. Use passphrase unlock as the fallback when PRF support is unavailable.