Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

Exchange and merchant integration

Epic requires a different deposit architecture from address-based chains. The per-user deposit-address model used by most exchange backends does not exist.

Per-user deposit addresses do not exist

The usual pattern is to derive an address per user, watch the chain for credits to it, and attribute each credit to that user. None of those steps is available:

  • There are no addresses in the ledger. See the MimbleWimble model.
  • There is no balance or history query. Nothing to poll.
  • The only on-chain identifier is an output commitment, a blinded point that reveals neither party nor amount.
  • A deposit cannot arrive unattended. An Epic transfer is interactive, so your side must take part in building it. See interactive transactions.

Attribution therefore cannot come from the chain. It has to come from the transport.

What replaces it

Carry the account identifier in the deposit endpoint. Expose a per-user URL, have the user's wallet send to that URL, and record who paid from the path. The transport carries identity; the chain carries none.

Deploy wallet listeners behind a reverse proxy that maps each per-user path onto a wallet's Foreign API, and write attribution when the slate arrives.

Map a per-user path onto a listener

The Epic-specific part is one location block inside your existing TLS server. It captures the account identifier from the path, passes it to your attribution service as a header, and refuses every other path:

/etc/nginx/conf.d/epic-deposits.conf
location ~ ^/deposit/([A-Za-z0-9_-]{1,64})$ {
proxy_set_header X-Epic-Account $1;
proxy_pass http://127.0.0.1:8080/receive;

client_max_body_size 1m; # slates are small
proxy_read_timeout 120s; # the exchange is interactive
}

location / { return 404; }

proxy_read_timeout has to outlast a round of slate building. The wallet's Foreign API is never the proxy_pass target directly. The attribution service is, so the account is recorded before anything is signed.

Record the account before you co-sign

Your service at 127.0.0.1:8080 records X-Epic-Account against the incoming slate id, then calls receive_tx on the wallet's Foreign API. The receive example shows that call. The Epic-specific requirement is the ordering:

# Write attribution first and commit it. An unattributed credit cannot be
# reconstructed from the chain later, so a failed write refuses the deposit.
await record_pending_deposit(account=x_epic_account, slate_id=slate["id"])
return await receive_tx(slate)

The slate id is the join key. It is the id field of the slate you were handed, and it is the same value that appears as tx_slate_id in the wallet's own transaction log, so attribution can be reconciled against the wallet afterwards even though it cannot be reconciled against the chain.

Your database is the only record of who paid you

The chain never held the account identifier, so lost or corrupted attribution data cannot be reconstructed from it. Back it up as primary financial data.

Architecture

  1. Your own fully validating node. See node and wallet setup.
  2. Hot wallets running the Foreign API to receive and the Owner API for your backend to drive.
  3. A reverse proxy terminating TLS, mapping per-user paths onto listeners, and exposing nothing but those paths. The Foreign API takes no credential, because a counterparty cannot hold one, so the proxy is the whole of the access control on that surface.
  4. A credential on the Owner API. That surface does take one, and your backend is the only client of it. See the surfaces.
  5. Your attribution database, written synchronously as slates arrive.
  6. Withdrawal processing through the Owner API, with retry and cancellation logic.

Withdrawals need a state machine

A withdrawal is an exchange with the user's wallet that may not complete.

  • Starting a send reserves your outputs immediately, so a queue of incomplete withdrawals progressively immobilises your hot wallet balance. See what reserves outputs.
  • Nothing expires on its own. You decide when to give up, and cancel explicitly. See cancelling has preconditions.
  • The send path blocks while it polls the mempool, so do not run it on a request thread. See the send appears to hang.

Model a withdrawal as: created, awaiting counterparty, finalised, posted, confirmed, or cancelled. Set your own deadline, since the protocol does not provide one, and cancel on expiry to release the outputs.

Confirmation depth

The protocol prescribes no deposit confirmation depth. What it fixes is the cost of one block of depth, 60 seconds at the target block time. 1,440 blocks is the depth consensus itself requires before a coinbase can be spent (emission timing).

Read the depth of a specific deposit as the chain tip height minus the transaction's confirmation_height, described in confirmation depth of a transaction. Both values come from calls your backend already makes, get_status on the node and retrieve_txs on the wallet.

Cold storage

Receiving requires interactive participation, so the keys that receive have to sign at the moment of receipt. That rules out receiving to an offline key the way a transparent chain allows.

The file transport is the building block that separates the network from the keys. It needs no network at either end, and the slate is a file. A receiving wallet can therefore live off the network, with the slate carried to it and the response carried back, at the cost of the exchange no longer being automatic. The transports and what each requires to be online are in transports.

Sweeping to cold storage after the fact is a normal send from a hot wallet to a wallet you control, and it carries the same reservation and cancellation behaviour as any other send.

Next