UpshiftDocs

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:

  1. Takes originFee bps off the incoming deposit amount.
  2. Transfers that amount to your originFeeCollector.
  3. 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

ValueOn-chain sourceCurrent
Basis-point denominatorBPS_DENOMINATOR()10000
Maximum origin feeMAX_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:

WhatNotes
A short labele.g. acme-app. Becomes your bytes32 code.
Your fee, in bps100 = 1%. Must be ≤ MAX_ORIGIN_FEE_BPS().
Your collector addressReceives 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 });
// 0x61636d652d617070000000000000000000000000000000000000000000000000

With 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:

FunctionUse 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, swapRouterDeposit accepts originCode but drops it before dispatch, so deposits settle on the zero code and no fee is collected. On an older version, call swapAndDeposit, depositViaSwapRouter, or depositNativeViaSwapRouter directly 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) — a referenceAsset of the zero address means it is not registered, and deposits revert with InvalidVault. 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() (currently 9) 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 receiver

Errors

ErrorCause
InvalidOriginThe code is not registered, or was revoked.
OriginAlreadyExistsaddOrigin on a code that already exists — use updateOrigin.
OriginFeeTooHighRequested fee exceeds MAX_ORIGIN_FEE_BPS().
OriginFeeTooLowThe requested fee is rejected as too small to be meaningful at the vault's decimals.
InvalidFeeCollectorThe collector address is not usable.
InvalidVaultThe vault is not enabled on the adapter.
InputTokenNotWhitelistedThe deposit token is not whitelisted.
OutputTokenNotWhitelistedA swap leg's output token is not whitelisted.
TooManySwapsMore than MAX_SWAPS() legs in one call.
SlippageErrorA swap leg produced less than its minAmountOut.
ContractIsPausedThe 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.