omnigate

Payments

omnigate can take payments for prepaid credit by itself: the holder of a prepaid key asks for a payment, pays it with a crypto asset of their choice, and the key is credited when the payment has settled. An agent whose credit is spent can do this without a person.

This is opt-in. Without a "payments" section in the config there are no payment endpoints (they answer 404), nothing is asked of any provider, and nothing on this page applies. omnigate stays a model gateway.

There is one payment provider today: NEAR Intents, through its 1Click API. Payments are built as a provider interface with that one adapter, the way media providers are; the config names the provider. Other ways of taking money still work as before, from outside: anything that can call POST /admin/keys/{name}/topup can credit a key.

What the gateway does and does not do

The flow

payer (a person or an agent)         omnigate                    NEAR Intents (1Click)        operator's account
        |  POST /v1/payments            |                               |                            |
        |  {amount_usd, asset,          |  quote: "exactly N USDC to    |                            |
        |   refund_to}  --------------> |  the operator; how much of    |                            |
        |                               |  this asset, sent where?" --> |                            |
        |                               | <-- deposit address, amount,  |                            |
        | <-- deposit address, amount,  |     deadline (signed)         |                            |
        |     deadline                  |                               |                            |
        |  sends the funds  ------------------------------------------> |  swaps, delivers  -------> |
        |                               |  status? (every few seconds)->|                            |
        |                               | <-- SUCCESS, amount delivered |                            |
        |                               |  credits the key, once        |                            |
        |  GET /v1/payments/{id} -----> |                               |                            |
        | <-- settled, credited_usd     |                               |                            |

Whatever the payer pays with (the provider lists a few hundred assets on a few dozen chains), the operator receives one asset: the settle asset, native USDC on NEAR unless configured otherwise.

Switching it on

"payments": {
  "provider": "near_intents",
  "recipient": "your-account.near",
  "min_usd": 1,
  "max_usd": 1000
}

config/omnigate.payments.json is a complete example (a text route, a burn multiplier, a bonus rate, payments).

Field Default Meaning
provider required "near_intents". The only one there is.
recipient required The operator's account at the provider: for near_intents, the NEAR account that receives the settle asset. Check it twice: it is where the money goes. It must be able to receive the settle asset (for a NEAR token: registered with the token's contract, which holding the token once takes care of).
min_usd, max_usd 1, 10000 Bounds on one payment.
quote_ttl_s 1800 How long a payer has to send the deposit. 60 to 86,400.
bonus_eligible true Whether credit bought this way earns the weekly bonus.
poll_interval_s 15 How often an open payment's status is asked for. After twenty polls they are spaced further apart.
settle_asset native USDC on NEAR The asset delivered to the operator, as a 1Click asset id. It should be a dollar stablecoin: what arrives is credited one to one.
settle_decimals 6 That asset's decimals.
slippage_bps 100 Tolerance on the amount the payer is asked to send (1%). What the swap does not need is refunded to the payer by the provider.
jwt_env none Name of an environment variable (of the control plane) holding a 1Click partner token. Without one, 1Click adds 0.25% to every quote.
base_url, public_key 1Click's Where the provider is and the key it signs quotes with. Only ever changed to point at a test double.

An unknown field, or a provider that does not exist, is refused when the config is loaded. The control plane needs outbound HTTPS to 1click.chaindefuser.com. Payments are kept in OMNIGATE_PAYMENTS (default data/payments.json; /data/payments.json in the image).

Only prepaid managed keys can be paid for: keys created through the admin API with a total budget ("budget_usd": 0 is the usual start). A key from the config file, a key without a budget and a key with a monthly or daily budget are answered 409 not_prepaid.

The API

On the gateway's port, with a gateway key, like every other endpoint. A key whose credit is spent can still use all of these; that is what they are for.

Method and path Purpose
GET /v1/payments/assets What can be paid with. ?chain= and ?symbol= narrow the list.
POST /v1/payments/quote What a payment would take, without starting one.
POST /v1/payments Start a payment: get a deposit address and the amount to send.
GET /v1/payments/{id} The payment and its state.
GET /v1/payments The key's payments, newest first (up to 50).
POST /v1/payments/{id}/tx Optional: tell the provider which transaction carries the deposit.

Assets

curl -s "http://127.0.0.1:8080/v1/payments/assets?chain=near" -H "Authorization: Bearer $KEY"
{ "assets": [ { "asset": "nep141:wrap.near", "symbol": "wNEAR", "chain": "near", "decimals": 24, "price_usd": 3.7 } ] }

The list is the provider's, fetched at most every five minutes. asset is what the other calls take.

A quote, without starting anything

curl -s http://127.0.0.1:8080/v1/payments/quote -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"amount_usd": 5, "asset": "nep141:wrap.near", "refund_to": "payer.near"}'
{ "amount_usd": 5.0, "asset": "nep141:wrap.near", "symbol": "wNEAR", "chain": "near",
  "amount": "1.3638", "amount_units": "1363800000000000000000000", "min_amount_units": "1350200000000000000000000",
  "deposit_usd": "5.06", "time_estimate_s": 20, "provider": "near_intents" }

Nothing is created at the provider and there is no deposit address.

Starting a payment

curl -s http://127.0.0.1:8080/v1/payments -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"amount_usd": 5, "asset": "nep141:wrap.near", "refund_to": "payer.near"}'
Field Meaning
amount_usd How much credit to buy, in US dollars, between min_usd and max_usd. Rounded to whole cents.
asset The asset to pay with, from the assets list.
refund_to An address on the chain of that asset that gets the funds back if the payment fails, and anything the swap did not need. Use the address the payment is sent from.

The answer is 201:

{
  "id": "pay_3f9c1e0a5b7d2c4e6f8a0b1c",
  "status": "awaiting_deposit",
  "amount_usd": 5.0,
  "credited_usd": null,
  "provider": "near_intents",
  "deposit": {
    "address": "8f3a…c21e",
    "memo": null,
    "asset": "nep141:wrap.near", "symbol": "wNEAR", "chain": "near",
    "amount": "1.3638", "amount_units": "1363800000000000000000000",
    "min_amount_units": "1350200000000000000000000", "amount_usd": "5.06"
  },
  "refund_to": "payer.near",
  "deadline": "2026-11-11T21:00:00.000Z",
  "time_estimate_s": 20,
  "detail": null, "provider_status": null, "tx": [],
  "created_at": "2026-11-11T20:30:00Z", "updated_at": "2026-11-11T20:30:00Z"
}

Send exactly deposit.amount of the asset to deposit.address before deadline, from a wallet of your own. When deposit.memo is not null the chain needs it with the transfer (Stellar, for one): a deposit without it is not recognized. Sending less than min_amount_units is refunded by the provider when the deadline passes; sending more is swapped and the excess refunded.

Then ask for the payment until its status is an end state. An agent's loop, with the three calls, is examples/agent_pays.py.

Errors

Status code When
400 invalid_request amount_usd outside the bounds or not a number; asset or refund_to not well-formed.
400 payment_quote_refused The provider refused the request. Its own words are in the message: an address that is not one on that chain, an amount below what a bridge takes, a chain's minimum, a pair it does not quote.
404 not_found Payments are not configured on this gateway.
404 payment_not_found No such payment, or it is another key's.
409 not_prepaid The key is not a prepaid managed key.
409 payment_closed POST …/tx on a payment that has ended.
429 too_many_open_payments The key has five payments waiting for a deposit.
429 rate_limited More than six quotes or payments a minute for one key (the key's own rpm also applies).
502 payment_provider_error The provider answered something that was not used: an answer that could not be read, or a quote that is not signed by its key or does not repeat what was asked. No deposit address is shown in that case.
503 payment_provider_unavailable The provider could not be reached, or is rate limiting.

States of a payment

awaiting_deposit → processing → settled      credited_usd is set: the key was credited
                              → refunded     the provider gave the funds back to refund_to
                              → failed       the provider could not complete the swap
awaiting_deposit → expired                   nothing arrived by the deadline (plus 15 minutes)

What is credited, and when

What is checked, and what is trusted

Checked by the gateway:

Trusted, and you should know it:

One oddity of the real service: some assets are listed twice (Bitcoin as nep141:btc.omft.near and as 1cs_v1:btc:native:coin), and a quote asked for under the first id is signed as if it had been asked for under the second. The gateway therefore accepts a signature that names the paying asset by another id the provider's own list gives the same coin on the same chain. Everything else in the signature must be exactly what was asked.

Fees, limits and refunds

All of these are the provider's; the gateway adds nothing of its own to a payment. (What a request then burns of the credit is the burn multiplier, which is the operator's.)

For operators

What has been tested

Trying it with real funds

One small payment, by hand. There is no test network for NEAR Intents: this uses real funds, so use a dollar or two.

  1. Put your own NEAR account in payments.recipient of config/omnigate.payments.json, and make sure it can receive USDC (hold a little of it once).

  2. Start the gateway with that config and a prepaid key:

    python tools/near_intents_live.py --serve
    

    It prints the recipient, a key with no credit and the admin token, and the commands below with the key filled in. It sends nothing itself.

  3. See what $1 would take, without starting anything:

    curl -s http://127.0.0.1:8080/v1/payments/quote -H "authorization: Bearer $KEY" -H "content-type: application/json" -d '{"amount_usd": 1, "asset": "nep141:wrap.near", "refund_to": "<your NEAR account>"}'
    
  4. Start the payment (this creates a deposit address at NEAR Intents):

    curl -s http://127.0.0.1:8080/v1/payments -H "authorization: Bearer $KEY" -H "content-type: application/json" -d '{"amount_usd": 1, "asset": "nep141:wrap.near", "refund_to": "<your NEAR account>"}'
    
  5. From your own wallet, send exactly deposit.amount of the asset to deposit.address before deadline.

  6. Watch it: GET /v1/payments/<id> should go from awaiting_deposit to settled with credited_usd of 1, and GET /admin/keys should show the key with a budget of 1 and one dollar of eligible credit. Your account should hold one more USDC.

Paying with wrapped NEAR from a NEAR account keeps the fees lowest. If the payment ends as refunded or expired, nothing was credited and the provider returns what it received to refund_to.

This page on GitHub