Skip to content

Vanilla JS / TS

The Sigil class is the only public surface of the vanilla entrypoint. It mounts the iframe, drives the postMessage protocol, and exposes a small async API.

Full example

import { Sigil } from '@sigilkeys/sdk';
const wallet = new Sigil({
organizationId: 'org_xxx',
publishableKey: 'pk_live_xxx',
iframeUrl: 'https://wallet.sigilkeys.com',
authMode: 'sigil',
// optional: where the iframe gets mounted. Defaults to document.body.
iframeContainer: document.getElementById('wallet-slot') ?? undefined,
});
await wallet.init();
console.log(wallet.getAddress()); // 0x...
console.log(wallet.getWallets()); // [{ network, address, curve }, ...]
const sig = await wallet.signMessage('hello');
await wallet.logout();
wallet.destroy(); // tear down iframe + transport

Methods

new Sigil(config)

Synchronous. Just wires the config; no network, no DOM mutation yet.

await sigil.init()

Mounts the iframe in iframeContainer (or document.body), waits for its load event, opens a postMessage transport against its origin, and runs the auth handshake:

  • authMode: 'sigil' — the iframe shows the email-OTP UI; init() resolves once the user has signed in (or immediately if the user already has a session in localStorage).
  • authMode: 'oidc' — the SDK calls getToken() once, sends the JWT to the iframe; the iframe validates it against your provider via Sigil backend and either reuses the existing wallet or creates one.

After init() resolves you can call the remaining methods. Calling it a second time is a no-op.

sigil.getAddress(): string | null

Returns the wallet address for the primary network (Ethereum by default), or null if there’s no wallet yet (authenticated user without a wallet — happens between sign-in and wallet creation).

sigil.getWallets(): WalletInfo[]

Returns all wallets across configured networks (one per curve). Each entry has { network, address, curve }. Useful when your integration targets multiple chains and you need the address on each.

sigil.getAddressForNetwork(network: string): string | null

Shorthand for finding the address on a specific network. Returns null if no wallet exists for that network.

const arbAddress = sigil.getAddressForNetwork('arbitrum');

await sigil.signMessage(message: string, options?): Promise<string>

Opens a confirmation modal in the iframe, the user approves, the iframe reconstructs the key in memory, signs (ECDSA secp256k1), wipes, and returns the signature in hex.

Pass { network } in options to select which network’s key signs the message. Defaults to the primary network.

const sig = await sigil.signMessage('hello', { network: 'arbitrum' });

If the user rejects, signMessage rejects with SigilRejectedError. If the iframe doesn’t respond within 30s, it rejects with SigilTimeoutError.

await sigil.signRaw(digest: Uint8Array, options?): Promise<string>

Signs a raw 32-byte digest without any prefix or hashing. This is the lowest-level signing primitive — use it when you need to sign a digest that has already been hashed by your application (e.g. for protocols with custom signing schemes).

Pass { network } to select which network’s key signs the digest.

const sig = await sigil.signRaw(digest, { network: 'ethereum' });

await sigil.signTypedData(td): Promise<{ signature, digest }>

EIP-712 typed-data signing. Returns both the 65-byte signature and the 32-byte EIP-712 digest the iframe actually hashed, so you can ecrecover the signer server-side without re-implementing EIP-712 in your backend.

await sigil.sendTransaction(input): Promise<{ chain, chainId, txHash, from }>

Raw EIP-1559 send from the wallet’s EOA. The user pays gas from their wallet’s native balance on the target chain.

const r = await sigil.sendTransaction({
chain: 'arbitrum',
to: '0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7', // Hyperliquid Bridge2
data: encodeDeposit(amountUSDC),
value: '0',
preview: { title: 'Deposit to Hyperliquid' },
});
console.log(r.txHash); // on-chain hash
console.log(r.from); // EOA that signed
FieldTypeNotes
chainstring'ethereum' | 'polygon' | 'arbitrum' | 'base' | 'optimism' | 'bsc' | 'avalanche' | 'solana'
tostringContract or recipient.
datastring | undefined0x-prefixed call data; omit for native transfers.
valuestring | undefinedDecimal wei as a string.
gasLimitstring | undefinedOverride the estimate when needed.
previewobject | undefinedUX summary the iframe shows on confirm.

await sigil.logout(): Promise<void>

Clears the session token. The Device share stays so the same end user can sign in again without going through recovery.

sigil.destroy(): void

Removes the iframe from the DOM, closes the transport, and frees memory. After destroy() the instance is unusable; create a new one for another session.

Lifecycle in a single function

async function signOnce(message: string) {
const wallet = new Sigil({ /* ... */ });
try {
await wallet.init();
return await wallet.signMessage(message);
} finally {
wallet.destroy();
}
}

For long-lived integrations, prefer the React provider which keeps one instance alive for the lifetime of the app.