SIGIL · INTEGRATION DOCS← Back to site
[ REFERENCE ]

Errors & examples

Server callback example

A transfer request from start to finish — build the URI, hand off to Sigil, receive the result via server POST.

CLIENT (TYPESCRIPT)
import { buildUri } from '@sigil-oss/connect';

// 1. Store the nonce so you can verify it in the callback
const nonce = crypto.randomUUID();
sessionStorage.setItem('pending_nonce', nonce);

// 2. Build the URI
const uri = buildUri({
  request: {
    type: 'transfer',
    nonce,
    dapp: { name: 'Acme', origin: 'https://acme.example' },
    to: 'NQZBXKZP4MTLDUVWXYZK8MFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
    amount: 1_000_000,
    exp: Math.floor(Date.now() / 1000) + 300, // 5 minutes
  },
  callback: 'https://acme.example/api/sigil/callback',
});

// 3. Hand off to Sigil
window.location.href = uri;
SERVER CALLBACK (TYPESCRIPT)
// POST https://acme.example/api/sigil/callback
app.post('/api/sigil/callback', express.json(), (req, res) => {
  const { status, type, nonce, ...rest } = req.body;

  // Reject unknown nonces immediately
  if (!pendingNonces.has(nonce)) {
    return res.sendStatus(400);
  }
  pendingNonces.delete(nonce);

  switch (status) {
    case 'signed':
      // type === 'transfer' | 'sc_call'
      handleSigned(rest.identity, rest.tx_hash, rest.target_tick);
      break;
    case 'connected':
      handleConnect(rest.identity, rest.permissions);
      break;
    case 'signed': // sign_message
      handleSignature(rest.identity, rest.signature, rest.public_key);
      break;
    case 'verified':
      handleVerify(rest.valid, rest.identity);
      break;
    case 'rejected':
      handleRejection(rest.reason);
      break;
  }

  res.sendStatus(200);
});

Redirect URI example

Static site or SPA with no server — result delivered as a query parameter when Sigil redirects the browser back to your page.

TYPESCRIPT (CLIENT-ONLY)
// Static site / SPA — no server required
function b64url(str: string): string {
  return btoa(str).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
}

// 1. Build the request, store nonce for verification after redirect
const nonce = crypto.randomUUID();
sessionStorage.setItem('sigil_nonce', nonce);

const envelope = {
  request: {
    type: 'sign_message',
    nonce,
    dapp: { name: 'Acme', origin: 'https://acme.example' },
    message: 'Sign in to Acme · ' + new Date().toISOString(),
  },
  redirect_uri: 'https://acme.example/signed', // Sigil opens this when done
};

window.location.href = 'sigil://v1/request?d=' + b64url(JSON.stringify(envelope));

// 2. On https://acme.example/signed — parse the result
const encoded = new URLSearchParams(location.search).get('result');
if (encoded) {
  const result = JSON.parse(atob(encoded.replace(/-/g, '+').replace(/_/g, '/')));
  const storedNonce = sessionStorage.getItem('sigil_nonce');

  if (result.nonce !== storedNonce) throw new Error('nonce mismatch');

  if (result.status === 'signed') {
    console.log('Signed by:', result.identity);
    console.log('Signature:', result.signature);
  } else {
    console.log('Rejected:', result.reason);
  }
}

Rejection reasons

reasonMeaning
user_rejectedThe user tapped Reject or closed the review screen
expiredThe request's exp timestamp passed before the user acted
wallet_lockedWallet was locked and the user did not unlock in time (queue timeout)

Validation errors

These cause Sigil to silently discard the request before showing it to the user. Check the Rust validator at src-tauri/src/deep_link.rs for the canonical list.

ConditionCause
Envelope too largeBase64-encoded d exceeds 8 192 bytes
Invalid scheme / pathURI is not sigil://v1/request?d=…
Nonce too short / too longMust be 16–128 chars, alphanumeric or -_=+
Nonce replaySame nonce seen within the last hour
Missing dapp.origindapp.origin must be a valid HTTPS URL
exp too far aheadMore than 1 hour from the current time
Callback not HTTPSProduction callbacks must be https://; only http://localhost and http://127.0.0.1 are allowed for dev

Trust levels recap

LevelBlocks?
legacy_unverifiedNo — shown with a warning badge
signed_untrustedNo — valid signature, issuer not in user's registry
verified_registryNo — fully verified
signature_invalidYes — proof present but signature check failed
registry_revokedYes
registry_origin_mismatchYes — registered issuer but wrong origin

Without the SDK

If you'd rather not add a dependency, the whole protocol fits in a handful of lines.

TYPESCRIPT
// Without the SDK — matches what buildUri() does internally
function b64url(str: string): string {
  return btoa(str)
    .replaceAll('+', '-')
    .replaceAll('/', '_')
    .replaceAll('=', '');
}

const envelope = {
  request: {
    type: 'sign_message',
    nonce: crypto.randomUUID(),
    dapp: { name: 'Acme', origin: 'https://acme.example' },
    message: 'Sign in to Acme · ' + new Date().toISOString(),
  },
  callback: 'https://acme.example/api/sigil/callback',
};

const uri = 'sigil://v1/request?d=' + b64url(JSON.stringify(envelope));
window.location.href = uri;