Fund Integration Guide (Onboarding API + Fund API)

See core product page here

Setup

In this guide, the Platform is setup to use the Onboarding API (Reliance) plus the Fund API.

Flow

In this example, the Platform is configured to have the converted USD be automatically transferred to the Platform's account. We'll also assume that this is an investment platform that allows "issuers" to source investments from End Customers. Summary of all involved participants:

  • Platform (participant_code: PLAT01)
  • End Customer (participant_code: CUST01)
  • Issuer 1 (participant_code: ISSUR1)

Submit Customer

Example request - POST /participants/customers/new

{
"first_name": "John",
"last_name": "Smith",
"email": "[email protected]",
"phone_number": "9545551234",
"address_one": "1 Main St.",
"address_two": "Suite 1000",
"city": "Chicago",
"state": "IL",
"zip": "12345",
"country": "United States",
"date_of_birth": "1985-09-02",
"citizenship": "United States",
"tax_id": "123456789",
"risk_rating": "low",
"kyc": "pass",
"kyc_timestamp": 1630623005000,
"sanction_screening": "pass",
"sanction_screening_timestamp": 1630623005000,
"idv": "pass",
"liveness_check": "pass",
"signed_timestamp": 1630623005000,
"metadata": {},
"signed_agreements": [
     {
        "type": "fund_auto_convert",
        "region": "us",
        "signed_timestamp": 1712008721000
     }
 ]
}

If you fail to indicate that the end customer has agreed to the fund-specific terms, in the signed_agreements object shown above, /fund API calls will fail.

Get quote - POST /fund/rfq

Example request

{
  "participant_code": "CUST01",
  "fund_asset": "USDC.ETH",
  "account_label": "ISSUR1"
}

Interpretation of this request - the end customer (CUST01) is funding their account using USDC as the asset and ETH as the blockchain network. And to be specific, the end customer is funding the account for the purposes of investing into the issuer 1's company. The platform uses the optional, free-form account_label field for reconciliation and reporting. The entirety of this fund event and related ledger movements will be tied to issuer 1.

Example response - POST /fund/rfq

{
  "message": {
    "request_id": "14f8ebb8-7530-4aa4-bef9-9d73d56313f3",
    "participant_code": "CUST01",  
    "fund_asset": "USDC.ETH",  
    "rate": "1",
    "quoted_currency": "USD",
    "expiry_timestamp": 2554408627334,
    "deposit_address": "0x5f59B625036ccB4f7aD27Ca4Cb896e4452AfFDAF",
    "minimum_deposit": "1",
    "maximum_deposit": "250000"
  }
}

End customer initiates on-chain deposit

The end customer is presented with an address on the platform's user interface. They use their Metamask wallet, for example, to trigger a withdrawal into the deposit_address for the amount of their choice. Let's say the deposit is for 1,000 USDC.

Deposit received

The transaction has received the proper amount of on-chain confirmations (for USDC in this case, it is 1) and Zero Hash initiates a series of movements:

Accounts for this example:

account_idaccount_owneraccount_groupaccount_labelaccount_typeasset
47a854fa-d0e4-405b-831b-f1a86bbf7988CUST01PLAT01ISSUR1availableUSDC.ETH
52c29f2d-8298-4e8c-84bc-7773960bee12CUST01PLAT01ISSUR1availableUSD
  1. Deposit: Using our account nomenclature, the movement would appear as:
account_owneraccount_groupaccount_labelaccount_typemovement_typeassetchange
CUST01PLAT01ISSUR1availabledepositUSDC.ETH+1,000
  1. Conversion: Automatically, Zero Hash will convert the USDC to USD. This technically creates a trade, which has a movement_type of final_settlement:
account_owneraccount_groupaccount_labelaccount_typemovement_typeassetchange
CUST01PLAT01ISSUR1availablefinal_settlementUSDC.ETH-1,000
CUST01PLAT01ISSUR1availablefinal_settlementUSD+1,000
  1. Transfer: Automatically, Zero Hash will transfer the USD from the end customer account to the platform's:
account_owneraccount_groupaccount_labelaccount_typemovement_typeassetchange
CUST01PLAT01ISSUR1availabletransferUSD-1,000
PLAT01PLAT01ISSUR1availabletransferUSD+1,000

Webhook Event Triggered

The platform can subscribe to a newly built webhook event for this product. It will be triggered at the completion of step 3 above (or step 2 if you are not configured for a subsequent transfer). For more information on webhook messages for Fund, check out webhook documentation.

Platform Queries REST Fund Endpoint

The platform can also retroactively query the GET /fund/transactions endpoint to view details of all particular fund event.

Example response - GET /fund/transactions

{
  "message": [
    {
      "participant_code": "CUST01",
      "fund_asset": "USDC.ETH",
      "rate": "1",
      "quoted_currency": "USD",
      "source_address": "0x3A45a60c62EE6cD616B1C4510404Eba88116044I",
      "deposit_address": "0x3A45a60c635E6cD616B1C4510404Eba88116050C",
      "quantity": "1000",
      "notional": "1000",
      "success": true,
      "fund_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
      "is_first_deposit": true,
      "fund_timestamp": 1727380576846,
      "deposit_timestamp": 1727380579415,
      "transaction_id": "0x72e399164e29bada83075cd44eacfc40c1656bc35bf38f0cf661bf00dfa3b2b2",
      "account_label": "ISSUR1",
      "raw_fee_bps": "50",
      "actual_fee_bps": "1000",
      "raw_fee_notional": "0.05",
      "actual_fee_notional": "1",
      "deposited_asset": "USDC"
    }
  ],
  "page": 1,
  "page_size": 50,
  "total_pages": 1
}

Platform Settlement - Platform initiates a withdrawal request

The platform can then withdraw the USD using the POST /withdrawals/requests endpoint.

Example Request - POST /withdrawals/requests

{ 
  "withdrawal_account_id":"abc12",
  "participant_code":"PLAT01",
  "amount":"1000",
  "asset":"USD",
  "account_group":"PLAT01"
}

Reconciliation - platform queries REST movements endpoint

The platform can now query GET /movements?parent_link_id=5155f7c9-95cb-4556-ab89-c178943a7111 in order to view all related ledger movements to this fund event.

Example response - GET /movements?parent_link_id=

{
  "message": [
    {
      "movement_timestamp": 1550174570000,
      "account_id": "47a854fa-d0e4-405b-831b-f1a86bbf7988",
      "movement_id": "5d4d962d-dc37-4ddc-b0c5-b8980d1d4421",
      "movement_type": "deposit",
      "transfer_type": null,
      "deposit_reference_id": null,
      "withdrawal_request_id": null,
      "parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
      "trade_id": null,
      "change": "1000"
    },
    {
      "movement_timestamp": 1550174571000,
      "account_id": "47a854fa-d0e4-405b-831b-f1a86bbf7988",
      "movement_id": "3d4d962d-dc37-4ddc-b0c5-b8980d1d4422",
      "movement_type": "final_settlement",
      "transfer_type": null,
      "deposit_reference_id": null,
      "withdrawal_request_id": null,
      "parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
      "trade_id": null,
      "change": "-1000"
    },
    {
      "movement_timestamp": 1550174572000,
      "account_id": "52c29f2d-8298-4e8c-84bc-7773960bee12",
      "movement_id": "80818b7b-92ec-4882-a371-a59fb3f8bce0",
      "movement_type": "final_settlement",
      "transfer_type": null,
      "deposit_reference_id": null,
      "withdrawal_request_id": null,
      "parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
      "trade_id": null,
      "change": "1000"
    },
    {
      "movement_timestamp": 1550174573000,
      "account_id": "52c29f2d-8298-4e8c-84bc-7773960bee12",
      "movement_id": "80818b7b-92ec-4882-a371-a59fb3f8bce0",
      "movement_type": "transfer",
      "transfer_type": null,
      "deposit_reference_id": null,
      "withdrawal_request_id": null,
      "parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
      "trade_id": null,
    "change": "-1000"
    },
    {
      "movement_timestamp": 1550174574000,
      "account_id": "77c29f2d-8298-4e8c-84bc-7773960bee10",
      "movement_id": "80818b7b-92ec-4882-a371-a59fb3f8bce0",
      "movement_type": "transfer",
      "transfer_type": null,
      "deposit_reference_id": null,
      "withdrawal_request_id": null,
      "parent_link_id": "5155f7c9-95cb-4556-ab89-c178943a7111",
      "trade_id": null,
      "change": "1000"
    }
  ]
}

Platform Settlement

Zero Hash will, one time per day, send a fiat settlement wire to the Platform where the amount represents the sum of all converted deposits from the prior trading session. Here is the settlement schedule:

SessionStartEndExpected Settlement Time*
MondayMonday 9:00a ESTTuesday 8:59:59a ESTTuesday EOD
TuesdayTuesday 9:00a ESTWednesday 8:59:59a ESTWednesday EOD
WednesdayWednesday 9:00a ESTThursday 8:59:59a ESTThursday EOD
ThursdayThursday 9:00a ESTFriday 8:59:59a ESTFriday EOD
FridayFriday 9:00a ESTMonday 8:59:59a ESTMonday EOD

During US holidays, Platforms should expect their settlements to arrive by EOD on the next business day. For example, for the August 30th 2024 session, the settlement will arrive by Tuesday EOD (because Monday was Labor Day)

Tracking the Settlement Amount

The recommended way to track the impending settlement amount is to query the GET /accounts/{account_id} endpoint. In order to retrieve the correct account_id to use in that call, use the following query parameters when hitting the GET /accounts endpoint:

  • participant_code: [your platform code]
  • account_group: [your platform code]
  • account_label: general
  • account_type: available
  • asset: [fiat currency code, typically "USD"]

Use the account_id in the response, ie af063ca6-836f-4677-8ebf-edbe1d049938, to query GET /accounts/af063ca6-836f-4677-8ebf-edbe1d049938. Example response:

{
    "message": [
        {
            "asset": "USD"
            "account_owner": "PLAT01",
            "account_type": "available",
            "account_group": "PLAT01",
            "account_label": "general",
            "balance": "25000",
            "account_id": "af063ca6-836f-4677-8ebf-edbe1d049938",
            "last_update": 1727214271011
        }
    ]
}

Retroactively Querying Settlements in the Past

Platform settlements are technically a withdrawal. In order to query settlements from the past, use the GET /withdrawals/requests endpoint. The response will show all withdrawals made from the Platform's account, or one of your Customer's (Customer withdrawals are extremely rare and reserved for edge case Fund failures only).

In order to filter for withdrawals made from the Platform account only, query GET /withdrawals/requests using the participant_code query parameter, where the value is the Platform's platform code:

Example GET /withdrawals/requests?participant_code=PLAT01 response:

{
    "message": [
        {
            "id": "152b8276-0585-45ec-bf62-85e134b3ff43",
            "withdrawal_account_id": 346030,
            "participant_code": "PLAT01",
            "account_group": "PLAT01",
            "account_label": null,
            "requestor_participant_code": "PLAT01",
            "asset": "USDC.SOL",
            "requested_amount": "25000",
            "settled_amount": "25000",
            "gas_price": null,
            "status": "SETTLED",
            "on_chain_status": "",
            "client_withdrawal_request_id": null,
            "requested_timestamp": 1727921516403,
            "transaction_id": "HND48J83HGR55LLP",
            "input_data": null,
            "fee_amount": "",
            "quoted_fee_amount": null,
            "quoted_fee_notional": null,
            "trade_id": null,
            "quoted_fee_asset": null,
            "withdrawal_fee": "",
            "parent_link_id": null,
            "parent_link_id_source": null
        }
    ]
}