Pay for a plan with USDC on Sui
You can pay a monthly Inodra plan with USDC on Sui. You do not need a card. Inodra uses Sui native allowances (sui::allowance). You give Inodra a limited right to take the plan price once a month.
Note: This is available on Sui testnet now. Mainnet follows when Sui mainnet enables allowances (protocol version 138, Sui 1.81). Until then, the dashboard shows a "testnet only" note.
Who It Is For
- Teams that hold USDC on Sui and do not want to use a card
- AI agents and scripts that manage their own plan with a
managekey - Organizations on the Team, Growth, Professional or Business plan
| Plan | Price per month |
|---|---|
| Team | $49 |
| Growth | $199 |
| Professional | $499 |
| Business | $999 |
Enterprise stays on card. An organization uses one payment method at a time: card or on-chain. To pay per request without a plan, see Pay per request with x402.
How It Works
- You connect your wallet and choose a plan.
- You sign one transaction. It does three things:
- It moves your prefund (1 to 12 months of USDC) into your USDC address balance.
- It proposes an allowance for Inodra's billing app. The monthly limit is the plan price.
- Inodra's billing module issues the allowance.
- Inodra takes the first charge within a minute. Your plan is active.
- Once a month, Inodra takes the plan price from your USDC address balance.
- The on-chain module sends each charge straight to Inodra's treasury.
Inodra never holds your funds and never holds your keys. A leaked Inodra key can only pay Inodra.
What Is an Allowance
An allowance is a Sui object that lets one address (the spender) take funds from your address balance. Its rules are on chain:
- It names the spender. For this feature, the spender is Inodra's billing address.
- It is bound to one app. Only Inodra's billing module can use it.
- It has a limit per month. The limit is your plan price.
- You can revoke it from your wallet at any time.
Set Up From the Dashboard
- Open Billing.
- Select Pay with USDC on Sui.
- Connect your wallet.
- Choose a plan.
- Choose a prefund: 1, 3 or 6 months.
- Sign the transaction in your wallet.
- Wait for the first charge. It runs within a minute. Then your plan is active.
You pay the gas for this transaction.
Set Up From the API
Use a key with the manage scope. See Management API.
1. Read the Plans
curl https://mainnet-api.inodra.com/v1/billing/plans \
-H "x-api-key: indr_pat_..."The response lists the networks, the spender address, the treasury address, the package ID, the accepted coins and the plan prices.
2. Prepare the Transaction
curl -X POST https://mainnet-api.inodra.com/v1/billing/onchain/prepare \
-H "x-api-key: indr_pat_..." \
-H "Content-Type: application/json" \
-d '{ "network": "testnet", "tier": "team", "interval": "month", "funderAddress": "0x...", "prefundPeriods": 3 }'tieristeam,growth,professionalorbusiness.funderAddressis the wallet that pays. It must sign the transaction.intervalismonth(default) oryear. An annual plan uses a 12-month allowance window and one charge per year at the annual price.prefundPeriodsis0to12periods (months or years) of the plan price. The default is1.- The allowance is named for your organization. Another organization cannot register it.
- The response holds
txBytes: the transaction bytes in base64.
3. Sign and Execute
Sign txBytes with the funder's key and execute it with any Sui SDK or wallet. This example uses @mysten/sui v2:
import { SuiGrpcClient } from '@mysten/sui/grpc'
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
import { Transaction } from '@mysten/sui/transactions'
import { fromBase64 } from '@mysten/sui/utils'
const client = new SuiGrpcClient({
network: 'testnet',
baseUrl: 'https://fullnode.testnet.sui.io:443'
})
const keypair = Ed25519Keypair.fromSecretKey(process.env.SUI_PRIVATE_KEY!)
const tx = Transaction.from(fromBase64(txBytes))
const bytes = await tx.build({ client })
const { signature } = await keypair.signTransaction(bytes)
const result = await client.core.executeTransaction({
transaction: bytes,
signatures: [signature],
include: { effects: true, objectTypes: true }
})
if (result.$kind !== 'Transaction') throw new Error('Transaction failed')
const { effects, objectTypes } = result.Transaction
const allowanceId = effects.changedObjects.find(
(o) =>
o.idOperation === 'Created' && /::allowance::Allowance</.test(objectTypes[o.objectId] ?? '')
)?.objectId4. Activate the Plan
Send the Allowance object ID from the created objects:
curl -X POST https://mainnet-api.inodra.com/v1/billing/onchain/activate \
-H "x-api-key: indr_pat_..." \
-H "Content-Type: application/json" \
-d '{ "network": "testnet", "tier": "team", "allowanceId": "0x..." }'allowanceCapIdis optional.- Inodra checks the allowance on chain, then takes the first charge.
5. Check the State
curl https://mainnet-api.inodra.com/v1/billing/onchain \
-H "x-api-key: indr_pat_..."The response shows the state, the plan, the allowance, the funder, the billing balance, the next charge and the last charges.
Renewals and Failed Payments
Inodra takes one charge per period (a calendar month, or a year for annual plans), at the end of the period. The charge day is the same day of the month as the first charge. In a short month, Inodra uses the last day. The next month returns to the original day.
Seven days before each renewal, Inodra checks your USDC address balance. If the balance is below the plan price, Inodra emails the owner.
A charge fails when:
- Your address balance holds less USDC than the plan price.
- You revoked the allowance.
- The allowance expired.
After a failed charge:
- The plan goes to
past_due. Your access continues. - Inodra retries once a day for 7 days.
- Inodra emails the owner on attempts 1, 3, 5 and 7.
- After the 7th failure, the organization returns to Free. Webhooks and streams above the Free limits pause.
To fix a failed charge, add USDC to your address balance. The next retry succeeds.
A problem on Inodra's side (a fullnode outage, for example) does not count as a failed charge. Inodra retries every hour and does not email you or change the plan state.
| State | Meaning |
|---|---|
awaiting_first_charge | The allowance is active. Inodra has not taken the first charge yet |
active | The plan is paid for the current month |
past_due | A charge failed. Access continues. Inodra retries once a day for 7 days |
cancel_pending | You cancelled. Access continues until the end of the period |
ended | The plan ended. The organization is on Free |
Change Your Plan
- Downgrade: The change applies at the next renewal. You do not sign anything. The existing allowance already covers the lower price.
- Upgrade: You need a new allowance with the higher monthly limit. Inodra registers the new allowance first. Then the dashboard asks you to revoke the old one.
You choose when the change applies:
- At the next renewal: The new price starts with the next period.
- Now: Inodra charges the new price minus the unused part of the current month. A new month starts today. The next renewal is the full new price. If this charge fails, your current plan continues and the change applies at the next renewal.
From the API:
curl -X POST https://mainnet-api.inodra.com/v1/billing/onchain/change \
-H "x-api-key: indr_pat_..." \
-H "Content-Type: application/json" \
-d '{ "tier": "growth", "when": "now", "allowanceId": "0x..." }'whenisnext_renewalornow.- For an upgrade, call
prepareagain with the newtier. Sign and execute the returned bytes. Then send the new allowance ID inallowanceId. Revoke the old allowance from your wallet only afterchangereturns200. The dashboard does these steps for you. - A downgrade with
nowfails withDOWNGRADE_NEEDS_RENEWAL. Usenext_renewal. - A cancelled plan cannot be changed. Set it up again with the same plan first (this resumes it).
- A plan keeps its billing period. To move from monthly to annual, cancel and set up an annual plan after the period ends.
changereturns409 CHARGE_IN_PROGRESSwhile Inodra is taking a charge. Retry in a minute.
Cancel and Revoke
Cancel the plan in the dashboard, or call the API:
bashcurl -X POST https://mainnet-api.inodra.com/v1/billing/onchain/cancel \ -H "x-api-key: indr_pat_..."Your access continues until the end of the period.
Revoke the allowance from your wallet. The dashboard has a Revoke button.
If Inodra finds that you revoked the allowance, it treats this as a cancel at the end of the period.
Plans and Pay-Per-Request Credits
An organization is billed one way at a time. A wallet organization that holds pay-per-request credits must spend them before it starts a plan. An organization on a plan cannot receive pay-per-request deposits; use its API key. Prepaid credits as plan overage is planned for a later release.
Billing Balance and Wallet Balance
On Sui, an address can hold funds in two forms: Coin objects and an address balance. Your wallet usually shows the total of both. An allowance can only take funds from the address balance. It cannot use your Coin objects. The setup transaction moves your prefund into the address balance. The dashboard shows this amount as the billing balance.
To pay for more months, keep enough USDC in the address balance, or top up before the renewal.
Security
- The allowance limit is the plan price per month. Inodra cannot take more.
- Only Inodra's billing module can use the allowance. It sends each charge to Inodra's treasury.
- You can revoke the allowance at any time.
- Inodra never holds your keys or your funds.
Money Facts
| Item | Value |
|---|---|
| Coin | USDC only (6 decimals) |
| Mainnet USDC | 0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC |
| Testnet USDC | 0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC |
| Gas for pulls | Inodra pays |
| Gas for setup | You pay. This includes top-ups and revokes |
| Refunds | None for a month that started |
Error Codes
Errors return a JSON body with a code field. The status is 400 unless the table shows another value.
| Code | Status | Meaning | What to do |
|---|---|---|---|
ONCHAIN_BILLING_DISABLED | 403 | On-chain billing is off for this network | Use card billing, or use testnet |
PLAN_NOT_AVAILABLE | 400 | You cannot pay this plan on chain | Choose a monthly paid plan |
STRIPE_SUBSCRIPTION_ACTIVE | 409 | The organization pays by card | Cancel the card plan first |
ONCHAIN_SUBSCRIPTION_EXISTS | 409 | The organization already has an on-chain plan | Use change or cancel |
ONCHAIN_SUBSCRIPTION_NOT_FOUND | 404 | The organization has no on-chain plan | Set one up first |
ALLOWANCE_NOT_FOUND | 404 | Inodra cannot find the allowance on chain | Check the ID and the network |
ALLOWANCE_WRONG_COIN | 400 | The allowance is not for USDC | Create a USDC allowance |
ALLOWANCE_WRONG_SPENDER | 400 | The allowance does not name Inodra's spender | Use the spender from plans |
ALLOWANCE_WRONG_APP | 400 | The allowance is not bound to Inodra's app | Use the transaction from prepare |
ALLOWANCE_WRONG_FUNDER | 400 | The funder is not the expected address | Sign with funderAddress |
ALLOWANCE_WRONG_ORGANIZATION | 400 | The allowance was made for another organization or plan | Call prepare from this organization |
ALLOWANCE_IN_USE | 409 | The allowance already pays for a plan | Prepare a new one |
PLAN_CANCELLED | 409 | The plan is cancelled | Set the plan up again to resume it |
CHARGE_IN_PROGRESS | 409 | Inodra is taking a charge right now | Retry in a minute |
INVALID_FUNDER_ADDRESS | 400 | funderAddress is not a Sui address | Check the address |
COIN_NOT_ACCEPTED | 400 | coinType is not accepted on this network | Use a coin from plans |
TX_BUILD_FAILED | 400 | The transaction could not be built | Read the message; often a balance |
ALLOWANCE_LIMIT_TOO_LOW | 400 | The monthly limit is below the plan price | Create one with a higher limit |
ALLOWANCE_EXPIRES_TOO_SOON | 400 | The allowance expires before the next charge | Create one with a later expiry |
ALLOWANCE_NOT_STARTED | 400 | The allowance is not active yet | Wait for its start time, then retry |
PLAN_UNCHANGED | 400 | You asked for the plan that you have, or a cancelled plan with another tier or period | Choose another plan, or resume first |
PREPAID_BALANCE_PRESENT | 409 | The organization still holds prepaid credits | Spend them before you start a plan |
BALANCE_UNAVAILABLE | 503 | Inodra could not read the prepaid balance | Retry in a minute |
DOWNGRADE_NEEDS_RENEWAL | 400 | A downgrade cannot apply now | Use "when": "next_renewal" |
FAQ
Can I get a refund? No. Inodra does not refund a month that started. When you cancel, access continues until the end of the period.
What if I run out of USDC? The charge fails and the plan goes to past_due. Access continues while Inodra retries once a day for 7 days. Add USDC to your address balance and the next retry succeeds. After 7 failures, the organization returns to Free.
Can Inodra take more than the plan price? No. The allowance limit is the plan price per month. The on-chain module enforces it. The module also sends every charge to Inodra's treasury. You can revoke the allowance at any time.
Who pays the gas? Inodra pays the gas for each monthly charge. You pay the gas for setup, top-ups and revokes.
When is this available on mainnet? When Sui mainnet enables allowances (protocol version 138, Sui 1.81). Until then, it works on testnet only.