Guide โ Acquirer Registration
Register as an Acquirer¶
An Acquirer is any registered participant who distributes the Stablecoin Stack payment service and earns a percentage of the processing fee on every payment they refer. Commission is distributed automatically by the Settlement Contract โ in the same atomic transaction as the payment itself. No invoicing. No reconciliation cycles. No trust relationship required with the processor.
This guide covers the full registration flow: constructing and signing the BuyAcquiringPackRequest, submitting it, retrieving your Acquirer ID, and embedding it in payment flows.
What registration gives you¶
When a payment includes your Acquirer ID in its ref field, the Settlement Contract:
- Computes your commission as a percentage of the principal.
- Credits it to your wallet's internal balance โ atomically with the payment.
- Emits a
CommissionGeneratedevent you can track via the Event Explorer.
Your commission accumulates on-chain. Withdraw at any time. No billing period. No float.
Prerequisites¶
- A wallet with enough stablecoin to pay
acquiringPrice(the one-time registration fee โ fetch the current value from the processor before starting) - A local stack (see Run Locally) or access to a live processor's gateway
- The TypeScript SDK:
npm install @stablecoin-stack/wallet-sdk ethers
Step 1 โ Fetch registration parameters¶
import { ethers } from 'ethers';
import SettlementContractABI from '@stablecoin-stack/abis/SettlementContract.json';
const provider = new ethers.JsonRpcProvider(RPC_URL);
const contract = new ethers.Contract(SETTLEMENT_CONTRACT_ADDRESS, SettlementContractABI, provider);
// Current registration fee (in token units)
const acquiringPrice = await contract.acquiringPrice();
console.log(`Registration fee: $${ethers.formatUnits(acquiringPrice, 6)}`);
// Maximum fee percentage you are allowed to set
const maxAcquiringFee = await contract.maxAcquiringFee();
console.log(`Max acquiring fee: ${maxAcquiringFee} (processor-defined units)`);
// Tokens accepted for the registration payment
const allowedTokens = await walletGateway.getAcquiringAllowedTokens();
console.log('Accepted tokens:', allowedTokens);
Pick a token from allowedTokens for the registration payment. This does not have to be the same token used in the payments you will later acquire.
Step 2 โ Decide your acquiring fee percentage¶
Your acquiring fee percentage must be at or below maxAcquiringFee. The units are processor-defined โ check the processor's documentation for the exact interpretation (e.g. basis points, hundredths of a percent, etc.).
// Example: if 100 units = 1%, setting 150 = 1.5%
const desiredFeePercent = 150;
if (desiredFeePercent > maxAcquiringFee) {
throw new Error(`Fee ${desiredFeePercent} exceeds maximum ${maxAcquiringFee}`);
}
You can update your fee after registration, but you cannot exceed maxAcquiringFee at any point.
Step 3 โ Fetch the nonce¶
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
const payerAddress = await signer.getAddress();
// The wallet being registered may differ from the payer
// (sponsored registration is supported โ see note below)
const acquiringWalletAddress = payerAddress; // or a different address
const tokenContract = new ethers.Contract(TOKEN_ADDRESS, ERC20_PERMIT_ABI, provider);
const nonce = await tokenContract.nonces(payerAddress);
Sponsored registration
The payer of the fee and the wallet being registered do not need to be the same address. A company can pay the fee on behalf of a partner's wallet. Set payerAddress to the address paying, and acquiringWalletAddress to the address being registered.
Step 4 โ Build PermitParams¶
The permit must be signed for exactly acquiringPrice โ no more, no less.
const deadline = Math.floor(Date.now() / 1000) + 900; // 15 minutes from now
const permitParams = {
owner: payerAddress,
spender: SETTLEMENT_CONTRACT_ADDRESS,
value: acquiringPrice.toString(), // MUST equal acquiringPrice exactly
nonce: nonce.toString(),
deadline,
};
Step 5 โ Sign the Permit (Signature 1)¶
const tokenDomain = {
name: await tokenContract.name(),
version: '1',
chainId: CHAIN_ID,
verifyingContract: TOKEN_ADDRESS,
};
const permitTypes = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
};
const permitSigRaw = await signer.signTypedData(tokenDomain, permitTypes, permitParams);
const { v: v1, r: r1, s: s1 } = ethers.Signature.from(permitSigRaw);
const permitHash = ethers.TypedDataEncoder.hash(tokenDomain, permitTypes, permitParams);
const permitSig = {
hash: permitHash,
v: v1 < 27 ? v1 + 27 : v1,
r: r1,
s: s1,
};
Step 6 โ Build BuyAcquiringPackPermitParams¶
const buyAcquiringPackParams = {
token: TOKEN_ADDRESS,
feeValue: acquiringPrice.toString(), // MUST equal permitParams.value
acquiring: acquiringWalletAddress, // wallet to register
permitParams,
};
Both feeValue and permitParams.value must equal the current acquiringPrice. The Settlement Contract verifies this on-chain and reverts if they differ.
Step 7 โ Sign the operation (Signature 2)¶
const settlementDomain = {
name: 'SettlementContract',
version: '1',
chainId: CHAIN_ID,
verifyingContract: SETTLEMENT_CONTRACT_ADDRESS,
};
const buyAcquiringPackTypes = {
BuyAcquiringPackPermitParams: [
{ name: 'token', type: 'address' },
{ name: 'feeValue', type: 'uint256' },
{ name: 'acquiring', type: 'address' },
{ name: 'permitParams', type: 'PermitParams' },
],
PermitParams: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
};
const bindingSigRaw = await signer.signTypedData(
settlementDomain, buyAcquiringPackTypes, buyAcquiringPackParams
);
const { v: v2, r: r2, s: s2 } = ethers.Signature.from(bindingSigRaw);
const bindingHash = ethers.TypedDataEncoder.hash(
settlementDomain, buyAcquiringPackTypes, buyAcquiringPackParams
);
const payWithPermitSig = {
hash: bindingHash,
v: v2 < 27 ? v2 + 27 : v2,
r: r2,
s: s2,
};
Step 8 โ Submit the registration request¶
const buyAcquiringPackRequest = {
buyAcquiringPackParams,
payWithPermitSig,
permitSig,
};
const response = await fetch(`${WALLET_GATEWAY_URL}/api/submit-acquiring`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(buyAcquiringPackRequest),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Rejected: ${error.category} โ ${error.message}`);
}
const { submissionId } = await response.json();
console.log('Registration submitted:', submissionId);
Step 9 โ Retrieve your Acquirer ID¶
After the transaction confirms on-chain, the Settlement Contract emits an AcquirerCreated event containing your Acquirer ID.
import { ExplorerClient } from '@stablecoin-stack/explorer-sdk';
const explorer = new ExplorerClient({ baseUrl: EXPLORER_URL });
// Option A: watch for the event in real time
await explorer.connect();
explorer.onAcquirerCreated({ wallet: acquiringWalletAddress }, (event) => {
console.log('Registered!');
console.log('Acquirer ID:', event.acquirerId);
console.log('Fee percent:', event.feePercent);
explorer.disconnect();
});
// Option B: look up after confirmation
const acquirerId = await contract.getAcquiringWallet(acquiringWalletAddress);
// Returns the Zero-UUID if not registered, or your Acquirer ID once confirmed
Store your Acquirer ID. You will distribute it to payers and embed it in your distribution tooling. It is a bytes16 value โ 16 bytes, formatted as a 32-character hex string with a 0x prefix.
Step 10 โ Embed your Acquirer ID in payments¶
Your Acquirer ID earns commission only when it is present in the ref field of a payment. The ref field is 32 bytes: the first 16 bytes are the Order Reference, the last 16 bytes are the Acquirer ID.
// When a payer submits a payment through your distribution channel,
// your acquirer ID must be embedded in the ref field.
const ref = '0x'
+ orderReference.replace('0x', '') // 32 hex chars (16 bytes) โ from the merchant's session
+ YOUR_ACQUIRER_ID.replace('0x', ''); // 32 hex chars (16 bytes) โ your registered ID
If you are distributing the payment service via a wallet application, a POS terminal, or a platform integration, embed your Acquirer ID automatically when constructing the ref field. Payers and merchants do not need to know it exists.
Step 11 โ Track your commissions¶
import { ExplorerClient } from '@stablecoin-stack/explorer-sdk';
const explorer = new ExplorerClient({ baseUrl: EXPLORER_URL });
// Historical commissions
const commissions = await explorer.getCommissions({
acquirerId: YOUR_ACQUIRER_ID,
fromTimestamp: startOfMonth,
toTimestamp: endOfMonth,
});
const total = commissions.reduce((sum, c) => sum + BigInt(c.amount), 0n);
console.log(`Monthly commission: $${ethers.formatUnits(total, 6)}`);
// Real-time commission stream
await explorer.connect();
explorer.onCommission({ acquirerId: YOUR_ACQUIRER_ID }, (event) => {
console.log(`Commission earned: $${ethers.formatUnits(event.amount, 6)}`);
});
Step 12 โ Withdraw your balance¶
Commissions accumulate as an internal balance in the Settlement Contract. Withdraw to your wallet at any time:
const balance = await contract.getBalances(TOKEN_ADDRESS, [acquiringWalletAddress]);
console.log(`Balance: $${ethers.formatUnits(balance[0], 6)}`);
const acquiringSigner = new ethers.Wallet(ACQUIRING_WALLET_PRIVATE_KEY, provider);
await contract.connect(acquiringSigner).withdraw(TOKEN_ADDRESS, balance[0]);
Common mistakes¶
| Mistake | Result | Fix |
|---|---|---|
feeValue โ permitParams.value |
SEMANTIC_ERROR rejection |
Both must equal current acquiringPrice exactly |
feePercent > maxAcquiringFee |
On-chain revert | Fetch maxAcquiringFee first |
| Registering an already-registered wallet | On-chain revert | Each wallet registers once only |
Using a token not in acquiringAllowedTokens |
On-chain revert | Fetch the allowed list first |
Wrong v value (0 or 1) |
CRYPTOGRAPHIC_ERROR |
Normalise: if v < 27, add 27 |
Not embedding Acquirer ID in ref |
No commission earned | Always embed in the ref field |
Next steps¶
- Use the Event Explorer โ โ track your commissions in real time
- Build a Wallet โ โ distribute payments through your own wallet with your Acquirer ID embedded
- Business Model Catalogue โ โ all acquirer business models explained