UMA Deposits and Withdrawals

Deposits and Withdrawals using the UMA (LNURL-p) standard on the Lightning Network including generating and sending to invoices.

Universal Money Address (UMA) is an open source project designed to make improvements over standard BOLT11 Lightning Network transactions.

These configurations will typically result in addresses taking one of the following formats:

  • $[email protected] [Default]
  • $satoshi@[platform].zerohash.id
  • $satoshi@uma.[platform].com
  • $satoshi@[platform].com

Note: The first release of UMA functionality supports invoice generation for the deposit of BTC which will be converted to fiat and sent via ACH/RTP. Additional functionality is listed under the “Upcoming Features” header below.


Register UMA Address

An invoice must be generated in order to receive a deposit over the Lightning Network. The customer can choose the username and the domain it is registered can take one of the forms listed above.

Once your UMA server is setup, a user must first be registered by your server and then sent to Zero Hash via POST /deposits/uma

{  
    "username": {UMA_ADDRESS}, // $username, see <https://uma.me> documentation  
    "participant_code": "{{participant_code}}"  
}

Confirm Creation of UMA Address

The UMA address creation can be confirmed by calling GET /deposits/uma
This call will return a list of created addresses.


Update or Change UMA Address

An UMA address can be changed after creation, such as in the case of a typo, by calling PATCH /deposits/uma

Request:

{  
    "new_username": "$scdgcr2",  
    "previous_username":"$scdgcr",  
    "participant_code": "3QX004"  
}

Success: 200

Error:
HTTP CODE 409 - conflict  
 {"message":"previous username does not match"}

Generate UMA Invoice

Once successfully registered, that platform can then generate an invoice by calling a LNURL request using a different Zero Hash URL depending on the environment (below example payloads use the CERT URL for illustration purposes):

  • CERT: uma.cert.zerohash.com/.well-known/lnurlp
  • PROD: uma.zerohash.com/.well-known/lnurlp

Note: The LNURL and PAYREQ must be sent from the onboarded Zero Hash platform. If the originating VASP is not the onboarded entity then these requests must be proxied to pass IP address / domain validation rules or else will be automatically rejected by default.

The LNURL request needs to follow the UMA documentation found here. A sample payload could look like the following:

GET uma.zerohash.com/.well-known/lnurlp/$bob?umaVersion=1.0&nonce=1234&vaspDomain=vasp1.com&signature=abcd&isSubjectToTravelRule=true×tamp=12345678

Note: The error message "UMA recipient not found" is returned if attempting to generate an invoice without registering the participant first.

The step following a successful LNURL request is to send the PayReq request with the generated invoice ID “lnurlp_callback”. The payreq call will look like the following:

POST uma.zerohash.com/api/payreq/<participant_code>

{  
   "amount":"1000.USD",  
   "payerData":{  
      "identifier":"$[[email protected]](mailto:[email protected])",  
      "email":"[[email protected]](mailto:[email protected])",  
      "name":"payer",  
      "compliance":{  
         "kycStatus":"VERIFIED",  
         "utxos":\[
           
            ],
     "nodePubKey":"",
     "signature":"",
     "signatureNonce":"",
     "signatureTimestamp":,
     "utxoCallback":""
  }
 },  
   "payeeData":{  
      "compliance":{  
         "mandatory":true  
      },  
      "identifier":{  
         "mandatory":true  
      },  
      "email":{  
         "mandatory":false  
      },  
      "name":{  
         "mandatory":false  
      }  
   },  
   "convert":"USD"  
}

The invoice is then returned and ready to be paid.


USD Funds

In the initial MVP implementation, once the invoice is paid, the BTC is offramped to USD. Standard implementations will choose if the BTC will stay as BTC or be offramped as part of the transaction messages.

The USD funds will sit in the participant’s account until they are debited by the Real-Time Payment process outlined in the next section.


The following sections review calls that can be made to confirm each step of the process.


Confirming the Deposit

The following command will return the deposits to a participant code

GET /deposits?participant_code={{participant_code}}

{  
    "message": [  
        {  
            "settle_timestamp": 1718813440365,  
            "account_id": "31206b3e-4305-4b74-bf23-3d1eaec3ae00",  
            "participant_code": "7D1QCQ",  
            "account_group": "UKYI3K",  
            "asset": "BTC",  
            "amount": "0.00001558",  
            "reference_id": "Invoice:01903143-4368-52d6-0000-07acc1700390",  
            "source": "[email protected]",  
            "received_address": "$testaddress",  
            "account_label": "general",  
            "movement_id": "93f778b2-3620-493e-8c44-4468ab3483e3", ‘movement_id will be set to the payment_hash of the invoice which originated that deposit.  
            "run_id": "3848133"  
        }  
    ],  
    "page": 1,  
    "page_size": 200,  
    "total_pages": 1,  
    "count": 1  
}

Confirming Trade Details (BTC to USD)

The following command will return the trades for a participant code

GET /trades?participant_code={{participant_code}}


Confirm Participant Balance Increase

The following command will return the trades for a participant code

GET /accounts?account_owner={{participant_code}}

{  
    "message": [  
        {  
            "asset": "USD",  
            "account_owner": "7D1QCQ",  
            "account_type": "available",  
            "account_group": "UKYI3K",  
            "account_label": "general",  
            "balance": "1",  
            "account_id": "1058112a-b1e4-4142-9fe2-47cd2284bb99",  
            "last_update": 1718813461541  
        }  
    ],  
    "page": 1,  
    "total_pages": 1  
}

Confirm Participant Transaction Movements

The following command will return the movement of both digital assets and fiat balances for a given participant code

GET /accounts/{{account_id}}/movements

{  
    "message": [  
        {  
            "asset": "USD",  
            "account_owner": "7D1QCQ",  
            "account_type": "available",  
            "account_group": "UKYI3K",  
            "account_label": "general",  
            "balance": "1",  
            "account_id": "1058112a-b1e4-4142-9fe2-47cd2284bb99",  
            "last_update": 1718813461541  
        }  
    ],  
    "page": 1,  
    "total_pages": 1  
}

Transaction Limits and Controls (UMA)

UMA deposits and withdrawals are only available with VASPs that have completed Zero Hash’s Compliance UMA Due Diligence and have been added to the allowlist as part of that process. UMA transactions to/from external VASPs will be blocked until they are part of the allowlist.

The minimum allowed transaction amount using UMA is 1 satoshi and millisatoshis are not recognized because these are below the minimum precision amount.


Upcoming features

Below are a list of features related to UMA Transactions that are on the roadmap for a future release:

  • Universal Money Address (UMA) Domain Registration
    • Until this feature is live, custom domains must be setup with the Zero Hash team directly
  • Universal Money Address (UMA) Withdrawals [Mid-September]
    • The ability to fulfill invoices and send funds to UMA addresses

ACH and Real-Time Payments (RTP)

Please see the separate guide for ACH and Real-time Payments that will cover steps to withdraw fiat funds from the participant account to an external bank account.


Error Codes & States

Please see the following table of error states and courses of action

ErrorZero Hash ActionPlatform Action
API to initiate offramp failsFail every htlc composing the invoice, funds revert to sender.
No error is currently returned in this case.
Sending VASP would need to retry transaction due to canceled invoice
API to initiate offramp succeeded but later payment status webhook comes back as payment failedZH notifies the platform via webhook of the failure. There is no automated retry of this step.Platform to retry POST /payments
When UMA address is incorrect → valid format $[email protected] uma-server responds with bad request response and a proper error message.
Resubmit with proper format
When UMA address is not found (non-existent)
uma-server responds with bad request response and a proper error message.
When any of the following query params is not sent:

- umaVersion * only version 1.0 will be supported for now
- nonce
- vaspDomain
- signature
- isSubjectToTravelRule
- Timestamp
uma-server responds with a bad request response and a proper error message.
When request goes OKuma-server responds with StatusOK and the following data
preferred currency
estimated exchange rate
compliance data
vasp status: (should always be verified)
When request sends a version other than 1.0 (not supported)uma-server responds with a bad request response and a proper error message.
Requesting the callback url multiple timesShould not be valid, resulting in bad request the second time the same callback url is used.
Sending payReq with not mandatory payer infoShould result in invalid request
receivingCurrencyCode is set to any value other than USD.When sender-vasp sends a payreq with receivingCurrencyCode set to MSat, it should result in a rejection
Conversion is OK according to payreq response.The conversion rate of the payreq response should be OK. Example:
Request: {
SendingCurrency: USD
ReceivingCurrency: USD
Amount: 10000 // means a rfq by total of $100 USD Notional
Response:
invoice with amount in MSats.
Check if the value actually corresponds to 100 USD given the conversion rate exposed in the response
Verify responseOnly USD, BTC and SAT should be supported currencies