> For the complete documentation index, see [llms.txt](https://wiki.gen6.life/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.gen6.life/technology-core/developer-resources/sdk-and-tooling/gen6-mw-and-chain-api-guide/03.-realseal.md).

# 03. RealSeal

## Real-Seal

Real-Seal provides document and text signing with blockchain verification, enabling tamper-proof digital signatures with public/private sharing.

### Overview

**Purpose**: Sign and verify documents and texts using blockchain-anchored cryptographic signatures.

**Key Features**:

* Upload and sign PDF, Word, images, and other documents
* Sign text content and URLs
* Blockchain anchoring for tamper-proof verification
* Share signed documents with specific addresses
* Create public verification links

**Location**: src/packages/real-seal/

***

### Documents

#### Upload Document

Upload a document to Real-Seal for signing.

**Endpoint**: `POST /real-seal/documents/`

**Authentication**: Required (JWT)

**Request**:

```typescript
import { axiosInstance } from '@/packages/real-seal/api/axiosInstance'

export const uploadDocument = async (file: File, title?: string) => {
  const formData = new FormData()
  formData.append('file', file)
  if (title) formData.append('title', title)

  const response = await axiosInstance.post<DocumentUploadResponse>(
    '/real-seal/documents/',
    formData
  )
  return response.data
}
```

**Request Body**: FormData

* `file` (required): Document file
* `title` (optional): Custom title for the document

**Supported File Types**:

* PDF: `.pdf`
* Word: `.doc`, `.docx`
* Excel: `.xls`, `.xlsx`
* Images: `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`
* Text: `.txt`, `.md`
* Archives: `.zip`

**File Size Limit**: Check configuration via `GET /real-seal/config`

**Response**:

```typescript
interface DocumentUploadResponse {
  document_id: string
  title?: string
  filename: string
  mimetype: string
  content_length: number
  file_hash: string
  visibility: string
  created_at: string
}
```

**Example Response**:

```json
{
  "document_id": "123e4567-e89b-12d3-a456-426614174000",
  "title": "Contract Agreement",
  "filename": "contract.pdf",
  "mimetype": "application/pdf",
  "content_length": 524288,
  "file_hash": "0x7f8a9b3c...",
  "visibility": "private",
  "created_at": "2025-01-15T10:30:00Z"
}
```

**Usage**:

```typescript
import { useMutation } from '@tanstack/react-query'

function DocumentUpload() {
  const uploadMutation = useMutation({
    mutationFn: ({ file, title }: { file: File; title?: string }) =>
      uploadDocument(file, title),
    onSuccess: (data) => {
      toast.success('Document uploaded!')
      navigate(`/real-seal/documents/${data.uuid}`)
    }
  })

  const handleUpload = (file: File) => {
    uploadMutation.mutate({ file })
  }

  return <FileDropzone onDrop={handleUpload} />
}
```

***

#### Get Document Metadata

Fetch metadata for a specific document.

**Endpoint**: `GET /real-seal/documents/{id}`

**Authentication**: Required (JWT)

**Parameters**:

* `id` (path): Document UUID

**Implementation**:

```typescript
export const getDocumentMetadata = async (id: string) => {
  const response = await axiosInstance.get<DocumentMetadata>(
    `/real-seal/documents/${id}`
  )
  return response.data
}
```

**Response**: `DocumentMetadata` object (see Upload Document)

**Access Control**:

* Owner can always access
* Shared addresses can access if in `authorized_addresses`
* Public access only if `public_enabled: true`

***

#### Download Document

Download the actual document file.

**Endpoint**: `GET /real-seal/documents/{id}/download`

**Authentication**: Required (JWT)

**Parameters**:

* `id` (path): Document UUID

**Implementation**:

```typescript
export const downloadDocument = async (id: string) => {
  const response = await axiosInstance.get<Blob>(
    `/real-seal/documents/${id}/download`,
    { responseType: 'blob' }
  )
  return response.data
}
```

**Response**: File blob with original content type

**Usage**:

```typescript
function DownloadButton({ documentId }: { documentId: string }) {
  const downloadMutation = useMutation({
    mutationFn: () => downloadDocument(documentId),
    onSuccess: async (blob, variables) => {
      // Get filename from metadata
      const metadata = await getDocumentMetadata(documentId)

      // Create download link
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = metadata.filename
      a.click()
      URL.revokeObjectURL(url)
    }
  })

  return (
    <button onClick={() => downloadMutation.mutate()}>
      Download
    </button>
  )
}
```

***

#### List User Documents

List all documents owned by or shared with the authenticated user.

**Endpoint**: `GET /real-seal/documents?with_shared_addresses=true`

**Authentication**: Required (JWT)

**Implementation**:

```typescript
export const listUserDocuments = async () => {
  const response = await axiosInstance.get<DocumentListResponse>(
    '/real-seal/documents',
    { params: { with_shared_addresses: true } }
  )
  return response.data
}
```

**Response**:

```typescript
interface DocumentListResponse {
  documents: DocumentMetadata[]
  total_count: number
}
```

**Usage**:

```typescript
function DocumentList() {
  const { data, isLoading } = useQuery({
    queryKey: ['real-seal', 'documents', 'user'],
    queryFn: listUserDocuments
  })

  if (isLoading) return <div>Loading...</div>

  return (
    <div>
      <p>Total: {data.total_count}</p>
      {data.documents.map(doc => (
        <DocumentCard key={doc.uuid} document={doc} />
      ))}
    </div>
  )
}
```

***

#### List Documents with Filters

List documents with advanced filtering options.

**Endpoint**: `GET /real-seal/documents?scope=owned&...`

**Authentication**: Required (JWT)

**Query Parameters**:

* `scope` (optional): `'owned'` or `'shared'`
* `start_date` (optional): ISO 8601 date string
* `end_date` (optional): ISO 8601 date string
* `min_size` (optional): Minimum file size in bytes
* `max_size` (optional): Maximum file size in bytes
* `file_format` (optional): MIME type filter
* `search` (optional): Search in filename/title

**Implementation**:

```typescript
interface DocumentFilters {
  scope?: 'owned' | 'shared'
  start_date?: string
  end_date?: string
  min_size?: number
  max_size?: number
  file_format?: string
  search?: string
}

export const listDocumentsByScope = async (filters: DocumentFilters) => {
  const response = await axiosInstance.get<DocumentListResponse>(
    '/real-seal/documents',
    { params: filters }
  )
  return response.data
}
```

**Example Usage**:

```typescript
// Get only owned PDFs from last month
const filters = {
  scope: 'owned',
  start_date: '2025-01-01T00:00:00Z',
  file_format: 'application/pdf'
}

const { data } = useQuery({
  queryKey: ['real-seal', 'documents', 'user', 'owned', filters],
  queryFn: () => listDocumentsByScope(filters)
})
```

***

#### Sign Document

Sign a document with a blockchain-anchored cryptographic signature.

**Endpoint**: `POST /real-seal/documents/{id}/sign`

**Authentication**: Required (JWT)

**Parameters**:

* `id` (path): Document UUID

**Request**:

```typescript
interface SignDocumentRequest {
  file_hash: string // Blake2 hash of file content
  signature: string // Wallet signature of the signing message
  timestamp: string // Unix timestamp (seconds)
}

export const signDocument = async (id: string, data: SignDocumentRequest) => {
  const response = await axiosInstance.post<DocumentMetadata>(
    `/real-seal/documents/${id}/sign`,
    data
  )
  return response.data
}
```

**Request Body**:

```json
{
  "file_hash": "0x7f8a9b3c...",
  "signature": "0xabc123...",
  "timestamp": "1705320600"
}
```

**Response**: Updated `DocumentMetadata` with signature

**Signature Process**:

1. Download document and calculate Blake2 hash
2. Construct signing message: `REAL_SEAL::SIGN_DOCUMENT::${documentHash}::${timestamp}`
3. Sign the message with wallet
4. Submit signature, hash, and timestamp to backend
5. Backend verifies signature matches owner and message
6. Document marked as signed

**Implementation**:

```typescript
import { blake2AsHex } from '@polkadot/util-crypto'
import { encodeAddress } from '@polkadot/util-crypto'

async function signDocumentFlow(documentId: string) {
  // 1. Download document
  const blob = await downloadDocument(documentId)
  const arrayBuffer = await blob.arrayBuffer()
  const uint8Array = new Uint8Array(arrayBuffer)

  // 2. Calculate hash
  const fileHash = blake2AsHex(uint8Array)

  // 3. Construct message and sign
  const timestamp = Math.floor(Date.now() / 1000).toString()
  const signingMessage = `REAL_SEAL::SIGN_DOCUMENT::${fileHash}::${timestamp}`

  const signerResult = await getSigner() // Use useSigner hook

  const signatureResult = await signerResult.signer.signRaw({
    address: encodeAddress(currentAccount.address, 355),
    data: signingMessage,
    type: 'bytes'
  })

  // 4. Submit signature
  await signDocument(documentId, {
    file_hash: fileHash,
    signature: signatureResult.signature,
    timestamp
  })

  toast.success('Document signed!')
}
```

***

#### Verify Document Signature

Verify that a document's signature is valid and hasn't been tampered with.

**Endpoint**: `GET /real-seal/documents/{id}/verify`

**Authentication**: Not required for public documents

**Parameters**:

* `id` (path): Document UUID

**Implementation**:

```typescript
export const verifyDocumentSignature = async (id: string) => {
  const response = await axiosInstance.get<VerificationResponse>(
    `/real-seal/documents/${id}/verify`
  )
  return response.data
}
```

**Response**:

```typescript
interface VerificationResponse {
  valid: boolean
  signature: DocumentSignature | null
  on_chain_verified: boolean
  message?: string
}

interface DocumentSignature {
  signer_address: string
  signature: string
  signed_at: string
  signed_message: string
}
```

**Example Response**:

```json
{
  "valid": true,
  "signature": {
    "signer_address": "g6x...",
    "signature": "0xabc123...",
    "signed_at": "2025-01-15T10:30:00Z",
    "signed_message": "0x7f8a9b3c..."
  },
  "on_chain_verified": true
}
```

**Usage**:

```typescript
function VerificationBadge({ documentId }: { documentId: string }) {
  const { data: verification } = useQuery({
    queryKey: ['real-seal', 'documents', documentId, 'verification'],
    queryFn: () => verifyDocumentSignature(documentId)
  })

  if (!verification?.valid) {
    return <Badge variant="destructive">Invalid Signature</Badge>
  }

  return (
    <Badge variant="success">
      ✓ Verified {verification.on_chain_verified && '(On-Chain)'}
    </Badge>
  )
}
```

***

#### Share Document

Share a document with specific Gen6 addresses.

**Endpoint**: `POST /real-seal/documents/{id}/share`

**Authentication**: Required (JWT - must be owner)

**Parameters**:

* `id` (path): Document UUID

**Request**:

```typescript
interface ShareRequest {
  addresses?: string[] // SS58-355 encoded addresses
}

export const shareDocument = async (id: string, addresses?: string[]) => {
  const response = await axiosInstance.post<DocumentMetadata>(
    `/real-seal/documents/${id}/share`,
    { addresses }
  )
  return response.data
}
```

**Request Body**:

```json
{
  "addresses": ["g6x123...", "g6x456..."]
}
```

**Response**: Updated `DocumentMetadata` with `authorized_addresses`

**Behavior**:

* If `addresses` is empty/undefined, shares with everyone
* If `addresses` has values, shares only with those addresses
* Replaces existing share list (not additive)

**Usage**:

```typescript
function ShareDocumentDialog({ documentId }: { documentId: string }) {
  const [addresses, setAddresses] = useState<string[]>([])

  const shareMutation = useMutation({
    mutationFn: () => shareDocument(documentId, addresses),
    onSuccess: () => toast.success('Document shared!')
  })

  return (
    <Dialog>
      <Input
        placeholder="Enter Gen6 addresses..."
        onChange={(e) => setAddresses(e.target.value.split(','))}
      />
      <Button onClick={() => shareMutation.mutate()}>
        Share
      </Button>
    </Dialog>
  )
}
```

***

#### Unshare Document

Revoke document access for specific addresses.

**Endpoint**: `DELETE /real-seal/documents/{id}/share`

**Authentication**: Required (JWT - must be owner)

**Parameters**:

* `id` (path): Document UUID

**Request**:

```typescript
interface UnshareRequest {
  addresses: string[] // Addresses to revoke
}

export const unshareDocument = async (id: string, addresses: string[]) => {
  const response = await axiosInstance.delete<DocumentMetadata>(
    `/real-seal/documents/${id}/share`,
    { data: { addresses } }
  )
  return response.data
}
```

**Request Body**:

```json
{
  "addresses": ["g6x123..."]
}
```

**Response**: Updated `DocumentMetadata`

***

#### Create Public Link

Generate a public link for document verification without authentication.

**Endpoint**: `POST /real-seal/documents/{id}/public`

**Authentication**: Required (JWT - must be owner)

**Parameters**:

* `id` (path): Document UUID

**Implementation**:

```typescript
export const createPublicLink = async (id: string) => {
  const response = await axiosInstance.post<PublicLinkResponse>(
    `/real-seal/documents/${id}/public`
  )
  return response.data
}
```

**Response**:

```typescript
interface PublicLinkResponse {
  public_url: string
  created_at: string
}
```

**Example Response**:

```json
{
  "public_url": "https://app.gen6.com/real-seal/public/abc123def456",
  "created_at": "2025-01-15T10:30:00Z"
}
```

**Usage**:

```typescript
function CreatePublicLinkButton({ documentId }: { documentId: string }) {
  const createMutation = useMutation({
    mutationFn: () => createPublicLink(documentId),
    onSuccess: (data) => {
      navigator.clipboard.writeText(data.public_url)
      toast.success('Public link copied!')
    }
  })

  return (
    <Button onClick={() => createMutation.mutate()}>
      Create Public Link
    </Button>
  )
}
```

***

#### Revoke Public Link

Disable public access to a document.

**Endpoint**: `DELETE /real-seal/documents/{id}/public`

**Authentication**: Required (JWT - must be owner)

**Parameters**:

* `id` (path): Document UUID

**Implementation**:

```typescript
export const revokePublicLink = async (id: string) => {
  const response = await axiosInstance.delete<DocumentMetadata>(
    `/real-seal/documents/${id}/public`
  )
  return response.data
}
```

**Response**: Updated `DocumentMetadata` with `public_enabled: false`

***

#### Get Public Link

Retrieve the current public link for a document (if enabled).

**Endpoint**: `GET /real-seal/documents/{id}/public`

**Authentication**: Required (JWT - must be owner)

**Parameters**:

* `id` (path): Document UUID

**Implementation**:

```typescript
export const getPublicLink = async (id: string) => {
  const response = await axiosInstance.get<PublicLinkResponse>(
    `/real-seal/documents/${id}/public`
  )
  return response.data
}
```

**Response**: `PublicLinkResponse` or 404 if not enabled

***

#### Delete Document

Permanently delete a document.

**Endpoint**: `DELETE /real-seal/documents/{id}`

**Authentication**: Required (JWT - must be owner)

**Parameters**:

* `id` (path): Document UUID

**Implementation**:

```typescript
export const deleteDocument = async (id: string) => {
  await axiosInstance.delete(`/real-seal/documents/${id}`)
}
```

**Response**: 204 No Content

**Warning**: This action is permanent and cannot be undone.

**Usage**:

```typescript
function DeleteDocumentButton({ documentId }: { documentId: string }) {
  const deleteMutation = useMutation({
    mutationFn: () => deleteDocument(documentId),
    onSuccess: () => {
      toast.success('Document deleted')
      navigate('/real-seal/documents')
    }
  })

  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive">Delete</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogTitle>Are you sure?</AlertDialogTitle>
        <AlertDialogDescription>
          This action cannot be undone.
        </AlertDialogDescription>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction onClick={() => deleteMutation.mutate()}>
            Delete
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

***

### Public Document Access

#### Get Public Document Metadata

Access document metadata via public token (no authentication required).

**Endpoint**: `GET /real-seal/public/{token}/metadata`

**Authentication**: Not required

**Parameters**:

* `token` (path): Public access token from public URL

**Implementation**:

```typescript
export const getPublicDocumentMetadataByToken = async (token: string) => {
  const response = await axiosInstance.get<DocumentMetadata>(
    `/real-seal/public/${token}/metadata`
  )
  return response.data
}
```

**Response**: `DocumentMetadata` object

**Usage**:

```typescript
// From public URL: https://app.gen6.com/real-seal/public/abc123def456
const token = 'abc123def456'

function PublicDocumentPage({ token }: { token: string }) {
  const { data: metadata } = useQuery({
    queryKey: ['real-seal', 'public', token],
    queryFn: () => getPublicDocumentMetadataByToken(token)
  })

  return (
    <div>
      <h1>{metadata.title || metadata.filename}</h1>
      <p>Signed by: {metadata.signature?.signer_address}</p>
    </div>
  )
}
```

***

#### Download Public Document

Download document via public token (no authentication required).

**Endpoint**: `GET /real-seal/public/{token}/download`

**Authentication**: Not required

**Parameters**:

* `token` (path): Public access token

**Implementation**:

```typescript
export const downloadPublicDocumentByToken = async (token: string) => {
  const response = await axiosInstance.get<Blob>(
    `/real-seal/public/${token}/download`,
    { responseType: 'blob' }
  )
  return response.data
}
```

**Response**: File blob

***

### Text Signing

#### Create Text to Sign

Create a text or URL entry for signing.

**Endpoint**: `POST /real-seal/texts/`

**Authentication**: Required (JWT)

**Request**:

```typescript
interface CreateTextRequest {
  content: string
  type: 'text' | 'url'
  title?: string
}

export const createTextSign = async (data: CreateTextRequest) => {
  const response = await axiosInstance.post<TextMetadata>(
    '/real-seal/texts/',
    data
  )
  return response.data
}
```

**Request Body**:

```json
{
  "content": "I hereby agree to the terms...",
  "type": "text",
  "title": "Agreement"
}
```

**Response**:

```typescript
interface TextMetadata {
  uuid: string
  title: string | null
  content: string
  type: 'text' | 'url'
  content_hash: string // Blake2 hash of content
  visibility: string
  owner_address: string
  signature: TextSignature | null
  created_at: string
  updated_at: string
  authorized_addresses: string[]
  public_enabled: boolean
  public_created_at: string | null
}

interface TextSignature {
  signer_address: string
  signature: string
  signed_at: string
  signed_message: string
}
```

***

#### Get Text Metadata

Fetch metadata for a specific text entry.

**Endpoint**: `GET /real-seal/texts/{id}`

**Authentication**: Required (JWT)

**Parameters**:

* `id` (path): Text UUID

**Implementation**:

```typescript
export const getTextMetadata = async (id: string) => {
  const response = await axiosInstance.get<TextMetadata>(
    `/real-seal/texts/${id}`
  )
  return response.data
}
```

**Response**: `TextMetadata` object

***

#### Sign Text

Sign a text entry with a cryptographic signature.

**Endpoint**: `POST /real-seal/texts/{id}/sign`

**Authentication**: Required (JWT)

**Parameters**:

* `id` (path): Text UUID

**Request**:

```typescript
interface SignTextRequest {
  content_hash: string // Blake2 hash of content
  signature: string // Wallet signature
  timestamp: string // ISO 8601
}

export const signText = async (id: string, data: SignTextRequest) => {
  const response = await axiosInstance.post<TextMetadata>(
    `/real-seal/texts/${id}/sign`,
    data
  )
  return response.data
}
```

**Signing Process**:

```typescript
import { blake2AsHex } from '@polkadot/util-crypto'
import { stringToU8a } from '@polkadot/util'
import { web3FromAddress } from '@polkadot/extension-dapp'

async function signTextFlow(textId: string, content: string) {
  // 1. Calculate hash
  const contentHash = blake2AsHex(stringToU8a(content))

  // 2. Sign hash
  const injector = await web3FromAddress(currentAccount.address)
  const signatureResult = await injector.signer.signRaw({
    address: currentAccount.address,
    data: contentHash,
    type: 'bytes'
  })

  // 3. Submit signature
  const timestamp = new Date().toISOString()
  await signText(textId, {
    content_hash: contentHash,
    signature: signatureResult.signature,
    timestamp
  })

  toast.success('Text signed!')
}
```

***

#### Create and Sign Text (Single Call)

Create and sign a text in one API call.

**Endpoint**: `POST /real-seal/texts/sign`

**Authentication**: Required (JWT)

**Request**:

```typescript
interface CreateAndSignTextRequest {
  content: string
  type: 'text' | 'url'
  title?: string
  signature: string
  timestamp: string
}

export const createAndSignText = async (data: CreateAndSignTextRequest) => {
  const response = await axiosInstance.post<TextMetadata>(
    '/real-seal/texts/sign',
    data
  )
  return response.data
}
```

**Usage**: Convenience method to avoid separate create + sign calls.

***

#### List Text Entries

List text entries with optional filters.

**Endpoint**: `GET /real-seal/texts/`

**Authentication**: Required (JWT)

**Query Parameters**:

* `scope`: `'owned'` or `'shared'`
* `type`: `'text'` or `'url'`
* `start_date`, `end_date`: Date range
* `search`: Search in content/title

**Implementation**:

```typescript
export const listTextSigns = async (filters?: {
  scope?: 'owned' | 'shared'
  type?: 'text' | 'url'
  start_date?: string
  end_date?: string
  search?: string
}) => {
  const response = await axiosInstance.get<TextListResponse>(
    '/real-seal/texts/',
    { params: filters }
  )
  return response.data
}
```

**Response**:

```typescript
interface TextListResponse {
  texts: TextMetadata[]
  total_count: number
}
```

***

#### Delete Text

Delete a text entry.

**Endpoint**: `DELETE /real-seal/texts/{id}`

**Authentication**: Required (JWT - must be owner)

**Parameters**:

* `id` (path): Text UUID

**Implementation**:

```typescript
export const deleteTextSign = async (id: string) => {
  await axiosInstance.delete(`/real-seal/texts/${id}`)
}
```

***

#### Text Sharing & Public Links

Text entries support the same sharing and public link features as documents:

* `POST /real-seal/texts/{id}/share` - Share with addresses
* `DELETE /real-seal/texts/{id}/share` - Revoke sharing
* `POST /real-seal/texts/{id}/public` - Create public link
* `DELETE /real-seal/texts/{id}/public` - Revoke public link
* `GET /real-seal/texts/{id}/public` - Get public link

APIs identical to document endpoints (see above).

***

#### Query Account Balance

Check account balance for transaction fee estimation.

**Blockchain Query**: `api.query.system.account()`

**Parameters**:

* `address`: SS58-355 encoded account address

**Implementation**:

```typescript
// Used in upload-document-modal.tsx for fee estimation
const balance = await api.query.system.account(currentAccount.address)
const accountData = balance.toJSON()
const freeBalance = accountData.data.free
```

**Response**: Account info including free balance for fee calculation

**Usage**: Automatically checked before document upload to ensure sufficient balance for blockchain transactions.

***

### Blockchain Integration

#### Store Document Hash On-Chain

Anchor the document hash on the blockchain for immutable verification.

**Extrinsic**: `api.tx.dataRegistry.storeData()`

**Parameters**:

* `projectId`: `886` (Real-Seal project ID)
* `dataHash`: Blake2 hash of document content

**Implementation**:

```typescript
import { useGen6 } from '@/contexts/gen6-context'
import { useSigner } from '@/hooks/useSigner'

export function useRealSeal() {
  const { api, currentAccount } = useGen6()
  const { getSigner } = useSigner()

  const storeDocumentHash = async (fileHash: string) => {
    const signerResult = await getSigner()

    const extrinsic = api.tx.dataRegistry.storeData(
      886, // Project ID for Real-Seal
      fileHash // Blake2 hash
    )

    return new Promise<void>((resolve, reject) => {
      extrinsic
        .signAndSend(
          currentAccount.address,
          { signer: signerResult.signer },
          ({ status, dispatchError }) => {
            if (status.isFinalized) {
              if (dispatchError) {
                reject(new Error('Transaction failed'))
              } else {
                resolve()
              }
            }
          }
        )
        .catch(reject)
    })
  }

  return { storeDocumentHash }
}
```

**Complete Signing Flow**:

```typescript
async function completeSigningFlow(documentId: string) {
  // 1. Download and hash document
  const blob = await downloadDocument(documentId)
  const arrayBuffer = await blob.arrayBuffer()
  const uint8Array = new Uint8Array(arrayBuffer)
  const fileHash = blake2AsHex(uint8Array)

  // 2. Sign hash with wallet
  const injector = await web3FromAddress(currentAccount.address)
  const signatureResult = await injector.signer.signRaw({
    address: currentAccount.address,
    data: fileHash,
    type: 'bytes'
  })

  // 3. Store hash on blockchain
  await storeDocumentHash(fileHash)

  // 4. Submit signature to backend
  const timestamp = new Date().toISOString()
  await signDocument(documentId, {
    file_hash: fileHash,
    signature: signatureResult.signature,
    timestamp
  })

  toast.success('Document signed and verified on blockchain!')
}
```

***

### Configuration

#### Get Real-Seal Config

Retrieve Real-Seal configuration (e.g., max file size).

**Endpoint**: `GET /real-seal/config`

**Authentication**: Not required

**Implementation**:

```typescript
export const getRealSealConfig = async () => {
  const response = await axiosInstance.get<RealSealConfig>('/real-seal/config')
  return response.data
}
```

**Response**:

```typescript
interface RealSealConfig {
  max_file_size: number // In bytes
  allowed_mimetypes: string[]
}
```

**Example Response**:

```json
{
  "max_file_size": 10485760,
  "allowed_mimetypes": [
    "application/pdf",
    "application/msword",
    "image/jpeg",
    "image/png"
  ]
}
```

***

### Query Keys

**TanStack Query Keys**:

```typescript
const realSealKeys = {
  all: ['real-seal'] as const,
  documents: () => [...realSealKeys.all, 'documents'] as const,
  document: (id: string) => [...realSealKeys.documents(), id] as const,
  userDocuments: () => [...realSealKeys.documents(), 'user'] as const,
  userDocumentsOwned: (filters) =>
    [...realSealKeys.userDocuments(), 'owned', filters] as const,
  userDocumentsShared: (filters) =>
    [...realSealKeys.userDocuments(), 'shared', filters] as const,
  verification: (id: string) =>
    [...realSealKeys.document(id), 'verification'] as const,
  publicDocument: (token: string) =>
    [...realSealKeys.all, 'public', token] as const,
  texts: () => [...realSealKeys.all, 'texts'] as const,
  text: (id: string) => [...realSealKeys.texts(), id] as const
}
```

***

### Next Steps

* Ncrypt - Send encrypted messages
* Identity - Link signed documents to verified identities
