For brands
A brand works in the brand console at https://swayoffers.com/console with a browser wallet. This page follows the console from top to bottom: register, post a bond, create and fund a campaign, post sealed offers, record orders, refund returns and follow conversions. Connecting and signing in are covered in Connect a wallet.
Before you start, you need:
- a wallet with SOL and USDC on the cluster named in the console's top bar;
- one browser profile that you keep for the console, because it holds your receipt signer.
Register the brand
After you sign in with a wallet that has no merchant yet, the console shows Register the brand.
| Field | What to enter |
|---|---|
| Brand name | Up to 120 characters |
| Return window, days | Your store's return window, from 0 to 365 days in the console. Every campaign's lock must be at least this long |
Below the fields, the console shows "Receipt signer (this browser)" with a short form of a public key. The console created that Ed25519 key in this browser the first time it opened, and it stores the private half in the browser profile's local storage. Registration puts the public key on chain as your receipt signer. Every order you record is signed with this key, and Sway never receives the private half.
Select Register and approve the register_merchant transaction. The panel then shows your brand name with Status, Bond, Return window and Receipt signer.
Keeping the receipt signer
Under the merchant's details, the console names the key it holds: "Receipt key in this browser", a short form of its public key and its epoch, followed by "It matches the registered signer." Use the console from the same browser profile every time. Another browser or profile, or cleared site data, holds a different key, and the note then reads "It differs from the registered signer; rotate to register it."
Rotate the receipt key
Select Rotate receipt key. The console creates a replacement key in this browser, and your wallet signs a rotate_receipt_signer transaction with its public key. The program closes the current signer epoch and opens the next one for the replacement. Once the transaction is confirmed, the replacement becomes this browser's receipt key and the note shows the new epoch. Each receipt names the epoch it was signed in, and verification accepts it only for a purchase time inside that epoch.
A store's checkout that holds its own receipt key registers that public key through POST /v1/merchant/receipt-signers, and your wallet signs the same rotate_receipt_signer transaction.
Post a bond
Enter an amount under Post bond, USDC and select Post bond. The post_bond transaction moves that USDC from your wallet into your merchant's bond account, which the program controls. The bond backs disputes: when a shopper's dispute is resolved in the shopper's favor, the compensation is paid from the bond, up to 50 USDC per case.
Activation
A new merchant starts as pending and is listed on the Sway operator's page with its name, wallet, bond and return window. After your bond is posted, the operator activates the merchant with an activate_merchant transaction signed by the deployment's Config admin wallet. Sway prepares that transaction once your registration is final on chain, and the program accepts it only when the bond holds at least the minimum set in the deployment's on-chain configuration (minimum_bond). The console reads the merchant again every 5 seconds, and Status then reads active.
| Status | Meaning |
|---|---|
pending | Registered, waiting for the operator's activation |
active | Can create campaigns and post offers |
suspended | Suspended by the operator. A suspended merchant cannot create campaigns or post offers |
Creating a campaign requires an active merchant whose bond meets the configured minimum. Posting an offer requires an active merchant.
The operator suspends a merchant and returns its bond from the operator page. Suspend prepares a suspend_merchant transaction, and Release bond prepares a release_bond transaction; the deployment's Config admin wallet signs each one. A release returns the whole bond, and the program pays it only to your wallet's own USDC account, which the same transaction creates if you have none. It allows the release once the merchant is suspended, no conversion is waiting for verification, and the last lock plus the dispute window has passed. Until then Sway answers the release with BOND_LOCKED.
Create a campaign
The form at the bottom of Campaigns creates one.
| Field | What it sets |
|---|---|
| New campaign | A name, up to 120 characters |
| Cap per conversion, USDC | The largest rebate one sale can pay from this campaign, up to 50 USDC. The console suggests 50 |
| Lock, days | How long each verified rebate is held before the shopper can claim it. The console starts at your return window and allows nothing shorter; the program refuses a shorter lock too |
| Daily cap, USDC | The most the campaign can spend in one UTC day, counting rebates and fees, up to 5,000 USDC. The console suggests 500 |
Select Create campaign and approve create_campaign. The campaign appears with its Vault balance, Cap per conversion and Lock.
Fund and withdraw
Each campaign has an Amount, USDC field with two buttons.
- Fund moves USDC from your wallet into the campaign vault. A campaign can hold at most 5,000 USDC of funding, counted net of withdrawals.
- Withdraw moves USDC back to your wallet's USDC account. Anything still in the campaign vault can be withdrawn. Verified rebates have already left the vault for escrow, so they are not part of the balance.
Through the API, a campaign whose vault falls below the deployment's vault floor reports the status underfunded.
Post a sealed offer
Each campaign carries an offer form. The offer is a rule: which shoppers it fits, the product price and the rebate.
| Field | What it means |
|---|---|
| Product price, USDC | The exact price the shopper pays at your checkout. An intent whose maximum price is lower skips the offer. The console suggests 120 |
| Rebate, USDC | The USDC the shopper receives for the purchase. It must not exceed the campaign's cap per conversion. The console suggests 15 |
| Sizes | Smallest and largest size, from 4 to 16 in half steps. The console suggests 8 to 12 |
| Category | Appears when the deployment allows more than one category; otherwise the first allowed category is used |
| Widths | narrow, standard, wide. standard is checked by default, and an offer with none checked uses standard |
| new customers only | Skip shoppers whose agent lists your brand among the brands they already buy from |
Offers from the console cover the US region.
Select Seal and post offer. The browser reads the MXE public key from https://api.swayoffers.com/v1/encryption, encrypts the rule, and sends only ciphertext. Your wallet then signs submit_offer. An ingest computation in MPC checks that the rebate is within the campaign's cap and that the brand and category match, then places the offer in one of the 32 slots of the category book.
The sealed rule names your brand by its brand ID, which on the deployed service is your merchant wallet address. Shoppers' agents use the same address for brand bonuses and for the brands a shopper already buys from. The console also keeps the offer's price and rebate in this browser, so that Record an order can fill in the rebate later.
Under the campaign, each offer shows as "Sealed offer" with its slot and state.
| Shown | Meaning |
|---|---|
| slot pending | The submission has no book slot yet |
| slot n | The offer holds slot n of the category book |
| ingest pending | Waiting for the ingest computation |
| accepted | In the book and part of matching |
| rejected · REBATE_OVER_CAP | The rebate exceeds the campaign's cap per conversion |
| rejected · COMPUTATION_FAILED | The ingest computation did not complete |
For each intent, matching scores every eligible offer as brand_bonus + rebate − price and returns the three highest. brand_bonus is a bonus the shopper chose to give your brand, and zero otherwise; ties go to the lower slot. The rebate a shopper receives is the one in your offer.
Record an order
A shopper who buys through an agent arrives at your checkout with a voucher. The voucher's attachAtCheckout string reads sway:<intentId>:<slot>, and the full voucher is a JSON object with intentId, intentAccountAddress and slot. Use the Record an order panel to turn the order into a receipt.
| Field | What to enter |
|---|---|
| Voucher | The voucher JSON the agent attached. The console accepts the bare voucher or the agent's whole get_voucher output with its data field |
| Order reference | Your order number. The console hashes it with SHA-256 into the receipt's order hash, and keeps the reference in this browser for a later refund. Use a different reference for each order. Left empty, the console makes one up |
| Price paid, USDC (the offer's exact price) | The offer's product price, to the cent. Verification compares it with the sealed price in base units |
| Rebate, USDC (filled from the sealed offer when left empty) | The offer's rebate. Left empty, the console fills it from the terms it kept when the offer at the voucher's slot was sealed in this browser. For an offer sealed in another browser, enter the rebate |
Select Sign and send receipt. The receipt key in this browser signs a 128-byte receipt that binds the intent account, slot, order hash, amount, purchase time, signer epoch and program. The console sends it to POST /v1/receipts with the rebate as proposedRebateUsdc, your proposal of what this sale pays. Your wallet signs nothing.
Then:
- Sway checks the receipt against the receipt signer registered for its purchase time, and checks that the proposed rebate is within the campaign's cap per conversion. The conversion shows
verification pending. - A verification computation in Arcium's MPC confirms that the slot was in the shopper's top three, and that the price paid and the proposed rebate equal the sealed winning offer. Nothing moves before it does.
- The program records the conversion and moves the rebate plus 15% from the campaign vault into escrow, within the vault's balance and daily cap, in one transaction. The unlock time is set to that time plus the campaign's lock days. The conversion shows
locked.
When verification rejects the receipt, for example for a proposed rebate that differs from the sealed offer, the conversion shows failed and nothing is debited.
The purchase time in the receipt must fall before the intent's purchase deadline, and the receipt must reach the chain before the settlement deadline, which is the purchase deadline plus the settlement grace period. https://api.swayoffers.com/v1/encryption publishes both windows as purchaseWindowSeconds and settlementGraceSeconds.
A store's checkout can record orders the same way: sign the 128-byte receipt with your registered receipt key and post it to POST /v1/receipts with merchantId, signerPublicKey, payloadBase64, signatureBase64 and proposedRebateUsdc in base units. Anyone may relay a valid receipt. To correct a rejected proposal, send the same signed receipt again with the corrected proposedRebateUsdc and a new Idempotency-Key. Byte layouts are in the whitepaper.
Refunds inside the lock
When a customer returns the product before the conversion unlocks, select Refund on that conversion under Conversions. The button appears while a refund is still possible. For an order recorded in this browser, the console already holds its order reference; for any other order, it asks for the "Order reference this receipt was signed for". The receipt key in this browser signs the refund notice, and the console sends it to POST /v1/conversions/{id}/refund. Your wallet signs nothing.
The notice is a 128-byte SWAYREF1 message that binds the conversion account, the original receipt hash, the program and your current signer epoch, signed by your current receipt key. The request also carries the conversion ID, intent ID, slot, order hash, issue time and a nonce. A store's checkout can send the same request.
Sway accepts a refund while the conversion is locked: its refundAllowed is true until refundableUntil, which is the unlock time. The whole debit, rebate and fees, returns to the campaign vault, and the campaign's spent totals go down by the same amount. The conversion then shows refunded. After the unlock time the API answers REFUND_WINDOW_CLOSED.
Conversions
The Conversions panel lists each verified or pending sale with the intent, slot, rebate, unlock time and state, and a Refund button while a refund is possible. It refreshes every 5 seconds while open.
| State | Meaning |
|---|---|
| verification pending | Sway accepted the receipt and verification is running. Nothing has moved from the vault |
| locked | Verified. Rebate and fees are in escrow until the unlock time, and a refund is still possible |
| claimable | The lock has ended. The shopper can claim, and refunds are closed |
| claimed | Paid out: the rebate to the shopper's address, 10% to Sway, 5% to the integrator |
| refunded | Refunded inside the lock. The full debit went back to the vault |
| disputed | The shopper opened a dispute on this sale |
| failed | Verification rejected the receipt, or the settlement deadline passed before it was recorded. Nothing was debited |
For accounting, GET /v1/campaigns/{id}/spend returns daily rebate and fee debits per campaign, with inclusive UTC from and to dates. Fees and timing are in Fees and settlement.