> For the complete documentation index, see [llms.txt](https://docs.zetrix.com/en/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zetrix.com/en/developer-resources/blockchain-as-a-services-baas/tradetrust-api/transaction.md).

# Transaction

Build, sign, submit and track a raw transaction, plus fee estimation. The blob/sign/submit split lets a caller keep key custody.

## POST /tx/estimate-fee

> Estimate the transaction fee

```json
{"openapi":"3.1.0","info":{"title":"myeg-z2-service - TradeTrust","version":"0.0.1"},"tags":[{"name":"Transaction","description":"Build, sign, submit and track a raw transaction, plus fee estimation. The blob/sign/submit split lets a caller keep key custody."}],"servers":[{"url":"https://api-sandbox.zetrix.com/z2","description":"Testnet / UAT, through AWS API Gateway. Needs X-API-Key in addition to the bearer token."}],"security":[{"bearerAuth":[],"apiKeyAuth":[]},{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"OAuth2 bearer token from the shared BaaS auth server.\n\nEvery endpoint in this subset requires one. Note the security filter runs BEFORE routing, so an unauthenticated request to a path that does not exist also answers 401, not 404 - a 404 is only visible to an authenticated caller.","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"EstimateFeeReqDto":{"type":"object","description":"Request for <code>POST /tx/estimate-fee</code>.\n\n <code>from</code> matters: gas depends on the sender's state (whether a storage slot is already\n set, whether the account exists), so estimating without it can give a materially different\n number than the real transaction will use.","properties":{"from":{"type":"string","minLength":1},"to":{"type":"string","minLength":1},"value":{"type":"integer"},"data":{"type":"string"}},"required":["from","to"]},"ResponseWrapperEstimateFeeRespDto":{"type":"object","description":"Standard BaaS response envelope. Mirrors the shape used across the microservices so\n callers get a consistent contract on L2 as on L1.","properties":{"success":{"type":"boolean"},"errorCode":{"type":"string"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/EstimateFeeRespDto"}}},"EstimateFeeRespDto":{"type":"object","description":"Response of <code>POST /tx/estimate-fee</code>. All values in wei.\n\n <code>fee</code> is <code>gasLimit * gasPrice</code> — the ceiling the sender must be able to cover, not\n what the transaction will necessarily cost. Unused gas is not charged, so the actual spend is\n <code>gasUsed * gasPrice</code>, which is only known from the receipt.\n\n No safety margin is applied to gasPrice: it is the node's <code>eth_gasPrice</code> verbatim, per\n the 2026-08-05 decision to follow the node. (myeg-ms-ethereum doubles it below 25 gwei — a\n threshold tuned for Ethereum mainnet and meaningless on a chain quoting 7 wei.)","properties":{"gasLimit":{"type":"integer"},"gasPrice":{"type":"integer"},"fee":{"type":"string","description":"<b>A decimal String, not a JSON number</b>, unlike the two factors above.\n\n <p><code>gasLimit</code> and <code>gasPrice</code> are each safely inside a JavaScript number — a block\n gas limit is millions, and even 20 gwei is 2 × 10^10. Their <b>product</b> is not: 10^6 gas at\n 20 gwei is 2 × 10^16, past <code>Number.MAX_SAFE_INTEGER</code> (~9.007 × 10^15). Z2 quoting 7 wei\n hides this today, so the field would look fine right up until this service pointed at a chain\n with an ordinary fee market."}}}}},"paths":{"/tx/estimate-fee":{"post":{"tags":["Transaction"],"operationId":"estimateFee","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EstimateFeeReqDto"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ResponseWrapperEstimateFeeRespDto"}}}}},"summary":"Estimate the transaction fee"}}}}
```

## POST /tx/generate-blob

> Build a signable transaction blob

```json
{"openapi":"3.1.0","info":{"title":"myeg-z2-service - TradeTrust","version":"0.0.1"},"tags":[{"name":"Transaction","description":"Build, sign, submit and track a raw transaction, plus fee estimation. The blob/sign/submit split lets a caller keep key custody."}],"servers":[{"url":"https://api-sandbox.zetrix.com/z2","description":"Testnet / UAT, through AWS API Gateway. Needs X-API-Key in addition to the bearer token."}],"security":[{"bearerAuth":[],"apiKeyAuth":[]},{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"OAuth2 bearer token from the shared BaaS auth server.\n\nEvery endpoint in this subset requires one. Note the security filter runs BEFORE routing, so an unauthenticated request to a path that does not exist also answers 401, not 404 - a 404 is only visible to an authenticated caller.","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"GenerateBlobReqDto":{"type":"object","description":"Request for <code>POST /tx/generate-blob</code>.\n\n The three override fields exist so a caller can pin a transaction exactly (replacing a stuck\n nonce, for instance). Left null, each is resolved from the node: nonce from\n <code>eth_getTransactionCount(pending)</code>, gasPrice from <code>eth_gasPrice</code>, gasLimit from\n <code>eth_estimateGas</code>.","properties":{"from":{"type":"string","minLength":1},"to":{"type":"string","description":"Recipient. Required here — contract deployment (no recipient) is BT-2584.","minLength":1},"value":{"type":"integer","description":"Native value in wei. Defaults to zero."},"data":{"type":"string","description":"Hex-encoded call data. Null or empty for a plain transfer."},"nonce":{"type":"integer"},"gasPrice":{"type":"integer"},"gasLimit":{"type":"integer"},"maxFeePerGas":{"type":"integer","description":"EIP-1559 ceiling, in wei. Supplying it builds a <b>type-2</b> transaction instead of a\n legacy one; leave it null and nothing changes.\n\n <p>You are charged <code>baseFeePerGas + maxPriorityFeePerGas</code> and refunded the rest, so a\n generous ceiling does not cost more - it only has to be high enough that the transaction\n stays includable if the base fee rises.\n\n <p>Mutually exclusive with <code>gasPrice</code>: naming both a legacy bid and a 1559 ceiling is\n contradictory, and guessing which one the caller meant is worse than refusing."},"maxPriorityFeePerGas":{"type":"integer","description":"EIP-1559 validator tip, in wei. Only meaningful with <code>maxFeePerGas</code>; defaults to 0."}},"required":["from","to"]},"ResponseWrapperGenerateBlobRespDto":{"type":"object","description":"Standard BaaS response envelope. Mirrors the shape used across the microservices so\n callers get a consistent contract on L2 as on L1.","properties":{"success":{"type":"boolean"},"errorCode":{"type":"string"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/GenerateBlobRespDto"}}},"GenerateBlobRespDto":{"type":"object","description":"Response of <code>POST /tx/generate-blob</code>.\n\n The resolved nonce/gasPrice/gasLimit are echoed back deliberately: they are already encoded\n inside the blob, but a caller signing client-side needs to see what they are about to commit\n to without having to RLP-decode it first.","properties":{"blob":{"type":"string","description":"Unsigned transaction, RLP hex. Feed this to /tx/sign-blob or sign it yourself."},"nonce":{"type":"integer"},"gasPrice":{"type":"integer"},"gasLimit":{"type":"integer"},"chainId":{"type":"integer","format":"int64","description":"The chain id the blob must be signed with (EIP-155). Read from the node."},"maxFeePerGas":{"type":"integer","description":"Set only when the blob is a type-2 transaction; null for a legacy one."},"maxPriorityFeePerGas":{"type":"integer"}}}}},"paths":{"/tx/generate-blob":{"post":{"tags":["Transaction"],"operationId":"generateBlob","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateBlobReqDto"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ResponseWrapperGenerateBlobRespDto"}}}}},"summary":"Build a signable transaction blob"}}}}
```

## POST /tx/sign-blob

> Sign a transaction blob

```json
{"openapi":"3.1.0","info":{"title":"myeg-z2-service - TradeTrust","version":"0.0.1"},"tags":[{"name":"Transaction","description":"Build, sign, submit and track a raw transaction, plus fee estimation. The blob/sign/submit split lets a caller keep key custody."}],"servers":[{"url":"https://api-sandbox.zetrix.com/z2","description":"Testnet / UAT, through AWS API Gateway. Needs X-API-Key in addition to the bearer token."}],"security":[{"bearerAuth":[],"apiKeyAuth":[]},{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"OAuth2 bearer token from the shared BaaS auth server.\n\nEvery endpoint in this subset requires one. Note the security filter runs BEFORE routing, so an unauthenticated request to a path that does not exist also answers 401, not 404 - a 404 is only visible to an authenticated caller.","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"SignBlobReqDto":{"type":"object","description":"Request for <code>POST /tx/sign-blob</code>.\n\n <b>Carries a private key.</b> This is why the endpoint is POST rather than GET: a key in a\n query string ends up in access logs, proxy logs and browser history. The key is used to sign\n and then discarded — never logged, never persisted. Callers who would rather not send it at\n all should sign the blob client-side; that is the reason the blob/sign/submit split exists.","properties":{"blob":{"type":"string","description":"Unsigned transaction as RLP hex, from <code>/tx/generate-blob</code>.","minLength":1},"privateKey":{"type":"string","minLength":1}},"required":["blob","privateKey"]},"ResponseWrapperSignBlobRespDto":{"type":"object","description":"Standard BaaS response envelope. Mirrors the shape used across the microservices so\n callers get a consistent contract on L2 as on L1.","properties":{"success":{"type":"boolean"},"errorCode":{"type":"string"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/SignBlobRespDto"}}},"SignBlobRespDto":{"type":"object","description":"Response of <code>POST /tx/sign-blob</code>.\n\n Contains no echo of the submitted private key, by design — see <code>SignBlobReqDto</code>.","properties":{"signedBlob":{"type":"string","description":"Signed transaction, RLP hex. Ready for /tx/submit."},"transactionHash":{"type":"string","description":"The hash this transaction will have once submitted (keccak256 of the signed RLP).\n Computable before submission, so a caller can start watching for it immediately."},"chainId":{"type":"integer","format":"int64","description":"The chain id baked into the signature, for the caller to verify."}}}}},"paths":{"/tx/sign-blob":{"post":{"tags":["Transaction"],"operationId":"signBlob","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignBlobReqDto"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ResponseWrapperSignBlobRespDto"}}}}},"summary":"Sign a transaction blob"}}}}
```

## Get transaction status

> What became of a submitted transaction.\
> \
> &#x20;\<p>GET with a query parameter, matching L1's \<code>GET /tx/status?hash=\</code>. Safe as a GET\
> &#x20;because a transaction hash is public information — unlike a private key, it does no harm in\
> &#x20;an access log.\
> \
> &#x20;\<p>A hash the node has not seen returns \<code>PENDING\</code>, not 404: over JSON-RPC "still in the\
> &#x20;mempool" and "never existed" are indistinguishable, so reporting not-found would be a guess.

```json
{"openapi":"3.1.0","info":{"title":"myeg-z2-service - TradeTrust","version":"0.0.1"},"tags":[{"name":"Transaction","description":"Build, sign, submit and track a raw transaction, plus fee estimation. The blob/sign/submit split lets a caller keep key custody."}],"servers":[{"url":"https://api-sandbox.zetrix.com/z2","description":"Testnet / UAT, through AWS API Gateway. Needs X-API-Key in addition to the bearer token."}],"security":[{"bearerAuth":[],"apiKeyAuth":[]},{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"OAuth2 bearer token from the shared BaaS auth server.\n\nEvery endpoint in this subset requires one. Note the security filter runs BEFORE routing, so an unauthenticated request to a path that does not exist also answers 401, not 404 - a 404 is only visible to an authenticated caller.","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"ResponseWrapperTxStatusRespDto":{"type":"object","description":"Standard BaaS response envelope. Mirrors the shape used across the microservices so\n callers get a consistent contract on L2 as on L1.","properties":{"success":{"type":"boolean"},"errorCode":{"type":"string"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/TxStatusRespDto"}}},"TxStatusRespDto":{"type":"object","description":"Response of <code>GET /tx/status?hash=…</code> — the outcome of a submitted transaction.\n\n <p>This closes the gap that made <code>/tx/submit</code> a dead end: you got a hash and had no way to\n ask what became of it. A hash means only that the node accepted the transaction into its pool; a\n transaction that <b>reverts is still mined and still has that hash</b>, so the hash alone can\n never tell success from failure.\n\n <p>The four states are genuinely different and are worth distinguishing:\n <ul>\n   <li><code>SUCCESS</code> — mined, <code>status: 0x1</code>. It ran and did not revert.</li>\n   <li><code>FAILED</code> — mined, <code>status: 0x0</code>. It reverted; gas was still consumed. See\n       <code>revertReason</code>.</li>\n   <li><code>PENDING</code> — no receipt yet. Either still in the pool, or the hash never existed —\n       JSON-RPC cannot tell those apart, so neither can we, and claiming otherwise would be a\n       guess. Poll, and treat a hash that stays pending indefinitely as never having landed.</li>\n </ul>","properties":{"transactionHash":{"type":"string"},"status":{"type":"string","enum":["SUCCESS","FAILED","PENDING"]},"blockNumber":{"type":"integer"},"gasUsed":{"type":"integer"},"from":{"type":"string"},"to":{"type":"string"},"contractAddress":{"type":"string","description":"Set only for a contract creation, and this is the <b>authoritative</b> address — unlike the\n prediction from <code>/contract/deploy/generate-blob</code>, which is conditional on the nonce\n being spent as planned. Once a deploy reaches <code>SUCCESS</code>, use this one."},"revertReason":{"type":"string","description":"Why a <code>FAILED</code> transaction reverted, when it can be recovered.\n\n <p>Revert data is not stored on chain, so this comes from replaying the call against the\n state it ran in. That needs historical state: a pruned node cannot serve it and this stays\n null. Null therefore means \"could not be recovered\", never \"no reason\"."}}}}},"paths":{"/tx/status":{"get":{"tags":["Transaction"],"summary":"Get transaction status","description":"What became of a submitted transaction.\n\n <p>GET with a query parameter, matching L1's <code>GET /tx/status?hash=</code>. Safe as a GET\n because a transaction hash is public information — unlike a private key, it does no harm in\n an access log.\n\n <p>A hash the node has not seen returns <code>PENDING</code>, not 404: over JSON-RPC \"still in the\n mempool\" and \"never existed\" are indistinguishable, so reporting not-found would be a guess.","operationId":"status","parameters":[{"name":"hash","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ResponseWrapperTxStatusRespDto"}}}}}}}}}
```

## POST /tx/submit

> Submit a signed transaction

```json
{"openapi":"3.1.0","info":{"title":"myeg-z2-service - TradeTrust","version":"0.0.1"},"tags":[{"name":"Transaction","description":"Build, sign, submit and track a raw transaction, plus fee estimation. The blob/sign/submit split lets a caller keep key custody."}],"servers":[{"url":"https://api-sandbox.zetrix.com/z2","description":"Testnet / UAT, through AWS API Gateway. Needs X-API-Key in addition to the bearer token."}],"security":[{"bearerAuth":[],"apiKeyAuth":[]},{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","description":"OAuth2 bearer token from the shared BaaS auth server.\n\nEvery endpoint in this subset requires one. Note the security filter runs BEFORE routing, so an unauthenticated request to a path that does not exist also answers 401, not 404 - a 404 is only visible to an authenticated caller.","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"SubmitTxReqDto":{"type":"object","description":"Request for <code>POST /tx/submit</code>: a signed transaction as RLP hex.","properties":{"signedBlob":{"type":"string","minLength":1}},"required":["signedBlob"]},"ResponseWrapperSubmitTxRespDto":{"type":"object","description":"Standard BaaS response envelope. Mirrors the shape used across the microservices so\n callers get a consistent contract on L2 as on L1.","properties":{"success":{"type":"boolean"},"errorCode":{"type":"string"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/SubmitTxRespDto"}}},"SubmitTxRespDto":{"type":"object","description":"Response of <code>POST /tx/submit</code>.\n\n A hash here means the node <b>accepted</b> the transaction into its pool — not that it has\n been mined, and not that it will succeed. There is currently no way to follow up on it: a\n status/receipt endpoint is a known gap with no ticket (see the BT-2583 plan doc §1).","properties":{"transactionHash":{"type":"string"}}}}},"paths":{"/tx/submit":{"post":{"tags":["Transaction"],"operationId":"submit","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitTxReqDto"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ResponseWrapperSubmitTxRespDto"}}}}},"summary":"Submit a signed transaction"}}}}
```
