Use Case 4: Custodian Provider

Built on top of the Distribution & Access Hub primitives

This guide shows how to integrate a custody service with the Ownera Router, enabling digital asset safekeeping, transaction signing, and execution plan approvals across the network.


What You're Building

As a Custody Provider Utility SuperApp, your service:

  • Creates accounts - Generate FinIDs (cryptographic identities) for investors
  • Signs transactions - Produce signatures for asset transfers, trades, and other operations
  • Approves execution plans - Authorize multi-party workflows that involve your custodied assets

The Router calls your Custody Adapter when orchestration plans require custody operations.


Integration Pattern

You implement the Custody Adapter API - a contract the Router uses to communicate with your custody system.

┌─────────────────┐         ┌─────────────────┐         ┌─────────────────┐
│ Business        │         │                 │         │ Your Custody    │
│ SuperApp        │────────►│     Router      │────────►│ Adapter         │
│                 │         │                 │         │                 │
└─────────────────┘         └────────┬────────┘         └────────┬────────┘
                                     │                           │
                                     │                           ▼
                                     │                  ┌─────────────────┐
                                     │                  │ Your Key Mgmt   │
                                     │                  │ System (HSM,    │
                                     │                  │ MPC, etc.)      │
                                     └─────────────────►└─────────────────┘

Two Integration Models

ModelHow It WorksWhen to Use
Push (Direct)Router calls your adapter endpoints directlyYour adapter can expose HTTP endpoints the Router can reach
Proxy (Pull)Your adapter polls the Router for pending commands and submits responsesFirewall constraints, or you prefer to pull work rather than receive pushes

→ See Custody Adapter Specification for Push API
→ See Proxy Custody Adapter for Pull API


Custody Adapter Endpoints

Your adapter implements these endpoints:

EndpointMethodPurpose
/accountsPOSTCreate an account and generate a FinID for an investor
/signaturesPOSTSign a transaction hash
/plan/approvePOSTApprove an execution plan
/plan/proposalPOSTApprove a plan proposal
/plan/proposal/statusPOSTReceive notification of proposal agreement status
/operations/status/{cid}GETReturn status of an async operation
/healthGETHealth check

Flow 1: Create an Account

When an investor links to your custody service, the Router calls your adapter to create an account and generate a FinID.

Router calls your adapter:

POST /accounts
{
  "ownerId": "bank-x:101:b9ab438a-ca78-4b86-8584-19f6f888fd91"
}

Your adapter responds:

{
  "type": "accountResponse",
  "finId": "034ac8721ec234bff33a73b9820b855f854165dc25acfcbcefe8984200b94f09b7"
}

The finId is a compressed secp256k1 public key (33 bytes, hex encoded) that becomes the investor's cryptographic identity for signing transactions.


Flow 2: Sign a Transaction

When an orchestration plan requires a signature from a custodied account, the Router sends a signing request.

Router calls your adapter:

POST /signatures

The request includes:

  • source - The account (FinID) that should sign
  • template - The structured data to be signed, with field names, types, and values
  • hash - The hash to sign
  • executionContext - Links this signature to a specific execution plan and instruction

Your adapter responds:

{
  "type": "signatureResponse",
  "signature": "3045022100..."
}

The signature is the hex-encoded result of signing the hash with the private key corresponding to the source FinID.


Flow 3: Approve Execution Plans

For high-value or policy-controlled operations, the Router may request explicit approval before proceeding.

Router calls your adapter:

POST /plan/approve
{
  "executionPlan": {
    "id": "bank-x:106:7218a929-62bb-4bf0-a4e4-411b4924731f"
  }
}

Your adapter should:

  1. Validate the execution plan against your policies
  2. Return approval or rejection

Async Operations

Some custody operations (especially those requiring HSM or MPC ceremonies) may take time. Use the async pattern:

  1. Return 202 Accepted with a cid (correlation ID)
  2. Router polls GET /operations/status/{cid}
  3. Return final result when ready

→ See API Response Patterns for details


Implementation Checklist

  • Implement /accounts - Generate keypairs and return FinIDs
  • Implement /signatures - Sign hashes using the correct private key
  • Implement /plan/approve - Apply your approval policies
  • Implement /health - Return service health status
  • Handle async operations if your signing process is not immediate
  • Secure your endpoints - The Router authenticates; verify requests
  • Test with sandbox - Use the Ownera sandbox environment

Next Steps

curl --request POST \
     --url https://example.com/accounts \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
   "asset":{
      "resourceId":"simulation:102:25552a0c-4bc6-4702-ba80-2f589d98b581",
      "type":"finp2p"
   },
   "executionContext":{
      "executionPlanId":"simulation:106:7218a929-62bb-4bf0-a4e4-411b4924731f",
      "instructionSequenceNumber":2
   },
   "expiry":500,
   "nonce":"0bf0068f05ea8eea6d340f386c42bf969322b97732d0b79e0000000000000000",
   "operationId":"simulation:106:7218a929-62bb-4bf0-a4e4-411b4924731f_2",
   "quantity":"1",
   "":{
      "signature":"b6e01c693796a132541e62c399c07f667dcbe4862e4b34c02872d9f94c38b8015f1dc7ed966133f1751aa9d01eb25f9d64eb7b8c2dba4071786ca98812ba9a4c",
      "template":{
         "hash":"3b97a6b4134bd8e7ea4e621775ebaabe780de48758924d54df0d6d9251b96f50",
         "hashGroups":[
            {
               "fields":[
                  {
                     "name":"nonce",
                     "type":"bytes",
                     "value":"0bf0068f05ea8eea6d340f386c42bf969322b97732d0b79e0000000000000000"
                  },
                  {
                     "name":"operation",
                     "type":"string",
                     "value":"loan"
                  },
                  {
                     "name":"pledgeAssetType",
                     "type":"string",
                     "value":"finp2p"
                  },
                  {
                     "name":"pledgeAssetId",
                     "type":"string",
                     "value":"simulation:102:25552a0c-4bc6-4702-ba80-2f589d98b581"
                  },
                  {
                     "name":"pledgeBorrowerAccountType",
                     "type":"string",
                     "value":"finId"
                  },
                  {
                     "name":"pledgeBorrowerAccountId",
                     "type":"string",
                     "value":"039b75d4316fd84fec8833b47c283e02faa1d451065bc95a67f35030010ec50fa7"
                  },
                  {
                     "name":"pledgeLenderAccountType",
                     "type":"string",
                     "value":"finId"
                  },
                  {
                     "name":"pledgeLenderAccountId",
                     "type":"string",
                     "value":"02e33480e71104d6bc24aedf675086cdfcd763ffbfddf8c2a36ec0cc8e1e9cb153"
                  },
                  {
                     "name":"pledgeAmount",
                     "type":"string",
                     "value":"1"
                  },
                  {
                     "name":"moneyAssetType",
                     "type":"string",
                     "value":"fiat"
                  },
                  {
                     "name":"moneyAssetId",
                     "type":"string",
                     "value":"USD"
                  },
                  {
                     "name":"moneyLenderAccountType",
                     "type":"string",
                     "value":"finId"
                  },
                  {
                     "name":"moneyLenderAccountId",
                     "type":"string",
                     "value":"02e33480e71104d6bc24aedf675086cdfcd763ffbfddf8c2a36ec0cc8e1e9cb153"
                  },
                  {
                     "name":"moneyBorrowerAccountType",
                     "type":"string",
                     "value":"finId"
                  },
                  {
                     "name":"moneyBorrowerAccountId",
                     "type":"string",
                     "value":"039b75d4316fd84fec8833b47c283e02faa1d451065bc95a67f35030010ec50fa7"
                  },
                  {
                     "name":"borrowedMoneyAmount",
                     "type":"string",
                     "value":"1000"
                  },
                  {
                     "name":"returnedMoneyAmount",
                     "type":"string",
                     "value":"1000"
                  },
                  {
                     "name":"openTime",
                     "type":"string",
                     "value":"1720438384"
                  },
                  {
                     "name":"closeTime",
                     "type":"string",
                     "value":"1720438384"
                  }
               ],
               "hash":"872ee05516d62a8638bceec5c810de2ff47841fc218f23e7334278ca0acce892"
            }
         ]
      }
   },
   "source":{
      "account":{
         "finId":"039b75d4316fd84fec8833b47c283e02faa1d451065bc95a67f35030010ec50fa7",
         "type":"finId"
      },
      "finId":"039b75d4316fd84fec8833b47c283e02faa1d451065bc95a67f35030010ec50fa7"
   }
{
    "id": "bedefc1b-3c2f-11ef-9bb8-f60d12cdf03e",
    "signature": "034ac8721ec234bff33a73b9820b855f854165dc25acfcbcefe8984200b94f09b7",
    "status": "READY"
}