Fee Sharing
Earn a share of every deposit you route into Upshift vaults, using origin codes on the Universal Adapter.
If you route user deposits into Upshift vaults from your own app, you can take a fee on top of those deposits. The Universal Adapter has this built in: Upshift registers an origin code for you, you pass that code on every deposit call, and the adapter sends your fee straight to a wallet you control in the same transaction.
There is nothing to deploy, no wrapper contract to write, and no invoicing. Your fee settles on-chain at deposit time.
How it works
An origin code is a 32-byte identifier. On-chain it maps to two values: a fee in basis points, and the address that collects that fee.
mapping(bytes32 => OriginEntry) public origins;
struct OriginEntry {
uint256 originFee; // basis points
address originFeeCollector; // your wallet
}When a deposit arrives carrying your code, the adapter:
- Takes
originFeebps off the incoming deposit amount. - Transfers that amount to your
originFeeCollector. - Deposits the remainder into the vault and mints shares to the end user.
The fee comes out of the deposit, so the user receives shares for the net amount. Price this into whatever you show them before they sign.
Passing the all-zero code (bytes32(0)) means no origin fee — that is the
default for anyone who has not registered.
Fee limits
| Value | On-chain source | Current |
|---|---|---|
| Basis-point denominator | BPS_DENOMINATOR() | 10000 |
| Maximum origin fee | MAX_ORIGIN_FEE_BPS() | 7000 |
7000 bps is the contract's hard ceiling, not a recommendation. Agree a realistic
number with us — it is visible on-chain and it reduces the user's deposit.
Two fee layers
Each enabled vault also carries an Upshift swap fee (vaultInfo(vault).swapFee,
capped by MAX_SWAP_FEE_BPS()), applied on the swap paths. Your origin fee is
separate and additive. Read both before you quote a net deposit amount to a user.
Getting a code
addOrigin is owner-only, so codes are registered by Upshift — you cannot
self-register. Send us three things:
| What | Notes |
|---|---|
| A short label | e.g. acme-app. Becomes your bytes32 code. |
| Your fee, in bps | 100 = 1%. Must be ≤ MAX_ORIGIN_FEE_BPS(). |
| Your collector address | Receives the fee. Must be able to hold arbitrary ERC-20s. |
Reach us at support@upshift.finance. Registration is a single transaction on our side.
Once registered we can change your fee or collector (updateOrigin) or retire the
code (revokeOrigin) without you shipping anything — your integration keeps
passing the same code.
Deriving your code
By convention a code is its label as UTF-8 bytes, right-padded with zeros. Derive it yourself rather than hard-coding a hex blob:
import { stringToHex } from 'viem';
const ORIGIN_CODE = stringToHex('acme-app', { size: 32 });
// 0x61636d652d617070000000000000000000000000000000000000000000000000With ethers v6:
import { encodeBytes32String } from 'ethers';
const ORIGIN_CODE = encodeBytes32String('acme-app');Labels are capped at 31 bytes with encodeBytes32String (it reserves a
terminator) and 32 bytes with stringToHex. Confirm the exact code with us after
registration — the contract stores raw bytes and treats an unregistered code as
invalid.
Passing the code
Every deposit entry point on the adapter takes originCode as its first argument:
| Function | Use for |
|---|---|
deposit(bytes32 originCode, uint256 depositAmount, address vaultAddr, address assetAddr, address receiverAddr) | Depositing the vault's reference asset directly. |
depositNativeToken(bytes32 originCode, address vaultAddr, address receiverAddr) | Native ETH into a wrapped-native vault. payable. |
swapAndDeposit(bytes32 originCode, address vaultAddr, address receiverAddr, SwapParams[] swapParams) | Any whitelisted ERC-20, swapped to the reference asset en route. |
swapAndDepositNativeToken(bytes32 originCode, address vaultAddr, address receiverAddr) | Native ETH into a non-wrapped-native vault. payable. |
Each returns the shares minted to receiverAddr.
Via the SDK
The SDK picks the right entry point from the asset you pass, so you call one method for all four paths:
import AugustSDK from '@augustdigital/sdk';
import { stringToHex } from 'viem';
const sdk = new AugustSDK({ appName: '<APP_NAME>' });
sdk.evm.setSigner(signer);
const txHash = await sdk.evm.swapRouterDeposit({
chainId: 1,
vault: '0x74ad2f789ed583dbd141bbdafc673fe1f033718b',
depositAsset: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
amount: '1000',
originCode: stringToHex('acme-app', { size: 32 }),
slippageBps: 50,
});swapRouterDeposit reads the vault's reference asset on-chain, chooses the
direct / swap / native path, fetches and pins the swap quote, handles the ERC-20
approval, and forwards your originCode on whichever path it takes. Omit
originCode and it sends the zero sentinel.
Requires an SDK release newer than 8.24.0. Up to and including 8.24.0,
swapRouterDepositacceptsoriginCodebut drops it before dispatch, so deposits settle on the zero code and no fee is collected. On an older version, callswapAndDeposit,depositViaSwapRouter, ordepositNativeViaSwapRouterdirectly instead — those have always forwarded it.
Reading a code's settings
const [originFee, originFeeCollector] = await adapter.origins(ORIGIN_CODE);An unregistered code returns a zero fee and the zero address.
Prerequisites
Fee sharing rides on the adapter, so the adapter's constraints are yours too.
- Ethereum mainnet only. The Universal Adapter is deployed at
0xAC771209FF2b71EECfF6E85a9AD01db8Ff2618B0. No other chain has a deployment yet. - The vault must be enabled on the adapter. Check with
vaultInfo(vault)— areferenceAssetof the zero address means it is not registered, and deposits revert withInvalidVault. Enabling a vault is a config call on our side; ask us which vaults you need. - Deposit tokens must be whitelisted. Both the token in and the token out of
a swap leg. Non-whitelisted tokens revert with
InputTokenNotWhitelisted/OutputTokenNotWhitelisted. - Swap legs are capped at
MAX_SWAPS()(currently9) per call. - Deposits only. The deployed adapter handles deposits and swap-then-deposit. Redemptions go through the vault directly — see the vault interface.
Reporting on your fees
Every applied fee emits an event, indexed by origin code, so you can reconcile independently of anything Upshift reports:
event OriginFeeApplied(
address indexed vaultAddr,
address indexed assetAddr,
uint256 consumableAmount, // deposit amount the fee was taken from
uint256 feeAmount, // what your collector received
bytes32 indexed originCode
);Filter on originCode for your whole history. OriginAdded, OriginUpdated, and
OriginRevoked give you the audit trail of your code's configuration.
The adapter itself emits no deposit event — the vault does. To resolve what a given deposit actually produced (the real post-swap amount, which differs from the pre-trade quote), use the SDK:
const result = await sdk.evm.getSwapRouterDepositResult({
txHash,
vault: '0x74ad2f789ed583dbd141bbdafc673fe1f033718b',
});
// result?.amountOut — reference asset that actually reached the vault
// result?.shares — shares minted to the receiverErrors
| Error | Cause |
|---|---|
InvalidOrigin | The code is not registered, or was revoked. |
OriginAlreadyExists | addOrigin on a code that already exists — use updateOrigin. |
OriginFeeTooHigh | Requested fee exceeds MAX_ORIGIN_FEE_BPS(). |
OriginFeeTooLow | The requested fee is rejected as too small to be meaningful at the vault's decimals. |
InvalidFeeCollector | The collector address is not usable. |
InvalidVault | The vault is not enabled on the adapter. |
InputTokenNotWhitelisted | The deposit token is not whitelisted. |
OutputTokenNotWhitelisted | A swap leg's output token is not whitelisted. |
TooManySwaps | More than MAX_SWAPS() legs in one call. |
SlippageError | A swap leg produced less than its minAmountOut. |
ContractIsPaused | The adapter is paused. |
The Universal Adapter reference covers the wider function, event, and error surface — note the accuracy caveat at the top of that page, and treat the signatures here as the verified ones for deposits.
Universal Adapter
Full reference for the Universal Adapter (SwapRouter) — the single entry point for atomically swapping any whitelisted token and depositing the proceeds into an Upshift vault.
Deployed Contracts
Registry of Upshift protocol contract addresses and live vaults across every supported chain, consolidated from the protocol docs and the live vault API.