Skip to main content
In the event that a payment fails to complete there are several options for a user to recover their assets.

Causes of payment interruptions

Crypto deposits require orchestration of several independent protocols like bridges, DEXs, blockchains, onramp providers and more. Mid-workflow, an asset’s price could change significantly, a fee could increase beyond the bounds of the quote, an onramp could experience an unexpected delay or other unforseen setbacks. Halliday Payments was built to be robust under all of these conditions within the ever-growing ecosystem of onchain protocols. The Halliday payments OTWs are self-custodial. Users are always the sole controllers of these addresses through their wallet.

Recoveries

Halliday Payments provides two options for recovering assets within an interrupted payment.

Retry a payment

An interrupted payment can be retried with a new quote. The new output amount may differ from the original quote based on asset price changes. If a payment expires, and has not been funded, it is safe to abandon it and create a whole new payment. Effectively, a new payment is created and then funded using the interrupted payment by transferring tokens from the old deposit address to the new deposit address using an EIP-712 signature. An example of this signature request is shown below. Retry a payment using the API
  • A funded payment is not completing.
  • Query the balances API endpoint (POST /payments/balances) and pass the incomplete payment ID which we will call failed payment_id. This returns the deposit address (address) and the balance. Check that the balance is greater than zero. If so, a recovery can be performed.
  • Get new quotes using POST /payments/quotes. Provide the failed payment_id as parent_payment_id in the request body.
  • Once the user selects a quote, confirm the new quote using POST /payments/confirm. To do this, pass the new quote’s payment ID, which we will call new payment_id, to the confirm endpoint.
  • Next call the withdraw endpoint (POST /payments/withdraw) with the parameters:
    • payment_id: The failed payment’s ID.
    • token_amount: The returned amount of token from the prior POST /payments/balances endpoint call.
    • recipient_address: The address of the new payment’s deposit address.
  • The owner wallet of the failed payment must sign an EIP-712 withdrawal transaction. This is returned from the POST /payments/withdraw call response object as the withdraw_authorization property.
  • Next call the confirm withdraw endpoint (POST /payments/withdraw/confirm) with the same parameters passed to the prior API call along with the newly created owner_signature. The withdrawal will be executed onchain automatically and transfer the assets from the old deposit address to the new one.

Withdrawals

Assets lingering in a deposit address can be withdrawn to any address specified in a withdrawal signature created by the owner wallet. In some situations, this may result in the asset being moved to a user-controlled wallet on a different chain than the intended destination. Withdrawal steps using the API
  • A funded payment is not completing.
  • Query the balances API endpoint (POST /payments/balances) and pass the incomplete payment ID which we will call failed payment_id. This returns the deposit address (address) and the balance. Check that the balance is greater than zero. If so, a recovery can be performed.
  • Next call the withdraw endpoint (POST /payments/withdraw) with the parameters:
    • payment_id: The failed payment’s ID.
    • token_amount: The returned amount of token from the prior POST /payments/balances endpoint call.
    • recipient_address: The address to withdraw the tokens to, usually the owner’s wallet.
  • The owner wallet of the failed payment must sign an EIP-712 withdrawal transaction. This is returned from the POST /payments/withdraw call response object as the withdraw_authorization property.
  • Next call the confirm withdraw endpoint (POST /payments/withdraw/confirm) with the same parameters passed to the prior API call along with the newly created owner_signature. This signature will be executed onchain automatically and transfer the assets.

Example Withdrawal Signature Request

The following is an example withdrawal signature request on EVM chains using EIP-712 which is returned from the POST /payments/withdraw API endpoint. The owner of the payment will generate a signature using their private key in order to confirm a withdrawal.
{
  "domain": { "name": "Halliday Workflow Protocol", "version": "1" },
  "types": {
    "EIP712Domain": [ { "type": "string", "name": "name" }, { "type": "string", "name": "version" } ],
    "Call": [ { "type": "address", "name": "target" }, { "type": "bytes", "name": "data" }, { "type": "uint256", "name": "value" } ],
    "HallidayAccount": [ { "type": "string", "name": "description" }, { "type": "Call[]", "name": "actions" }, { "name": "nonce", "type": "uint256" }, { "type": "bytes32", "name": "signatory_declaration_hash" }, { "type": "uint256", "name": "chain_id" }, { "type": "bool", "name": "accept_blame" } ]
  },
  "primaryType": "HallidayAccount",
  "message": {
    "description": "Transfer 50 USDC on Base to address 0x...",
    "actions": [
      {
        "target": "0xrecipient",
        "data": "0xdata",
        "value": "0"
      }
    ],
    "nonce": "0",
    "signatory_declaration_hash": "0x123...",
    "chain_id": "8453",
    "accept_blame": true
  }
}

API Errors

The REST API returns formatted errors with a corresponding error code, such as 400 for bad request and 401 for unauthorized. Example 401 response
{
  "errors": [
    {
      "kind": "other",
      "message": "Invalid public api key"
    }
  ]
}
Example 400 response
{
  "errors": [
    {
      "origin": "string",
      "code": "invalid_format",
      "format": "regex",
      "pattern": "/^[a-zA-Z]\\w{1,7}$/",
      "path": [ "inputs", 0 ],
      "message": "Must be a short alpha-numeric symbol"
    }
  ]
}
The GET /assets/available-inputs and GET /assets/available-outputs endpoints will return bad request errors in the scenario that an unsupported asset address is passed as a parameter.