Nexa mAPI

Nexa mAPI Specification

Nexa mAPI Specification

1 Overview

The Nexa Merchant API (mAPI) is a RESTful & TLS-JSON service offered to merchants for submitting transactions & querying policy/fee quotes by Bitcoin miners. Unlike the BRFC-mAPI spec the mAPI will be available to authenticated users only. The existing Nexa Auth framework, with robust user account management, session keys, API calls quota/limits will be extended to mAPI endpoints.

1.1 Design Objectives

The API specification intends to produce an simple and elegant API which is yet secure, powerful & extensible. Scalability, performance and stability is sought, and the interface limits excessive fields/use-cases which tend to bloat the client and server side implementations. Compatibility with BRFC-mAPI is sought in semantics and syntax where feasible.

As it operates within the Nexa Auth framework, ECDSA signing of responses with MinerID is superflous and thus dropped (& in-turn the Json envelope).

2 API Endpoints

2.1 Get policy quote

2.1.1 Purpose:

This endpoints is used to get the fees & policy quotes from the miner, there are global default quotes which can be overridden by user-level quotes tied to user's plan/usage.

2.1.2 Request:

curl -X GET -H "Authorization: Bearer <Session-Key>" -k 'https://<domain>:<port>/v1/mapi/policy

P.S: Corresponding POST, PUT & DEL methods will be authorized for admin accounts and have been omittted for brevity.

2.1.3 Response:

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "expiryTime":"2022-04-03T19:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "currentHighestBlockHash":"00000000000000000d33a33383b387aba91b50bb77fd0fc06789e82ac62d265e",
   "currentHighestBlockHeight":720321,
   "policy":{
      "maxTxSize":10000000,
      "maxUnitScriptSize":5000000,
      "maxCumulativeOpCodeCount":1000,
      "maxCumulativeDataSize":10000000,
      "txAncestorLimit":1000,
      "minUnitTxInputSatoshis":200,
      "minUnitTxOutputSatoshis":200,
      "graceConsolidationTxnsQuota":100,
      "maxTxFeesExceedLimit":5000000,
      "fees":{
         "data":{
            "bytes":32,
            "satoshis":20
         },
         "opcodes":{
            "OPCODE":{
               "satoshis":10
            },
            "OP_PUSHDATA1":{
               "satoshis":20
            },
            "OP_PUSHDATA2":{
               "satoshis":30
            },
            "OP_PUSHDATA4":{
               "satoshis":50
            },
            "OP_0":{
               "satoshis":10
            },
            "OP_RIPEMD160":{
               "satoshis":10
            },
            "OP_SHA256":{
               "satoshis":20
            },
            "OP_HASH160":{
               "satoshis":20
            },
            "OP_CODESEPARATOR":{
               "satoshis":10
            },
            "OP_CHECKSIG":{
               "satoshis":100
            },
            "OP_CHECKSIGVERIFY":{
               "satoshis":200
            },
            ...
            ...
            ...

         }
      }
   }
}
Field Description
apiVersion version of the merchant API specification used
timestamp timestamp of the payload document
expiryTime expiry time of quote
minerId minerID / public key of miner
currentHighestBlockHash hash of the current blockchain tip
currentHighestBlockHeight height of the current blockchain tip
policy policy details including limits & fee requirements
maxTxSize maximum transaction size in bytes
maxUnitScriptSize maximum individual script size in bytes
maxCumulativeOpCodeCount maximum cumulative opcodes allowed in a given transaction
maxCumulativeDataSize maximum cumulative (push)data allowed in a given transaction
txAncestorLimit unconfirmed chained tx ancestor limit
minUnitTxInputSatoshis dust limit on spending inputs in satoshis
minUnitTxOutputSatoshis dust limit on outputs created in satoshis
graceConsolidationTxnsQuota grace quota per account, meant for consolidation of dust UTxOs where it might not be economical to meet the fees and dust limits
maxTxFeesExceedLimit a safety threshold to prevent clients from accidentally allocating too much miner fee; txns which exceed the required fees by this threshold will be rejected
fees : data fees in satoshis per bytestring chunks of given bytes for (push)data contained in locking/unlocking scripts
fees : opcodes fees in satoshis per unit occurance of given opcodes in locking/unlocking scripts

2.2 Callback registration

2.2.1 Purpose:

This endpoint is used to subscribe for callback events by registering one or more callback URLs & corresponding authentication details.

2.2.2 Request:

curl -X POST -H "Authorization: Bearer <Session-Key>" -k 'https://<domain>:<port>/v1/mapi/registercallback
  1. HTTP Bearer token
    {
       "callBackName":"test_env",
       "callBackUrl":"https://foo.bar/callback",
       "callBackAuth":{
          "authType":"Bearer",
          "authToken":"<token>"
       },
       "events":[
          "merkleProofSingle",
          "merkleProofComposite",
          "doubleSpendingCheck",
          "ancestorsDiscovered"
       ]
    }
    
  2. HTTP Basic Authentication
    {
       "callBackName":"hosting_prov",
       "callBackUrl":"https://some.bar/somecallback",
       "callBackAuth":{
          "authType":"Basic",
          "username":"alice",
          "password":"wonderland123"
       },
       "events":[
          "doubleSpendingCheck"
       ]
    }
    
    Field Description
    callbackName name to identify a callback registration
    callbackURL HTTPS endpoint used to receive messages from the miner ( Plain HTTP is not secure and hence not accepted )
    callbackAuth : authType HTTP authorization mechanism when authenticating against callbackURL, must be either 'Bearer' or 'Basic'
    callbackAuth : authToken HTTP authorization header Bearer token
    callbackAuth : username/password Meant for HTTP Basic authentication. The Authorization header will be prepared using the credentials provided.
    events subscribe for a set of callback events, values limited to 'merkleProofSingle', 'merkleProofComposite', 'doubleSpendingCheck', 'ancestorsDiscovered'

2.2.3 Response:

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "expiryTime":"2022-04-03T19:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "callBackName":"hosting_prov",
   "errorCode":0,
   "errorDescription":""
   "originatingIPAddresses":["10.23.63.23","10.23.45.11"]
}
Field Description
callbackName name used in registration request
errorCode 0 if success; non zero code in case of failure
errorDescription will contain the error details on failure; empty on success
originatingIPAddresses list of IP addresses from which the callback requests will originate, meant for IP whitelisting

2.3 Submit transaction

2.3.1 Purpose:

This endpoint is used to send a raw transaction to a miner for inclusion in the subsequent block.

2.3.2 Request:

curl -X POST -H "Authorization: Bearer <Session-Key>" -k 'https://<domain>:<port>/v1/mapi/submittx
  1. JSON body
    {
        "rawtx": "H4sIAAAAAAACA2NkYGBg+nv/4u8f7LrTHcL8FtlyK.......",
        "callBackName":"hosting_prov"
    }
    
    Field Description
    rawtx gzip => base64 => utf-8 encoded transaction string
    callbackName pre-registered callback name
    merkleProofSingle (optional; boolean) used to request a merkle proof, overrides callback registration value
    merkleProofComposite (optional; boolean) used to request a composite merkle proof, overrides callback registration value
    doubleSpendingCheck (optional; boolean) used to request double spend notification, overrides callback registration value
    ancestorsDiscovered (optional; boolean) used to request double spend notification, overrides callback registration value


  2. Binary Data

    Submit the transaction with the binary serialized transaction in the request body. The other fields can be included as query parameters. Set Content-Type to application/octet-stream.

2.3.3 Response:

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "expiryTime":"2022-04-03T19:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "currentHighestBlockHash":"00000000000000000d33a33383b387aba91b50bb77fd0fc06789e82ac62d265e",
   "currentHighestBlockHeight":720321,
   "txId":"fed22f5ab54202e2ec39cb745d427fcfff960254cde0cf283493ac545f5737f6",
   "errorCode":0,
   "errorDescription":""
}
Field Description
txid transaction ID
errorCode 0 if success; non zero code in case of failure
errorDescription will contain the error details on failure; empty on success
conflictedWith list of known conflicting transactions which spent the same output already

2.4 Subscribe transaction

2.4.1 Purpose:

This endpoint is used to subscribe for a transaction (by txId). If the transaction is yet to be included in the subsequent block, then subscription is registered and subsequently asynchronous callbacks will notify on the tx status. Else, if the tx is already mined into a block, then the Merkle proof for the same if returned. Anyone can subscribe to a transaction, so the party submitting the transaction can be different from the subscriber. Also, multiple parties can subscribe for the same txid.

2.4.2 Request:

curl -X POST -H "Authorization: Bearer <Session-Key>" -k 'https://<domain>:<port>/v1/mapi/subscribetx
  1. JSON body
    {
        "txId": "9b7280ec3273028a1d6093cb38dda0b963812de5b1d11ce47c9b7def690e6e64",
        "callBackName":"hosting_prov",
        "state": {...}
    }
    
    Field Description
    txId TxId string
    callbackName pre-registered callback name
    state (optional; nested JSON object) can be used to store/relay arbitrary state info associated with the tx into the callback api
    merkleProofSingle (optional; boolean) used to request a merkle proof, overrides callback registration value
    merkleProofComposite (optional; boolean) used to request a composite merkle proof, overrides callback registration value
    doubleSpendingCheck (optional; boolean) used to request double spend notification, overrides callback registration value
    ancestorsDiscovered (optional; boolean) used to request ancestors discovered notification, overrides callback registration value

2.4.3 Response:

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "expiryTime":"2022-04-03T19:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "currentHighestBlockHash":"00000000000000000d33a33383b387aba91b50bb77fd0fc06789e82ac62d265e",
   "currentHighestBlockHeight":720321,
   "txId":"fed22f5ab54202e2ec39cb745d427fcfff960254cde0cf283493ac545f5737f6",
   "errorCode":0,
   "errorDescription":""
}
Field Description
txid transaction ID
errorCode 0 if success; non zero code in case of failure
errorDescription will contain the error details on failure; empty on success
conflictedWith list of known conflicting transactions which spent the same output already

2.5 Callbacks

2.5.1 Tx confirmed (Single Merkle proof)

This callback is invoked when a previously submitted transaction is successfully included in the mined block. The client can use the contained Merkle root and branch details to validate the same. Individual callbacks will be issued for every subscribed transaction, this is invoked asynchronously and clients should not expect to receive callbacks in tx-index order.

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "callBackType":"merkleProofSingle",
   "txId":"e7b3eefab33072e62283255f193ef5d22f26bbcfc0a80688fe2cc5178a32dda6",
   "blockHash":"00000000000000000d33a33383b387aba91b50bb77fd0fc06789e82ac62d265e",
   "blockHeight":720321,
   "merkleRoot":"70a920b1002bed05379a0d2650bb13eb216138f28ee80172f4cf21048528dc60",
   "txIndex": 1464,
   "state": {...},
   "merkleBranch":[
      {
         "hash":"fa9413f13fddeabef277a8f654f27a86673cffe87ab1c90930d51fa23b94f926",
         "isLeft":true
      },
      {
         "hash":"97b57176888398ccfde6ea672ba1fe4f41c186d3cb6a8833bee0df4674d0e46b",
         "isLeft":false
      },
      {
         "hash":"6b45c0a03f27fe58a946deed22274d93ea90bc49ef0ce0508bf9eecccbe9ad6c",
         "isLeft":false
      },
      {
         "hash":"f36b8f725f266657db454d8996ebd633c35c01c896cf8f6da91f5443295fa0d8",
         "isLeft":false
      },
      {
         "hash":"d772606e6716c55ddab4740aa867efc43140d9eb2b8c6419f4b385e91df33de7",
         "isLeft":false
      },
      {
         "hash":"119599c2724b2d62176bc74582618d4a362199c692863ae30a0940b47180019d",
         "isLeft":false
      },
      {
         "hash":"d592a7459cf743e9184d28267a8e433d0c980a91ac9c95869c2e1382d8408be0",
         "isLeft":false
      },
      {
         "hash":"7db7beaf03b2557e4e9289e33bf96444a7670b72998df646a02d902f9a2c31c0",
         "isLeft":false
      },
      {
         "hash":"f0caff9e93bc0ffe943538e242acdcbcdfb1759f59b0ef06da2ca834187cbb18",
         "isLeft":true
      }
   ]
}

2.5.2 Transactions confirmed (Composite Merkle proof)

This callback is invoked once and final when previously submitted transactions are successfully included in the mined block. The client can use the contained Merkle root and branch details to validate the same. A single composite callback containing the subset of transactions that were newly included in the block. Clients can treat this as a complete & final update.

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "callBackType":"merkleProofComposite",
   "blockHash":"00000000000000000d33a33383b387aba91b50bb77fd0fc06789e82ac62d265e",
   "blockHeight":720335,
   "merkleRoot":"70a920b1002bed05379a0d2650bb13eb216138f28ee80172f4cf21048528dc60",
   "merkleBranches":[
      {
         "txId":"e7b3eefab33072e62283255f193ef5d22f26bbcfc0a80688fe2cc5178a32dda6",
         "txIndex":43,
         "state": {...},
         "merkleBranch":[
            {
               "hash":"fa9413f13fddeabef277a8f654f27a86673cffe87ab1c90930d51fa23b94f926",
               "isLeft":true
            },
            {
               "hash":"97b57176888398ccfde6ea672ba1fe4f41c186d3cb6a8833bee0df4674d0e46b",
               "isLeft":false
            },
            {
               "hash":"6b45c0a03f27fe58a946deed22274d93ea90bc49ef0ce0508bf9eecccbe9ad6c",
               "isLeft":false
            },
            {
               "hash":"f36b8f725f266657db454d8996ebd633c35c01c896cf8f6da91f5443295fa0d8",
               "isLeft":false
            }
         ]
      },
      {
         "txId":"22f263ef5dddae2cefcfc0a8c5178a32e7b3e068bbf198f62283255ab33072e6",
         "txIndex":79,
         "state": {...},
         "merkleBranch":[
            {
               "hash":"d772606e6716c55ddab4740aa867efc43140d9eb2b8c6419f4b385e91df33de7",
               "isLeft":false
            },
            {
               "hash":"119599c2724b2d62176bc74582618d4a362199c692863ae30a0940b47180019d",
               "isLeft":false
            },
            {
               "hash":"d592a7459cf743e9184d28267a8e433d0c980a91ac9c95869c2e1382d8408be0",
               "isLeft":false
            },
            {
               "hash":"7db7beaf03b2557e4e9289e33bf96444a7670b72998df646a02d902f9a2c31c0",
               "isLeft":false
            }
         ]
      }
   ]
}

2.5.3 Double Spending notification

This callback is invoked when UTxOs contained in a previously submitted transaction is subsequently caught being double spent.

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "callBackType":"doubleSpendAttempt",
   "txid":"e7b3eefab33072e62283255f193ef5d22f26bbcfc0a80688fe2cc5178a32dda6"
   "blockHash":"00000000000000000d33a33383b387aba91b50bb77fd0fc06789e82ac62d265e",
   "blockHeight":720321,
   "doubleSpendingTxIds":[ "70a920b1002bed05379a0d2650bb13eb216138f28ee80172f4cf21048528dc60" ]
}

2.5.4 Ancestors discovered

This callback is invoked on a submitted transaction when all its ancestors are discovered & validated effectively accepting this transaction which was reported as an orphan at the time of submission.

{
   "apiVersion":"1.0.0",
   "timestamp":"2022-04-03T18:25:43.511Z",
   "minerId":"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
   "callBackType":"ancestorsDiscovered",
   "txid":"e7b3eefab33072e62283255f193ef5d22f26bbcfc0a80688fe2cc5178a32dda6"
   "blockHash":"00000000000000000d33a33383b387aba91b50bb77fd0fc06789e82ac62d265e",
   "blockHeight":720321
}

2.6 Addendum

2.6.1 Query transaction status

A set of existing Nexa APIs offer more insightful and versatile replacement to the one listed in BRFC-mAPI specification.

2.6.2 Convenience API for validating Tx fees

This spec proposes an entirely new (& appropriate) fee calculation model, it discards the 'standard' vs 'data' template based dichotomy, and instead adopts a more granular opcode based model which better fits the underlying computation & data-size metrics that matter to the miner. Although the fee calculation at the client side is entirely objective given the policy template, it could be a slight challenge for some clients, so to mitigate this - a convenience service endpoint that offers fee insights & validity can be offered.

Created: 2022-04-21 Thu 20:11

Validate