Skip to content
OpenSouk

Merchant tools

Nine tools that run a catalogue from your own key

ToolActs byWhat it does
activate_merchanttransaction + RESTSets the on-chain active flag and creates the backend records
add_producttransaction + RESTWrites a product on-chain and syncs its metadata
set_product_commissiontransactionChanges one product's commission rate
set_product_activetransactionLists or delists one product
grant_merchant_roletransactionDelegates one capability to another address
revoke_merchant_roletransactionTakes that capability back
set_merchant_profileRESTReplaces the public profile copy
check_product_readinessRESTProbes an endpoint's referral wiring
get_merchant_statsRESTReads the merchant's own revenue figures

Repeating a call is the retry path, not a hazard. Every write here is idempotent: the transaction is skipped when the chain already says what you are asking for, and an already-registered record counts as done. A call that failed partway through can simply be made again.

The six on-chain tools take an optional chain_id, a decimal string overriding the CHAIN_ID environment variable. The three REST tools do not.

The signer settings these tools read — MERCHANT_REGISTRY_ADDRESS, PRODUCT_REGISTRY_ADDRESS and REFERRER_API_URL, beside the key and chain settings — are in the signer's configuration.

activate_merchant

Brings a merchant online. Three steps in one call, in this order because each is a precondition of the next: set the on-chain active flag (MerchantRegistry.setActive), register the agent record, register the merchant record. The merchant registration needs an agents row, and refuses a merchant that is not active on-chain.

Call this before set_merchant_profile or add_product, both of which need the backend record it creates.

{ "merchant_id": "42" }

Parameters

NameTypeNotes
merchant_idstringRequired. Decimal. The signing key must own it
chain_idstringOptional. Decimal, overrides CHAIN_ID

Returns

tx_hash, already_active, and agent_registered / merchant_registered, each created or already_registered. A partial run reports which steps landed.

Suspended merchants cannot reactivate themselves. Suspension is an admin action on MerchantRegistry and this tool cannot undo it.

add_product

Writes a product on-chain (ProductRegistry.addProduct, which holds the commission rate and the active flag) and syncs its metadata to the backend so it becomes discoverable.

A product with only the on-chain half is payable but invisible. If the chain write lands and the metadata sync does not, the tool says so — repeat the call and only the sync is retried, because addProduct itself cannot be re-sent.

{
  "merchant_id": "42",
  "product_id": "0x1f9a…",
  "commission_bps": 1000,
  "endpoint_url": "https://api.example.com/x402/rpc",
  "name": "Low-latency RPC"
}

Parameters

NameTypeNotes
merchant_idstringRequired. Decimal
product_idstringRequired. 0x-prefixed 32-byte hex, unique per merchant — typically keccak256 of a slug you choose
commission_bpsintegerRequired. 0–10000; 1000 = 10%
endpoint_urlstringRequired. The product's x402 payment endpoint
namestringRequired. Buyers and discovery see it
descriptionstringOptional
categorystringOptional. A slug from GET /v1/product-categories, such as chain.rpc. Anything else is refused before addProduct is sent
product_card_uristringOptional
chain_idstringOptional

Returns

tx_hash, already_on_chain, commission_bps as it now stands on-chain, metadata_synced, and next_steps. If the product already exists at a different rate the rate is not changed, and commission_warning says so — use set_product_commission.

Requires the merchant to be active on-chain and registered with the backend. All products are repeat-purchase; one-off products were retired from the protocol.

Afterwards, call check_product_readiness on the endpoint_url.

set_product_commission

Changes the commission rate a merchant pays referrers for one product (ProductRegistry.setCommission).

The new rate is read live by SplitRouter at settlement. It applies to every purchase that settles after this call — including ref links already issued and payments already authorised under the old rate. Nothing on-chain locks the previous rate.

{ "merchant_id": "42", "product_id": "0x1f9a…", "commission_bps": 1500 }

Parameters

NameTypeNotes
merchant_idstringRequired
product_idstringRequired. On-chain bytes32, not a slug
commission_bpsintegerRequired. 0–10000
chain_idstringOptional

Returns

tx_hash, the new commission_bps, and set_by — the signer address that sent it.

The key must own merchant_id or hold COMMISSION_MANAGER_ROLE for it. That role is separate from PRODUCT_MANAGER_ROLE, so a key that can add products cannot necessarily reprice them.

set_product_active

Lists or delists one product (ProductRegistry.setProductActive).

active is required, so a call that omits it fails rather than delisting.

{ "merchant_id": "42", "product_id": "0x1f9a…", "active": false }

Parameters

NameTypeNotes
merchant_idstringRequired
product_idstringRequired
activebooleanRequired. true accepts new purchases and ref links, false stops them
chain_idstringOptional

Returns

tx_hash, active, and set_by.

Delisting does not touch existing escrows or published reviews, and does not remove the product — set it active again to relist. The key must own merchant_id or hold PRODUCT_MANAGER_ROLE.

Errors

MessageCause
active is required (true to list the product, false to delist it)The field was omitted

grant_merchant_role

Delegates one capability of a merchant to another address (MerchantRegistry.grantRole), without handing over the merchant identity.

RoleLets the holder
SETTLEMENT_SIGNER_ROLESign the merchant's x402 /verify and /settle requests to the facilitator
PRODUCT_MANAGER_ROLEAdd and delist products
COMMISSION_MANAGER_ROLEChange commission rates

The three are independent — granting one grants nothing else — and each is scoped to this merchant alone. The signing key must own merchant_id: holding a role never lets a key pass that role on.

SETTLEMENT_SIGNER_ROLE is how a merchant server gets its own hot key instead of running the owner key, which controls rates, delisting and the payout wallet. Grant before deploying that key — the facilitator refuses a signature it cannot place, on every sale, whatever its rollout setting.

{ "merchant_id": "42", "role": "SETTLEMENT_SIGNER_ROLE", "account": "0xabc…" }

Parameters

NameTypeNotes
merchant_idstringRequired. The signing key must own it
rolestringRequired. One of the three above
accountstringRequired. The 0x address to delegate to
chain_idstringOptional

Returns

tx_hash, role_id (the role's bytes32), has_role — the state after the call — and no_change, true when the account already held it and nothing was sent.

revoke_merchant_role

Takes a per-merchant role back (MerchantRegistry.revokeRole). Same arguments and the same ownership rule as grant_merchant_role.

Revoking takes effect immediately. For SETTLEMENT_SIGNER_ROLE, stop that key signing first — otherwise its next /verify or /settle is refused and sales fail. To rotate a settlement key: grant the new one, deploy it, then revoke the old one.

{ "merchant_id": "42", "role": "SETTLEMENT_SIGNER_ROLE", "account": "0xabc…" }

Returns

tx_hash, has_role (false after a successful revoke) and no_change.

set_merchant_profile

Sets the merchant's profile — public description and landing page URL, and the default commission the console prefills for new products — through POST /v1/merchant/profile, signed with the merchant's own key.

This is a partial update. Pass only the fields to change: an omitted field keeps its stored value, and an empty string clears a text field. At least one field is required.

{ "merchant_id": "42", "default_commission_bps": 1000 }

Parameters

NameTypeNotes
merchant_idstringRequired. The signing key must own it
descriptionstringOptional. Empty clears it
landing_page_urlstringOptional. http/https only. Empty clears it
default_commission_bpsintegerOptional. 0–10000. Off-chain default; on-chain commission stays per product (set_product_commission)

The description and URL are public. The backend owns the limits and its rejection names the offending field — see POST /v1/merchant/profile. Requires activate_merchant first: without a backend record this fails with a conflict pointing there.

check_product_readiness

Probes a product's x402 endpoint and reports whether it is wired for referral payments (POST /v1/merchant/product/readiness). The backend fetches the resource with the referral-mode header set and checks three things: that it answers 402, that payTo is this chain's SplitRouter, and that the attribution token is echoed back.

A misconfigured endpoint returns pass: false with a per-check breakdown. That is a successful probe, not an error.

{ "merchant_id": "42", "resource": "https://api.example.com/x402/rpc" }

Parameters

NameTypeNotes
merchant_idstringRequired — but it only selects the signing key
resourcestringRequired. The product's x402 endpoint URL

Returns

pass, resource, and checks — each with name, ok and a detail when it failed.

The endpoint is authenticated but not owner-scoped, so this does not assert that you own the resource. Call it after add_product and before advertising a product: an endpoint that fails these checks takes payments that never attribute a referral.

get_merchant_stats

Reads a merchant's own performance figures (GET /v1/merchant/{id}/stats).

Owner-only, which is why it lives on referrer-signer rather than the keyless server: a merchant sees only its own revenue.

{ "merchant_id": "42" }

Returns

conversions, revenue_gross, escrow_holding, escrow_settled, product_count and active_product_count. Amounts are USDC base units, six decimals. product_count includes inactive products; active_product_count is the subset that is listed.

Next steps

  • Signer tools — the fourteen signing and transaction tools on the same server
  • Discovery tools — get_merchant, get_product and get_products, the keyless reads of what these tools write
  • Merchant routes — the REST surface three of these wrap