# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki.gen6.life/developer-resources/sdk-and-tooling/g6-mw-and-chain-api-guide/03.-realseal.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
