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
| Model | How It Works | When to Use |
|---|---|---|
| Push (Direct) | Router calls your adapter endpoints directly | Your adapter can expose HTTP endpoints the Router can reach |
| Proxy (Pull) | Your adapter polls the Router for pending commands and submits responses | Firewall 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:
| Endpoint | Method | Purpose |
|---|---|---|
/accounts | POST | Create an account and generate a FinID for an investor |
/signatures | POST | Sign a transaction hash |
/plan/approve | POST | Approve an execution plan |
/plan/proposal | POST | Approve a plan proposal |
/plan/proposal/status | POST | Receive notification of proposal agreement status |
/operations/status/{cid} | GET | Return status of an async operation |
/health | GET | Health 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 signtemplate- The structured data to be signed, with field names, types, and valueshash- The hash to signexecutionContext- 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:
- Validate the execution plan against your policies
- Return approval or rejection
Async Operations
Some custody operations (especially those requiring HSM or MPC ceremonies) may take time. Use the async pattern:
- Return
202 Acceptedwith acid(correlation ID) - Router polls
GET /operations/status/{cid} - 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
- Custody Adapter API Reference - Full endpoint specifications
- Proxy Custody Adapter - Pull-based integration pattern
- Authorization - How requests are authenticated
- Hello World Sample - See custody integration from the SuperApp side
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"
}Updated 10 days ago
